View Javadoc

1   package com.explosion.utilities.classes;
2   
3   /*
4    * =============================================================================
5    * 
6    * Copyright 2004 Stephen Cowx
7    * 
8    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
9    * use this file except in compliance with the License. You may obtain a copy of
10   * the License at
11   * 
12   * http://www.apache.org/licenses/LICENSE-2.0
13   * 
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   * 
20   * =============================================================================
21   */
22  
23  import java.io.DataInputStream;
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.security.SecureClassLoader;
28  import java.util.Enumeration;
29  import java.util.HashMap;
30  import java.util.jar.JarEntry;
31  import java.util.jar.JarFile;
32  import java.util.jar.Manifest;
33  
34  import org.apache.log4j.LogManager;
35  import org.apache.log4j.Logger;
36  
37  import com.explosion.utilities.exception.ExceptionManagerFactory;
38  
39  public class JarFileClassLoader extends SecureClassLoader
40  {
41  
42      private JarFile jarFile;
43  
44      private Manifest manifest;
45  
46      private HashMap classNameIndex = new HashMap();
47  
48      private HashMap loadedClasses = new HashMap();
49  
50      private static Logger log = LogManager.getLogger(JarFileClassLoader.class);
51  
52      public JarFileClassLoader(ClassLoader parent, String jarFilePath) throws IllegalArgumentException, IOException
53      {
54          super(parent);
55          log.debug("Constructing new JarFileCLassLoader");
56          if (jarFilePath == null)
57              throw new IllegalArgumentException("Null jarfile path");
58  
59          File file = new File(jarFilePath);
60          if (!file.exists())
61              throw new IllegalArgumentException("File '" + jarFilePath + "' does not exist.");
62  
63          if (!file.exists())
64              throw new IllegalArgumentException("Path '" + jarFilePath + "' points to a directory and not a jar file.");
65  
66          this.jarFile = new JarFile(jarFilePath);
67  
68          manifest = jarFile.getManifest();
69  
70          buildIndex(jarFilePath);
71      }
72  
73      /***
74       * This method builds an index of entries in the jarfile
75       * 
76       * @param jarFile
77       * @throws Exception
78       */
79      private void buildIndex(String jarFile) throws IOException
80      {
81          log.debug("Building the entry name index");
82          if (jarFile != null)
83          {
84              JarFile file = new JarFile(jarFile);
85              Enumeration en = file.entries();
86              while (en.hasMoreElements())
87              {
88                  JarEntry entry = (JarEntry) en.nextElement();
89                  String name = entry.getName();
90  
91                  int index = name.lastIndexOf(".");
92  
93                  if (index < 0)
94                      index = name.length();
95  
96                  String key = name.substring(0, index);
97                  log.debug("Indexing: Entry " + name + " Key derived " + key);
98  
99                  if (classNameIndex.get(key) != null)
100                     throw new IOException("Duplicate key '" + key + "' found in jar file " + jarFile);
101                 else
102                     classNameIndex.put(key, entry.getName());
103             }
104 
105         }
106     }
107 
108     /***
109      * This method loads a class
110      *  
111      */
112     public Class findClass(String name) throws ClassNotFoundException
113     {
114         try
115         {   
116             log.debug("Looking for "+name+" in the store");
117             /* First check in our store to see if we have loaded this class before */
118             Class c = (Class) loadedClasses.get(name);
119             if (c != null)
120                 return c;
121 
122             log.debug("Not found in the store, .loading from disk");
123             /* If we haven't then try to load it from the file */
124             byte data[] = null;
125             data = loadClassData(name);
126 
127             if (data == null)
128                 throw new ClassNotFoundException(name);
129             
130             log.debug("Found on disk");
131             
132             try
133             {
134                 /*  Create a class */
135                 c = defineClass(name, data, 0, data.length);
136                 log.debug("Defined "+name+".");
137                 
138                 /* Store it so that we don't reload it again later it */
139                 log.debug("Inserting "+name+" into store.");
140                 loadedClasses.put(name, c);
141 
142             } catch (NoClassDefFoundError cnfe)
143             {
144                 c = null;
145             }
146 
147             /*  */
148             if (c == null) { throw new ClassNotFoundException(name); }
149 
150             return c;
151         } catch (IOException e)
152         {
153             throw new ClassNotFoundException("Error reading file: " + name);
154         }
155 
156     }
157 
158     /***
159      * Looks up the entryname in the index and then loads the class
160      * 
161      * @param name
162      * @param jarFile
163      * @return @throws IOException
164      */
165     private byte[] loadClassData(String name) throws IOException
166     {
167         log.debug("loadClassData() Loading: " + name);
168         JarEntry entry = getEntry(name, jarFile);
169         if (entry != null)
170         {
171             byte buff[] = new byte[(int) entry.getSize()];
172             DataInputStream dis = new DataInputStream(jarFile.getInputStream(entry));
173             try
174             {
175                 dis.readFully(buff);
176             } finally
177             {
178                 try
179                 {
180                     dis.close();
181                 } catch (Exception e)
182                 {// ignore
183                 }
184             }
185             return buff;
186         }
187 
188         return null;
189     }
190 
191     /***
192      * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String)
193      * @param name
194      * @return
195      */
196     public InputStream getResourceAsStream(String name)
197     {
198         log.debug("getResourceAsStream() getting: " + name);
199         JarEntry entry = getEntry(name, jarFile);
200         if (entry != null)
201         {
202             try
203             {
204                 return jarFile.getInputStream(entry);
205             } catch (IOException e)
206             {
207                 ExceptionManagerFactory.getExceptionManager().manageException(e, "Exception caught while obtaingin resource.");
208             }
209         }
210 
211         return null;
212     }
213 
214     /***
215      * This method looks up a particular entry in the jar file. It performs
216      * string manipulation on the name to convert "." to "/" and other things
217      * 
218      * @param name
219      * @param file
220      * @return
221      */
222     private JarEntry getEntry(String name, JarFile file)
223     {
224         log.debug("getEntry getting entry " + name);
225         String entryName = (String) classNameIndex.get(name.replace('.', '/'));
226         if (entryName == null && File.separatorChar != '/')
227             entryName = (String) classNameIndex.get(name.replace('.', File.separatorChar));
228 
229         if (entryName == null)
230             log.debug("Name " + name + " not found in index.");
231         else
232             log.debug("Name " + name + " was found in index.  Using corresponding lookup entry '" + entryName + "'.");
233 
234         return file.getJarEntry(entryName);
235     }
236 }