001/*
002 * Copyright 2010-2015 Institut Pasteur.
003 * 
004 * This file is part of Icy.
005 * 
006 * Icy is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 * 
011 * Icy is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014 * GNU General Public License for more details.
015 * 
016 * You should have received a copy of the GNU General Public License
017 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
018 */
019package icy.plugin;
020
021import icy.file.FileUtil;
022import icy.file.Loader;
023import icy.gui.frame.progress.ProgressFrame;
024import icy.main.Icy;
025import icy.network.NetworkUtil;
026import icy.plugin.PluginDescriptor.PluginIdent;
027import icy.plugin.PluginDescriptor.PluginKernelNameSorter;
028import icy.plugin.abstract_.Plugin;
029import icy.plugin.classloader.JarClassLoader;
030import icy.plugin.interface_.PluginBundled;
031import icy.plugin.interface_.PluginDaemon;
032import icy.preferences.PluginPreferences;
033import icy.system.IcyExceptionHandler;
034import icy.system.thread.SingleProcessor;
035import icy.system.thread.ThreadUtil;
036import icy.util.ClassUtil;
037
038import java.io.IOException;
039import java.io.InputStream;
040import java.net.URL;
041import java.util.ArrayList;
042import java.util.Collections;
043import java.util.EventListener;
044import java.util.HashMap;
045import java.util.HashSet;
046import java.util.List;
047import java.util.Map;
048import java.util.Map.Entry;
049import java.util.Set;
050
051import javax.swing.event.EventListenerList;
052
053/**
054 * Plugin Loader class.<br>
055 * This class is used to load plugins from "plugins" package and "plugins" directory
056 * 
057 * @author Stephane<br>
058 */
059public class PluginLoader
060{
061    public final static String PLUGIN_PACKAGE = "plugins";
062    public final static String PLUGIN_KERNEL_PACKAGE = "plugins.kernel";
063    public final static String PLUGIN_PATH = "plugins";
064
065    // used to identify java version problem
066    public final static String NEWER_JAVA_REQUIRED = "Newer java version required";
067
068    /**
069     * static class
070     */
071    private static final PluginLoader instance = new PluginLoader();
072
073    /**
074     * class loader
075     */
076    private ClassLoader loader;
077    /**
078     * active daemons plugins
079     */
080    private List<PluginDaemon> activeDaemons;
081    /**
082     * Loaded plugin list
083     */
084    private List<PluginDescriptor> plugins;
085
086    /**
087     * listeners
088     */
089    private final EventListenerList listeners;
090
091    /**
092     * JAR Class Loader disabled flag
093     */
094    protected boolean JCLDisabled;
095
096    /**
097     * internals
098     */
099    private final Runnable reloader;
100    final SingleProcessor processor;
101
102    private boolean initialized;
103    private boolean loading;
104
105    // private boolean logError;
106
107    /**
108     * static class
109     */
110    private PluginLoader()
111    {
112        super();
113
114        // default class loader
115        loader = new PluginClassLoader();
116        // active daemons
117        activeDaemons = new ArrayList<PluginDaemon>();
118
119        JCLDisabled = false;
120        initialized = false;
121        loading = false;
122        // needReload = false;
123        // logError = true;
124
125        plugins = new ArrayList<PluginDescriptor>();
126        listeners = new EventListenerList();
127
128        // reloader
129        reloader = new Runnable()
130        {
131            @Override
132            public void run()
133            {
134                reloadInternal();
135            }
136        };
137
138        processor = new SingleProcessor(true, "Local Plugin Loader");
139
140        // don't load by default as we need Preferences to be ready first
141    };
142
143    static void prepare()
144    {
145        if (!instance.initialized)
146        {
147            if (isLoading())
148                waitWhileLoading();
149            else
150                reload();
151        }
152    }
153
154    /**
155     * Reload the list of installed plugins (asynchronous version).
156     */
157    public static void reloadAsynch()
158    {
159        instance.processor.submit(instance.reloader);
160    }
161
162    /**
163     * Reload the list of installed plugins (wait for completion).
164     */
165    public static void reload()
166    {
167        instance.processor.submit(instance.reloader);
168        // ensure we don't miss the reloading
169        ThreadUtil.sleep(500);
170        waitWhileLoading();
171    }
172
173    /**
174     * @deprecated Use {@link #reload()} instead.
175     */
176    @SuppressWarnings("unused")
177    @Deprecated
178    public static void reload(boolean forceNow)
179    {
180        reload();
181    }
182
183    /**
184     * Stop and restart all daemons plugins.
185     */
186    public static synchronized void resetDaemons()
187    {
188        // reset will be done later
189        if (isLoading())
190            return;
191
192        stopDaemons();
193        startDaemons();
194    }
195
196    /**
197     * Reload the list of installed plugins (in "plugins" directory)
198     */
199    void reloadInternal()
200    {
201        // needReload = false;
202        loading = true;
203
204        // stop daemon plugins
205        stopDaemons();
206
207        // reset plugins and loader
208        final List<PluginDescriptor> newPlugins = new ArrayList<PluginDescriptor>();
209        final ClassLoader newLoader;
210
211        // special case where JCL is disabled
212        if (JCLDisabled)
213            newLoader = PluginLoader.class.getClassLoader();
214        else
215        {
216            newLoader = new PluginClassLoader();
217
218            // reload plugins directory to search path
219            ((PluginClassLoader) newLoader).add(PLUGIN_PATH);
220        }
221
222        // no need to complete loading...
223        if (processor.hasWaitingTasks())
224            return;
225
226        final Set<String> classes = new HashSet<String>();
227
228        try
229        {
230            // search for plugins in "Plugins" package (needed when working from JAR archive)
231            ClassUtil.findClassNamesInPackage(PLUGIN_PACKAGE, true, classes);
232            // search for plugins in "Plugins" directory with default plugin package name
233            ClassUtil.findClassNamesInPath(PLUGIN_PATH, PLUGIN_PACKAGE, true, classes);
234        }
235        catch (IOException e)
236        {
237            System.err.println("Error loading plugins :");
238            IcyExceptionHandler.showErrorMessage(e, true);
239        }
240
241        for (String className : classes)
242        {
243            // we only want to load classes from 'plugins' package
244            if (!className.startsWith(PLUGIN_PACKAGE))
245                continue;
246            // filter incorrect named classes (Jython classes for instances)
247            if (className.contains("$"))
248                continue;
249
250            // no need to complete loading...
251            if (processor.hasWaitingTasks())
252                return;
253
254            try
255            {
256                // try to load class and check we have a Plugin class at same time
257                final Class<? extends Plugin> pluginClass = newLoader.loadClass(className).asSubclass(Plugin.class);
258                // add to list
259                newPlugins.add(new PluginDescriptor(pluginClass));
260            }
261            catch (NoClassDefFoundError e)
262            {
263                // fatal error
264                System.err.println("Class '" + className + "' cannot be loaded :");
265                System.err.println(
266                        "Required class '" + ClassUtil.getQualifiedNameFromPath(e.getMessage()) + "' not found.");
267            }
268            catch (OutOfMemoryError e)
269            {
270                // fatal error
271                IcyExceptionHandler.showErrorMessage(e, false);
272                System.err.println("Class '" + className + "' is discarded");
273            }
274            catch (UnsupportedClassVersionError e)
275            {
276                // java version error (here we just notify in the console)
277                System.err.println(NEWER_JAVA_REQUIRED + " for class '" + className + "' (discarded)");
278            }
279            catch (Error e)
280            {
281                // fatal error
282                IcyExceptionHandler.showErrorMessage(e, false);
283                System.err.println("Class '" + className + "' is discarded");
284            }
285            catch (ClassCastException e)
286            {
287                // ignore ClassCastException (for classes which doesn't extend Plugin)
288            }
289            catch (ClassNotFoundException e)
290            {
291                // ignore ClassNotFoundException (for no public classes)
292            }
293            catch (Exception e)
294            {
295                // fatal error
296                IcyExceptionHandler.showErrorMessage(e, false);
297                System.err.println("Class '" + className + "' is discarded");
298            }
299        }
300
301        // sort list
302        Collections.sort(newPlugins, PluginKernelNameSorter.instance);
303
304        // release loaded resources
305        if (loader instanceof JarClassLoader)
306            ((JarClassLoader) loader).unloadAll();
307
308        loader = newLoader;
309        plugins = newPlugins;
310
311        loading = false;
312
313        // notify change
314        changed();
315    }
316
317    /**
318     * Returns the list of daemon type plugins.
319     */
320    public static ArrayList<PluginDescriptor> getDaemonPlugins()
321    {
322        final ArrayList<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
323
324        synchronized (instance.plugins)
325        {
326            for (PluginDescriptor pluginDescriptor : instance.plugins)
327            {
328                if (pluginDescriptor.isInstanceOf(PluginDaemon.class))
329                {
330                    // accept class ?
331                    if (!pluginDescriptor.isAbstract() && !pluginDescriptor.isInterface())
332                        result.add(pluginDescriptor);
333                }
334            }
335        }
336
337        return result;
338    }
339
340    /**
341     * Returns the list of active daemon plugins.
342     */
343    public static ArrayList<PluginDaemon> getActiveDaemons()
344    {
345        synchronized (instance.activeDaemons)
346        {
347            return new ArrayList<PluginDaemon>(instance.activeDaemons);
348        }
349    }
350
351    /**
352     * Start daemons plugins.
353     */
354    static synchronized void startDaemons()
355    {
356        // at this point active daemons should be empty !
357        if (!instance.activeDaemons.isEmpty())
358            stopDaemons();
359
360        final List<String> inactives = PluginPreferences.getInactiveDaemons();
361        final List<PluginDaemon> newDaemons = new ArrayList<PluginDaemon>();
362
363        for (PluginDescriptor pluginDesc : getDaemonPlugins())
364        {
365            // not found in inactives ?
366            if (inactives.indexOf(pluginDesc.getClassName()) == -1)
367            {
368                try
369                {
370                    final PluginDaemon plugin = (PluginDaemon) pluginDesc.getPluginClass().newInstance();
371                    final Thread thread = new Thread(plugin, pluginDesc.getName());
372
373                    thread.setName(pluginDesc.getName());
374                    // so icy can exit even with running daemon plugin
375                    thread.setDaemon(true);
376
377                    // init daemon
378                    plugin.init();
379                    // start daemon
380                    thread.start();
381                    // register daemon plugin (so we can stop it later)
382                    Icy.getMainInterface().registerPlugin((Plugin) plugin);
383
384                    // add daemon plugin to list
385                    newDaemons.add(plugin);
386                }
387                catch (Throwable t)
388                {
389                    IcyExceptionHandler.handleException(pluginDesc, t, true);
390                }
391            }
392        }
393
394        instance.activeDaemons = newDaemons;
395    }
396
397    /**
398     * Stop daemons plugins.
399     */
400    public synchronized static void stopDaemons()
401    {
402        for (PluginDaemon daemonPlug : getActiveDaemons())
403        {
404            try
405            {
406                // stop the daemon
407                daemonPlug.stop();
408            }
409            catch (Throwable t)
410            {
411                IcyExceptionHandler.handleException(((Plugin) daemonPlug).getDescriptor(), t, true);
412            }
413        }
414
415        // no more active daemons
416        instance.activeDaemons = new ArrayList<PluginDaemon>();
417    }
418
419    /**
420     * Return the loader
421     */
422    public static ClassLoader getLoader()
423    {
424        return instance.loader;
425    }
426
427    /**
428     * Return all resources present in the Plugin class loader.
429     */
430    public static Map<String, URL> getAllResources()
431    {
432        prepare();
433
434        synchronized (instance.loader)
435        {
436            if (instance.loader instanceof JarClassLoader)
437                return ((JarClassLoader) instance.loader).getResources();
438        }
439
440        return new HashMap<String, URL>();
441    }
442
443    /**
444     * Return content of all loaded resources.
445     */
446    public static Map<String, byte[]> getLoadedResources()
447    {
448        prepare();
449
450        synchronized (instance.loader)
451        {
452            if (instance.loader instanceof JarClassLoader)
453                return ((JarClassLoader) instance.loader).getLoadedResources();
454        }
455
456        return new HashMap<String, byte[]>();
457    }
458
459    /**
460     * Return all loaded classes.
461     */
462    @SuppressWarnings("rawtypes")
463    public static Map<String, Class<?>> getLoadedClasses()
464    {
465        prepare();
466
467        synchronized (instance.loader)
468        {
469            if (instance.loader instanceof JarClassLoader)
470            {
471                final HashMap<String, Class<?>> result = new HashMap<String, Class<?>>();
472                final Map<String, Class> classes = ((JarClassLoader) instance.loader).getLoadedClasses();
473
474                for (Entry<String, Class> entry : classes.entrySet())
475                    result.put(entry.getKey(), entry.getValue());
476
477                return result;
478            }
479        }
480
481        return new HashMap<String, Class<?>>();
482    }
483
484    /**
485     * Return all classes.
486     * 
487     * @deprecated Use {@link #getLoadedClasses()} instead as we load classes on demand.
488     */
489    @Deprecated
490    public static Map<String, Class<?>> getAllClasses()
491    {
492        return getLoadedClasses();
493    }
494
495    /**
496     * Return a resource as data stream from given resource name
497     * 
498     * @param name
499     *        resource name
500     */
501    public static InputStream getResourceAsStream(String name)
502    {
503        prepare();
504
505        synchronized (instance.loader)
506        {
507            return instance.loader.getResourceAsStream(name);
508        }
509    }
510
511    /**
512     * Return the list of loaded plugins.
513     */
514    public static ArrayList<PluginDescriptor> getPlugins()
515    {
516        return getPlugins(true);
517    }
518
519    /**
520     * Return the list of loaded plugins.
521     * 
522     * @param wantBundled
523     *        specify if we also want plugin implementing the {@link PluginBundled} interface.
524     */
525    public static ArrayList<PluginDescriptor> getPlugins(boolean wantBundled)
526    {
527        prepare();
528
529        final ArrayList<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
530
531        // better to return a copy as we have async list loading
532        synchronized (instance.plugins)
533        {
534            for (PluginDescriptor plugin : instance.plugins)
535            {
536                if (wantBundled || (!plugin.isBundled()))
537                    result.add(plugin);
538            }
539        }
540
541        return result;
542    }
543
544    /**
545     * Return the list of loaded plugins which derive from the specified class.
546     * 
547     * @param clazz
548     *        The class object defining the class we want plugin derive from.
549     */
550    public static ArrayList<PluginDescriptor> getPlugins(Class<?> clazz)
551    {
552        return getPlugins(clazz, true, false, false);
553    }
554
555    /**
556     * Return the list of loaded plugins which derive from the specified class.
557     * 
558     * @param clazz
559     *        The class object defining the class we want plugin derive from.
560     * @param wantBundled
561     *        specify if we also want plugin implementing the {@link PluginBundled} interface
562     * @param wantAbstract
563     *        specify if we also want abstract classes
564     * @param wantInterface
565     *        specify if we also want interfaces
566     */
567    public static ArrayList<PluginDescriptor> getPlugins(Class<?> clazz, boolean wantBundled, boolean wantAbstract,
568            boolean wantInterface)
569    {
570        prepare();
571
572        final ArrayList<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
573
574        if (clazz != null)
575        {
576            synchronized (instance.plugins)
577            {
578                for (PluginDescriptor pluginDescriptor : instance.plugins)
579                {
580                    if (pluginDescriptor.isInstanceOf(clazz))
581                    {
582                        // accept class ?
583                        if ((wantAbstract || !pluginDescriptor.isAbstract())
584                                && (wantInterface || !pluginDescriptor.isInterface())
585                                && (wantBundled || !pluginDescriptor.isBundled()))
586                            result.add(pluginDescriptor);
587                    }
588                }
589            }
590        }
591
592        return result;
593    }
594
595    /**
596     * Return the list of "actionable" plugins (mean we can launch them from GUI).
597     * 
598     * @param wantBundled
599     *        specify if we also want plugin implementing the {@link PluginBundled} interface
600     */
601    public static ArrayList<PluginDescriptor> getActionablePlugins(boolean wantBundled)
602    {
603        prepare();
604
605        final ArrayList<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
606
607        synchronized (instance.plugins)
608        {
609            for (PluginDescriptor pluginDescriptor : instance.plugins)
610            {
611                if (pluginDescriptor.isActionable() && (wantBundled || !pluginDescriptor.isBundled()))
612                    result.add(pluginDescriptor);
613            }
614        }
615
616        return result;
617    }
618
619    /**
620     * Return the list of "actionable" plugins (mean we can launch them from GUI).<br>
621     * By default plugin implementing the {@link PluginBundled} interface are also returned.
622     */
623    public static ArrayList<PluginDescriptor> getActionablePlugins()
624    {
625        return getActionablePlugins(true);
626    }
627
628    /**
629     * @return the loading
630     */
631    public static boolean isLoading()
632    {
633        return instance.processor.hasWaitingTasks() || instance.loading;
634    }
635
636    /**
637     * wait until loading completed
638     */
639    public static void waitWhileLoading()
640    {
641        while (isLoading())
642            ThreadUtil.sleep(100);
643    }
644
645    /**
646     * Returns <code>true</code> if the specified plugin exists in the {@link PluginLoader}.
647     * 
648     * @param plugin
649     *        the plugin we are looking for.
650     * @param acceptNewer
651     *        allow newer version of the plugin
652     */
653    public static boolean isLoaded(PluginDescriptor plugin, boolean acceptNewer)
654    {
655        return (getPlugin(plugin.getIdent(), acceptNewer) != null);
656    }
657
658    /**
659     * Returns <code>true</code> if the specified plugin class exists in the {@link PluginLoader}.
660     * 
661     * @param className
662     *        class name of the plugin we are looking for.
663     */
664    public static boolean isLoaded(String className)
665    {
666        return (getPlugin(className) != null);
667    }
668
669    /**
670     * Returns the plugin corresponding to the specified plugin identity structure.<br>
671     * Returns <code>null</code> if the plugin does not exists in the {@link PluginLoader}.
672     * 
673     * @param ident
674     *        plugin identity
675     * @param acceptNewer
676     *        allow newer version of the plugin
677     */
678    public static PluginDescriptor getPlugin(PluginIdent ident, boolean acceptNewer)
679    {
680        prepare();
681
682        synchronized (instance.plugins)
683        {
684            return PluginDescriptor.getPlugin(instance.plugins, ident, acceptNewer);
685        }
686    }
687
688    /**
689     * Returns the plugin corresponding to the specified plugin class name.<br>
690     * Returns <code>null</code> if the plugin does not exists in the {@link PluginLoader}.
691     * 
692     * @param className
693     *        class name of the plugin we are looking for.
694     */
695    public static PluginDescriptor getPlugin(String className)
696    {
697        prepare();
698
699        synchronized (instance.plugins)
700        {
701            return PluginDescriptor.getPlugin(instance.plugins, className);
702        }
703    }
704
705    /**
706     * Returns the plugin class corresponding to the specified plugin class name.<br>
707     * Returns <code>null</code> if the plugin does not exists in the {@link PluginLoader}.
708     * 
709     * @param className
710     *        class name of the plugin we are looking for.
711     */
712    public static Class<? extends Plugin> getPluginClass(String className)
713    {
714        prepare();
715
716        final PluginDescriptor descriptor = getPlugin(className);
717
718        if (descriptor != null)
719            return descriptor.getPluginClass();
720
721        return null;
722    }
723
724    /**
725     * Try to load and returns the specified class from the {@link PluginLoader}.<br>
726     * This method is equivalent to call {@link #getLoader()} then call
727     * <code>loadClass(String)</code> method from it.
728     * 
729     * @param className
730     *        class name of the class we want to load.
731     */
732    public static Class<?> loadClass(String className) throws ClassNotFoundException
733    {
734        prepare();
735
736        synchronized (instance.loader)
737        {
738            // try to load class
739            return instance.loader.loadClass(className);
740        }
741    }
742
743    /**
744     * Verify the specified plugin is correctly installed.<br>
745     * Returns an empty string if the plugin is valid otherwise it returns the error message.
746     */
747    public static String verifyPlugin(PluginDescriptor plugin)
748    {
749        synchronized (instance.loader)
750        {
751            try
752            {
753                // then try to load the plugin class as Plugin class
754                instance.loader.loadClass(plugin.getClassName()).asSubclass(Plugin.class);
755            }
756            catch (UnsupportedClassVersionError e)
757            {
758                return NEWER_JAVA_REQUIRED + ".";
759            }
760            catch (Error e)
761            {
762                return e.toString();
763            }
764            catch (ClassCastException e)
765            {
766                return IcyExceptionHandler.getErrorMessage(e, false)
767                        + "Your plugin class should extends 'icy.plugin.abstract_.Plugin' class.";
768            }
769            catch (ClassNotFoundException e)
770            {
771                return IcyExceptionHandler.getErrorMessage(e, false)
772                        + "Verify you correctly set the class name in your plugin description.";
773            }
774            catch (Exception e)
775            {
776                return IcyExceptionHandler.getErrorMessage(e, false);
777            }
778        }
779
780        return "";
781    }
782
783    /**
784     * Load all classes from specified path
785     */
786    // private static ArrayList<String> loadAllClasses(String path)
787    // {
788    // // search for class names in that path
789    // final HashSet<String> classNames = ClassUtil.findClassNamesInPath(path, true);
790    // final ArrayList<String> result = new ArrayList<String>();
791    //
792    // synchronized (loader)
793    // {
794    // for (String className : classNames)
795    // {
796    // try
797    // {
798    // // try to load class
799    // loader.loadClass(className);
800    // }
801    // catch (Error err)
802    // {
803    // // fatal error while loading class, store error String
804    // result.add("Fatal error while loading " + className + " :\n" + err.toString() + "\n");
805    // }
806    // catch (ClassNotFoundException cnfe)
807    // {
808    // // ignore ClassNotFoundException (happen with private class)
809    // }
810    // catch (Exception exc)
811    // {
812    // result.add("Fatal error while loading " + className + " :\n" + exc.toString() + "\n");
813    // }
814    // }
815    // }
816    //
817    // return result;
818    // }
819
820    public static boolean isJCLDisabled()
821    {
822        return instance.JCLDisabled;
823    }
824
825    public static void setJCLDisabled(boolean value)
826    {
827        instance.JCLDisabled = value;
828    }
829
830    /**
831     * @deprecated
832     */
833    @Deprecated
834    public static boolean getLogError()
835    {
836        return false;
837        // return instance.logError;
838    }
839
840    /**
841     * @deprecated
842     */
843    @Deprecated
844    public static void setLogError(boolean value)
845    {
846        // instance.logError = value;
847    }
848
849    /**
850     * Called when class loader
851     */
852    protected void changed()
853    {
854        // check for missing or mis-installed plugins on first start
855        if (!initialized)
856        {
857            initialized = true;
858            checkPlugins(false);
859        }
860
861        // start daemon plugins
862        startDaemons();
863        // notify listener we have changed
864        fireEvent(new PluginLoaderEvent());
865
866        ThreadUtil.bgRun(new Runnable()
867        {
868            @Override
869            public void run()
870            {
871                // pre load the importers classes as they can be heavy
872                Loader.getSequenceFileImporters();
873                Loader.getFileImporters();
874                Loader.getImporters();
875            }
876        });
877    }
878
879    /**
880     * Check for missing plugins and install them if needed.
881     */
882    public static void checkPlugins(boolean showProgress)
883    {
884        final List<PluginDescriptor> plugins = getPlugins(false);
885        final List<PluginDescriptor> required = new ArrayList<PluginDescriptor>();
886        final List<PluginDescriptor> missings = new ArrayList<PluginDescriptor>();
887        final List<PluginDescriptor> faulties = new ArrayList<PluginDescriptor>();
888
889        if (NetworkUtil.hasInternetAccess())
890        {
891            ProgressFrame pf;
892
893            if (showProgress)
894            {
895                pf = new ProgressFrame("Checking plugins...");
896                pf.setLength(plugins.size());
897                pf.setPosition(0);
898            }
899            else
900                pf = null;
901
902            PluginRepositoryLoader.waitLoaded();
903
904            // get list of required and faulty plugins
905            for (PluginDescriptor plugin : plugins)
906            {
907                // get dependencies
908                if (!PluginInstaller.getDependencies(plugin, required, null, false))
909                    // error in dependencies --> try to reinstall the plugin
910                    faulties.add(PluginRepositoryLoader.getPlugin(plugin.getClassName()));
911
912                if (pf != null)
913                    pf.incPosition();
914            }
915
916            if (pf != null)
917                pf.setLength(required.size());
918
919            // check for missing plugins
920            for (PluginDescriptor plugin : required)
921            {
922                // dependency missing ? --> try to reinstall the plugin
923                if (!plugin.isInstalled())
924                {
925                    final PluginDescriptor toInstall = PluginRepositoryLoader.getPlugin(plugin.getClassName());
926                    if (toInstall != null)
927                        missings.add(toInstall);
928                }
929
930                if (pf != null)
931                    pf.incPosition();
932            }
933
934            if ((faulties.size() > 0) || (missings.size() > 0))
935            {
936                if (pf != null)
937                {
938                    pf.setMessage("Installing missing plugins...");
939                    pf.setPosition(0);
940                    pf.setLength(faulties.size() + missings.size());
941                }
942
943                // remove faulty plugins
944                // for (PluginDescriptor plugin : faulties)
945                // PluginInstaller.desinstall(plugin, false, false);
946                // PluginInstaller.waitDesinstall();
947
948                // install missing plugins
949                for (PluginDescriptor plugin : missings)
950                {
951                    PluginInstaller.install(plugin, true);
952                    if (pf != null)
953                        pf.incPosition();
954                }
955                // and reinstall faulty plugins
956                for (PluginDescriptor plugin : faulties)
957                {
958                    PluginInstaller.install(plugin, true);
959                    if (pf != null)
960                        pf.incPosition();
961                }
962            }
963        }
964    }
965
966    /**
967     * Add a listener
968     * 
969     * @param listener
970     */
971    public static void addListener(PluginLoaderListener listener)
972    {
973        synchronized (instance.listeners)
974        {
975            instance.listeners.add(PluginLoaderListener.class, listener);
976        }
977    }
978
979    /**
980     * Remove a listener
981     * 
982     * @param listener
983     */
984    public static void removeListener(PluginLoaderListener listener)
985    {
986        synchronized (instance.listeners)
987        {
988            instance.listeners.remove(PluginLoaderListener.class, listener);
989        }
990    }
991
992    /**
993     * fire event
994     */
995    void fireEvent(PluginLoaderEvent e)
996    {
997        synchronized (listeners)
998        {
999            for (PluginLoaderListener listener : listeners.getListeners(PluginLoaderListener.class))
1000                listener.pluginLoaderChanged(e);
1001        }
1002    }
1003
1004    public static class PluginClassLoader extends JarClassLoader
1005    {
1006        public PluginClassLoader()
1007        {
1008            super();
1009        }
1010
1011        /**
1012         * Give access to this method
1013         */
1014        public Class<?> getLoadedClass(String name)
1015        {
1016            return super.findLoadedClass(name);
1017        }
1018
1019        /**
1020         * Give access to this method
1021         */
1022        public boolean isLoadedClass(String name)
1023        {
1024            return getLoadedClass(name) != null;
1025        }
1026    }
1027
1028    public static interface PluginLoaderListener extends EventListener
1029    {
1030        public void pluginLoaderChanged(PluginLoaderEvent e);
1031    }
1032
1033    public static class PluginLoaderEvent
1034    {
1035        public PluginLoaderEvent()
1036        {
1037            super();
1038        }
1039
1040        @Override
1041        public boolean equals(Object obj)
1042        {
1043            if (obj instanceof PluginLoaderEvent)
1044                return true;
1045
1046            return super.equals(obj);
1047        }
1048    }
1049}