1 | /****************************************************************************** |
---|
2 | * (c) Copyright 2002,2003, 1060 Research Ltd |
---|
3 | * |
---|
4 | * This Software is licensed to You, the licensee, for use under the terms of |
---|
5 | * the 1060 Public License v1.0. Please read and agree to the 1060 Public |
---|
6 | * License v1.0 [www.1060research.com/license] before using or redistributing |
---|
7 | * this software. |
---|
8 | * |
---|
9 | * In summary the 1060 Public license has the following conditions. |
---|
10 | * A. You may use the Software free of charge provided you agree to the terms |
---|
11 | * laid out in the 1060 Public License v1.0 |
---|
12 | * B. You are only permitted to use the Software with components or applications |
---|
13 | * that provide you with OSI Certified Open Source Code [www.opensource.org], or |
---|
14 | * for which licensing has been approved by 1060 Research Limited. |
---|
15 | * You may write your own software for execution by this Software provided any |
---|
16 | * distribution of your software with this Software complies with terms set out |
---|
17 | * in section 2 of the 1060 Public License v1.0 |
---|
18 | * C. You may redistribute the Software provided you comply with the terms of |
---|
19 | * the 1060 Public License v1.0 and that no warranty is implied or given. |
---|
20 | * D. If you find you are unable to comply with this license you may seek to |
---|
21 | * obtain an alternative license from 1060 Research Limited by contacting |
---|
22 | * license@1060research.com or by visiting www.1060research.com |
---|
23 | * |
---|
24 | * NO WARRANTY: THIS SOFTWARE IS NOT COVERED BY ANY WARRANTY. SEE 1060 PUBLIC |
---|
25 | * LICENSE V1.0 FOR DETAILS |
---|
26 | * |
---|
27 | * THIS COPYRIGHT NOTICE IS *NOT* THE 1060 PUBLIC LICENSE v1.0. PLEASE READ |
---|
28 | * THE DISTRIBUTED 1060_Public_License.txt OR www.1060research.com/license |
---|
29 | * |
---|
30 | * File: $RCSfile: DynamicURLClassLoader.java,v $ |
---|
31 | * Version: $Name: $ $Revision: 1.16 $ |
---|
32 | * Last Modified: $Date: 2008/03/26 10:28:57 $ |
---|
33 | * |
---|
34 | * CHANGELOG |
---|
35 | * [18/8/2009 maw] Applied patch from tab to createClassFromInputStream() which |
---|
36 | * should fix a concurrent class loading bug. |
---|
37 | *****************************************************************************/ |
---|
38 | package com.ten60.netkernel.util; |
---|
39 | import java.util.*; |
---|
40 | import java.net.*; |
---|
41 | import java.util.jar.*; |
---|
42 | import java.io.*; |
---|
43 | |
---|
44 | /** |
---|
45 | * DynamicURLClassLoader locates class and resources from a list of URLs before resorting to |
---|
46 | * parent classloader. |
---|
47 | * @author tab |
---|
48 | */ |
---|
49 | public class DynamicURLClassLoader extends ClassLoader |
---|
50 | { |
---|
51 | private List mURLs; |
---|
52 | |
---|
53 | private static byte[] mBuffer1 = new byte[40000]; // hopefully big enough for the largest class |
---|
54 | private static Thread mBuffer1User = null; |
---|
55 | private static int mBuffer1Count=0; |
---|
56 | |
---|
57 | private static byte[] mBuffer2 = new byte[40000]; // hopefully big enough for the largest class |
---|
58 | private static Thread mBuffer2User = null; |
---|
59 | private static int mBuffer2Count=0; |
---|
60 | |
---|
61 | private static Integer mBufferSync = new Integer(0); |
---|
62 | |
---|
63 | private static Enumeration sEmptyEnum; |
---|
64 | static |
---|
65 | { sEmptyEnum = new Enumeration() |
---|
66 | { |
---|
67 | public boolean hasMoreElements() |
---|
68 | { return false; |
---|
69 | } |
---|
70 | |
---|
71 | public Object nextElement() |
---|
72 | { return null; |
---|
73 | } |
---|
74 | }; |
---|
75 | } |
---|
76 | |
---|
77 | private static class ClassPathElement |
---|
78 | { public JarFile mJarFile; |
---|
79 | public File mDirectory; |
---|
80 | public String mBaseURL; |
---|
81 | public ClassPathElement(JarFile aJar, String aBaseURL) |
---|
82 | { mJarFile = aJar; |
---|
83 | mBaseURL = aBaseURL; |
---|
84 | } |
---|
85 | public ClassPathElement(File aDir, String aBaseURL) |
---|
86 | { mDirectory = aDir; |
---|
87 | mBaseURL = aBaseURL; |
---|
88 | } |
---|
89 | } |
---|
90 | |
---|
91 | public DynamicURLClassLoader(List aURLs) |
---|
92 | { mURLs = parseURLs(aURLs); |
---|
93 | } |
---|
94 | |
---|
95 | public void cleanup() |
---|
96 | { mURLs=null; |
---|
97 | } |
---|
98 | |
---|
99 | private List parseURLs(List aURLs) |
---|
100 | { List result = new ArrayList(aURLs.size()); |
---|
101 | for (Iterator i=aURLs.iterator(); i.hasNext(); ) |
---|
102 | { String url = (String)i.next(); |
---|
103 | String message=null; |
---|
104 | try |
---|
105 | { if (url.startsWith("file:") && url.endsWith("/")) |
---|
106 | { URI uri = URI.create(url); |
---|
107 | File f = new File(uri); |
---|
108 | if (f.exists()) |
---|
109 | { ClassPathElement cpe = new ClassPathElement(f,url); |
---|
110 | result.add(cpe); |
---|
111 | } |
---|
112 | else |
---|
113 | { message = "file doesn't exist"; |
---|
114 | } |
---|
115 | } |
---|
116 | else if (url.startsWith("jar:file:") && url.endsWith("!/")) |
---|
117 | { String fileURI = url.substring(4,url.length()-2); |
---|
118 | File f = new File(URI.create(fileURI)); |
---|
119 | JarFile jf = new JarFile(f); |
---|
120 | ClassPathElement cpe = new ClassPathElement(jf,url); |
---|
121 | result.add(cpe); |
---|
122 | } |
---|
123 | else |
---|
124 | { message = "this URL isn't valid"; |
---|
125 | } |
---|
126 | } catch (Exception e) |
---|
127 | { message = "Unhandled exception "+e.getClass().getName()+": "+e.getMessage(); |
---|
128 | } |
---|
129 | if (message!=null) |
---|
130 | { System.out.println("DynamicURLClassLoader failed to parse "+url+": "+message); |
---|
131 | } |
---|
132 | |
---|
133 | } |
---|
134 | return result; |
---|
135 | } |
---|
136 | |
---|
137 | public Class loadClass(String aName, boolean aResolve) throws ClassNotFoundException |
---|
138 | { Class c= loadClass(aName); |
---|
139 | if (aResolve) |
---|
140 | { resolveClass(c); |
---|
141 | } |
---|
142 | return c; |
---|
143 | } |
---|
144 | |
---|
145 | public Class loadClass(String aName) throws ClassNotFoundException |
---|
146 | { Class result = innerLocalLoadClass(aName); |
---|
147 | if (result==null) |
---|
148 | { throw new ClassNotFoundException(aName); |
---|
149 | } |
---|
150 | return result; |
---|
151 | } |
---|
152 | |
---|
153 | protected Class innerLocalLoadClass(String aName) |
---|
154 | { Class result = findLoadedClass(aName); |
---|
155 | if (result==null) |
---|
156 | { result = innerLoadClass(aName); |
---|
157 | } |
---|
158 | return result; |
---|
159 | } |
---|
160 | |
---|
161 | public URL getResource(String aName) |
---|
162 | { try |
---|
163 | { boolean found=false; |
---|
164 | for (Iterator i = mURLs.iterator(); i.hasNext(); ) |
---|
165 | { ClassPathElement cpe = (ClassPathElement)i.next(); |
---|
166 | if (cpe.mJarFile!=null) |
---|
167 | { JarEntry je = cpe.mJarFile.getJarEntry(aName); |
---|
168 | found=(je!=null); |
---|
169 | } |
---|
170 | else |
---|
171 | { File f = new File(cpe.mDirectory, aName); |
---|
172 | found=f.exists(); |
---|
173 | } |
---|
174 | if (found) |
---|
175 | { StringBuffer sb = new StringBuffer(aName.length()+cpe.mBaseURL.length()); |
---|
176 | sb.append(cpe.mBaseURL); |
---|
177 | Utils.appendUnreservedURIChar(sb, aName); |
---|
178 | return new URL(sb.toString()); |
---|
179 | } |
---|
180 | |
---|
181 | } |
---|
182 | } catch (MalformedURLException e) |
---|
183 | { // just return null |
---|
184 | } |
---|
185 | return null; |
---|
186 | } |
---|
187 | |
---|
188 | /*Cannot override in JDK 1.4.2 |
---|
189 | public Enumeration getResources(String aName) |
---|
190 | { URL resource = getResource(aName); |
---|
191 | if (resource!=null) |
---|
192 | { Vector v=new Vector(1); |
---|
193 | v.add(resource); |
---|
194 | return v.elements(); |
---|
195 | } |
---|
196 | else |
---|
197 | { return sEmptyEnum; |
---|
198 | } |
---|
199 | } |
---|
200 | */ |
---|
201 | |
---|
202 | /** create or return a buffer that is big enough. This buffer is designed |
---|
203 | * to be reused as much as possible to avoid memory thrashing but avoids |
---|
204 | * a possible deadlock scenario when two threads are loading in parallel |
---|
205 | * (very rare) |
---|
206 | * @param aLength the minimum size of buffer acceptable |
---|
207 | * @return the buffer |
---|
208 | */ |
---|
209 | private static byte[] getBuffer(int aLength) |
---|
210 | { byte[] result; |
---|
211 | synchronized(mBufferSync) |
---|
212 | { Thread current = Thread.currentThread(); |
---|
213 | if (mBuffer1User!=null && mBuffer1User!=current) |
---|
214 | { if (mBuffer2User!=null && mBuffer2User!=current) |
---|
215 | { result = new byte[aLength]; |
---|
216 | } |
---|
217 | else |
---|
218 | { if (mBuffer2.length<aLength) |
---|
219 | { mBuffer2 = new byte[aLength]; |
---|
220 | } |
---|
221 | result=mBuffer2; |
---|
222 | mBuffer2User = current; |
---|
223 | mBuffer2Count++; |
---|
224 | } |
---|
225 | } |
---|
226 | else |
---|
227 | { if (mBuffer1.length<aLength) |
---|
228 | { mBuffer1 = new byte[aLength]; |
---|
229 | } |
---|
230 | result=mBuffer1; |
---|
231 | mBuffer1User = current; |
---|
232 | mBuffer1Count++; |
---|
233 | } |
---|
234 | } |
---|
235 | return result; |
---|
236 | } |
---|
237 | |
---|
238 | /** release buffer for reuse |
---|
239 | */ |
---|
240 | private static void releaseBuffer(byte[] aBuffer) |
---|
241 | { synchronized(mBufferSync) |
---|
242 | { if (aBuffer==mBuffer1) |
---|
243 | { if (--mBuffer1Count==0) |
---|
244 | { mBuffer1User=null; |
---|
245 | } |
---|
246 | } |
---|
247 | else if (aBuffer==mBuffer2) |
---|
248 | { if (--mBuffer2Count==0) |
---|
249 | { mBuffer2User=null; |
---|
250 | } |
---|
251 | } |
---|
252 | } |
---|
253 | } |
---|
254 | |
---|
255 | private Class innerLoadClass(String aName) |
---|
256 | { StringBuffer sb=new StringBuffer(aName.length()+6); |
---|
257 | sb.append(aName.replace('.','/')); |
---|
258 | sb.append(".class"); |
---|
259 | String resource = sb.toString(); |
---|
260 | Class result = null; |
---|
261 | try |
---|
262 | { for (Iterator i = mURLs.iterator(); i.hasNext(); ) |
---|
263 | { ClassPathElement cpe = (ClassPathElement)i.next(); |
---|
264 | if (cpe.mJarFile!=null) |
---|
265 | { JarEntry je = cpe.mJarFile.getJarEntry(resource); |
---|
266 | if (je!=null) |
---|
267 | { result = createClassFromInputStream(aName,cpe.mJarFile.getInputStream(je),(int)je.getSize()); |
---|
268 | break; |
---|
269 | } |
---|
270 | } |
---|
271 | else |
---|
272 | { File f = new File(cpe.mDirectory, resource); |
---|
273 | if (f.exists()) |
---|
274 | { result = createClassFromInputStream(aName,new FileInputStream(f), (int)f.length()); |
---|
275 | break; |
---|
276 | } |
---|
277 | } |
---|
278 | } |
---|
279 | } catch (IOException e) |
---|
280 | { /* just return null */ |
---|
281 | } |
---|
282 | return result; |
---|
283 | } |
---|
284 | /* |
---|
285 | protected Class createClassFromInputStream(String aName,InputStream aStream, int aLength) throws IOException |
---|
286 | { Class result; |
---|
287 | int j=0; |
---|
288 | byte[] buffer = getBuffer(aLength); |
---|
289 | try |
---|
290 | { while (j<aLength) |
---|
291 | { int r = aStream.read(buffer,j, aLength-j); |
---|
292 | j+=r; |
---|
293 | } |
---|
294 | aStream.close(); |
---|
295 | result = defineClass(aName,buffer,0,aLength); |
---|
296 | |
---|
297 | int i = aName.lastIndexOf('.'); |
---|
298 | if (i != -1) |
---|
299 | { String pkgname = aName.substring(0, i); |
---|
300 | Package pkg = getPackage(pkgname); |
---|
301 | if (pkg==null) |
---|
302 | { definePackage(pkgname, null, null, null, null, null, null, null ); |
---|
303 | } |
---|
304 | } |
---|
305 | |
---|
306 | return result; |
---|
307 | } |
---|
308 | finally |
---|
309 | { releaseBuffer(buffer); |
---|
310 | } |
---|
311 | |
---|
312 | } |
---|
313 | */ |
---|
314 | protected Class createClassFromInputStream(String aName,InputStream aStream, int aLength) throws IOException |
---|
315 | { Class result; |
---|
316 | int j=0; |
---|
317 | byte[] buffer = getBuffer(aLength); |
---|
318 | try |
---|
319 | { while (j<aLength) |
---|
320 | { int r = aStream.read(buffer,j, aLength-j); |
---|
321 | j+=r; |
---|
322 | } |
---|
323 | aStream.close(); |
---|
324 | result = defineClass(aName,buffer,0,aLength); |
---|
325 | int i = aName.lastIndexOf('.'); |
---|
326 | if (i != -1) |
---|
327 | { String pkgname = aName.substring(0, i); |
---|
328 | Package pkg = getPackage(pkgname); |
---|
329 | if (pkg==null) |
---|
330 | { definePackage(pkgname, null, null, null, null, null, null, null ); |
---|
331 | } |
---|
332 | } |
---|
333 | |
---|
334 | return result; |
---|
335 | } |
---|
336 | catch (LinkageError e) |
---|
337 | { if (e.getMessage().indexOf("duplicate")>=0) |
---|
338 | { //this can happen if two threads race to load the class |
---|
339 | return this.findLoadedClass(aName); |
---|
340 | } |
---|
341 | else |
---|
342 | { throw e; |
---|
343 | } |
---|
344 | } |
---|
345 | finally |
---|
346 | { releaseBuffer(buffer); |
---|
347 | } |
---|
348 | } |
---|
349 | |
---|
350 | |
---|
351 | /** append XML representation of classloader structure |
---|
352 | */ |
---|
353 | public void appendXML(Writer aWriter) throws IOException |
---|
354 | { aWriter.write("<DynamicURLClassLoader>"); |
---|
355 | for (Iterator i=mURLs.iterator(); i.hasNext(); ) |
---|
356 | { ClassPathElement cpe = (ClassPathElement)i.next(); |
---|
357 | XMLUtils.write(aWriter, "url", XMLUtils.escape(cpe.mBaseURL)); |
---|
358 | } |
---|
359 | aWriter.write("</DynamicURLClassLoader>"); |
---|
360 | } |
---|
361 | } |
---|