View Javadoc

1   /*
2    * ====================================================================
3    *
4    * The Apache Software License, Version 1.1
5    *
6    * Copyright (c) 1999 The Apache Software Foundation.  All rights 
7    * reserved.
8    *
9    * Redistribution and use in source and binary forms, with or without
10   * modification, are permitted provided that the following conditions
11   * are met:
12   *
13   * 1. Redistributions of source code must retain the above copyright
14   *    notice, this list of conditions and the following disclaimer. 
15   *
16   * 2. Redistributions in binary form must reproduce the above copyright
17   *    notice, this list of conditions and the following disclaimer in
18   *    the documentation and/or other materials provided with the
19   *    distribution.
20   *
21   * 3. The end-user documentation included with the redistribution, if
22   *    any, must include the following acknowlegement:  
23   *       "This product includes software developed by the 
24   *        Apache Software Foundation (http://www.apache.org/)."
25   *    Alternately, this acknowlegement may appear in the software itself,
26   *    if and wherever such third-party acknowlegements normally appear.
27   *
28   * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
29   *    Foundation" must not be used to endorse or promote products derived
30   *    from this software without prior written permission. For written 
31   *    permission, please contact apache@apache.org.
32   *
33   * 5. Products derived from this software may not be called "Apache"
34   *    nor may "Apache" appear in their names without prior written
35   *    permission of the Apache Group.
36   *
37   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48   * SUCH DAMAGE.
49   * ====================================================================
50   */ 
51  
52  package net.wotonomy.foundation.internal;
53  
54  import java.io.ByteArrayInputStream;
55  import java.io.IOException;
56  import java.io.InputStream;
57  import java.net.URL;
58  import java.util.Enumeration;
59  import java.util.Hashtable;
60  
61  /***
62   * The correct name for this class should be URLClassLoader.
63   * But there is already a class by that name in JDK1.2.
64   *
65   * I have had quite a few problems with URLClassLoader in
66   * past, so I ended up writing this ClassLoader. I found that
67   * the Java 2's URLClassLoader, does not close the Jar file once
68   * opened. It is a pretty good optimization step, but if you
69   * modify the class in the jar file, it does not pick it up. Some
70   * operating systems may not let you modify the jar file while it is
71   * still open. IMHO, it does make sense to close the jar file
72   * after you are done reading the class data. But this approach may not
73   * get you the performance of the URLClassLoader, but it works in all
74   * cases and also runs on JDK1.1. I have enhanced this class loader
75   * to read all the zip/jar entries once & cache the data, so that
76   * there is no overhead of opening/closing jar file to pick up
77   * each entry.
78   *
79   *
80   * @author Harish Prabandham
81   */
82  public class NetworkClassLoader extends ClassLoader {
83      private ClassLoader parent = null; // parent classloader
84      private Hashtable classCache = new Hashtable();
85      private Hashtable urlset = new Hashtable();
86  
87      /***
88       * Creates a new instance of the class loader.
89       * @param delegate/parent class loader.
90       */
91      public NetworkClassLoader(ClassLoader parent) {
92          setParent(parent);
93      }
94  
95      /***
96       * Sets the parent/delegate class loader.
97       * @param delegate/parent class loader.
98       */
99      protected final void setParent(ClassLoader parent) {
100         this.parent = parent;
101     }
102 
103     /***
104      * Adds the given URL to this class loader. If the URL
105      * ends with "/", then it is assumed to be a directory
106      * otherwise, it is assumed to be a zip/jar file. If the
107      * same URL is added again, the URL is re-opened and this
108      * zip/jar file is used for serving any future class requests.
109      * @param URL where to look for the classes.
110      */
111     public synchronized void addURL(URL url) {
112         // System.out.println("Adding url: " + url);
113         if(!urlset.containsKey(url)) {
114             try {
115                 urlset.put(url, new URLResourceReader(url));
116             }catch(IOException ioe){
117                 // Probably a bad url...
118             }
119         } else {
120             // remove the old one & add a new one...
121             try{
122                 URLResourceReader newu = new URLResourceReader(url);
123                 URLResourceReader oldu = (URLResourceReader) urlset.get(url);
124                 oldu.close();
125                 urlset.remove(url);
126                 urlset.put(url, newu);
127             } catch (IOException ioe) {
128             }
129         }
130     }
131 
132     /***
133      * @return An enumeration of  URLs where this class loader
134      * looks for classes.
135      */
136     public Enumeration getURLs() {
137         return urlset.keys();
138     }
139 
140     /***
141      * Call this to bypass the implementation of loadClass.
142      */
143     public Class findClass(String name) {
144         byte[] b = loadClassData(name);
145         if ( b == null ) return null;
146         return defineClass(name, b, 0, b.length);
147     }
148 
149     protected byte[] loadResource(URL url, String resourceName)
150         throws IOException {
151         URLResourceReader urr = (URLResourceReader) urlset.get(url);
152         // System.out.println("Loading from " + urr + " " + resourceName);
153         if(urr != null) {
154             return urr.getResource(resourceName);
155         }
156 
157         return null;
158     }
159 
160     protected byte[] loadResource(String resource) {
161         byte[] barray = null;
162         for(Enumeration e = urlset.keys(); e.hasMoreElements();) {
163             URL url = (URL) e.nextElement();
164 
165             try {
166                 barray = loadResource(url, resource);
167             } catch(Exception ex) {
168             } finally {
169                 if(barray != null)
170                     break;
171             }
172         }
173 
174         return barray;
175     }
176 
177     protected byte[] loadClassData(String classname) {
178         String resourceName = classname.replace('.', '/') + ".class";
179         return loadResource(resourceName);
180     }
181 
182     /***
183      * Overridden to search for a resource and return 
184      * a "jar"-style URL or normal "file" URL as necessary.
185      */
186     protected URL findResource(String name) 
187     { //System.out.println( "findResource: " + name ); 
188         URL url;
189         byte[] barray = null;
190         
191         for ( Enumeration e = urlset.keys(); e.hasMoreElements();  ) 
192         {
193             url = (URL) e.nextElement();
194             try 
195             {
196                 barray = loadResource(url, name); // loads fully: wasteful
197             } 
198             catch(Exception ex) 
199             {
200                 // do nothing
201             } 
202             if( barray != null ) 
203             {
204                 try
205                 {
206                     String ref = url.toString();
207                     if ( ref.endsWith( ".jar" ) )
208                     {
209                         //System.out.println( "jar:" + ref + "!/" + name );
210                         return new URL( "jar:" + ref + "!/" + name );
211                     }
212                     else
213                     {
214                         //System.out.println( new URL( url, name ).toString() );
215                         return new URL( url, name );
216                     }
217                 }
218                 catch ( Throwable t )
219                 {
220                     t.printStackTrace();
221                 }
222             }
223         }
224 
225         return null;
226     }
227 
228     /***
229      * @return The resource as the input stream if such a resource
230      * exists, otherwise returns null.
231      */
232     public InputStream getResourceAsStream(String name) {
233         //System.out.println( "getResourceAsStream: " + name );        
234         InputStream istream = null;
235         
236         // Algorithm:
237         //
238         // 1. first check the system path for the resource
239         // 2. next  check the  delegate/parent class loader for the resource
240         // 3. then attempt to get the resource from the url set.
241         //
242 
243         // Lets check the system path for the resource.
244         istream = getSystemResourceAsStream(name);
245         if(istream != null)
246             return istream;
247 
248         // Lets check the parent/delegate class loader for the resource.
249         if(parent != null) {
250             istream = parent.getResourceAsStream(name);
251             if(istream != null)
252                 return istream;
253         }
254 
255         // Lets load it ourselves.
256         byte[] data = loadResource(name);
257         if(data != null) {
258             istream = new ByteArrayInputStream(data);
259         }
260 
261         return istream;
262     }
263 
264     /***
265      * java.lang.ClassLoader's defineClass method is final, so the
266      * its subclasses cannot override this method. But, this class
267      * calls this method in the loadClass() instead.
268      * @param The name of the class without ".class" extension.
269      * @param The class data bytes.
270      * @return The class object.
271      */
272     protected Class defineClass(String classname, byte[] classdata) {
273         return defineClass(classname, classdata, 0, classdata.length);
274     }
275 
276     public synchronized Class loadClass(String name, boolean resolve)
277         throws ClassNotFoundException {
278         Class c = null;
279 
280         // Algorithm: (Please do not change the order; unless you
281         // have a good reason to do so).
282         //
283         // 1. first check the system class loader.
284         // 2. next  check the  delegate/parent class loader.
285         // 3. next  check the class cache
286         // 4. then attempt to load classes from the URL set.
287         //
288         
289         // Lets see if the class is in system class loader.
290         try {
291             c = findSystemClass(name);
292         }catch(ClassNotFoundException cnfe) {
293         }finally {
294             if(c != null)
295                 return c;
296         }
297 
298         // Lets see if the class is in parent class loader.
299         try {
300             if(parent != null)
301                 c = parent.loadClass(name);
302         }catch(ClassNotFoundException cnfe) {
303         }finally {
304             if(c != null)
305                 return c;
306         }
307 
308         // Lets see if the class is in the cache..
309         c = (Class) classCache.get(name);
310 
311         if(c != null)
312             return c;
313 
314 
315         // Lets see if we find the class all by ourselves.
316         byte[] data = loadClassData(name);
317 
318         if(data != null) {
319             // we did !!
320             c = defineClass(name, data);
321             classCache.put(name, c);
322             if(resolve)
323                 resolveClass(c);
324         } else {
325             // We are out of luck at this point...
326             throw new ClassNotFoundException(name);
327         }
328 
329         return c;
330     }
331 
332     /***
333      * This method resets this ClassLoader's state. It completely
334      * removes all the URLs and classes in this class loader cache. 
335      */
336     public final void clear() {
337         urlset.clear();
338         classCache.clear();
339     }
340 
341     /***
342      * This method resets this ClassLoader's state and resets the
343      * references for garbage collection.
344      */
345     protected void finalize() throws Throwable {
346         // Cleanup real well. Otherwise, this can be
347         // a major source of memory leaks...
348 
349         // remove all the urls & class entries.
350         clear();
351         
352         parent = null;
353         urlset = null;
354         classCache = null;
355     }
356 }
357 
358 
359 
360 
361 
362 
363 
364 
365 
366 
367 
368