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.common.Version;
022import icy.file.FileUtil;
023import icy.file.xml.XMLPersistent;
024import icy.file.xml.XMLPersistentHelper;
025import icy.image.ImageUtil;
026import icy.network.NetworkUtil;
027import icy.network.URLUtil;
028import icy.plugin.abstract_.Plugin;
029import icy.plugin.interface_.PluginBundled;
030import icy.plugin.interface_.PluginImageAnalysis;
031import icy.preferences.RepositoryPreferences.RepositoryInfo;
032import icy.resource.ResourceUtil;
033import icy.util.ClassUtil;
034import icy.util.JarUtil;
035import icy.util.StringUtil;
036import icy.util.XMLUtil;
037
038import java.awt.Image;
039import java.net.URL;
040import java.util.ArrayList;
041import java.util.Comparator;
042import java.util.List;
043import java.util.Set;
044
045import javax.swing.ImageIcon;
046
047import org.w3c.dom.Document;
048import org.w3c.dom.Element;
049import org.w3c.dom.Node;
050
051/**
052 * <br>
053 * The plugin descriptor contains all the data needed to launch a plugin. <br>
054 * 
055 * @see PluginLauncher
056 * @author Fabrice de Chaumont & Stephane
057 */
058public class PluginDescriptor implements XMLPersistent
059{
060    public static final int ICON_SIZE = 64;
061    public static final int IMAGE_SIZE = 256;
062
063    public static final ImageIcon DEFAULT_ICON = ResourceUtil.getImageIcon(ResourceUtil.IMAGE_PLUGIN_SMALL);
064    public static final Image DEFAULT_IMAGE = ResourceUtil.IMAGE_PLUGIN;
065
066    /**
067     * @deprecated Use {@link PluginIdent#ID_CLASSNAME} instead
068     */
069    @Deprecated
070    public static final String ID_CLASSNAME = PluginIdent.ID_CLASSNAME;
071    /**
072     * @deprecated Use {@link PluginIdent#ID_REQUIRED_KERNEL_VERSION} instead
073     */
074    @Deprecated
075    public static final String ID_REQUIRED_KERNEL_VERSION = PluginIdent.ID_REQUIRED_KERNEL_VERSION;
076
077    public static final String ID_URL = "url";
078    public static final String ID_NAME = "name";
079
080    public static final String ID_JAR_URL = "jar_url";
081    public static final String ID_IMAGE_URL = "image_url";
082    public static final String ID_ICON_URL = "icon_url";
083    public static final String ID_AUTHOR = "author";
084    public static final String ID_CHANGELOG = "changelog";
085    public static final String ID_WEB = "web";
086    public static final String ID_EMAIL = "email";
087    public static final String ID_DESCRIPTION = "description";
088    public static final String ID_DEPENDENCIES = "dependencies";
089    public static final String ID_DEPENDENCY = "dependency";
090
091    protected Class<? extends Plugin> pluginClass;
092
093    protected ImageIcon icon;
094    protected Image image;
095
096    protected String name;
097    protected PluginIdent ident;
098    protected String localXmlUrl;
099    protected String xmlUrl;
100    protected String jarUrl;
101    protected String imageUrl;
102    protected String iconUrl;
103    protected String author;
104    protected String web;
105    protected String email;
106    protected String desc;
107    protected String changeLog;
108
109    protected boolean enabled;
110    protected boolean descriptorLoaded;
111    protected boolean iconLoaded;
112    protected boolean imageLoaded;
113    protected boolean changeLogLoaded;
114    // boolean checkingForUpdate;
115    // boolean updateChecked;
116    // PluginDescriptor onlineDescriptor;
117
118    // private final List<String> publicClasseNames;
119    protected final List<PluginIdent> required;
120
121    // only for online descriptor
122    protected RepositoryInfo repository;
123
124    // /**
125    // * Get online plugin of specified PluginIdent<br>
126    // * Take care of "allow beta" global flag<br>
127    // * This method can take sometime !<br>
128    // */
129    // public static PluginDescriptor getOnlinePlugin(PluginIdent ident, boolean loadImage)
130    // {
131    // PluginDescriptor betaDescriptor = null;
132    // PluginDescriptor stableDescriptor = null;
133    //
134    // try
135    // {
136    // // get beta online plugin descriptor if allowed
137    // if (PluginPreferences.getAllowBeta())
138    // betaDescriptor = new PluginDescriptor(ident.getUrlBeta(), loadImage);
139    // }
140    // catch (Exception e)
141    // {
142    // betaDescriptor = null;
143    // }
144    //
145    // try
146    // {
147    // // get stable online plugin descriptor
148    // stableDescriptor = new PluginDescriptor(ident.getUrlStable(), loadImage);
149    // }
150    // catch (Exception e)
151    // {
152    // stableDescriptor = null;
153    // }
154    //
155    // if ((betaDescriptor != null) && ((stableDescriptor == null) ||
156    // betaDescriptor.isNewerOrEqual(stableDescriptor)))
157    // return betaDescriptor;
158    //
159    // return stableDescriptor;
160    // }
161
162    /**
163     * Returns the index for the specified plugin in the specified list.<br>
164     * Returns -1 if not found.
165     */
166    public static int getIndex(List<PluginDescriptor> list, PluginDescriptor plugin)
167    {
168        return getIndex(list, plugin.getIdent());
169    }
170
171    /**
172     * Returns the index for the specified plugin in the specified list.<br>
173     * Returns -1 if not found.
174     */
175    public static int getIndex(List<PluginDescriptor> list, PluginIdent ident)
176    {
177        final int size = list.size();
178
179        for (int i = 0; i < size; i++)
180            if (list.get(i).getIdent().equals(ident))
181                return i;
182
183        return -1;
184    }
185
186    /**
187     * Returns the index for the specified plugin in the specified list.<br>
188     * Returns -1 if not found.
189     */
190    public static int getIndex(List<PluginDescriptor> list, String className)
191    {
192        final int size = list.size();
193
194        for (int i = 0; i < size; i++)
195            if (list.get(i).getClassName().equals(className))
196                return i;
197
198        return -1;
199    }
200
201    /**
202     * Returns true if the specified plugin is present in the specified list.
203     */
204    public static boolean existInList(List<PluginDescriptor> list, PluginDescriptor plugin)
205    {
206        return existInList(list, plugin.getIdent());
207    }
208
209    /**
210     * Returns true if the specified plugin is present in the specified list.
211     */
212    public static boolean existInList(List<PluginDescriptor> list, PluginIdent ident)
213    {
214        return getIndex(list, ident) != -1;
215    }
216
217    /**
218     * Returns true if the specified plugin is present in the specified list.
219     */
220    public static boolean existInList(List<PluginDescriptor> list, String className)
221    {
222        return getIndex(list, className) != -1;
223    }
224
225    public static boolean existInList(Set<PluginDescriptor> plugins, PluginIdent ident)
226    {
227        for (PluginDescriptor plugin : plugins)
228            if (plugin.getIdent().equals(ident))
229                return true;
230
231        return false;
232    }
233
234    public static void addToList(List<PluginDescriptor> list, PluginDescriptor plugin, int position)
235    {
236        if ((plugin != null) && !existInList(list, plugin))
237            list.add(position, plugin);
238    }
239
240    public static void addToList(List<PluginDescriptor> list, PluginDescriptor plugin)
241    {
242        if ((plugin != null) && !existInList(list, plugin))
243            list.add(plugin);
244    }
245
246    public static boolean removeFromList(List<PluginDescriptor> list, String className)
247    {
248        for (int i = list.size() - 1; i >= 0; i--)
249        {
250            final PluginDescriptor p = list.get(i);
251
252            if (p.getClassName().equals(className))
253            {
254                list.remove(i);
255                return true;
256            }
257        }
258
259        return false;
260    }
261
262    // public static String getPluginTypeString(int type)
263    // {
264    // if ((type >= 0) && (type < pluginTypeString.length))
265    // return pluginTypeString[type];
266    //
267    // return "";
268    // }
269
270    public static ArrayList<PluginDescriptor> getPlugins(List<PluginDescriptor> list, String className)
271    {
272        final ArrayList<PluginDescriptor> result = new ArrayList<PluginDescriptor>();
273
274        for (PluginDescriptor plugin : list)
275            if (plugin.getClassName().equals(className))
276                result.add(plugin);
277
278        return result;
279    }
280
281    public static PluginDescriptor getPlugin(List<PluginDescriptor> list, String className)
282    {
283        for (PluginDescriptor plugin : list)
284            if (plugin.getClassName().equals(className))
285                return plugin;
286
287        return null;
288    }
289
290    public static PluginDescriptor getPlugin(List<PluginDescriptor> list, PluginIdent ident, boolean acceptNewer)
291    {
292        if (acceptNewer)
293        {
294            for (PluginDescriptor plugin : list)
295                if (plugin.getIdent().isNewerOrEqual(ident))
296                    return plugin;
297        }
298        else
299        {
300            for (PluginDescriptor plugin : list)
301                if (plugin.getIdent().equals(ident))
302                    return plugin;
303        }
304
305        return null;
306    }
307
308    public PluginDescriptor()
309    {
310        super();
311
312        pluginClass = null;
313
314        icon = DEFAULT_ICON;
315        image = DEFAULT_IMAGE;
316
317        localXmlUrl = "";
318        xmlUrl = "";
319        name = "";
320        ident = new PluginIdent();
321        jarUrl = "";
322        imageUrl = "";
323        iconUrl = "";
324        author = "";
325        web = "";
326        email = "";
327        desc = "";
328        changeLog = "";
329
330        required = new ArrayList<PluginIdent>();
331        repository = null;
332
333        // default
334        enabled = true;
335        descriptorLoaded = true;
336        changeLogLoaded = true;
337        iconLoaded = true;
338        imageLoaded = true;
339    }
340
341    /**
342     * Create from class, used for local plugin.
343     */
344    public PluginDescriptor(Class<? extends Plugin> clazz)
345    {
346        this();
347
348        this.pluginClass = clazz;
349
350        final String baseResourceName;
351        final String baseLocalName;
352        final boolean bundled = isBundled();
353
354        // bundled plugin ?
355        if (bundled)
356        {
357            // find original JAR file
358            final String jarPath = getPluginJarPath();
359
360            // get base resource and local name from it
361            baseResourceName = FileUtil.getFileName(jarPath, false);
362            baseLocalName = FileUtil.setExtension(jarPath, "");
363        }
364        else
365        {
366            baseResourceName = clazz.getSimpleName();
367            baseLocalName = ClassUtil.getPathFromQualifiedName(clazz.getName());
368        }
369
370        // load icon
371        URL iconUrl = clazz.getResource(baseResourceName + getIconExtension());
372        if (iconUrl == null)
373            iconUrl = URLUtil.getURL(baseLocalName + getIconExtension());
374        // loadIcon(url);
375
376        // load image
377        URL imageUrl = clazz.getResource(baseResourceName + getImageExtension());
378        if (imageUrl == null)
379            imageUrl = URLUtil.getURL(baseLocalName + getImageExtension());
380        // loadImage(url);
381
382        // load xml
383        URL xmlUrl = clazz.getResource(baseResourceName + getXMLExtension());
384        if (xmlUrl == null)
385            xmlUrl = URLUtil.getURL(baseLocalName + getXMLExtension());
386
387        // can't load details from XML file or bundled plugin
388        if (!loadFromXML(xmlUrl) || bundled)
389        {
390            // set default informations
391            name = pluginClass.getSimpleName();
392
393            if (bundled)
394                desc = name + " plugin (Bundled)" + (!StringUtil.isEmpty(desc) ? "\n" + desc : "");
395            else
396                desc = name + " plugin";
397        }
398
399        // always overwrite class name from class object (more as bundled plugin may have incorrect one from XML file
400        ident.setClassName(pluginClass.getName());
401
402        // overwrite image, icon url with their local equivalent
403        this.iconUrl = iconUrl.toString();
404        this.imageUrl = imageUrl.toString();
405        // store local XML URL
406        this.localXmlUrl = xmlUrl.toString();
407
408        // only descriptor is loaded here
409        descriptorLoaded = true;
410        changeLogLoaded = false;
411        iconLoaded = false;
412        imageLoaded = false;
413    }
414
415    /**
416     * Create from plugin online identifier, used for online plugin only.
417     * 
418     * @throws IllegalArgumentException
419     */
420    public PluginDescriptor(PluginOnlineIdent ident, RepositoryInfo repos) throws IllegalArgumentException
421    {
422        this();
423
424        this.ident.setClassName(ident.getClassName());
425        this.ident.setVersion(ident.getVersion());
426        this.ident.setRequiredKernelVersion(ident.getRequiredKernelVersion());
427        this.xmlUrl = ident.getUrl();
428        this.name = ident.getName();
429        this.repository = repos;
430
431        // mark descriptor and images as not yet loaded
432        descriptorLoaded = false;
433        changeLogLoaded = false;
434        iconLoaded = false;
435        imageLoaded = false;
436    }
437
438    /**
439     * @deprecated Use {@link #loadDescriptor()} or {@link #loadAll()} instead
440     */
441    @Deprecated
442    public boolean load(boolean loadImages)
443    {
444        if (loadDescriptor())
445        {
446            loadChangeLog();
447            if (loadImages)
448                return loadImages();
449        }
450
451        return false;
452    }
453
454    /**
455     * Load descriptor informations (xmlUrl field should be correctly filled)
456     */
457    public boolean loadDescriptor()
458    {
459        return loadDescriptor(false);
460    }
461
462    /**
463     * Load descriptor informations (xmlUrl field should be correctly filled).<br>
464     * Returns <code>false</code> if the operation failed.
465     */
466    public boolean loadDescriptor(boolean reload)
467    {
468        // already loaded ?
469        if (descriptorLoaded && !reload)
470            return true;
471
472        // just to avoid retry indefinitely if it fails
473        descriptorLoaded = true;
474
475        // retrieve document
476        final Document document = XMLUtil.loadDocument(xmlUrl,
477                (repository != null) ? repository.getAuthenticationInfo() : null, true);
478
479        if (document != null)
480        {
481            // load xml
482            if (!loadFromXML(document.getDocumentElement()))
483            {
484                System.err.println("Can't find valid XML file from '" + xmlUrl + "' for plugin class '"
485                        + ident.getClassName() + "'");
486                return false;
487            }
488
489            return true;
490        }
491
492        // display error only for first load
493        if (!reload)
494            System.err.println(
495                    "Can't load XML file from '" + xmlUrl + "' for plugin class '" + ident.getClassName() + "'");
496
497        return false;
498    }
499
500    /**
501     * Load change log field (xmlUrl field should be correctly filled)
502     */
503    public boolean loadChangeLog()
504    {
505        // already loaded ?
506        if (changeLogLoaded)
507            return true;
508
509        // just to avoid retry indefinitely if it fails
510        changeLogLoaded = true;
511
512        // retrieve document
513        final Document document = XMLUtil.loadDocument(xmlUrl,
514                (repository != null) ? repository.getAuthenticationInfo() : null, true);
515
516        if (document != null)
517        {
518            final Element node = document.getDocumentElement();
519
520            if (node != null)
521            {
522                setChangeLog(XMLUtil.getElementValue(node, ID_CHANGELOG, ""));
523                return true;
524            }
525
526            System.err.println(
527                    "Can't find valid XML file from '" + xmlUrl + "' for plugin class '" + ident.getClassName() + "'");
528        }
529
530        System.err.println("Can't load XML file from '" + xmlUrl + "' for plugin class '" + ident.getClassName() + "'");
531
532        return false;
533    }
534
535    /**
536     * Load 64x64 icon (icon url field should be correctly filled)
537     */
538    public boolean loadIcon()
539    {
540        // already loaded ?
541        if (iconLoaded)
542            return true;
543
544        // need descriptor to be loaded first
545        loadDescriptor();
546        // just to avoid retry indefinitely if it fails
547        iconLoaded = true;
548
549        // load icon
550        return loadIcon(URLUtil.getURL(iconUrl));
551    }
552
553    /**
554     * Load 256x256 image (image url field should be correctly filled)
555     */
556    public boolean loadImage()
557    {
558        // already loaded ?
559        if (imageLoaded)
560            return true;
561
562        // need descriptor to be loaded first
563        loadDescriptor();
564        // just to avoid retry indefinitely if it fails
565        imageLoaded = true;
566
567        // load image
568        return loadImage(URLUtil.getURL(imageUrl));
569    }
570
571    /**
572     * Load icon and image (both icon and image url fields should be correctly filled)
573     */
574    public boolean loadImages()
575    {
576        return loadIcon() & loadImage();
577    }
578
579    /**
580     * Load descriptor and images if not already done
581     */
582    public boolean loadAll()
583    {
584        return loadDescriptor() & loadChangeLog() & loadImages();
585    }
586
587    /**
588     * Check if the plugin class is an instance of (or subclass of) the specified class.
589     */
590    public boolean isInstanceOf(Class<?> baseClazz)
591    {
592        return ClassUtil.isSubClass(pluginClass, baseClazz);
593    }
594
595    /**
596     * Return true if the plugin class is abstract
597     */
598    public boolean isAbstract()
599    {
600        return ClassUtil.isAbstract(pluginClass);
601    }
602
603    /**
604     * Return true if the plugin class is private
605     */
606    public boolean isPrivate()
607    {
608        return ClassUtil.isPrivate(pluginClass);
609    }
610
611    /**
612     * Return true if the plugin class is an interface
613     */
614    public boolean isInterface()
615    {
616        return pluginClass.isInterface();
617    }
618
619    /**
620     * return true if the plugin has an action which can be started from menu
621     */
622    public boolean isActionable()
623    {
624        return isClassLoaded() && !isPrivate() && !isAbstract() && !isInterface()
625                && isInstanceOf(PluginImageAnalysis.class);
626    }
627
628    /**
629     * Return true if the plugin is bundled inside another plugin (mean it does not have a proper
630     * descriptor)
631     */
632    public boolean isBundled()
633    {
634        return isClassLoaded() && isInstanceOf(PluginBundled.class);
635    }
636
637    /**
638     * Return true if the plugin is in beta state
639     */
640    public boolean isBeta()
641    {
642        return getVersion().isBeta();
643    }
644
645    /**
646     * Return true if this plugin is a system application plugin (declared in plugins.kernel
647     * package).
648     */
649    public boolean isKernelPlugin()
650    {
651        return getClassName().startsWith(PluginLoader.PLUGIN_KERNEL_PACKAGE + ".");
652    }
653
654    boolean loadIcon(URL url)
655    {
656        // load icon
657        if (url != null)
658            icon = ResourceUtil.getImageIcon(ImageUtil.load(NetworkUtil.getInputStream(url,
659                    (repository != null) ? repository.getAuthenticationInfo() : null, true, false), false), ICON_SIZE);
660
661        // get default icon
662        if (icon == null)
663        {
664            icon = DEFAULT_ICON;
665            return false;
666        }
667
668        return true;
669    }
670
671    boolean loadImage(URL url)
672    {
673        // load image
674        if (url != null)
675            image = ImageUtil
676                    .scale(ImageUtil.load(
677                            NetworkUtil.getInputStream(url,
678                                    (repository != null) ? repository.getAuthenticationInfo() : null, true, false),
679                            false), IMAGE_SIZE, IMAGE_SIZE);
680
681        // get default image
682        if (image == null)
683        {
684            image = DEFAULT_IMAGE;
685            return false;
686        }
687
688        return true;
689    }
690
691    // public void save()
692    // {
693    // // save icon
694    // if (icon != null)
695    // ImageUtil.saveImage(ImageUtil.toRenderedImage(icon.getImage()), "png", getIconFilename());
696    // // save image
697    // if (image != null)
698    // ImageUtil.saveImage(ImageUtil.toRenderedImage(image), "png", getImageFilename());
699    // // save xml
700    // saveToXML();
701    // }
702
703    public boolean loadFromXML(String path)
704    {
705        return XMLPersistentHelper.loadFromXML(this, path);
706    }
707
708    public boolean loadFromXML(URL xmlUrl)
709    {
710        return XMLPersistentHelper.loadFromXML(this, xmlUrl);
711    }
712
713    @Override
714    public boolean loadFromXML(Node node)
715    {
716        return loadFromXML(node, false);
717    }
718
719    public boolean loadFromXML(Node node, boolean loadChangeLog)
720    {
721        if (node == null)
722            return false;
723
724        // get the plugin ident
725        ident.loadFromXML(node);
726
727        setName(XMLUtil.getElementValue(node, ID_NAME, ""));
728        setXmlUrl(XMLUtil.getElementValue(node, ID_URL, ""));
729        setJarUrl(XMLUtil.getElementValue(node, ID_JAR_URL, ""));
730        setImageUrl(XMLUtil.getElementValue(node, ID_IMAGE_URL, ""));
731        setIconUrl(XMLUtil.getElementValue(node, ID_ICON_URL, ""));
732        setAuthor(XMLUtil.getElementValue(node, ID_AUTHOR, ""));
733        setWeb(XMLUtil.getElementValue(node, ID_WEB, ""));
734        setEmail(XMLUtil.getElementValue(node, ID_EMAIL, ""));
735        setDescription(XMLUtil.getElementValue(node, ID_DESCRIPTION, ""));
736        if (loadChangeLog)
737            setChangeLog(XMLUtil.getElementValue(node, ID_CHANGELOG, ""));
738        else
739            setChangeLog("");
740
741        final Node nodeDependances = XMLUtil.getElement(node, ID_DEPENDENCIES);
742        if (nodeDependances != null)
743        {
744            final ArrayList<Node> nodesDependances = XMLUtil.getChildren(nodeDependances, ID_DEPENDENCY);
745
746            for (Node n : nodesDependances)
747            {
748                final PluginIdent ident = new PluginIdent();
749                // required don't need URL information as we now search from classname
750                ident.loadFromXML(n);
751                if (!ident.isEmpty())
752                    required.add(ident);
753            }
754        }
755
756        return true;
757    }
758
759    public boolean saveToXML()
760    {
761        return XMLPersistentHelper.saveToXML(this, getXMLFilename());
762    }
763
764    @Override
765    public boolean saveToXML(Node node)
766    {
767        if (node == null)
768            return false;
769
770        ident.saveToXML(node);
771
772        XMLUtil.setElementValue(node, ID_NAME, getName());
773        XMLUtil.setElementValue(node, ID_URL, getXmlUrl());
774        XMLUtil.setElementValue(node, ID_JAR_URL, getJarUrl());
775        XMLUtil.setElementValue(node, ID_IMAGE_URL, getImageUrl());
776        XMLUtil.setElementValue(node, ID_ICON_URL, getIconUrl());
777        XMLUtil.setElementValue(node, ID_AUTHOR, getAuthor());
778        XMLUtil.setElementValue(node, ID_WEB, getWeb());
779        XMLUtil.setElementValue(node, ID_EMAIL, getEmail());
780        XMLUtil.setElementValue(node, ID_DESCRIPTION, getDescription());
781        loadChangeLog();
782        XMLUtil.setElementValue(node, ID_CHANGELOG, getChangeLog());
783
784        // synchronized (dateFormatter)
785        // {
786        // XMLUtil.addChildElement(root, ID_INSTALL_DATE, dateFormatter.format(installed));
787        // XMLUtil.addChildElement(root, ID_LASTUSE_DATE, dateFormatter.format(lastUse));
788        // }
789
790        // final Element publicClasses = XMLUtil.setElement(node, ID_PUBLIC_CLASSES);
791        // if (publicClasses != null)
792        // {
793        // XMLUtil.removeAllChilds(publicClasses);
794        // for (String className : publicClasseNames)
795        // XMLUtil.addValue(XMLUtil.addElement(publicClasses, ID_CLASSNAME), className);
796        // }
797
798        final Element dependances = XMLUtil.setElement(node, ID_DEPENDENCIES);
799        if (dependances != null)
800        {
801            XMLUtil.removeAllChildren(dependances);
802            for (PluginIdent dep : required)
803                dep.saveToXML(XMLUtil.addElement(dependances, ID_DEPENDENCY));
804        }
805
806        return true;
807    }
808
809    public boolean isClassLoaded()
810    {
811        return pluginClass != null;
812    }
813
814    /**
815     * Returns the plugin class name.<br>
816     * Ex: "plugins.tutorial.Example1"
817     */
818    public String getClassName()
819    {
820        return ident.getClassName();
821    }
822
823    public String getSimpleClassName()
824    {
825        return ident.getSimpleClassName();
826    }
827
828    /**
829     * Returns the package name of the plugin class.
830     */
831    public String getPackageName()
832    {
833        return ident.getPackageName();
834    }
835
836    /**
837     * Returns the minimum package name (remove "icy" or/and "plugin" header)<br>
838     */
839    public String getSimplePackageName()
840    {
841        return ident.getSimplePackageName();
842    }
843
844    /**
845     * Returns the author package name (first part of simple package name)
846     */
847    public String getAuthorPackageName()
848    {
849        return ident.getAuthorPackageName();
850    }
851
852    /**
853     * @deprecated useless method
854     */
855    @Deprecated
856    public String getClassAsString()
857    {
858        if (pluginClass != null)
859            return pluginClass.toString();
860
861        return "";
862    }
863
864    /**
865     * @return the pluginClass
866     */
867    public Class<? extends Plugin> getPluginClass()
868    {
869        return pluginClass;
870    }
871
872    /**
873     * @return the JAR file hosting this plugin (returns <code>null</code> if the plugin is not installed).<br>
874     */
875    public String getPluginJarPath()
876    {
877        if (pluginClass != null)
878            return ClassUtil.getJarPath(pluginClass);
879
880        return null;
881    }
882
883    /**
884     * return associated filename
885     */
886    public String getFilename()
887    {
888        return ClassUtil.getPathFromQualifiedName(getClassName());
889    }
890
891    /**
892     * Returns the XML file extension.
893     */
894    public String getXMLExtension()
895    {
896        return XMLUtil.FILE_DOT_EXTENSION;
897    }
898
899    /**
900     * return xml filename (local XML file)
901     */
902    public String getXMLFilename()
903    {
904        if (!StringUtil.isEmpty(localXmlUrl))
905            return localXmlUrl;
906
907        return getFilename() + getXMLExtension();
908    }
909
910    /**
911     * return icon extension
912     */
913    public String getIconExtension()
914    {
915        return "_icon.png";
916    }
917
918    /**
919     * return icon filename
920     */
921    public String getIconFilename()
922    {
923        return getFilename() + getIconExtension();
924    }
925
926    /**
927     * return image extension
928     */
929    public String getImageExtension()
930    {
931        return ".png";
932    }
933
934    /**
935     * return image filename
936     */
937    public String getImageFilename()
938    {
939        return getFilename() + getImageExtension();
940    }
941
942    /**
943     * Returns the JAR file extension.
944     */
945    public String getJarExtension()
946    {
947        return JarUtil.FILE_DOT_EXTENSION;
948    }
949
950    /**
951     * return jar filename
952     */
953    public String getJarFilename()
954    {
955        return getFilename() + getJarExtension();
956    }
957
958    /**
959     * @return the icon
960     */
961    public ImageIcon getIcon()
962    {
963        loadIcon();
964        return icon;
965    }
966
967    /**
968     * @return the icon as image
969     */
970    public Image getIconAsImage()
971    {
972        final ImageIcon i = getIcon();
973
974        if (i != null)
975            return i.getImage();
976
977        return null;
978    }
979
980    /**
981     * @return the image
982     */
983    public Image getImage()
984    {
985        loadImage();
986        return image;
987    }
988
989    // /**
990    // * @return the lastUse
991    // */
992    // public Date getLastUse()
993    // {
994    // return lastUse;
995    // }
996    //
997    // /**
998    // * @param lastUse
999    // * the lastUse to set
1000    // */
1001    // public void setLastUse(Date lastUse)
1002    // {
1003    // this.lastUse = lastUse;
1004    // }
1005
1006    /**
1007     * @return the ident
1008     */
1009    public PluginIdent getIdent()
1010    {
1011        return ident;
1012    }
1013
1014    /**
1015     * @return the name
1016     */
1017    public String getName()
1018    {
1019        return name;
1020    }
1021
1022    /**
1023     * @return the version
1024     */
1025    public Version getVersion()
1026    {
1027        if (ident != null)
1028            return ident.getVersion();
1029
1030        return new Version();
1031    }
1032
1033    // /**
1034    // * @return the url for current version
1035    // */
1036    // public String getUrlCurrent()
1037    // {
1038    // if (ident != null)
1039    // {
1040    // final Version ver = ident.getVersion();
1041    //
1042    // if (ver.isBeta())
1043    // return ident.getUrlBeta();
1044    //
1045    // return ident.getUrlStable();
1046    // }
1047    //
1048    // return "";
1049    // }
1050
1051    /**
1052     * @return the url
1053     */
1054    public String getUrl()
1055    {
1056        // url is default XML url
1057        return getXmlUrl();
1058    }
1059
1060    /**
1061     * @return the url for xml file
1062     */
1063    public String getXmlUrl()
1064    {
1065        return xmlUrl;
1066    }
1067
1068    /**
1069     * @return the desc
1070     * @deprecated use {@link #getDescription()} instead
1071     */
1072    @Deprecated
1073    public String getDesc()
1074    {
1075        return getDescription();
1076    }
1077
1078    /**
1079     * @return the description
1080     */
1081    public String getDescription()
1082    {
1083        return desc;
1084    }
1085
1086    /**
1087     * @param xmlUrl
1088     *        the xmlUrl to set
1089     */
1090    public void setXmlUrl(String xmlUrl)
1091    {
1092        this.xmlUrl = xmlUrl;
1093    }
1094
1095    /**
1096     * @param repository
1097     *        the repository to set
1098     */
1099    public void setRepository(RepositoryInfo repository)
1100    {
1101        this.repository = repository;
1102    }
1103
1104    /**
1105     * @return the jarUrl
1106     */
1107    public String getJarUrl()
1108    {
1109        return jarUrl;
1110    }
1111
1112    /**
1113     * @param jarUrl
1114     *        the jarUrl to set
1115     */
1116    public void setJarUrl(String jarUrl)
1117    {
1118        this.jarUrl = jarUrl;
1119    }
1120
1121    /**
1122     * @return the imageUrl
1123     */
1124    public String getImageUrl()
1125    {
1126        return imageUrl;
1127    }
1128
1129    /**
1130     * @param imageUrl
1131     *        the imageUrl to set
1132     */
1133    public void setImageUrl(String imageUrl)
1134    {
1135        this.imageUrl = imageUrl;
1136    }
1137
1138    /**
1139     * @return the iconUrl
1140     */
1141    public String getIconUrl()
1142    {
1143        return iconUrl;
1144    }
1145
1146    /**
1147     * @param iconUrl
1148     *        the iconUrl to set
1149     */
1150    public void setIconUrl(String iconUrl)
1151    {
1152        this.iconUrl = iconUrl;
1153    }
1154
1155    /**
1156     * Returns the author's plugin name.
1157     */
1158    public String getAuthor()
1159    {
1160        return author;
1161    }
1162
1163    /**
1164     * Returns the website url of this plugin.
1165     */
1166    public String getWeb()
1167    {
1168        return web;
1169    }
1170
1171    /**
1172     * @return the email
1173     */
1174    public String getEmail()
1175    {
1176        return email;
1177    }
1178
1179    /**
1180     * @return the changeLog
1181     */
1182    public String getChangeLog()
1183    {
1184        return changeLog;
1185    }
1186
1187    /**
1188     * @deprecated Use {@link #getChangeLog()} instead
1189     */
1190    @Deprecated
1191    public String getChangesLog()
1192    {
1193        return getChangeLog();
1194    }
1195
1196    /**
1197     * @return the requiredKernelVersion
1198     */
1199    public Version getRequiredKernelVersion()
1200    {
1201        return ident.getRequiredKernelVersion();
1202    }
1203
1204    /**
1205     * Returns true if descriptor is loaded.
1206     */
1207    public boolean isDescriptorLoaded()
1208    {
1209        return descriptorLoaded;
1210    }
1211
1212    /**
1213     * @deprecated Use {@link #isDescriptorLoaded()} instead
1214     */
1215    @Deprecated
1216    public boolean isLoaded()
1217    {
1218        return descriptorLoaded;
1219    }
1220
1221    /**
1222     * Returns true if change log is loaded.
1223     */
1224    public boolean isChangeLogLoaded()
1225    {
1226        return changeLogLoaded;
1227    }
1228
1229    /**
1230     * Returns true if icon is loaded.
1231     */
1232    public boolean isIconLoaded()
1233    {
1234        return iconLoaded;
1235    }
1236
1237    /**
1238     * Returns true if image is loaded.
1239     */
1240    public boolean isImageLoaded()
1241    {
1242        return imageLoaded;
1243    }
1244
1245    /**
1246     * Returns true if image and icon are loaded.
1247     */
1248    public boolean isImagesLoaded()
1249    {
1250        return iconLoaded && imageLoaded;
1251    }
1252
1253    /**
1254     * Returns true if both descriptor and images are loaded.
1255     */
1256    public boolean isAllLoaded()
1257    {
1258        return descriptorLoaded && changeLogLoaded && iconLoaded && imageLoaded;
1259    }
1260
1261    /**
1262     * @return the required
1263     */
1264    public List<PluginIdent> getRequired()
1265    {
1266        return new ArrayList<PluginIdent>(required);
1267    }
1268
1269    public RepositoryInfo getRepository()
1270    {
1271        return repository;
1272    }
1273
1274    /**
1275     * @return the enabled
1276     */
1277    public boolean isEnabled()
1278    {
1279        return enabled;
1280    }
1281
1282    /**
1283     * @param enabled
1284     *        the enabled to set
1285     */
1286    public void setEnabled(boolean enabled)
1287    {
1288        this.enabled = enabled;
1289    }
1290
1291    /**
1292     * Return true if plugin is installed (corresponding JAR file exist)
1293     */
1294    public boolean isInstalled()
1295    {
1296        return FileUtil.exists(getJarFilename());
1297    }
1298
1299    // /**
1300    // * @return the hasUpdate
1301    // */
1302    // public boolean getHasUpdate()
1303    // {
1304    // // true if online version > local version
1305    // return (onlineDescriptor != null) && onlineDescriptor.getVersion().isGreater(getVersion());
1306    // }
1307    //
1308    // /**
1309    // * @return the checkingForUpdate
1310    // */
1311    // public boolean isCheckingForUpdate()
1312    // {
1313    // return checkingForUpdate;
1314    // }
1315    //
1316    // /**
1317    // * @return the onlineDescriptor
1318    // */
1319    // public PluginDescriptor getOnlineDescriptor()
1320    // {
1321    // return onlineDescriptor;
1322    // }
1323
1324    // /**
1325    // * @return the updateChecked
1326    // */
1327    // public boolean isUpdateChecked()
1328    // {
1329    // return updateChecked;
1330    // }
1331    //
1332    // /**
1333    // * check for update (asynchronous as it can take sometime)
1334    // */
1335    // public void checkForUpdate()
1336    // {
1337    // if (updateChecked)
1338    // return;
1339    //
1340    // checkingForUpdate = true;
1341    //
1342    // ThreadUtil.bgRunWait(new Runnable()
1343    // {
1344    // @Override
1345    // public void run()
1346    // {
1347    // try
1348    // {
1349    // onlineDescriptor = getOnlinePlugin(getIdent(), false);
1350    // }
1351    // catch (Exception E)
1352    // {
1353    // onlineDescriptor = null;
1354    // }
1355    // finally
1356    // {
1357    // checkingForUpdate = false;
1358    // updateChecked = true;
1359    // }
1360    // }
1361    // });
1362    // }
1363
1364    /**
1365     * @param name
1366     *        the name to set
1367     */
1368    public void setName(String name)
1369    {
1370        this.name = name;
1371    }
1372
1373    /**
1374     * @param author
1375     *        the author to set
1376     */
1377    public void setAuthor(String author)
1378    {
1379        this.author = author;
1380    }
1381
1382    /**
1383     * @param web
1384     *        the web to set
1385     */
1386    public void setWeb(String web)
1387    {
1388        this.web = web;
1389    }
1390
1391    /**
1392     * @param email
1393     *        the email to set
1394     */
1395    public void setEmail(String email)
1396    {
1397        this.email = email;
1398    }
1399
1400    /**
1401     * @param desc
1402     *        the description to set
1403     */
1404    public void setDescription(String desc)
1405    {
1406        this.desc = desc;
1407    }
1408
1409    /**
1410     * @param value
1411     *        the changeLog to set
1412     */
1413    public void setChangeLog(String value)
1414    {
1415        this.changeLog = value;
1416    }
1417
1418    /**
1419     * @deprecated use {@link #setChangeLog(String)}
1420     */
1421    @Deprecated
1422    public void setChangesLog(String value)
1423    {
1424        setChangeLog(value);
1425    }
1426
1427    /**
1428     * Return true if specified plugin is required by current plugin
1429     */
1430    public boolean requires(PluginDescriptor plugin)
1431    {
1432        final PluginIdent curIdent = plugin.getIdent();
1433
1434        for (PluginIdent ident : required)
1435            if (ident.isOlderOrEqual(curIdent))
1436                return true;
1437
1438        return false;
1439    }
1440
1441    public boolean isOlderOrEqual(PluginDescriptor plugin)
1442    {
1443        return ident.isOlderOrEqual(plugin.getIdent());
1444    }
1445
1446    public boolean isOlder(PluginDescriptor plugin)
1447    {
1448        return ident.isOlder(plugin.getIdent());
1449    }
1450
1451    public boolean isNewerOrEqual(PluginDescriptor plugin)
1452    {
1453        return ident.isNewerOrEqual(plugin.getIdent());
1454    }
1455
1456    public boolean isNewer(PluginDescriptor plugin)
1457    {
1458        return ident.isNewer(plugin.getIdent());
1459    }
1460
1461    @Override
1462    public String toString()
1463    {
1464        return getName() + " " + getVersion().toString();
1465    }
1466
1467    @Override
1468    public boolean equals(Object obj)
1469    {
1470        if (obj instanceof PluginDescriptor)
1471        {
1472            final PluginDescriptor plug = (PluginDescriptor) obj;
1473
1474            return getClassName().equals(plug.getClassName()) && getVersion().equals(plug.getVersion());
1475        }
1476
1477        return super.equals(obj);
1478    }
1479
1480    @Override
1481    public int hashCode()
1482    {
1483        return getClassName().hashCode() ^ getVersion().hashCode();
1484    }
1485
1486    public static class PluginIdent implements XMLPersistent
1487    {
1488        /**
1489         * Returns the index for the specified plugin ident in the specified list.<br>
1490         * Returns -1 if not found.
1491         */
1492        public static int getIndex(List<PluginIdent> list, PluginIdent ident)
1493        {
1494            final int size = list.size();
1495
1496            for (int i = 0; i < size; i++)
1497                if (list.get(i).equals(ident))
1498                    return i;
1499
1500            return -1;
1501        }
1502
1503        /**
1504         * Returns the index for the specified plugin in the specified list.<br>
1505         * Returns -1 if not found.
1506         */
1507        public static int getIndex(List<? extends PluginIdent> list, String className)
1508        {
1509            final int size = list.size();
1510
1511            for (int i = 0; i < size; i++)
1512                if (list.get(i).getClassName().equals(className))
1513                    return i;
1514
1515            return -1;
1516        }
1517
1518        public static final String ID_CLASSNAME = "classname";
1519        public static final String ID_VERSION = "version";
1520        public static final String ID_REQUIRED_KERNEL_VERSION = "required_kernel_version";
1521
1522        protected String className;
1523        protected Version version;
1524        protected Version requiredKernelVersion;
1525
1526        /**
1527         * 
1528         */
1529        public PluginIdent()
1530        {
1531            super();
1532
1533            // default
1534            className = "";
1535            version = new Version();
1536            requiredKernelVersion = new Version();
1537        }
1538
1539        public boolean loadFromXMLShort(Node node)
1540        {
1541            if (node == null)
1542                return false;
1543
1544            setClassName(XMLUtil.getElementValue(node, ID_CLASSNAME, ""));
1545            setVersion(new Version(XMLUtil.getElementValue(node, ID_VERSION, "")));
1546
1547            return true;
1548        }
1549
1550        @Override
1551        public boolean loadFromXML(Node node)
1552        {
1553            if (!loadFromXMLShort(node))
1554                return false;
1555
1556            setRequiredKernelVersion(new Version(XMLUtil.getElementValue(node, ID_REQUIRED_KERNEL_VERSION, "")));
1557
1558            return true;
1559        }
1560
1561        public boolean saveToXMLShort(Node node)
1562        {
1563            if (node == null)
1564                return false;
1565
1566            XMLUtil.setElementValue(node, ID_CLASSNAME, getClassName());
1567            XMLUtil.setElementValue(node, ID_VERSION, getVersion().toString());
1568
1569            return true;
1570        }
1571
1572        @Override
1573        public boolean saveToXML(Node node)
1574        {
1575            if (!saveToXMLShort(node))
1576                return false;
1577
1578            XMLUtil.setElementValue(node, ID_REQUIRED_KERNEL_VERSION, getRequiredKernelVersion().toString());
1579
1580            return true;
1581        }
1582
1583        public boolean isEmpty()
1584        {
1585            return StringUtil.isEmpty(className) && version.isEmpty() && requiredKernelVersion.isEmpty();
1586        }
1587
1588        /**
1589         * @return the className
1590         */
1591        public String getClassName()
1592        {
1593            return className;
1594        }
1595
1596        /**
1597         * @param className
1598         *        the className to set
1599         */
1600        public void setClassName(String className)
1601        {
1602            this.className = className;
1603        }
1604
1605        /**
1606         * return the simple className
1607         */
1608        public String getSimpleClassName()
1609        {
1610            return ClassUtil.getSimpleClassName(className);
1611        }
1612
1613        /**
1614         * return the package name
1615         */
1616        public String getPackageName()
1617        {
1618            return ClassUtil.getPackageName(className);
1619        }
1620
1621        /**
1622         * return the minimum package name (remove "icy" or/and "plugin" header)<br>
1623         */
1624        public String getSimplePackageName()
1625        {
1626            String result = getPackageName();
1627
1628            if (result.startsWith("icy."))
1629                result = result.substring(4);
1630            if (result.startsWith(PluginLoader.PLUGIN_PACKAGE))
1631                result = result.substring(PluginLoader.PLUGIN_PACKAGE.length() + 1);
1632
1633            return result;
1634        }
1635
1636        /**
1637         * return the author package name (first part of simple package name)
1638         */
1639        public String getAuthorPackageName()
1640        {
1641            final String result = getSimplePackageName();
1642            final int index = result.indexOf('.');
1643
1644            if (index != -1)
1645                return result.substring(0, index);
1646
1647            return result;
1648        }
1649
1650        /**
1651         * @param version
1652         *        the version to set
1653         */
1654        public void setVersion(Version version)
1655        {
1656            this.version = version;
1657        }
1658
1659        /**
1660         * @return the version
1661         */
1662        public Version getVersion()
1663        {
1664            return version;
1665        }
1666
1667        /**
1668         * @return the requiredKernelVersion
1669         */
1670        public Version getRequiredKernelVersion()
1671        {
1672            return requiredKernelVersion;
1673        }
1674
1675        /**
1676         * @param requiredKernelVersion
1677         *        the requiredKernelVersion to set
1678         */
1679        public void setRequiredKernelVersion(Version requiredKernelVersion)
1680        {
1681            this.requiredKernelVersion = requiredKernelVersion;
1682        }
1683
1684        public boolean isOlderOrEqual(PluginIdent ident)
1685        {
1686            return className.equals(ident.getClassName()) && version.isOlderOrEqual(ident.getVersion());
1687        }
1688
1689        public boolean isOlder(PluginIdent ident)
1690        {
1691            return className.equals(ident.getClassName()) && version.isOlder(ident.getVersion());
1692        }
1693
1694        public boolean isNewerOrEqual(PluginIdent ident)
1695        {
1696            return className.equals(ident.getClassName()) && version.isNewerOrEqual(ident.getVersion());
1697        }
1698
1699        public boolean isNewer(PluginIdent ident)
1700        {
1701            return className.equals(ident.getClassName()) && version.isNewer(ident.getVersion());
1702        }
1703
1704        @Override
1705        public boolean equals(Object obj)
1706        {
1707            if (obj instanceof PluginIdent)
1708            {
1709                final PluginIdent ident = (PluginIdent) obj;
1710                return ident.getClassName().equals(className) && ident.getVersion().equals(getVersion());
1711            }
1712
1713            return super.equals(obj);
1714        }
1715
1716        @Override
1717        public int hashCode()
1718        {
1719            return className.hashCode() ^ version.hashCode();
1720        }
1721
1722        @Override
1723        public String toString()
1724        {
1725            return className + " " + version.toString();
1726        }
1727    }
1728
1729    public static class PluginOnlineIdent extends PluginIdent
1730    {
1731        protected String name;
1732        protected String url;
1733
1734        public PluginOnlineIdent()
1735        {
1736            super();
1737
1738            name = "";
1739            url = "";
1740        }
1741
1742        /**
1743         * @return the name
1744         */
1745        public String getName()
1746        {
1747            return name;
1748        }
1749
1750        public void setName(String name)
1751        {
1752            this.name = name;
1753        }
1754
1755        /**
1756         * @return the url
1757         */
1758        public String getUrl()
1759        {
1760            return url;
1761        }
1762
1763        public void setUrl(String url)
1764        {
1765            this.url = url;
1766        }
1767
1768        @Override
1769        public boolean loadFromXML(Node node)
1770        {
1771            if (super.loadFromXML(node))
1772            {
1773                setName(XMLUtil.getElementValue(node, PluginDescriptor.ID_NAME, ""));
1774                setUrl(XMLUtil.getElementValue(node, PluginDescriptor.ID_URL, ""));
1775                return true;
1776            }
1777
1778            return false;
1779        }
1780
1781        @Override
1782        public boolean saveToXML(Node node)
1783        {
1784            if (super.saveToXML(node))
1785            {
1786                XMLUtil.setElementValue(node, PluginDescriptor.ID_NAME, getName());
1787                XMLUtil.setElementValue(node, PluginDescriptor.ID_URL, getUrl());
1788                return true;
1789            }
1790
1791            return false;
1792        }
1793    }
1794
1795    /**
1796     * Sort plugins on name with kernel plugins appearing first.
1797     */
1798    public static class PluginKernelNameSorter implements Comparator<PluginDescriptor>
1799    {
1800        // static class
1801        public static PluginKernelNameSorter instance = new PluginKernelNameSorter();
1802
1803        // static class
1804        private PluginKernelNameSorter()
1805        {
1806            super();
1807        }
1808
1809        @Override
1810        public int compare(PluginDescriptor o1, PluginDescriptor o2)
1811        {
1812            final String packageName1 = o1.getPackageName();
1813            final String packageName2 = o2.getPackageName();
1814
1815            if (packageName1.startsWith(PluginLoader.PLUGIN_KERNEL_PACKAGE))
1816            {
1817                if (!packageName2.startsWith(PluginLoader.PLUGIN_KERNEL_PACKAGE))
1818                    return -1;
1819            }
1820            else if (packageName2.startsWith(PluginLoader.PLUGIN_KERNEL_PACKAGE))
1821                return 1;
1822
1823            return o1.toString().compareToIgnoreCase(o2.toString());
1824        }
1825    }
1826
1827    /**
1828     * Sort plugins on name.
1829     */
1830    public static class PluginNameSorter implements Comparator<PluginDescriptor>
1831    {
1832        // static class
1833        public static PluginNameSorter instance = new PluginNameSorter();
1834
1835        // static class
1836        private PluginNameSorter()
1837        {
1838            super();
1839        }
1840
1841        @Override
1842        public int compare(PluginDescriptor o1, PluginDescriptor o2)
1843        {
1844            return o1.toString().compareToIgnoreCase(o2.toString());
1845        }
1846    }
1847
1848    /**
1849     * Sort plugins on class name.
1850     */
1851    public static class PluginClassNameSorter implements Comparator<PluginDescriptor>
1852    {
1853        // static class
1854        public static PluginClassNameSorter instance = new PluginClassNameSorter();
1855
1856        // static class
1857        private PluginClassNameSorter()
1858        {
1859            super();
1860        }
1861
1862        @Override
1863        public int compare(PluginDescriptor o1, PluginDescriptor o2)
1864        {
1865            return o1.getClassName().compareToIgnoreCase(o2.getClassName());
1866        }
1867    }
1868
1869}