001/* ----------------------------------------------------------------------------
002   The Kiwi Toolkit - A Java Class Library
003   Copyright (C) 1998-2004 Mark A. Lindner
004
005   This library is free software; you can redistribute it and/or
006   modify it under the terms of the GNU General Public License as
007   published by the Free Software Foundation; either version 2 of the
008   License, or (at your option) any later version.
009
010   This library is distributed in the hope that it will be useful,
011   but WITHOUT ANY WARRANTY; without even the implied warranty of
012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013   General Public License for more details.
014
015   You should have received a copy of the GNU General Public License
016   along with this library; if not, write to the Free Software
017   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018   02111-1307, USA.
019 
020   The author may be contacted at: mark_a_lindner@yahoo.com
021   ----------------------------------------------------------------------------
022   $Log: Plugin.java,v $
023   Revision 1.7  2004/05/12 18:03:42  markl
024   javadoc updates
025
026   Revision 1.6  2004/03/15 06:47:46  markl
027   Major rewrite to allow multiple instantiations of a given plugin, and
028   reload capability.
029
030   Revision 1.5  2003/01/19 09:31:22  markl
031   Javadoc & comment header updates.
032
033   Revision 1.4  2001/06/26 06:15:58  markl
034   Added setConfig() method.
035
036   Revision 1.3  2001/03/20 00:54:56  markl
037   Fixed deprecated calls.
038
039   Revision 1.2  2001/03/18 07:58:50  markl
040   Added javadoc.
041
042   Revision 1.1  2001/03/12 10:20:59  markl
043   New classes.
044   ----------------------------------------------------------------------------
045*/
046
047package kiwi.util.plugin;
048
049import java.awt.*;
050import java.io.*;
051import java.net.*;
052import java.util.*;
053import java.util.jar.*;
054import javax.swing.Icon;
055import javax.swing.ImageIcon;
056import javax.swing.event.*;
057
058import kiwi.event.*;
059import kiwi.io.*;
060import kiwi.util.*;
061
062/** A class that represents a plugin. A <code>Plugin</code> object encapsulates
063 * all of the data that makes up a plugin, as specified by the plugin's
064 * entry in a JAR Manifest. Instances of the plugin object itself can be
065 * created with the <code>newInstance()</code> method.
066 *
067 * @since Kiwi 1.3
068 *
069 * @author Mark Lindner
070 */
071
072public final class Plugin
073  {
074  private boolean loaded = false;
075  private String className;
076  private String name;
077  private String type;
078  private String desc;
079  private String iconFile;
080  private Properties props = new Properties();
081  private String version;
082  private String expectedType;
083  private String jarFile;
084  /* stuff that must be loaded */
085  private Class pluginClass = null;
086  private Icon icon = null;
087  private JarFile jar = null;
088  private URL helpURL = null;
089  /* support objects */
090  private PluginClassLoader loader;
091  private PluginLocator locator;
092  private EventListenerList listeners = new EventListenerList(); 
093  
094  /* Construct a new plugin. A plugin is uniquely identified by a jar file
095   * and a class name. It's initially created by the PluginLocator which scans
096   * a jar file for plugin entires. When it's reloaded, it reopens the jar file
097   * and rescans everything. (hopefully...will this really work?)
098   */
099
100  Plugin(PluginLocator locator, String jarFile, String expectedType)
101    throws PluginException
102    {
103    this.locator = locator;
104    this.jarFile = jarFile;
105    this.expectedType = expectedType;
106    load();
107    }
108
109  /** Get the version number for this plugin.
110   *
111   * @return The version number.
112   */
113
114  public String getVersion()
115    {
116    return(version);
117    }
118
119  /** Get the class name for this plugin.
120   *
121   * @return The class name.
122   */
123  
124  public String getClassName()
125    {
126    return(className);
127    }
128
129  /** Get the <code>PluginContext</code> for this plugin.
130   *
131   * @return The context.
132   */
133
134  public PluginContext getContext()
135    {
136    return(locator.getContext());
137    }
138
139  /** Get the name of this plugin.
140   *
141   * @return The name.
142   */
143  
144  public String getName()
145    {
146    return(name);
147    }
148
149  /** Get the type of this plugin.
150   *
151   * @return The type.
152   */
153  
154  public String getType()
155    {
156    return(type);
157    }
158
159  /** Get the path to the file that the plugin was loaded from.
160   *
161   * @since Kiwi 2.0
162   */
163
164  public String getFile()
165    {
166    return(jarFile);
167    }
168  
169  /** Get the user-defined properties for the plugin. These correspond to the
170   * user-defined key/value pairs in the Manifest entry for this plugin.
171   *
172   * @return A <code>Properties</code> object containing the user-defined
173   * properties.
174   */
175
176  public Properties getProperties()
177    {
178    return(props);
179    }
180
181  /** Get a specific user-defined property for the plugin. User-defined
182   * properties are those fields in the Manifest entry for the plugin that are
183   * not defined and recognized by the plugin framework itself.
184   *
185   * @param name The property name (key).
186   * @return The value of the property, or <code>null</code> if there is no
187   * property with the given name.
188   */
189
190  public String getProperty(String name)
191    {
192    return(props.getProperty(name));
193    }
194
195  /** Get a specific user-defined property for the plugin.
196   *
197   * @param name The property name (key).
198   * @param defaultValue The default value for this property.
199   * @return The value of the property, or <code>defaultValue</code> if there
200   * is no property with the given name.
201   */
202  
203  public String getProperty(String name, String defaultValue)
204    {
205    return(props.getProperty(name, defaultValue));
206    }
207
208  /** Get the description of this plugin.
209   *
210   * @return The description, or <b>null</b> if no description is available.
211   */
212  
213  public String getDescription()
214    {
215    return(desc);
216    }
217  
218  /** Get the icon for this plugin.
219   *
220   * @return The icon, or <b>null</b> if no icon is available.
221   */
222  
223  public Icon getIcon()
224    {
225    return(icon);
226    }
227
228  /** Get the help URL for this plugin.
229   *
230   * @return The URL, or <b>null</b> if no URL is available.
231   *
232   * @since Kiwi 2.0
233   */
234  
235  public URL getHelpURL()
236    {
237    return(helpURL);
238    }
239
240  /** Determine if the plugin is loaded.
241   *
242   * @return <b>true</b> if the plugin is loaded and <b>false</b> otherwise.
243   */
244
245  public boolean isLoaded()
246    {
247    return(loaded);
248    }
249
250  /** Load the plugin. This method attempts to load the plugin entry-point
251   * class and create an instance of it. The entry-point class must have a
252   * default public constructor.
253   *
254   * @throws kiwi.util.plugin.PluginException If the plugin could not be
255   * loaded.
256   */
257
258  private void load() throws PluginException
259    {
260    Manifest mf = null;
261    
262    try
263      {
264      jar = new JarFile(new File(jarFile));
265
266      // open the manifest and find the plugin entry
267    
268      mf = jar.getManifest();
269      }
270    catch(IOException ex)
271      {
272      throw(new PluginException("Unable to read archive"));
273      }
274      
275    Plugin plugin = null;
276      
277    if(mf == null)
278      throw(new PluginException("No manifest found"));
279      
280    String classFile = null;
281    Attributes attrs = null;
282    boolean found = false;
283    
284    Map map = mf.getEntries();
285    Iterator iter = map.keySet().iterator();
286    while(iter.hasNext())
287      {
288      classFile = (String)iter.next();
289      attrs = mf.getAttributes(classFile);
290        
291      if(attrs.getValue("PluginName") != null)
292        {
293        found = true;
294        break;
295        }
296      }
297      
298    if(! found)
299      throw(new PluginException("No plugin entry found in manifest"));
300
301    className = PluginClassLoader.pathToClassName(classFile);
302    
303    // read in the attributes
304      
305    iter = attrs.keySet().iterator();
306      
307    while(iter.hasNext())
308      {
309      Attributes.Name nm = (Attributes.Name)iter.next();
310      String a = nm.toString();
311      String v = attrs.getValue(nm);
312        
313      if(a.equals("PluginName"))
314        name = v;
315      else if(a.equals("PluginType"))
316        type = v;
317      else if(a.equals("PluginDescription"))
318        desc = v;
319      else if(a.equals("PluginIcon"))
320        iconFile = v;
321      else if(a.equals("PluginVersion"))
322        version = v;
323      else if(a.equals("PluginHelpURL"))
324        {
325        try { helpURL = new URL(v); }
326        catch(MalformedURLException ex) { /* ignore */ };
327        }
328      else
329        props.put(a, v);
330      }
331      
332    if((type == null)  || (name == null))
333      {
334      try { jar.close(); } catch(IOException ex) { }
335      throw(new PluginException("Invalid plugin manifest entry"));
336      }
337      
338    if(! type.equals(expectedType))
339      {
340      try { jar.close(); } catch(IOException ex) { }
341      throw(new PluginException("Plugin type mismatch"));
342      }
343      
344    // create the classloader
345      
346    loader = locator.createClassLoader();
347    loader.addJarFile(jar);
348      
349    // load the icon
350      
351    if(iconFile != null
352       && ! GraphicsEnvironment.getLocalGraphicsEnvironment().isHeadless())
353      {
354      JarEntry entry = (JarEntry)jar.getEntry(iconFile);
355      if(entry != null)
356        {
357        try
358          {
359          InputStream in = jar.getInputStream(entry);
360          Image im = locator.getDecoder().decodeImage(in);
361          if(im != null)
362            icon = new ImageIcon(im);
363          in.close();
364          }
365        catch(IOException ex)
366          {
367          icon = null;
368          }
369        }
370      }
371    
372    // load the plugin class
373    
374    try
375      {
376      pluginClass = loader.loadClass(className);
377      }
378    catch(Exception ex)
379      {
380      ex.printStackTrace();
381
382      throw(new PluginException(ex.toString()));
383      }
384
385    loaded = true;
386    }
387
388  /* Unload the plugin.
389   */
390
391  private void unload()
392    {
393    loader = null;
394    pluginClass = null;
395    icon = null;
396    try { jar.close(); } catch(IOException ex) { }
397    jar = null;
398    loaded = false;
399    }
400
401  /** Reload the plugin.
402   * 
403   * @since Kiwi 2.0
404   */
405
406  public void reload() throws PluginException
407    {
408    unload();
409    load();
410    }
411
412  /** Create a new instance of the plugin object.
413   *
414   * @throws kiwi.util.plugin.PluginException If a problem occurs during
415   * class instantiation.
416   *
417   * @since Kiwi 2.0
418   */
419
420  public Object newInstance() throws PluginException
421    {
422    if(! loaded)
423      throw(new PluginException("Plugin is not loaded!"));
424    
425    Object obj = null;
426    
427    try
428      {
429      obj = pluginClass.newInstance();
430      }
431    catch(Exception ex)
432      {
433      System.out.println("While trying to instantiate: "
434                         + pluginClass.getName());
435      ex.printStackTrace();
436      throw(new PluginException(ex.toString()));
437      }
438
439    return(obj);
440    }
441
442  /** Add a <code>PluginReloadListener</code> to this model's list of
443   * listeners.
444   *
445   * @param listener The listener to add.
446   *
447   * @since Kiwi 2.0
448   */
449  
450  public void addPluginReloadListener(PluginReloadListener listener) 
451    { 
452    listeners.add(PluginReloadListener.class, listener); 
453    } 
454
455  /** Remove a <code>PluginReloadListener</code> from this model's list of
456   * listeners.
457   *
458   * @param listener The listener to remove.
459   *
460   * @since Kiwi 2.0
461   */
462
463  public void removePluginReloadListener(PluginReloadListener listener) 
464    {
465    listeners.remove(PluginReloadListener.class, listener);
466    }
467
468  /** Fire a <i>plugin reloaded</i> event.
469   */
470  
471  protected void firePluginReloaded() 
472    {
473    PluginReloadEvent evt = null; 
474    
475    Object[] list = listeners.getListenerList(); 
476    
477    for(int i = list.length - 2; i >= 0; i -= 2) 
478      { 
479      if(list[i] == PluginReloadListener.class) 
480        { 
481        // Lazily create the event: 
482        if(evt == null) 
483          evt = new PluginReloadEvent(this); 
484        ((PluginReloadListener)list[i + 1]).pluginReloaded(evt); 
485        }
486      }
487    }
488  
489  /** Return a string representation of this plugin, which consists of
490   * its name and version.
491   *
492   * @since Kiwi 2.0
493   */
494  
495  public String toString()
496    {
497    String n = getName();
498    String v = getVersion();
499
500    if(v != null)
501      n += " " + v;
502
503    return(n);
504    }
505  }
506
507/* end of source file */