comparison src/com/go/trove/util/ClassInjector.java @ 0:3dc0c5604566

Initial checkin of blitz 2.0 fcs - no installer yet.
author Dan Creswell <dan.creswell@gmail.com>
date Sat, 21 Mar 2009 11:00:06 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:3dc0c5604566
1 /* ====================================================================
2 * Trove - Copyright (c) 1997-2000 Walt Disney Internet Group
3 * ====================================================================
4 * The Tea Software License, Version 1.1
5 *
6 * Copyright (c) 2000 Walt Disney Internet Group. All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 *
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
18 * distribution.
19 *
20 * 3. The end-user documentation included with the redistribution,
21 * if any, must include the following acknowledgment:
22 * "This product includes software developed by the
23 * Walt Disney Internet Group (http://opensource.go.com/)."
24 * Alternately, this acknowledgment may appear in the software itself,
25 * if and wherever such third-party acknowledgments normally appear.
26 *
27 * 4. The names "Tea", "TeaServlet", "Kettle", "Trove" and "BeanDoc" must
28 * not be used to endorse or promote products derived from this
29 * software without prior written permission. For written
30 * permission, please contact opensource@dig.com.
31 *
32 * 5. Products derived from this software may not be called "Tea",
33 * "TeaServlet", "Kettle" or "Trove", nor may "Tea", "TeaServlet",
34 * "Kettle", "Trove" or "BeanDoc" appear in their name, without prior
35 * written permission of the Walt Disney Internet 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 WALT DISNEY INTERNET GROUP OR ITS
41 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
42 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
43 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
44 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
45 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
46 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
47 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48 * ====================================================================
49 *
50 * For more information about Tea, please see http://opensource.go.com/.
51 */
52
53 package com.go.trove.util;
54
55 import java.io.OutputStream;
56 import java.io.ByteArrayOutputStream;
57 import java.io.ByteArrayInputStream;
58 import java.io.File;
59 import java.io.InputStream;
60 import java.io.FileInputStream;
61 import java.io.IOException;
62 import java.lang.ref.*;
63 import java.net.URL;
64 import java.net.URLConnection;
65 import java.net.URLStreamHandler;
66 import java.util.*;
67 import java.util.zip.GZIPInputStream;
68 import java.util.zip.GZIPOutputStream;
69
70 /******************************************************************************
71 * A special ClassLoader that allows classes to be defined by directly
72 * injecting the bytecode. All classes other than those injected are loaded
73 * from the parent ClassLoader, which is the ClassLoader that loaded this
74 * class. If a directory is passed in, the ClassInjector looks there for
75 * non-injected class files before asking the parent ClassLoader for a class.
76 *
77 * @author Brian S O'Neill
78 * @version
79 * <!--$$Revision: 1.1 $-->, <!--$$JustDate:--> 7/30/01 <!-- $-->
80 */
81 public class ClassInjector extends ClassLoader {
82 private static Map cShared = new NullKeyMap(new IdentityMap());
83
84 /**
85 * Returns a shared ClassInjector instance.
86 */
87 public static ClassInjector getInstance() {
88 return getInstance(null);
89 }
90
91 /**
92 * Returns a shared ClassInjector instance for the given ClassLoader.
93 */
94 public static ClassInjector getInstance(ClassLoader loader) {
95 ClassInjector injector = null;
96
97 synchronized (cShared) {
98 Reference ref = (Reference)cShared.get(loader);
99 if (ref != null) {
100 injector = (ClassInjector)ref.get();
101 }
102 if (injector == null) {
103 injector = new ClassInjector(loader);
104 cShared.put(loader, new WeakReference(injector));
105 }
106 return injector;
107 }
108 }
109
110 // Parent ClassLoader, used to load classes that aren't defined by this.
111 private ClassLoader mSuperLoader;
112
113 private File[] mRootClassDirs;
114 private String mRootPackage;
115
116 // A set of all the classes defined by the ClassInjector.
117 private Map mDefined = Collections.synchronizedMap(new HashMap());
118
119 // A map to store raw bytecode for future use in getResourceAsStream().
120 private Map mGZippedBytecode;
121
122 private URLStreamHandler mFaker;
123
124 /**
125 * Construct a ClassInjector that uses the ClassLoader that loaded this
126 * class as a parent, and it has no root class directory or root package.
127 */
128 public ClassInjector() {
129 this(null, (File[])null, null);
130 }
131
132 /**
133 * Construct a ClassInjector that has no root class directory or root
134 * package.
135 *
136 * @param parent optional parent ClassLoader to default to when a class
137 * cannot be loaded with this ClassInjector.
138 */
139 public ClassInjector(ClassLoader parent) {
140 this(parent, (File[])null, null);
141 }
142
143 /**
144 * Construct a ClassInjector that uses the ClassLoader that loaded this
145 * class as a parent.
146 *
147 * @param rootClassDir optional directory to look for non-injected classes
148 * @param rootPackage optional package name for the root directory
149 */
150 public ClassInjector(File rootClassDir, String rootPackage) {
151 this(null, (rootClassDir == null) ? null : new File[]{rootClassDir},
152 rootPackage);
153 }
154
155 /**
156 * @param parent optional parent ClassLoader to default to when a class
157 * cannot be loaded with this ClassInjector.
158 * @param rootClassDir optional directory to look for non-injected classes
159 * @param rootPackage optional package name for the root directory
160 */
161 public ClassInjector(ClassLoader parent,
162 File rootClassDir, String rootPackage) {
163 this(parent, (rootClassDir == null) ? null : new File[]{rootClassDir},
164 rootPackage);
165 }
166
167 /**
168 * Construct a ClassInjector that uses the ClassLoader that loaded this
169 * class as a parent.
170 *
171 * @param rootClassDirs optional directories to look for non-injected
172 * classes
173 * @param rootPackage optional package name for the root directory
174 */
175 public ClassInjector(File[] rootClassDirs, String rootPackage) {
176 this(null, rootClassDirs, rootPackage);
177 }
178
179 /**
180 * @param parent optional parent ClassLoader to default to when a class
181 * cannot be loaded with this ClassInjector.
182 * @param rootClassDirs optional directories to look for non-injected
183 * classes
184 * @param rootPackage optional package name for the root directory
185 */
186 public ClassInjector(ClassLoader parent,
187 File[] rootClassDirs,
188 String rootPackage) {
189 this(parent, rootClassDirs, rootPackage, false);
190 }
191
192 /**
193 * @param parent optional parent ClassLoader to default to when a class
194 * cannot be loaded with this ClassInjector.
195 * @param rootClassDirs optional directories to look for non-injected
196 * classes
197 * @param rootPackage optional package name for the root directory
198 * @param keepRawBytecode if true, will cause the ClassInjector to store
199 * the raw bytecode of defined classes.
200 */
201 public ClassInjector(ClassLoader parent,
202 File[] rootClassDirs,
203 String rootPackage,
204 boolean keepRawBytecode) {
205 super();
206 if (parent == null) {
207 parent = getClass().getClassLoader();
208 }
209 mSuperLoader = parent;
210 if (rootClassDirs != null) {
211 mRootClassDirs = (File[])rootClassDirs.clone();
212 }
213 if (rootPackage != null && !rootPackage.endsWith(".")) {
214 rootPackage += '.';
215 }
216 mRootPackage = rootPackage;
217
218 if (keepRawBytecode) {
219 mGZippedBytecode = Collections.synchronizedMap(new HashMap());
220 }
221 }
222
223 /**
224 * Get a stream used to define a class. Close the stream to finish the
225 * definition.
226 *
227 * @param the fully qualified name of the class to be defined.
228 */
229 public OutputStream getStream(String name) {
230 return new Stream(name);
231 }
232
233 public URL getResource(String name) {
234
235 if (mGZippedBytecode != null) {
236 if (mGZippedBytecode.containsKey(name)) {
237 try {
238 return new URL("file", null, -1, name, getURLFaker());
239 }
240 catch (Exception e) {
241 e.printStackTrace();
242 }
243 System.out.println("created URL for " + name);
244 }
245 }
246 return mSuperLoader.getResource(name);
247 }
248
249 private URLStreamHandler getURLFaker() {
250 if (mFaker == null) {
251 mFaker = new URLFaker();
252 }
253 return mFaker;
254 }
255
256 protected Class loadClass(String name, boolean resolve)
257 throws ClassNotFoundException {
258
259 Class clazz = findLoadedClass(name);
260
261 if (clazz == null) {
262 synchronized (this) {
263 clazz = findLoadedClass(name);
264
265 if (clazz == null) {
266 clazz = loadFromFile(name);
267
268 if (clazz == null) {
269 if (mSuperLoader != null) {
270 clazz = mSuperLoader.loadClass(name);
271 }
272 else {
273 clazz = findSystemClass(name);
274 }
275
276 if (clazz == null) {
277 throw new ClassNotFoundException(name);
278 }
279 }
280 }
281 }
282 }
283
284 if (resolve) {
285 resolveClass(clazz);
286 }
287
288 return clazz;
289 }
290
291 protected void define(String name, byte[] data) {
292 defineClass(name, data, 0, data.length);
293 if (mGZippedBytecode != null) {
294 try {
295 ByteArrayOutputStream baos = new ByteArrayOutputStream();
296 GZIPOutputStream gz = new GZIPOutputStream(baos);
297 gz.write(data,0,data.length);
298 gz.close();
299 mGZippedBytecode.put(name.replace('.','/') + ".class",
300 baos.toByteArray());
301 }
302 catch (IOException ioe) {
303 ioe.printStackTrace();
304 }
305 }
306 }
307
308 private Class loadFromFile(String name) throws ClassNotFoundException {
309 if (mRootClassDirs == null) {
310 return null;
311 }
312
313 String fileName = name;
314
315 if (mRootPackage != null) {
316 if (fileName.startsWith(mRootPackage)) {
317 fileName = fileName.substring(mRootPackage.length());
318 }
319 else {
320 return null;
321 }
322 }
323
324 fileName = fileName.replace('.', File.separatorChar);
325 ClassNotFoundException error = null;
326
327 for (int i=0; i<mRootClassDirs.length; i++) {
328 File file = new File(mRootClassDirs[i], fileName + ".class");
329
330 if (file.exists()) {
331 try {
332 byte[] buffer = new byte[(int)file.length()];
333 int avail = buffer.length;
334 int offset = 0;
335 InputStream in = new FileInputStream(file);
336
337 int len = -1;
338 while ( (len = in.read(buffer, offset, avail)) > 0 ) {
339 offset += len;
340
341 if ( (avail -= len) <= 0 ) {
342 avail = buffer.length;
343 byte[] newBuffer = new byte[avail * 2];
344 System.arraycopy(buffer, 0, newBuffer, 0, avail);
345 buffer = newBuffer;
346 }
347 }
348
349 in.close();
350
351 return defineClass(name, buffer, 0, offset);
352 }
353 catch (IOException e) {
354 if (error == null) {
355 error = new ClassNotFoundException
356 (fileName + ": " + e.toString());
357 }
358 }
359 }
360 }
361
362 if (error != null) {
363 throw error;
364 }
365 else {
366 return null;
367 }
368 }
369
370 private class Stream extends ByteArrayOutputStream {
371 private String mName;
372
373 public Stream(String name) {
374 super(1024);
375 mName = name;
376 }
377
378 public void close() {
379 synchronized (mDefined) {
380 if (mDefined.get(mName) == null) {
381 define(mName, toByteArray());
382 mDefined.put(mName, mName);
383 }
384 }
385 }
386 }
387
388 private class URLFaker extends URLStreamHandler {
389
390 protected URLConnection openConnection(URL u) throws IOException {
391 return new ClassInjector.ResourceConnection(u);
392 }
393 }
394
395 private class ResourceConnection extends URLConnection {
396
397 String resourceName;
398 public ResourceConnection(URL u) {
399 super(u);
400 resourceName = u.getFile();
401 }
402
403 // not really needed here but it was abstract.
404 public void connect() {}
405
406 public InputStream getInputStream() throws IOException {
407
408 try {
409 if (mGZippedBytecode != null) {
410
411 if (mGZippedBytecode.get(resourceName) != null) {
412 return new GZIPInputStream(new ByteArrayInputStream
413 ((byte[])mGZippedBytecode.get(resourceName)));
414 }
415 else {
416 System.out.println(resourceName + " not found in bytecode map.");
417 }
418 }
419 else {
420 System.out.println("no bytecode map configured in "+ ClassInjector.this);
421 }
422 }
423 catch (Exception e) {
424 e.printStackTrace();
425 }
426
427 return null;
428 }
429 }
430 }