Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/dghda/module/ModuleLoader.java


1   /* Copyright (C) 2001 Duane Griffin <duanegriffin@users.sourceforge.net>
2      This file is part of Kent.
3   
4      Kent is free software; you can redistribute it and/or
5      modify it under the terms of the GNU General Public License as
6      published by the Free Software Foundation; either version 2 of the
7      License, or (at your option) any later version.
8   
9      Kent is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12     General Public License for more details.
13  
14     You should have received a copy of the GNU General Public
15     License along with Kent; see the file COPYING.  If not,
16     write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17     Boston, MA 02111-1307, USA.
18  */
19  
20  package com.dghda.module;
21  
22  import java.io.*;
23  import java.util.*;
24  import java.util.jar.*;
25  
26  import com.dghda.log.*;
27  
28  /**
29     A module loader object manages a set of loadable objects.
30     It is given a path string, which can contain directories, files or jar files.
31     Directories and jars are scanned and their contents treated as files. These 
32     are then treated as class files containing compiled module providers.
33  */
34  public class ModuleLoader {
35    
36    /** Create a new module loader for the given class, using the given path(s), and no constructor arguments. */
37    public ModuleLoader (Class modClass, String modulePath, Logger logger) throws InvalidPathException {
38      this (modClass, parseModulePath (modulePath), null, logger);
39    }
40    
41    /** Create a new module loader for the given class, using the given path(s), and the given constructor arguments. */
42    public ModuleLoader (Class modClass, String [] modulePaths, Logger logger) throws InvalidPathException {
43      this (modClass, modulePaths, null, logger);
44    }
45    
46    /** Create a new module loader for the given class, using the given path(s) and constructor arguments. */
47    public ModuleLoader (Class modClass, String modulePath, Object [] args, Logger logger) throws InvalidPathException {
48      this (modClass, parseModulePath (modulePath), args, logger);
49    }
50    
51    /** Create a new module loader for the given class, using the given path(s) and constructor arguments. */
52    public ModuleLoader (Class modClass, String [] modulePaths, Object [] args, Logger logger) throws InvalidPathException {
53      
54      // Check the given class implements the Module interface
55      if (!Module.class.isAssignableFrom (modClass))
56        throw new ClassCastException (modClass.getName());
57      
58      m_Logger = logger;
59      m_ModuleClass = modClass;
60      m_ConstructorArgs = args;
61      
62      // Add components for every element in the path, logging any failures
63      for (int index = 0; index != modulePaths.length; ++index) {
64        try {
65          addModulePath (modulePaths[index]);
66        } catch (InvalidPathException exc) {
67          m_Logger.log ("Could not add path component " + modulePaths[index], exc);
68        }
69      }
70      
71      // Load in all modules
72      scan (false);
73    }
74    
75    /**
76       Add a module path component to the scan list.
77       @param path The path to add.
78    */
79    public void addModulePath (String path) throws InvalidPathException {
80      ModulePathComponent component = createPathComponent (path);
81      if (component != null)
82        m_ModulePaths.add (component);
83    }
84    
85    /**
86       Remove a module path component from the scan list.
87       @param path The path to remove.
88       @returns False if the path was not in the scan list.
89    */
90    public boolean removeModulePath (String path) {
91      Iterator i = m_ModulePaths.iterator();
92      while (i.hasNext()) {
93        ModulePathComponent component = (ModulePathComponent) i.next();
94        if (component.getPath().equals (path)) {
95          i.remove();
96          return true;
97        }
98      }
99      return false;
100   }
101   
102   /**
103      Attempt to add the given module.
104      The module will be added if it is not already present, or is with a lower version number.
105      This method can be used to add non-dynamic reports.
106      @returns True if the module was added.
107      @throws ClassCastException If the given module is not derived from the module loader's module class.
108   */
109   public boolean addModule (Module module) {
110     
111     // Check the module is of the correct type
112     if (!m_ModuleClass.isAssignableFrom (module.getClass()))
113       throw new ClassCastException ("Module must be derived from " + m_ModuleClass.getName());
114     
115     // Check whether this is new or a newer version, and if so replace it
116     Module current = (Module) m_Modules.get (module.getID());
117     if (current == null || module.getVersion().compareTo (current.getVersion()) > 0) {
118       
119       // Log the fact we are loading the module
120       if (current == null) {
121         m_Logger.log ("Loading module " + module.getID() + " (" + 
122                       module.getID() + ") with version " + 
123                       module.getVersion());
124       } else {
125         m_Logger.log ("Replacing module " + module.getID() + " (" + 
126                       module.getID() + ") version " + 
127                       current.getVersion() + " with version " + 
128                       module.getVersion());
129       }
130       
131       m_Modules.put (module.getID(), module);
132       return true;
133     } else {
134       return false;
135     }
136   }
137   
138   /** Parses a path string in the same form as the classpath into an array of paths. */
139   public static String [] parseModulePath (String paths) {
140     StringTokenizer tokens = new StringTokenizer (paths, File.pathSeparator);
141     String [] result = new String[tokens.countTokens()];
142     int index = 0;
143     while (tokens.hasMoreTokens())
144       result[index++] = tokens.nextToken();
145     return result;
146   }
147   
148   /** Returns a list of all modules available to the given user. */
149   public synchronized List getAvailableModules() {
150     
151     // Update the list
152     scan (true);
153     
154     // Construct & return the result
155     return new ArrayList (m_Modules.values());
156   }
157   
158   /**
159      Returns the given module.
160      @param id The module's ID.
161      @returns null if no such module exists.
162   */
163   public synchronized Module getModule (String id) {
164     return (Module) m_Modules.get (id);
165   }
166   
167   /**
168      Scans the module path(s) for any new or updated modules.
169      New modules will be added to the list of available modules.
170      If a module class file has a more recent timestamp than the version that was used to load the module, it will be re-loaded.
171      @param updated If true then only new module providers, or ones modified by the last scan will be checked.
172   */
173   public synchronized void scan (boolean updated) {
174     
175     // Scan all paths
176     Iterator components = m_ModulePaths.iterator();
177     while (components.hasNext()) {
178       ModulePathComponent component = (ModulePathComponent) components.next();
179       
180       // Iterate over the contents of the path
181       Iterator providers = component.getProviders (updated);
182       while (providers.hasNext()) {
183         ModulePathComponent.Provider provider = (ModulePathComponent.Provider) providers.next();
184         checkProvidersModules (provider);
185       }
186     }
187   }
188   
189   /** Constructs a path string in the same form as the classpath from an array of paths. */
190   public static String constructModulePath (String [] paths) {
191     StringBuffer buffer = new StringBuffer();
192     for (int index = 0; index != paths.length; ++index) {
193       
194       // Seperate paths
195       if (index != 0)
196         buffer.append (File.pathSeparator);
197       
198       // Add next path
199       buffer.append (paths[index]);
200     }
201     
202     return buffer.toString();
203   }
204   
205   /** Converts a string into a ModulePathComponent object. */
206   protected ModulePathComponent createPathComponent (String path) throws InvalidPathException {
207     ModulePathComponent result;
208     
209     if (path.endsWith (".class")) {
210       
211       // If the component is a class file...
212       result = new ModulePathFileComponent (path);
213     } else if (path.endsWith (".jar")) {
214       
215       // If the component is a jar file...
216       result = new ModulePathJarComponent (path);
217     } else {
218       try {
219         
220         // Check whether the path is a valid class
221         // This is a bit of a hack
222         Class providerClass = Class.forName (path);
223         if (checkProvidersModules (providerClass))
224           return null;
225       } catch (ClassNotFoundException exc) {
226       }
227       
228       // Otherwise assume the component is a directory
229       result = new ModulePathDirComponent (path);
230     }
231     
232     return result;
233   }
234   
235   /**
236      (Re)loads the modules provided by the given provider, if appropriate.
237      Reports are loaded if they don't already exist, or if there is a newer version.
238      By using new class loaders each time we ensure that we don't use old versions of the module.
239   */
240   protected synchronized boolean checkProvidersModules (ModulePathComponent.Provider component) {
241     
242     // First load a new version of the provider's class and construct an instance of it
243     try {
244       StreamClassLoader loader = new StreamClassLoader (getClass().getClassLoader());
245       Class providerClass = loader.loadClass (null, component.getInputStream());
246       return checkProvidersModules (providerClass);
247     } catch (Exception exc) {
248       m_Logger.log ("An error occurred while attempting to check module provider " + component.getName(), exc);
249     }
250     
251     return false;
252   }
253   
254   /**
255      (Re)loads the modules provided by the given provider, if appropriate.
256      Reports are loaded if they don't already exist, or if there is a newer version.
257      By using new class loaders each time we ensure that we don't use old versions of the module.
258   */
259   protected synchronized boolean checkProvidersModules (Class providerClass) {
260     boolean result = false;
261     
262     try {
263       ModuleProvider provider = null;
264       if (m_ConstructorArgs == null) {
265         
266         // Use the constructor with no arguments
267         provider = (ModuleProvider) providerClass.newInstance();
268       } else {
269         
270         // Try all constructors using the given arguments
271         Exception lastException = null;
272         java.lang.reflect.Constructor [] constructors = providerClass.getConstructors();
273         for (int index = 0; index != constructors.length; ++index) {
274           
275           // TODO: Check whether the constructor takes appropriate arguments, instead of letting it throw
276           try {
277             
278             // Try and use the constructor to create a new object
279             provider = (ModuleProvider) constructors[index].newInstance (m_ConstructorArgs);
280             
281             // If we succeed, stop
282             break;
283           } catch (Exception exc) {
284             
285             // If not, save the error for now
286             lastException = exc;
287           }
288         }
289         
290         // If we couldn't create a module, we should throw
291         if (provider == null) {
292           if (lastException == null) {
293             
294             // It looks like there were no public constructors
295             throw new Exception ("No accessible module constructor found");
296           } else {
297             
298             // There was at least one, but all failed
299             throw lastException;
300           }
301         }
302       }
303       
304       m_Logger.log ("Checking provider " + provider.getClass().getName());
305       
306       // Iterate over all the modules provided
307       Iterator modules = provider.getModuleIterator();
308       while (modules.hasNext()) {
309         Module module = (Module) modules.next();
310         try {
311           
312           // Try to add the module
313           addModule (module);
314         } catch (ClassCastException exc) {
315           
316           // Log incorrect module types
317           // It should only appear once, unless the file is updated
318           m_Logger.log ("Unable to add module " + module.getID() + " as it is not derived from " + m_ModuleClass.getName());
319         }
320       }
321       
322       // Indicate we successfully checked the provider
323       result = true;
324     } catch (Exception exc) {
325       m_Logger.log ("An error occurred while attempting to check module provider class " + providerClass.getName(), exc);
326     }
327     
328     return result;
329   }
330   
331   private LinkedList m_ModulePaths = new LinkedList();
332   private TreeMap m_Modules = new TreeMap();
333   private Logger m_Logger;
334   private Class m_ModuleClass;
335   private Object [] m_ConstructorArgs;
336 }