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    }