KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > cojen > util > ClassInjector


1 /*
2  * Copyright 2004 Brian S O'Neill
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package org.cojen.util;
18
19 import java.io.ByteArrayOutputStream JavaDoc;
20 import java.io.File JavaDoc;
21 import java.io.FileOutputStream JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.io.OutputStream JavaDoc;
24 import java.lang.ref.SoftReference JavaDoc;
25 import java.util.HashSet JavaDoc;
26 import java.util.Map JavaDoc;
27 import java.util.Random JavaDoc;
28 import java.util.Set JavaDoc;
29
30 import org.cojen.classfile.ClassFile;
31
32 /**
33  * ClassInjector allows transient classes to be loaded, where a transient class
34  * is defined as being dynamically created at runtime. Unless explicit, the
35  * name given to transient classes is randomly assigned to prevent name
36  * collisions and to discourage referencing the classname persistently outside
37  * the runtime environment.
38  * <p>
39  * Classes defined by ClassInjector may be unloaded, if no references to it
40  * exist. Once unloaded, they cannot be loaded again by name since the
41  * original bytecode was never preserved.
42  * <p>
43  * Debugging can be enabled via the java command-line option
44  * "-Dcojen.util.ClassInjector.DEBUG=true". This causes all generated classes
45  * to be written to the temp directory, and a message is written to System.out
46  * indicating exactly where.
47  *
48  * @author Brian S O'Neill
49  */

