001 /* 002 Copyright (C) 2003 Adam Olsen 003 004 This program is free software; you can redistribute it and/or modify 005 it under the terms of the GNU General Public License as published by 006 the Free Software Foundation; either version 1, or (at your option) 007 any later version. 008 009 This program is distributed in the hope that it will be useful, 010 but WITHOUT ANY WARRANTY; without even the implied warranty of 011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 012 GNU General Public License for more details. 013 014 You should have received a copy of the GNU General Public License 015 along with this program; if not, write to the Free Software 016 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 017 */ 018 019 package com.valhalla.pluginmanager; 020 021 import java.util.*; 022 import java.util.jar.JarFile; 023 import java.util.jar.JarEntry; 024 import java.io.*; 025 import java.net.*; 026 027 /** 028 * This class supports the loading of custom plugins from jar files. 029 * The main class in the Jar should be named after the jar filename 030 * and should be in the "package" String passed to the PluginLoader package. 031 * For example, the plugin LookAndFeel would be in a jar file called LookAndFeel.jar, 032 * and if the package parameter was com.valhalla.jbother.plugins there would need to 033 * be a class called <code>com.valhalla.jbother.plugins.LookAndFeel</code>in the jar 034 * file 035 * 036 * @author Adam Olsen 037 * @version 1.0 038 */ 039 public class PluginLoader extends ClassLoader 040 { 041 private static PluginLoader instance = null; 042 private static int pluginAPIVersion = 91211; 043 private String pluginDir = null; 044 private Hashtable loadedClasses = new Hashtable(); // contains information about what classes have 045 // already been loaded 046 private static ArrayList availablePlugins = new ArrayList(); // the list of available plugins 047 private static ArrayList invalidPlugins = new ArrayList(); 048 private static ArrayList installedPlugins = new ArrayList(); 049 private static Hashtable loadedPlugins = new Hashtable(); 050 051 /** 052 * The default constructor 053 * 054 * @param pluginDir the directory to search for plugins in .jar format 055 **/ 056 private PluginLoader() 057 { 058 } 059 060 /** 061 * Gets the singleton of this class 062 * @return the PluginLoader singleton 063 */ 064 public static PluginLoader getInstance() 065 { 066 if( instance == null ) instance = new PluginLoader(); 067 return instance; 068 } 069 070 public static PluginLoader getNewInstance() 071 { 072 instance = new PluginLoader(); 073 return instance; 074 } 075 076 /** 077 * @return a list of currently loaded plugins 078 */ 079 public Hashtable getLoadedPlugins() { return loadedPlugins; } 080 081 /** 082 * @return a list of available plugins 083 */ 084 public ArrayList getAvailablePlugins() { return availablePlugins; } 085 086 /** 087 * @return a list of installed plugins 088 */ 089 public ArrayList getInstalledPlugins() { return installedPlugins; } 090 091 /** 092 * @return a list of invalid plugins 093 */ 094 public ArrayList getInvalidPlugins() { return invalidPlugins; } 095 096 /** 097 * Returns the jar file for the specified plugin name 098 * @param name the plugin name 099 * @return the jar file corresponding to the name 100 */ 101 public PluginJAR getPlugin( String name ) 102 { 103 PluginJAR jar = null; 104 for( int i = 0; i < availablePlugins.size(); i++ ) 105 { 106 PluginJAR temp = (PluginJAR)availablePlugins.get( i ); 107 if( temp.getName().equals( name ) ) jar = temp; 108 } 109 110 return jar; 111 } 112 113 /** 114 * Returns the PluginJAR that contains <tt>file</tt> 115 * @param file the file to search for 116 * @return the PluginJAR that contains <tt>file</tt> 117 */ 118 private PluginJAR getPluginThatContains( String file ) 119 { 120 PluginJAR jar = null; 121 for( int i = 0; i < availablePlugins.size(); i++ ) 122 { 123 PluginJAR temp = (PluginJAR)availablePlugins.get( i ); 124 if( temp.contains( file ) ) jar = temp; 125 } 126 127 return jar; 128 } 129 130 /** 131 * Returns the plugin that is represented by location 132 * @param location the location of the plugin 133 * @return the PluginJAR representing the specified location 134 */ 135 private PluginJAR getPluginFromLocation( String location ) 136 { 137 PluginJAR jar = null; 138 139 for( int i = 0; i < availablePlugins.size(); i++ ) 140 { 141 PluginJAR temp = (PluginJAR)availablePlugins.get( i ); 142 if( temp.getLocation().equals( location ) ) jar = temp; 143 } 144 145 return jar; 146 } 147 148 /** 149 * Gets the current plugin API version 150 * @return the current plugin API version 151 */ 152 public static int getAPIVersion() { return pluginAPIVersion; } 153 154 /** 155 * Attempts to load the available plugins 156 */ 157 public void loadPlugins() 158 { 159 for( int i = 0; i < availablePlugins.size(); i++ ) 160 { 161 PluginJAR jar = (PluginJAR)availablePlugins.get( i ); 162 163 if( !jar.getLoaded() ) 164 { 165 try { 166 jar.loadPlugin(); 167 } 168 catch( Exception e ) 169 { 170 com.valhalla.Logger.logException( e ); 171 } 172 } 173 else { 174 com.valhalla.Logger.debug( "Plugin is already loaded." ); 175 } 176 } 177 } 178 179 /** 180 * Reads in all the available plugins and the information about them 181 **/ 182 public void findPlugins( String d ) 183 { 184 this.pluginDir = d; 185 String files[] = null; 186 File pluginDir = null; 187 188 installedPlugins.removeAll( installedPlugins ); 189 ArrayList newAvailable = new ArrayList(); 190 invalidPlugins.removeAll( invalidPlugins ); 191 192 try { 193 // open up the plugin directory 194 pluginDir = new File( this.pluginDir ); 195 if( !pluginDir.isDirectory() ) return; 196 files = pluginDir.list(); 197 } 198 catch( Exception exception ) 199 { 200 return; 201 } 202 203 for( int i = 0; i < files.length; i++ ) 204 { 205 // if it's a jar file, it might be a plugin 206 if( files[i].endsWith( ".jar" ) ) 207 { 208 try { 209 PluginJAR jar = null; 210 String loc = pluginDir.getPath() + File.separatorChar + files[i]; 211 jar = getPluginFromLocation( loc ); 212 213 if( jar == null || !jar.getLoaded() ) 214 { 215 jar = new PluginJAR( pluginDir.getPath() + File.separatorChar + files[i] ); 216 } 217 218 Properties props = jar.getProperties(); 219 String name = null; 220 221 if( props == null || props.getProperty( "mainClass" ) == null || 222 props.getProperty( "name" ) == null ) invalidPlugins.add( files[i] ); 223 else { 224 int version = Integer.parseInt( props.getProperty( "APIVersion" ) ); 225 if( version != pluginAPIVersion ) 226 { 227 invalidPlugins.add( props.getProperty( "name" ) ); 228 } 229 else { 230 newAvailable.add( jar ); 231 } 232 233 installedPlugins.add( props ); 234 } 235 } 236 catch( Exception ex ) 237 { 238 com.valhalla.Logger.debug( "Invalid plugin found: " + files[i] ); 239 } 240 } 241 } 242 243 availablePlugins = newAvailable; 244 } 245 246 /** 247 * Gets a resource as a stream 248 * @param resource the resource to get 249 * @return the stream 250 */ 251 public InputStream getResourceAsStream( String resource ) 252 { 253 InputStream stream = null; 254 PluginJAR jar = getPluginThatContains( resource ); 255 256 if( jar != null ) 257 { 258 // find the entry in the Jar file 259 JarEntry entry = jar.getJarEntry( resource ); 260 261 stream = jar.getInputStream( entry ); 262 } 263 else { 264 com.valhalla.Logger.debug( "Stream was null for " + resource ); 265 } 266 267 return stream; 268 } 269 270 protected Hashtable getLoadedClasses() { return loadedClasses; } 271 272 /** 273 * Loads the Class out of the plugin file 274 * 275 * @param className the class name to load 276 * @param resolveIt true to resolve the class (load dependancies) 277 * @return the Class if it was found 278 * @throws <code>ClassNotFoundException</code> if the class could not be found in the system or any of the plugin files 279 **/ 280 public synchronized Class loadClass( String className, boolean resolveIt ) throws ClassNotFoundException 281 { 282 Class result = null; 283 284 String origName = className; 285 286 // if the Class was already loaded, return it 287 result = (Class)loadedClasses.get( origName ); 288 if( result != null ) return result; 289 290 // check to see if it's a system class 291 try { 292 result = super.findSystemClass( className ); 293 return result; 294 } 295 catch( ClassNotFoundException exception ) { } 296 297 className = className.replaceAll( "\\.", "/" ); 298 className += ".class"; 299 300 // if the class name starts with "java", we will not load it 301 // because of the security risks involved. A class in the package 302 // java. or javax. will be able to access protected members of system 303 // Classes, and this isn't good. 304 if( className.startsWith( "java" ) ) throw new ClassNotFoundException(); 305 306 PluginJAR jar = getPluginThatContains( className ); 307 308 if( jar == null ) throw new ClassNotFoundException(); 309 310 try { 311 // find the entry in the Jar file 312 JarEntry entry = jar.getJarEntry( className ); 313 InputStream stream = jar.getInputStream( entry ); 314 315 // read it into a class 316 int len = (int)entry.getSize(); 317 int offset = 0; 318 int success = 0; 319 byte[] contents = new byte[len]; 320 321 while( success < len ) 322 { 323 len -= success; 324 offset += success; 325 success = stream.read( contents, offset, len ); 326 if( success == -1 ) throw new ClassNotFoundException(); 327 } 328 329 stream.read( contents, 0, (int)entry.getSize() ); 330 331 // actually define the class 332 result = defineClass( origName, contents, 0, contents.length ); 333 334 if( resolveIt ) resolveClass( result ); 335 336 // mark this class as already loaded and put it in the cache 337 loadedClasses.put( origName, result ); 338 339 return result; 340 } 341 catch( Exception e ) 342 { 343 throw new ClassNotFoundException( className ); 344 } 345 } 346 }