001: /*
002: * Janino - An embedded Java[TM] compiler
003: *
004: * Copyright (c) 2001-2007, Arno Unkrig
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions
009: * are met:
010: *
011: * 1. Redistributions of source code must retain the above copyright
012: * notice, this list of conditions and the following disclaimer.
013: * 2. Redistributions in binary form must reproduce the above
014: * copyright notice, this list of conditions and the following
015: * disclaimer in the documentation and/or other materials
016: * provided with the distribution.
017: * 3. The name of the author may not be used to endorse or promote
018: * products derived from this software without specific prior
019: * written permission.
020: *
021: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
022: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
023: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
024: * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
025: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
026: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
027: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
028: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
029: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
030: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
031: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
032: */
033:
034: package org.codehaus.janino;
035:
036: import java.io.*;
037: import java.lang.reflect.*;
038: import java.util.*;
039:
040: import java.security.ProtectionDomain;
041:
042: import org.codehaus.janino.tools.Disassembler;
043: import org.codehaus.janino.util.*;
044: import org.codehaus.janino.util.enumerator.*;
045: import org.codehaus.janino.util.resource.*;
046:
047: /**
048: * A {@link ClassLoader} that, unlike usual {@link ClassLoader}s,
049: * does not load byte code, but reads Java<sup>TM</sup> source code and then scans, parses,
050: * compiles and loads it into the virtual machine.
051: * <p>
052: * As with any {@link ClassLoader}, it is not possible to "update" classes after they've been
053: * loaded. The way to achieve this is to give up on the {@link JavaSourceClassLoader} and create
054: * a new one.
055: */
056: public class JavaSourceClassLoader extends ClassLoader {
057: private final static boolean DEBUG = false;
058:
059: public interface ProtectionDomainFactory {
060: ProtectionDomain getProtectionDomain(String name);
061: }
062:
063: /**
064: * Read Java<sup>TM</sup> source code for a given class name, scan, parse, compile and load
065: * it into the virtual machine, and invoke its "main()" method with the given args.
066: * <p>
067: * Usage is as follows:
068: * <pre>
069: * java [ <i>java-option</i> ] org.codehaus.janino.JavaSourceClassLoader [ <i>option</i> ] ... <i>class-name</i> [ <i>arg</i> ] ...
070: * <i>java-option</i> Any valid option for the Java Virtual Machine (e.g. "-classpath <i>colon-separated-list-of-class-directories</i>")
071: * <i>option</i>:
072: * -sourcepath <i>colon-separated-list-of-source-directories</i>
073: * -encoding <i>character-encoding</i>
074: * -g Generate all debugging info");
075: * -g:none Generate no debugging info");
076: * -g:{lines,vars,source} Generate only some debugging info");
077: * -cache <i>dir</i> Cache compiled classes here");
078:
079: * </pre>
080: */
081: public static void main(String[] args) {
082: File[] optionalSourcePath = null;
083: String optionalCharacterEncoding = null;
084: EnumeratorSet debuggingInformation = DebuggingInformation.DEFAULT_DEBUGGING_INFORMATION;
085: String optionalCacheDirName = null;
086:
087: // Scan command line options.
088: int i;
089: for (i = 0; i < args.length; ++i) {
090: String arg = args[i];
091: if (!arg.startsWith("-"))
092: break;
093:
094: if ("-sourcepath".equals(arg)) {
095: optionalSourcePath = PathResourceFinder
096: .parsePath(args[++i]);
097: } else if ("-encoding".equals(arg)) {
098: optionalCharacterEncoding = args[++i];
099: } else if (arg.equals("-g")) {
100: debuggingInformation = DebuggingInformation.ALL;
101: } else if (arg.equals("-g:none")) {
102: debuggingInformation = DebuggingInformation.NONE;
103: } else if (arg.startsWith("-g:")) {
104: try {
105: debuggingInformation = new EnumeratorSet(
106: DebuggingInformation.class, arg
107: .substring(3));
108: } catch (EnumeratorFormatException ex) {
109: debuggingInformation = DebuggingInformation.NONE;
110: }
111: } else if ("-cache".equals(arg)) {
112: optionalCacheDirName = args[++i];
113: } else if ("-help".equals(arg)) {
114: System.out.println("Usage:");
115: System.out.println(" java [ <java-option> ] "
116: + JavaSourceClassLoader.class.getName()
117: + " { <option> } <class-name> { <argument> }");
118: System.out
119: .println("Load the named class by name and invoke its \"main(String[])\" method,");
120: System.out.println("passing the given <argument>s.");
121: System.out
122: .println(" <java-option> Any valid option for the Java Virtual Machine (e.g. \"-classpath <dir>\")");
123: System.out.println(" <option>:");
124: System.out.println(" -sourcepath <"
125: + File.pathSeparator
126: + "-separated-list-of-source-directories>");
127: System.out
128: .println(" -encoding <character-encoding>");
129: System.out
130: .println(" -g Generate all debugging info");
131: System.out
132: .println(" -g:none Generate no debugging info");
133: System.out
134: .println(" -g:{lines,vars,source} Generate only some debugging info");
135: System.out
136: .println(" -cache <dir> Cache compiled classes here");
137: System.exit(0);
138: } else {
139: System.err.println("Invalid command line option \""
140: + arg + "\"; try \"-help\"");
141: System.exit(1);
142: }
143: }
144:
145: // Determine class name.
146: if (i == args.length) {
147: System.err.println("No class name given, try \"-help\"");
148: System.exit(1);
149: }
150: String className = args[i++];
151:
152: // Determine arguments passed to "main()".
153: String[] mainArgs = new String[args.length - i];
154: System.arraycopy(args, i, mainArgs, 0, args.length - i);
155:
156: // Set up a JavaSourceClassLoader or a CachingJavaSourceClassLoader.
157: ClassLoader cl;
158: if (optionalCacheDirName == null) {
159: cl = new JavaSourceClassLoader(ClassLoader
160: .getSystemClassLoader(), // parentClassLoader
161: optionalSourcePath, // optionalSourcePath
162: optionalCharacterEncoding, // optionalCharacterEncoding
163: debuggingInformation // debuggingInformation
164: );
165: } else {
166: cl = new CachingJavaSourceClassLoader(
167: SimpleCompiler.BOOT_CLASS_LOADER, // parentClassLoader
168: optionalSourcePath, // optionalSourcePath
169: optionalCharacterEncoding, // optionalCharacterEncoding
170: new File(optionalCacheDirName), // cacheDirectory
171: debuggingInformation // debuggingInformation
172: );
173: }
174:
175: // Load the given class.
176: Class clazz;
177: try {
178: clazz = cl.loadClass(className);
179: } catch (ClassNotFoundException ex) {
180: System.err.println("Loading class \"" + className + "\": "
181: + ex.getMessage());
182: System.exit(1);
183: return; // NEVER REACHED
184: }
185:
186: // Find its "main" method.
187: Method mainMethod;
188: try {
189: mainMethod = clazz.getMethod("main",
190: new Class[] { String[].class });
191: } catch (NoSuchMethodException ex) {
192: System.err.println("Class \"" + className
193: + "\" has not public method \"main(String[])\".");
194: System.exit(1);
195: return; // NEVER REACHED
196: }
197:
198: // Invoke the "main" method.
199: try {
200: mainMethod.invoke(null, new Object[] { mainArgs });
201: } catch (IllegalArgumentException e) {
202: e.printStackTrace();
203: System.exit(1);
204: } catch (IllegalAccessException e) {
205: e.printStackTrace();
206: System.exit(1);
207: } catch (InvocationTargetException e) {
208: e.printStackTrace();
209: System.exit(1);
210: }
211: }
212:
213: /**
214: * Set up a {@link JavaSourceClassLoader} that finds Java<sup>TM</sup> source code in a file
215: * that resides in either of the directories specified by the given source path.
216: * <p>
217: * You can specify to include certain debugging information in the generated class files, which
218: * is useful if you want to debug through the generated classes (see
219: * {@link Scanner#Scanner(String, Reader)}).
220: *
221: * @param parentClassLoader See {@link ClassLoader}
222: * @param optionalSourcePath A collection of directories that are searched for Java<sup>TM</sup> source files in the given order
223: * @param optionalCharacterEncoding The encoding of the Java<sup>TM</sup> source files (<code>null</code> for platform default encoding)
224: * @param debuggingInformation What kind of debugging information to generate, see {@link DebuggingInformation}
225: */
226: public JavaSourceClassLoader(ClassLoader parentClassLoader,
227: File[] optionalSourcePath,
228: String optionalCharacterEncoding,
229: EnumeratorSet debuggingInformation) {
230: this (
231: parentClassLoader, // parentClassLoader
232: ( // sourceFinder
233: optionalSourcePath == null ? (ResourceFinder) new DirectoryResourceFinder(
234: new File("."))
235: : (ResourceFinder) new PathResourceFinder(
236: optionalSourcePath)),
237: optionalCharacterEncoding, // optionalCharacterEncoding
238: debuggingInformation // debuggingInformation
239: );
240: }
241:
242: /**
243: * Set up a {@link JavaSourceClassLoader} that finds Java<sup>TM</sup> source code through
244: * a given {@link ResourceFinder}.
245: * <p>
246: * You can specify to include certain debugging information in the generated class files, which
247: * is useful if you want to debug through the generated classes (see
248: * {@link Scanner#Scanner(String, Reader)}).
249: *
250: * @param parentClassLoader See {@link ClassLoader}
251: * @param sourceFinder Used to locate additional source files
252: * @param optionalCharacterEncoding The encoding of the Java<sup>TM</sup> source files (<code>null</code> for platform default encoding)
253: * @param debuggingInformation What kind of debugging information to generate, see {@link DebuggingInformation}
254: */
255: public JavaSourceClassLoader(ClassLoader parentClassLoader,
256: ResourceFinder sourceFinder,
257: String optionalCharacterEncoding,
258: EnumeratorSet debuggingInformation) {
259: super (parentClassLoader);
260:
261: this .iClassLoader = new JavaSourceIClassLoader(sourceFinder, // sourceFinder
262: optionalCharacterEncoding, // optionalCharacterEncoding
263: this .unitCompilers, // unitCompilers
264: new ClassLoaderIClassLoader(parentClassLoader) // optionalParentIClassLoader
265: );
266:
267: this .debuggingInformation = debuggingInformation;
268: }
269:
270: /**
271: * @see UnitCompiler#setCompileErrorHandler
272: */
273: public void setCompileErrorHandler(
274: UnitCompiler.ErrorHandler optionalCompileErrorHandler) {
275: this .iClassLoader
276: .setCompileErrorHandler(optionalCompileErrorHandler);
277: }
278:
279: /**
280: * @see Parser#setWarningHandler(WarningHandler)
281: * @see UnitCompiler#setCompileErrorHandler
282: */
283: public void setWarningHandler(WarningHandler optionalWarningHandler) {
284: this .iClassLoader.setWarningHandler(optionalWarningHandler);
285: }
286:
287: /**
288: * Implementation of {@link ClassLoader#findClass(String)}.
289: *
290: * @throws ClassNotFoundException
291: */
292: protected Class findClass(String name)
293: throws ClassNotFoundException {
294:
295: // Check if the bytecode for that class was generated already.
296: byte[] bytecode = (byte[]) this .precompiledClasses.remove(name);
297: if (bytecode == null) {
298:
299: // Read, scan, parse and compile the right compilation unit.
300: {
301: Map bytecodes = this .generateBytecodes(name);
302: if (bytecodes == null)
303: throw new ClassNotFoundException(name);
304: this .precompiledClasses.putAll(bytecodes);
305: }
306:
307: // Now the bytecode for our class should be available.
308: bytecode = (byte[]) this .precompiledClasses.remove(name);
309: if (bytecode == null)
310: throw new RuntimeException(
311: "SNO: Scanning, parsing and compiling class \""
312: + name
313: + "\" did not create a class file!?");
314: }
315:
316: return this .defineBytecode(name, bytecode);
317: }
318:
319: /**
320: * This {@link Map} keeps those classes which were already compiled, but not
321: * yet defined i.e. which were not yet passed to
322: * {@link ClassLoader#defineClass(java.lang.String, byte[], int, int)}.
323: */
324: Map precompiledClasses = new HashMap(); // String name => byte[] bytecode
325:
326: /**
327: * Find, scan, parse the right compilation unit. Compile the parsed compilation unit to
328: * bytecode. This may cause more compilation units being scanned and parsed. Continue until
329: * all compilation units are compiled.
330: *
331: * @return String name => byte[] bytecode, or <code>null</code> if no source code could be found
332: * @throws ClassNotFoundException on compilation problems
333: */
334: protected Map generateBytecodes(String name)
335: throws ClassNotFoundException {
336: if (this .iClassLoader
337: .loadIClass(Descriptor.fromClassName(name)) == null)
338: return null;
339:
340: Map bytecodes = new HashMap(); // String name => byte[] bytecode
341: Set compiledUnitCompilers = new HashSet();
342: COMPILE_UNITS: for (;;) {
343: for (Iterator it = this .unitCompilers.iterator(); it
344: .hasNext();) {
345: UnitCompiler uc = (UnitCompiler) it.next();
346: if (!compiledUnitCompilers.contains(uc)) {
347: ClassFile[] cfs;
348: try {
349: cfs = uc.compileUnit(this .debuggingInformation);
350: } catch (CompileException ex) {
351: throw new ClassNotFoundException(
352: "Compiling unit \""
353: + uc.compilationUnit.optionalFileName
354: + "\"", ex);
355: }
356: for (int i = 0; i < cfs.length; ++i) {
357: ClassFile cf = cfs[i];
358: bytecodes.put(cf.getThisClassName(), cf
359: .toByteArray());
360: }
361: compiledUnitCompilers.add(uc);
362: continue COMPILE_UNITS;
363: }
364: }
365: return bytecodes;
366: }
367: }
368:
369: /**
370: * Define a set of classes, like
371: * {@link java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int)}.
372: * If the <code>bytecodes</code> contains an entry for <code>name</code>, then the
373: * {@link Class} defined for that name is returned.
374: *
375: * @param bytecodes String name => byte[] bytecode
376: */
377: protected Class defineBytecodes(String name, Map bytecodes)
378: throws ClassFormatError {
379: Class clazz = null;
380: for (Iterator it = bytecodes.entrySet().iterator(); it
381: .hasNext();) {
382: Map.Entry me = (Map.Entry) it.next();
383: String name2 = (String) me.getKey();
384: byte[] ba = (byte[]) me.getValue();
385:
386: Class c = this .defineBytecode(name2, ba);
387: if (name2.equals(name))
388: clazz = c;
389: }
390: return clazz;
391: }
392:
393: /**
394: * Calls {@link java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int)}
395: * or {@link java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int, java.security.ProtectionDomain)},
396: * depending on whether or not a {@link ProtectionDomainFactory} was set.
397: *
398: * @see #setProtectionDomainFactory
399: */
400: protected Class defineBytecode(String className, byte[] ba)
401: throws ClassFormatError {
402:
403: // Disassemble the the class bytecode(for debugging).
404: if (JavaSourceClassLoader.DEBUG) {
405: System.out.println("*** Disassembly of class \""
406: + className + "\":");
407: try {
408: new Disassembler().disasm(new ByteArrayInputStream(ba));
409: System.out.flush();
410: } catch (IOException ex) {
411: throw new RuntimeException(
412: "SNO: IOException despite ByteArrayInputStream");
413: }
414: }
415:
416: if (this .protectionDomainFactory == null) {
417: return this .defineClass(className, ba, 0, ba.length);
418: } else {
419: String sourceName = ClassFile
420: .getSourceResourceName(className);
421: ProtectionDomain domain = this .protectionDomainFactory
422: .getProtectionDomain(sourceName);
423: return this
424: .defineClass(className, ba, 0, ba.length, domain);
425: }
426: }
427:
428: public void setProtectionDomainFactory(
429: ProtectionDomainFactory protectionDomainFactory) {
430: this .protectionDomainFactory = protectionDomainFactory;
431: }
432:
433: private final JavaSourceIClassLoader iClassLoader;
434: private final EnumeratorSet debuggingInformation;
435: private ProtectionDomainFactory protectionDomainFactory;
436:
437: /**
438: * Collection of parsed, but uncompiled compilation units.
439: */
440: private final Set unitCompilers = new HashSet(); // UnitCompiler
441: }
|