50 public class ClassInjector {
51     private static final boolean DEBUG;
52
53     static {
54         DEBUG = Boolean.getBoolean("org.cojen.util.ClassInjector.DEBUG");
55     }
56
57     private static final Random JavaDoc cRandom = new Random JavaDoc();
58
59     // Weakly maps ClassLoaders to softly referenced internal ClassLoaders.
60
private static Map JavaDoc cLoaders = new WeakIdentityMap();
61
62     /**
63      * Create a ClassInjector for defining one class. The parent ClassLoader
64      * used is the one which loaded the ClassInjector class.
65      */

66     public static ClassInjector create() {
67         return create(null, null);
68     }
69
70     /**
71      * Create a ClassInjector for defining one class. The prefix is optional,
72      * which is used as the start of the auto-generated class name. If the
73      * parent ClassLoader is not specified, it will default to the ClassLoader of
74      * the ClassInjector class.
75      * <p>
76      * If the parent loader was used for loading injected classes, the new
77      * class will be loaded by it. This allows auto-generated classes access to
78      * package accessible members, as long as they are defined in the same
79      * package.
80      *
81      * @param prefix optional class name prefix
82      * @param parent optional parent ClassLoader
83      */

84     public static ClassInjector create(String JavaDoc prefix, ClassLoader JavaDoc parent) {
85         return create(prefix, parent, false);
86     }
87
88     /**
89      * Create a ClassInjector for defining one class with an explicit name. If
90      * the parent ClassLoader is not specified, it will default to the
91      * ClassLoader of the ClassInjector class.
92      * <p>
93      * If the parent loader was used for loading injected classes, the new
94      * class will be loaded by it. This allows auto-generated classes access to
95      * package accessible members, as long as they are defined in the same
96      * package.
97      *
98      * @param name required class name
99      * @param parent optional parent ClassLoader
100      * @throws IllegalArgumentException if name is null
101      */

102     public static ClassInjector createExplicit(String JavaDoc name, ClassLoader JavaDoc parent) {
103         if (name == null) {
104             throw new IllegalArgumentException JavaDoc("Explicit class name not provided");
105         }
106         return create(name, parent, true);
107     }
108
109     private static ClassInjector create(String JavaDoc prefix, ClassLoader JavaDoc parent, boolean explicit) {
110         if (prefix == null) {
111             prefix = ClassInjector.class.getName();
112         }
113         if (parent == null) {
114             parent = ClassInjector.class.getClassLoader();
115             if (parent == null) {
116                 parent = ClassLoader.getSystemClassLoader();
117             }
118         }
119
120         String JavaDoc name = explicit ? prefix : null;
121         Loader loader;
122
123         synchronized (cRandom) {
124             getLoader: {
125                 if (parent instanceof Loader) {
126                     // Use the same loader, allowing the new class access to
127
// same package protected members.
128
loader = (Loader) parent;
129                     break getLoader;
130                 }
131                 SoftReference JavaDoc ref = (SoftReference JavaDoc) cLoaders.get(parent);
132                 if (ref != null) {
133                     loader = (Loader) ref.get();
134                     if (loader != null && loader.isValid()) {
135                         break getLoader;
136                     }
137                     ref.clear();
138                 }
139                 loader = parent == null ? new Loader() : new Loader(parent);
140                 cLoaders.put(parent, new SoftReference JavaDoc(loader));
141             }
142
143             if (explicit) {
144                 reserveCheck: {
145                     for (int i=0; i<2; i++) {
146                         if (loader.reserveName(name)) {
147                             try {
148                                 loader.loadClass(name);
149                             } catch (ClassNotFoundException JavaDoc e) {
150                                 break reserveCheck;
151                             }
152                         }
153                         if (i > 0) {
154                             throw new IllegalStateException JavaDoc
155                                 ("Class name already reserved: " + name);
156                         }
157                         // Make a new loader and try again.
158
loader = parent == null ? new Loader() : new Loader(parent);
159                     }
160
161                     // Save new loader.
162
cLoaders.put(parent, new SoftReference JavaDoc(loader));
163                 }
164             } else {
165                 for (int tryCount = 0; tryCount < 1000; tryCount++) {
166                     name = null;
167                     
168                     long ID = cRandom.nextInt();
169                     
170                     // Use a small identifier if possible, making it easier to read
171
// stack traces and decompiled classes.
172
switch (tryCount) {
173                     case 0:
174                         ID &= 0xffL;
175                         break;
176                     case 1:
177                         ID &= 0xffffL;
178                         break;
179                     default:
180                         ID &= 0xffffffffL;
181                         break;
182                     }
183                     
184                     name = prefix + '$' + ID;
185                     
186                     if (!loader.reserveName(name)) {
187                         continue;
188                     }
189                     
190                     try {
191                         loader.loadClass(name);
192                     } catch (ClassNotFoundException JavaDoc e) {
193                         break;
194                     } catch (LinkageError JavaDoc e) {
195                     }
196                 }
197             }
198         }
199
200         if (name == null) {
201             throw new InternalError JavaDoc("Unable to create unique class name");
202         }
203
204         return new ClassInjector(name, loader);
205     }
206
207     private final String JavaDoc mName;
208     private final Loader mLoader;
209
210     private ByteArrayOutputStream JavaDoc mData;
211     private Class JavaDoc mClass;
212
213     private ClassInjector(String JavaDoc name, Loader loader) {
214         mName = name;
215         mLoader = loader;
216     }
217
218     /**
219      * Returns the name that must be given to the new class.
220      */

221     public String JavaDoc getClassName() {
222         return mName;
223     }
224
225     /**
226      * Open a stream to define the new class into.
227      *
228      * @throws IllegalStateException if new class has already been defined
229      * or if a stream has already been opened
230      */

231     public OutputStream JavaDoc openStream() throws IllegalStateException JavaDoc {
232         if (mClass != null) {
233             throw new IllegalStateException JavaDoc("New class has already been defined");
234         }
235         ByteArrayOutputStream JavaDoc data = mData;
236         if (data != null) {
237             throw new IllegalStateException JavaDoc("Stream already opened");
238         }
239         mData = data = new ByteArrayOutputStream JavaDoc();
240         return data;
241     }
242
243     /**
244      * Define the new class from a ClassFile object.
245      *
246      * @return the newly created class
247      * @throws IllegalStateException if new class has already been defined
248      * or if a stream has already been opened
249      */

250     public Class JavaDoc defineClass(ClassFile cf) {
251         try {
252             cf.writeTo(openStream());
253         } catch (IOException JavaDoc e) {
254             throw new InternalError JavaDoc(e.toString());
255         }
256         return getNewClass();
257     }
258
259     /**
260      * Returns the newly defined class.
261      *
262      * @throws IllegalStateException if class was never defined
263      */

264     public Class JavaDoc getNewClass() throws IllegalStateException JavaDoc, ClassFormatError JavaDoc {
265         if (mClass != null) {
266             return mClass;
267         }
268         ByteArrayOutputStream JavaDoc data = mData;
269         if (data == null) {
270             throw new IllegalStateException JavaDoc("Class not defined yet");
271         }
272
273         byte[] bytes = data.toByteArray();
274
275         if (DEBUG) {
276             File JavaDoc file = new File JavaDoc(mName.replace('.', '/') + ".class");
277             try {
278                 File JavaDoc tempDir = new File JavaDoc(System.getProperty("java.io.tmpdir"));
279                 file = new File JavaDoc(tempDir, file.getPath());
280             } catch (SecurityException JavaDoc e) {
281             }
282             try {
283                 file.getParentFile().mkdirs();
284                 System.out.println("ClassInjector writing to " + file);
285                 OutputStream JavaDoc out = new FileOutputStream JavaDoc(file);
286                 out.write(bytes);
287                 out.close();
288             } catch (Exception JavaDoc e) {
289                 e.printStackTrace();
290             }
291         }
292
293         mClass = mLoader.define(mName, bytes);
294         mData = null;
295         return mClass;
296     }
297
298     private static final class Loader extends ClassLoader JavaDoc {
299         private Set JavaDoc mReservedNames = new HashSet JavaDoc();
300
301         Loader(ClassLoader JavaDoc parent) {
302             super(parent);
303         }
304
305         Loader() {
306             super();
307         }
308
309         // Prevent name collisions while multiple threads are injecting classes
310
// by reserving the name.
311
synchronized boolean reserveName(String JavaDoc name) {
312             return mReservedNames.add(name);
313         }
314
315         synchronized boolean isValid() {
316             // Only use loader for 100 injections, to facilitate class
317
// unloading.
318
return mReservedNames.size() < 100;
319         }
320
321         Class JavaDoc define(String JavaDoc name, byte[] b) {
322             Class JavaDoc clazz = defineClass(name, b, 0, b.length);
323             resolveClass(clazz);
324             return clazz;
325         }
326     }
327 }
328
Popular Tags