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: PluginClassLoader.java,v $
023   Revision 1.5  2004/05/12 18:03:42  markl
024   javadoc updates
025
026   Revision 1.4  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.3  2003/01/19 09:31:22  markl
031   Javadoc & comment header updates.
032
033   Revision 1.2  2001/03/18 06:41:21  markl
034   Rewrite to simplify code and fix bugs.
035
036   Revision 1.1  2001/03/12 10:20:59  markl
037   New classes.
038   ----------------------------------------------------------------------------
039*/
040
041package kiwi.util.plugin;
042
043import java.io.*;
044import java.util.*;
045import java.util.jar.*;
046
047/* The internal class loader for plugins.
048 *
049 * The plugin will be loaded in its own namespace. It will have access to all
050 * classes in its own namespace, AND access to any class that belongs to any
051 * of the _restrictedPackages_ list, BUT NOT access to any class that belongs
052 * to any of the _forbiddenPackages_ list.
053 *
054 * - If asked to load a class that is from a restricted package, the class
055 * loader delegates to the system class loader. Packages "java.*' and 'javax.*'
056 * are automatically added to the restricted package list. This prevents the
057 * plugin from loading classes into those packages, or replacing system classes
058 * with its own in its namespace.
059 *
060 * - If asked to load a class that is from a forbidden package, the class
061 * loader throws a ClassNotFoundException.
062 *
063 * - If asked to load any other class other than those listed above, the class
064 * loader attempts to load the class by searching for it in the registered JAR
065 * files.
066 *
067 * @author Mark Lindner
068 */
069
070class PluginClassLoader extends ClassLoader
071  {
072  private Vector forbiddenPackages;
073  private Vector restrictedPackages;
074  private Vector jars;
075  private Hashtable classCache;
076
077  /*
078   */
079  
080  PluginClassLoader(Vector forbiddenPackages, Vector restrictedPackages)
081    {
082    this.forbiddenPackages = forbiddenPackages;
083    this.restrictedPackages = restrictedPackages;
084    
085    classCache = new Hashtable();
086    jars = new Vector();
087    }
088
089  /*
090   */
091  
092  void addJarFile(JarFile file)
093    {
094    synchronized(jars)
095      {
096      if((file != null) && ! jars.contains(file))
097        jars.addElement(file);
098      }
099    }
100
101  /**
102   */
103  
104  public synchronized Class loadClass(String className, boolean resolve) 
105    throws ClassNotFoundException
106    {
107    Class result = null;
108
109    // extract package name
110    
111    int ind = className.lastIndexOf('.');
112    if(ind < 0)
113      return(null); // won't support classes in unnamed packages
114
115    String classPackage = className.substring(0, ind);
116    
117    // first check our cache
118
119    result = findLoadedClass(className);
120
121    // not in cache...
122    
123    if(result == null)
124      {
125
126      // try to load it from the system class loader first, but only if it's
127      // not from a forbidden package
128
129      if(! isForbiddenPackage(classPackage))
130        {
131        try
132          {
133          return(findSystemClass(className));
134          }
135        catch(ClassNotFoundException ex)
136          {
137          /* ignore & continue */
138          }
139
140        // no luck, so scan through JAR files looking for the class, but only
141        // if it's not a restricted class (or a forbidden class)
142      
143        if(! isRestrictedPackage(classPackage))
144          {
145          String path = classNameToPath(className);
146
147          Enumeration e = jars.elements();
148
149          while(e.hasMoreElements())
150            {
151            JarFile jar = (JarFile)e.nextElement();
152            JarEntry entry = (JarEntry)jar.getEntry(path);
153            if(entry == null)
154              continue; // move on to next JAR file
155                    
156            // found it! so let's load it...
157
158            BufferedInputStream ins = null;
159            int r = 0, size = 0;
160            byte b[] = null;
161            
162            try
163              {
164              ins = new BufferedInputStream(jar.getInputStream(entry));
165              size = (int)entry.getSize();
166              
167              b = new byte[size];
168              r = ins.read(b, 0, size);
169              }
170            catch(IOException ex)
171              {
172              r = -1;
173              }
174            
175            if(ins != null)
176              {
177              try
178                {
179                ins.close();
180                }
181              catch(IOException ex) { /* ignore */ }
182              }
183            
184            if(r != size) // got less or more bytes than we expected?
185              throw(new ClassFormatError(className));
186            
187            result = defineClass(className, b, 0, size);
188            break;
189            }
190          }
191        }
192      }
193
194    if(result == null)
195      throw(new ClassNotFoundException(className));
196
197    if(resolve)
198      resolveClass(result);
199
200    return(result);
201    }
202
203  /*
204   */
205
206  private boolean isForbiddenPackage(String packageName)
207    {
208    return(findPackage(packageName, forbiddenPackages));
209    }
210
211  /*
212   */
213
214  private boolean isRestrictedPackage(String packageName)
215    {
216    return(findPackage(packageName, restrictedPackages));
217    }
218
219  /*
220   */
221
222  private boolean findPackage(String packageName, Vector packageList)
223    {
224    synchronized(packageList)
225      {
226      Enumeration e = packageList.elements();
227    
228      while(e.hasMoreElements())
229        {
230        String pkg = (String)e.nextElement();
231        
232        if(pkg.endsWith(".*"))
233          {
234          if(packageName.startsWith(pkg.substring(0, pkg.length() - 1)))
235            return(true);
236          }
237        else if(pkg.equals(packageName))
238          return(true);
239        }
240      }
241
242    return(false);
243    }
244
245  /**
246   */
247
248  public synchronized InputStream getResourceAsStream(String name)
249    {
250    /* Scan through JAR files looking for the resource */
251
252    Enumeration e = jars.elements();
253    while(e.hasMoreElements())
254      {
255      JarFile jar = (JarFile)e.nextElement();
256      JarEntry entry = jar.getJarEntry(name);
257
258      if(entry != null)
259        {
260        try
261          {
262          return(jar.getInputStream(entry));
263          }
264        catch(IOException ex)
265          {
266          /* ignore error, & continue */
267          }        
268        }
269      }
270    
271    return(null);
272    }
273
274  /* convert a class name to its corresponding JAR entry name
275   */
276  
277  static String classNameToPath(String className)
278    {
279    return(className.replace('.', '/') + ".class");
280    }
281
282  /* convert a JAR entry name to its corresponding class name
283   */
284  
285  static String pathToClassName(String path)
286    {
287    return(path.substring(0, path.length() - 6).replace('/', '.'));
288    }
289
290  }
291
292/* end of source file */