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 */