001: /*
002: * Janino - An embedded Java[TM] compiler
003: *
004: * Copyright (c) 2006, 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.util.*;
043: import org.codehaus.janino.util.enumerator.*;
044: import org.codehaus.janino.util.resource.*;
045:
046: /**
047: * A {@link ClassLoader} that, unlike usual {@link ClassLoader}s,
048: * does not load byte code, but reads Java<sup>TM</sup> source code and then scans, parses,
049: * compiles and loads it into the virtual machine.
050: */
051: public class JavaSourceClassLoader extends ClassLoader {
052:
053: public interface ProtectionDomainFactory {
054: ProtectionDomain getProtectionDomain(String name);
055: }
056:
057: /**
058: * Read Java<sup>TM</sup> source code for a given class name, scan, parse, compile and load
059: * it into the virtual machine, and invoke its "main()" method.
060: * <p>
061: * Usage is as follows:
062: * <pre>
063: * java [ <i>java-option</i> ] org.codehaus.janino.JavaSourceClassLoader [ <i>option</i> ] ... <i>class-name</i> [ <i>arg</i> ] ...
064: * <i>java-option</i> Any valid option for the Java Virtual Machine (e.g. "-classpath <i>colon-separated-list-of-class-directories</i>")
065: * <i>option</i>:
066: * -sourcepath <i>colon-separated-list-of-source-directories</i>
067: * -encoding <i>character-encoding</i>
068: * -g Generate all debugging info");
069: * -g:none Generate no debugging info");
070: * -g:{lines,vars,source} Generate only some debugging info");
071: * -cache <i>dir</i> Cache compiled classes here");
072:
073: * </pre>
074: */
075: public static void main(String[] args) {
076: File[] optionalSourcePath = null;
077: String optionalCharacterEncoding = null;
078: EnumeratorSet debuggingInformation = DebuggingInformation.DEFAULT_DEBUGGING_INFORMATION;
079: String optionalCacheDirName = null;
080:
081: // Scan command line options.
082: int i;
083: for (i = 0; i < args.length; ++i) {
084: String arg = args[i];
085: if (!arg.startsWith("-"))
086: break;
087:
088: if ("-sourcepath".equals(arg)) {
089: optionalSourcePath = PathResourceFinder
090: .parsePath(args[++i]);
091: } else if ("-encoding".equals(arg)) {
092: optionalCharacterEncoding = args[++i];
093: } else if (arg.equals("-g")) {
094: debuggingInformation = DebuggingInformation.ALL;
095: } else if (arg.equals("-g:none")) {
096: debuggingInformation = DebuggingInformation.NONE;
097: } else if (arg.startsWith("-g:")) {
098: try {
099: debuggingInformation = new EnumeratorSet(
100: DebuggingInformation.class, arg
101: .substring(3));
102: } catch (EnumeratorFormatException ex) {
103: debuggingInformation = DebuggingInformation.NONE;
104: }
105: } else if ("-cache".equals(arg)) {
106: optionalCacheDirName = args[++i];
107: } else if ("-help".equals(arg)) {
108: System.out.println("Usage:");
109: System.out
110: .println(" java [ <java-option> ] "
111: + JavaSourceClassLoader.class.getName()
112: + " [ <option>] ... <class-name> [ <arg> ] ...");
113: System.out
114: .println("Load a Java class by name and invoke its \"main(String[])\" method,");
115: System.out.println("pass");
116: System.out
117: .println(" <java-option> Any valid option for the Java Virtual Machine (e.g. \"-classpath <dir>\")");
118: System.out.println(" <option>:");
119: System.out.println(" -sourcepath <"
120: + File.pathSeparator
121: + "-separated-list-of-source-directories>");
122: System.out
123: .println(" -encoding <character-encoding>");
124: System.out
125: .println(" -g Generate all debugging info");
126: System.out
127: .println(" -g:none Generate no debugging info");
128: System.out
129: .println(" -g:{lines,vars,source} Generate only some debugging info");
130: System.out
131: .println(" -cache <dir> Cache compiled classes here");
132: System.exit(0);
133: } else {
134: System.err.println("Invalid command line option \""
135: + arg + "\"; try \"-help\"");
136: System.exit(1);
137: }
138: }
139:
140: // Determine class name.
141: if (i == args.length) {
142: System.err.println("No class name given, try \"-help\"");
143: System.exit(1);
144: }
145: String className = args[i++];
146:
147: // Determine arguments passed to "main()".
148: String[] mainArgs = new String[args.length - i];
149: System.arraycopy(args, i, mainArgs, 0, args.length - i);
150:
151: // Set up a JavaSourceClassLoader or a CachingJavaSourceClassLoader.
152: ClassLoader cl;
153: if (optionalCacheDirName == null) {
154: cl = new JavaSourceClassLoader(ClassLoader
155: .getSystemClassLoader(), // parentClassLoader
156: optionalSourcePath, // optionalSourcePath
157: optionalCharacterEncoding, // optionalCharacterEncoding
158: debuggingInformation // debuggingInformation
159: );
160: } else {
161: cl = new CachingJavaSourceClassLoader(
162: new ClassLoader(null) {
163: }, // parentClassLoader
164: optionalSourcePath, // optionalSourcePath
165: optionalCharacterEncoding, // optionalCharacterEncoding
166: new File(optionalCacheDirName), // cacheDirectory
167: debuggingInformation // debuggingInformation
168: );
169: }
170:
171: // Load the given class.
172: Class clazz;
173: try {
174: clazz = cl.loadClass(className);
175: } catch (ClassNotFoundException ex) {
176: System.err.println("Loading class \"" + className + "\": "
177: + ex.getMessage());
178: System.exit(1);
179: return; // NEVER REACHED
180: }
181:
182: // Find its "main" method.
183: Method mainMethod;
184: try {
185: mainMethod = clazz.getMethod("main",
186: new Class[] { String[].class });
187: } catch (NoSuchMethodException ex) {
188: System.err.println("Class \"" + className
189: + "\" has not public method \"main(String[])\".");
190: System.exit(1);
191: return; // NEVER REACHED
192: }
193:
194: // Invoke the "main" method.
195: try {
196: mainMethod.invoke(null, new Object[] { mainArgs });
197: } catch (IllegalArgumentException e) {
198: e.printStackTrace();
199: System.exit(1);
200: } catch (IllegalAccessException e) {
201: e.printStackTrace();
202: System.exit(1);
203: } catch (InvocationTargetException e) {
204: e.printStackTrace();
205: System.exit(1);
206: }
207: }
208:
209: /**
210: * Set up a {@link JavaSourceClassLoader} that finds Java<sup>TM</sup> source code in a file
211: * that resides in either of the directories specified by the given source path.
212: *
213: * @param parentClassLoader See {@link ClassLoader}
214: * @param optionalSourcePath A collection of directories that are searched for Java<sup>TM</sup> source files in the given order
215: * @param optionalCharacterEncoding The encoding of the Java<sup>TM</sup> source files (<code>null</code> for platform default encoding)
216: * @param debuggingInformation What kind of debugging information to generate
217: */
218: public JavaSourceClassLoader(ClassLoader parentClassLoader,
219: File[] optionalSourcePath,
220: String optionalCharacterEncoding,
221: EnumeratorSet debuggingInformation) {
222: this (
223: parentClassLoader, // parentClassLoader
224: ( // sourceFinder
225: optionalSourcePath == null ? (ResourceFinder) new DirectoryResourceFinder(
226: new File("."))
227: : (ResourceFinder) new PathResourceFinder(
228: optionalSourcePath)),
229: optionalCharacterEncoding, // optionalCharacterEncoding
230: debuggingInformation // debuggingInformation
231: );
232: }
233:
234: /**
235: * Set up a {@link JavaSourceClassLoader} that finds Java<sup>TM</sup> source code through
236: * a given {@link ResourceFinder}.
237: *
238: * @param parentClassLoader See {@link ClassLoader}
239: * @param sourceFinder Used to locate additional source files
240: * @param optionalCharacterEncoding The encoding of the Java<sup>TM</sup> source files (<code>null</code> for platform default encoding)
241: * @param debuggingInformation What kind of debugging information to generate
242: */
243: public JavaSourceClassLoader(ClassLoader parentClassLoader,
244: ResourceFinder sourceFinder,
245: String optionalCharacterEncoding,
246: EnumeratorSet debuggingInformation) {
247: super (parentClassLoader);
248:
249: this .iClassLoader = new JavaSourceIClassLoader(sourceFinder, // sourceFinder
250: optionalCharacterEncoding, // optionalCharacterEncoding
251: this .unitCompilers, // unitCompilers
252: new ClassLoaderIClassLoader(parentClassLoader) // optionalParentIClassLoader
253: );
254:
255: this .debuggingInformation = debuggingInformation;
256: }
257:
258: /**
259: * @see UnitCompiler#setCompileErrorHandler
260: */
261: public void setCompileErrorHandler(
262: UnitCompiler.ErrorHandler optionalCompileErrorHandler) {
263: this .iClassLoader
264: .setCompileErrorHandler(optionalCompileErrorHandler);
265: }
266:
267: /**
268: * @see Parser#setWarningHandler(WarningHandler)
269: * @see UnitCompiler#setCompileErrorHandler
270: */
271: public void setWarningHandler(WarningHandler optionalWarningHandler) {
272: this .iClassLoader.setWarningHandler(optionalWarningHandler);
273: }
274:
275: /**
276: * Implementation of {@link ClassLoader#findClass(String)}.
277: *
278: * @throws ClassNotFoundException
279: */
280: protected Class findClass(String name)
281: throws ClassNotFoundException {
282:
283: Map bytecodes = this .generateBytecodes(name);
284: if (bytecodes == null)
285: throw new ClassNotFoundException(name);
286:
287: Class clazz = this .defineBytecodes(name, bytecodes);
288: if (clazz == null)
289: throw new RuntimeException(
290: "Scanning, parsing and compiling class \"" + name
291: + "\" did not create a class file!?");
292: return clazz;
293: }
294:
295: /**
296: * Find, scan, parse the right compilation unit. Compile the parsed compilation unit to
297: * bytecode. This may cause more compilation units being scanned and parsed. Continue until
298: * all compilation units are compiled.
299: *
300: * @return <code>null</code> if no source code could be found
301: */
302: protected Map generateBytecodes(String name)
303: throws ClassNotFoundException {
304: if (this .iClassLoader
305: .loadIClass(Descriptor.fromClassName(name)) == null)
306: return null;
307:
308: Map bytecodes = new HashMap(); // String name => byte[] bytecode
309: Set compiledUnitCompilers = new HashSet();
310: COMPILE_UNITS: for (;;) {
311: for (Iterator it = this .unitCompilers.iterator(); it
312: .hasNext();) {
313: UnitCompiler uc = (UnitCompiler) it.next();
314: if (!compiledUnitCompilers.contains(uc)) {
315: ClassFile[] cfs;
316: try {
317: cfs = uc.compileUnit(this .debuggingInformation);
318: } catch (CompileException ex) {
319: throw new ClassNotFoundException(
320: "Compiling unit \"" + uc + "\"", ex);
321: }
322: for (int i = 0; i < cfs.length; ++i) {
323: ClassFile cf = cfs[i];
324: bytecodes.put(cf.getThisClassName(), cf
325: .toByteArray());
326: }
327: compiledUnitCompilers.add(uc);
328: continue COMPILE_UNITS;
329: }
330: }
331: return bytecodes;
332: }
333: }
334:
335: /**
336: * Define a set of classes, like
337: * {@link java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int)}.
338: * If the <code>bytecodes</code> contains an entry for <code>name</code>, then the
339: * {@link Class} defined for that name is returned.
340: *
341: * @param bytecodes String name => byte[] bytecode
342: */
343: protected Class defineBytecodes(String name, Map bytecodes)
344: throws ClassFormatError {
345: Class clazz = null;
346: for (Iterator it = bytecodes.entrySet().iterator(); it
347: .hasNext();) {
348: Map.Entry me = (Map.Entry) it.next();
349: String name2 = (String) me.getKey();
350: byte[] ba = (byte[]) me.getValue();
351:
352: Class c = this .defineBytecode(name2, ba);
353: if (name2.equals(name))
354: clazz = c;
355: }
356: return clazz;
357: }
358:
359: /**
360: * Calls {@link java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int)}
361: * or {@link java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int, java.security.ProtectionDomain)},
362: * depending on whether or not a {@link ProtectionDomainFactory} was set.
363: *
364: * @see #setProtectionDomainFactory
365: */
366: protected Class defineBytecode(String className, byte[] ba)
367: throws ClassFormatError {
368: if (this .protectionDomainFactory == null) {
369: return this .defineClass(className, ba, 0, ba.length);
370: } else {
371: String sourceName = ClassFile
372: .getSourceResourceName(className);
373: ProtectionDomain domain = this .protectionDomainFactory
374: .getProtectionDomain(sourceName);
375: return this
376: .defineClass(className, ba, 0, ba.length, domain);
377: }
378: }
379:
380: public void setProtectionDomainFactory(
381: ProtectionDomainFactory protectionDomainFactory) {
382: this .protectionDomainFactory = protectionDomainFactory;
383: }
384:
385: private final JavaSourceIClassLoader iClassLoader;
386: private final EnumeratorSet debuggingInformation;
387: private ProtectionDomainFactory protectionDomainFactory;
388:
389: /**
390: * Collection of parsed, but uncompiled compilation units.
391: */
392: private final Set unitCompilers = new HashSet(); // UnitCompiler
393: }
|