0001: /*
0002: * Janino - An embedded Java[TM] compiler
0003: *
0004: * Copyright (c) 2001-2007, Arno Unkrig
0005: * All rights reserved.
0006: *
0007: * Redistribution and use in source and binary forms, with or without
0008: * modification, are permitted provided that the following conditions
0009: * are met:
0010: *
0011: * 1. Redistributions of source code must retain the above copyright
0012: * notice, this list of conditions and the following disclaimer.
0013: * 2. Redistributions in binary form must reproduce the above
0014: * copyright notice, this list of conditions and the following
0015: * disclaimer in the documentation and/or other materials
0016: * provided with the distribution.
0017: * 3. The name of the author may not be used to endorse or promote
0018: * products derived from this software without specific prior
0019: * written permission.
0020: *
0021: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
0022: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
0023: * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
0024: * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
0025: * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
0026: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
0027: * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
0028: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
0029: * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
0030: * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
0031: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0032: */
0033:
0034: package org.codehaus.janino;
0035:
0036: import java.io.*;
0037: import java.lang.reflect.*;
0038: import java.util.HashMap;
0039: import java.util.Map;
0040:
0041: import org.codehaus.janino.Parser.ParseException;
0042: import org.codehaus.janino.Scanner.ScanException;
0043:
0044: /**
0045: * An engine that executes a script in Java<sup>TM</sup> bytecode.
0046: * <p>
0047: * The syntax of the script to compile is a sequence of import declarations (not allowed if you
0048: * compile many scripts at a time, see below) followed by a
0049: * sequence of statements, as defined in the
0050: * <a href="http://java.sun.com/docs/books/jls/second_edition">Java Language Specification, 2nd
0051: * edition</a>, sections
0052: * <a href="http://java.sun.com/docs/books/jls/second_edition/html/packages.doc.html#70209">7.5</a>
0053: * and
0054: * <a href="http://java.sun.com/docs/books/jls/second_edition/html/statements.doc.html#101241">14</a>.
0055: * <p>
0056: * Example:
0057: * <pre>
0058: * import java.text.*;
0059: *
0060: * System.out.println("HELLO");
0061: * System.out.println(new DecimalFormat("####,###.##").format(a));
0062: * </pre>
0063: * (Notice that this expression refers to a parameter "a", as explained below.)
0064: * <p>
0065: * The script may complete abnormally, e.g. through a RETURN statement:
0066: * <pre>
0067: * if (a == null) {
0068: * System.out.println("Oops!");
0069: * return;
0070: * }
0071: * </pre>
0072: * Optionally, the script may be declared with a non-void return type. In this case, the last
0073: * statement of the script must be a RETURN statement (or a THROW statement), and all RETURN
0074: * statements in the script must return a value with the given type.
0075: * <p>
0076: * The script evaluator is implemented by creating and compiling a temporary compilation unit
0077: * defining one class with one method the body of which consists of the statements of the
0078: * script.
0079: * <p>
0080: * To set up a {@link ScriptEvaluator} object, proceed as follows:
0081: * <ol>
0082: * <li>
0083: * Create the {@link ScriptEvaluator} using {@link #ScriptEvaluator()}
0084: * <li>
0085: * Configure the {@link ScriptEvaluator} by calling any of the following methods:
0086: * <ul>
0087: * <li>{@link #setReturnType(Class)}
0088: * <li>{@link #setParameters(String[], Class[])}
0089: * <li>{@link #setThrownExceptions(Class[])}
0090: * <li>{@link org.codehaus.janino.SimpleCompiler#setParentClassLoader(ClassLoader)}
0091: * <li>{@link org.codehaus.janino.ClassBodyEvaluator#setDefaultImports(String[])}
0092: * </ul>
0093: * <li>
0094: * Call any of the {@link org.codehaus.janino.Cookable#cook(Scanner)} methods to scan,
0095: * parse, compile and load the script into the JVM.
0096: * </ol>
0097: * After the {@link ScriptEvaluator} object is created, the script can be executed as often with
0098: * different parameter values (see {@link #evaluate(Object[])}). This execution is very fast,
0099: * compared to the compilation.
0100: * <p>
0101: * Less common methods exist that allow for the specification of the name of the generated class,
0102: * the class it extends, the interfaces it implements, the name of the method that executes the
0103: * script, the exceptions that this method (i.e. the script) is allowed to throw, and the
0104: * {@link ClassLoader} that is used to define the generated class and to load classes referenced by
0105: * the script.
0106: * <p>
0107: * Alternatively, a number of "convenience constructors" exist that execute the steps described
0108: * above instantly. Their use is discouraged.
0109: * <p>
0110: * If you want to compile many scripts at the same time, you have the option to cook an
0111: * <i>array</i> of scripts in one {@link ScriptEvaluator} by using the following methods:
0112: * <ul>
0113: * <li>{@link #setMethodNames(String[])}
0114: * <li>{@link #setParameters(String[][], Class[][])}
0115: * <li>{@link #setReturnTypes(Class[])}
0116: * <li>{@link #setStaticMethod(boolean[])}
0117: * <li>{@link #setThrownExceptions(Class[][])}
0118: * <li>{@link #cook(Scanner[])}
0119: * <li>{@link #evaluate(int, Object[])}
0120: * </ul>
0121: * Notice that these methods have array parameters in contrast to their one-script brethren.
0122: */
0123: public class ScriptEvaluator extends ClassBodyEvaluator {
0124:
0125: protected boolean[] optionalStaticMethod = null;
0126: protected Class[] optionalReturnTypes = null;
0127: protected String[] optionalMethodNames = null;
0128: protected String[][] optionalParameterNames = null;
0129: protected Class[][] optionalParameterTypes = null;
0130: protected Class[][] optionalThrownExceptions = null;
0131:
0132: private Method[] result = null; // null=uncooked
0133:
0134: /**
0135: * Equivalent to<pre>
0136: * ScriptEvaluator se = new ScriptEvaluator();
0137: * se.cook(script);</pre>
0138: *
0139: * @see #ScriptEvaluator()
0140: * @see Cookable#cook(String)
0141: */
0142: public ScriptEvaluator(String script) throws CompileException,
0143: Parser.ParseException, Scanner.ScanException {
0144: this .cook(script);
0145: }
0146:
0147: /**
0148: * Equivalent to<pre>
0149: * ScriptEvaluator se = new ScriptEvaluator();
0150: * se.setReturnType(returnType);
0151: * se.cook(script);</pre>
0152: *
0153: * @see #ScriptEvaluator()
0154: * @see #setReturnType(Class)
0155: * @see Cookable#cook(String)
0156: */
0157: public ScriptEvaluator(String script, Class returnType)
0158: throws CompileException, Parser.ParseException,
0159: Scanner.ScanException {
0160: this .setReturnType(returnType);
0161: this .cook(script);
0162: }
0163:
0164: /**
0165: * Equivalent to<pre>
0166: * ScriptEvaluator se = new ScriptEvaluator();
0167: * se.setReturnType(returnType);
0168: * se.setParameters(parameterNames, parameterTypes);
0169: * se.cook(script);</pre>
0170: *
0171: * @see #ScriptEvaluator()
0172: * @see #setReturnType(Class)
0173: * @see #setParameters(String[], Class[])
0174: * @see Cookable#cook(String)
0175: */
0176: public ScriptEvaluator(String script, Class returnType,
0177: String[] parameterNames, Class[] parameterTypes)
0178: throws CompileException, Parser.ParseException,
0179: Scanner.ScanException {
0180: this .setReturnType(returnType);
0181: this .setParameters(parameterNames, parameterTypes);
0182: this .cook(script);
0183: }
0184:
0185: /**
0186: * Equivalent to<pre>
0187: * ScriptEvaluator se = new ScriptEvaluator();
0188: * se.setReturnType(returnType);
0189: * se.setParameters(parameterNames, parameterTypes);
0190: * se.setThrownExceptions(thrownExceptions);
0191: * se.cook(script);</pre>
0192: *
0193: * @see #ScriptEvaluator()
0194: * @see #setReturnType(Class)
0195: * @see #setParameters(String[], Class[])
0196: * @see #setThrownExceptions(Class[])
0197: * @see Cookable#cook(String)
0198: */
0199: public ScriptEvaluator(String script, Class returnType,
0200: String[] parameterNames, Class[] parameterTypes,
0201: Class[] thrownExceptions) throws CompileException,
0202: Parser.ParseException, Scanner.ScanException {
0203: this .setReturnType(returnType);
0204: this .setParameters(parameterNames, parameterTypes);
0205: this .setThrownExceptions(thrownExceptions);
0206: this .cook(script);
0207: }
0208:
0209: /**
0210: * Equivalent to<pre>
0211: * ScriptEvaluator se = new ScriptEvaluator();
0212: * se.setReturnType(returnType);
0213: * se.setParameters(parameterNames, parameterTypes);
0214: * se.setThrownExceptions(thrownExceptions);
0215: * se.setParentClassLoader(optionalParentClassLoader);
0216: * se.cook(optionalFileName, is);</pre>
0217: *
0218: * @see #ScriptEvaluator()
0219: * @see #setReturnType(Class)
0220: * @see #setParameters(String[], Class[])
0221: * @see #setThrownExceptions(Class[])
0222: * @see SimpleCompiler#setParentClassLoader(ClassLoader)
0223: * @see Cookable#cook(String, InputStream)
0224: */
0225: public ScriptEvaluator(String optionalFileName, InputStream is,
0226: Class returnType, String[] parameterNames,
0227: Class[] parameterTypes, Class[] thrownExceptions,
0228: ClassLoader optionalParentClassLoader // null = use current thread's context class loader
0229: ) throws CompileException, Parser.ParseException,
0230: Scanner.ScanException, IOException {
0231: this .setReturnType(returnType);
0232: this .setParameters(parameterNames, parameterTypes);
0233: this .setThrownExceptions(thrownExceptions);
0234: this .setParentClassLoader(optionalParentClassLoader);
0235: this .cook(optionalFileName, is);
0236: }
0237:
0238: /**
0239: * Equivalent to<pre>
0240: * ScriptEvaluator se = new ScriptEvaluator();
0241: * se.setReturnType(returnType);
0242: * se.setParameters(parameterNames, parameterTypes);
0243: * se.setThrownExceptions(thrownExceptions);
0244: * se.setParentClassLoader(optionalParentClassLoader);
0245: * se.cook(reader);</pre>
0246: *
0247: * @see #ScriptEvaluator()
0248: * @see #setReturnType(Class)
0249: * @see #setParameters(String[], Class[])
0250: * @see #setThrownExceptions(Class[])
0251: * @see SimpleCompiler#setParentClassLoader(ClassLoader)
0252: * @see Cookable#cook(String, Reader)
0253: */
0254: public ScriptEvaluator(String optionalFileName, Reader reader,
0255: Class returnType, String[] parameterNames,
0256: Class[] parameterTypes, Class[] thrownExceptions,
0257: ClassLoader optionalParentClassLoader // null = use current thread's context class loader
0258: ) throws CompileException, Parser.ParseException,
0259: Scanner.ScanException, IOException {
0260: this .setReturnType(returnType);
0261: this .setParameters(parameterNames, parameterTypes);
0262: this .setThrownExceptions(thrownExceptions);
0263: this .setParentClassLoader(optionalParentClassLoader);
0264: this .cook(optionalFileName, reader);
0265: }
0266:
0267: /**
0268: * Equivalent to<pre>
0269: * ScriptEvaluator se = new ScriptEvaluator();
0270: * se.setReturnType(returnType);
0271: * se.setParameters(parameterNames, parameterTypes);
0272: * se.setThrownExceptions(thrownExceptions);
0273: * se.setParentClassLoader(optionalParentClassLoader);
0274: * se.cook(scanner);</pre>
0275: *
0276: * @see #ScriptEvaluator()
0277: * @see #setReturnType(Class)
0278: * @see #setParameters(String[], Class[])
0279: * @see #setThrownExceptions(Class[])
0280: * @see SimpleCompiler#setParentClassLoader(ClassLoader)
0281: * @see Cookable#cook(Scanner)
0282: */
0283: public ScriptEvaluator(Scanner scanner, Class returnType,
0284: String[] parameterNames, Class[] parameterTypes,
0285: Class[] thrownExceptions,
0286: ClassLoader optionalParentClassLoader // null = use current thread's context class loader
0287: ) throws CompileException, Parser.ParseException,
0288: Scanner.ScanException, IOException {
0289: this .setReturnType(returnType);
0290: this .setParameters(parameterNames, parameterTypes);
0291: this .setThrownExceptions(thrownExceptions);
0292: this .setParentClassLoader(optionalParentClassLoader);
0293: this .cook(scanner);
0294: }
0295:
0296: /**
0297: * Equivalent to<pre>
0298: * ScriptEvaluator se = new ScriptEvaluator();
0299: * se.setExtendedType(optionalExtendedType);
0300: * se.setImplementedTypes(implementedTypes);
0301: * se.setReturnType(returnType);
0302: * se.setParameters(parameterNames, parameterTypes);
0303: * se.setThrownExceptions(thrownExceptions);
0304: * se.setParentClassLoader(optionalParentClassLoader);
0305: * se.cook(scanner);</pre>
0306: *
0307: * @see #ScriptEvaluator()
0308: * @see ClassBodyEvaluator#setExtendedType(Class)
0309: * @see ClassBodyEvaluator#setImplementedTypes(Class[])
0310: * @see #setReturnType(Class)
0311: * @see #setParameters(String[], Class[])
0312: * @see #setThrownExceptions(Class[])
0313: * @see SimpleCompiler#setParentClassLoader(ClassLoader)
0314: * @see Cookable#cook(Scanner)
0315: */
0316: public ScriptEvaluator(Scanner scanner, Class optionalExtendedType,
0317: Class[] implementedTypes, Class returnType,
0318: String[] parameterNames, Class[] parameterTypes,
0319: Class[] thrownExceptions,
0320: ClassLoader optionalParentClassLoader // null = use current thread's context class loader
0321: ) throws CompileException, Parser.ParseException,
0322: Scanner.ScanException, IOException {
0323: this .setExtendedType(optionalExtendedType);
0324: this .setImplementedTypes(implementedTypes);
0325: this .setReturnType(returnType);
0326: this .setParameters(parameterNames, parameterTypes);
0327: this .setThrownExceptions(thrownExceptions);
0328: this .setParentClassLoader(optionalParentClassLoader);
0329: this .cook(scanner);
0330: }
0331:
0332: /**
0333: * Equivalent to<pre>
0334: * ScriptEvaluator se = new ScriptEvaluator();
0335: * se.setClassName(className);
0336: * se.setExtendedType(optionalExtendedType);
0337: * se.setImplementedTypes(implementedTypes);
0338: * se.setStaticMethod(staticMethod);
0339: * se.setReturnType(returnType);
0340: * se.setMethodName(methodName);
0341: * se.setParameters(parameterNames, parameterTypes);
0342: * se.setThrownExceptions(thrownExceptions);
0343: * se.setParentClassLoader(optionalParentClassLoader);
0344: * se.cook(scanner);</pre>
0345: *
0346: * @see #ScriptEvaluator()
0347: * @see ClassBodyEvaluator#setClassName(String)
0348: * @see ClassBodyEvaluator#setExtendedType(Class)
0349: * @see ClassBodyEvaluator#setImplementedTypes(Class[])
0350: * @see #setStaticMethod(boolean)
0351: * @see #setReturnType(Class)
0352: * @see #setMethodName(String)
0353: * @see #setParameters(String[], Class[])
0354: * @see #setThrownExceptions(Class[])
0355: * @see SimpleCompiler#setParentClassLoader(ClassLoader)
0356: * @see Cookable#cook(Scanner)
0357: */
0358: public ScriptEvaluator(Scanner scanner, String className,
0359: Class optionalExtendedType, Class[] implementedTypes,
0360: boolean staticMethod, Class returnType, String methodName,
0361: String[] parameterNames, Class[] parameterTypes,
0362: Class[] thrownExceptions,
0363: ClassLoader optionalParentClassLoader // null = use current thread's context class loader
0364: ) throws Scanner.ScanException, Parser.ParseException,
0365: CompileException, IOException {
0366: this .setClassName(className);
0367: this .setExtendedType(optionalExtendedType);
0368: this .setImplementedTypes(implementedTypes);
0369: this .setStaticMethod(staticMethod);
0370: this .setReturnType(returnType);
0371: this .setMethodName(methodName);
0372: this .setParameters(parameterNames, parameterTypes);
0373: this .setThrownExceptions(thrownExceptions);
0374: this .setParentClassLoader(optionalParentClassLoader);
0375: this .cook(scanner);
0376: }
0377:
0378: public ScriptEvaluator() {
0379: }
0380:
0381: /**
0382: * Define whether the generated method should be STATIC or not. Defaults to <code>true</code>.
0383: */
0384: public void setStaticMethod(boolean staticMethod) {
0385: this .setStaticMethod(new boolean[] { staticMethod });
0386: }
0387:
0388: /**
0389: * Define the return type of the generated method. Defaults to <code>void.class</code>.
0390: */
0391: public void setReturnType(Class returnType) {
0392: this .setReturnTypes(new Class[] { returnType });
0393: }
0394:
0395: /**
0396: * Define the name of the generated method. Defaults to an unspecified name.
0397: */
0398: public void setMethodName(String methodName) {
0399: this .setMethodNames(new String[] { methodName });
0400: }
0401:
0402: /**
0403: * Define the names and types of the parameters of the generated method.
0404: */
0405: public void setParameters(String[] parameterNames,
0406: Class[] parameterTypes) {
0407: this .setParameters(new String[][] { parameterNames },
0408: new Class[][] { parameterTypes });
0409: }
0410:
0411: /**
0412: * Define the exceptions that the generated method may throw.
0413: */
0414: public void setThrownExceptions(Class[] thrownExceptions) {
0415: this .setThrownExceptions(new Class[][] { thrownExceptions });
0416: }
0417:
0418: public final void cook(Scanner scanner) throws CompileException,
0419: Parser.ParseException, Scanner.ScanException, IOException {
0420: this .cook(new Scanner[] { scanner });
0421: }
0422:
0423: /**
0424: * Calls the generated method with concrete parameter values.
0425: * <p>
0426: * Each parameter value must have the same type as specified through
0427: * the "parameterTypes" parameter of
0428: * {@link #setParameters(String[], Class[])}.
0429: * <p>
0430: * Parameters of primitive type must passed with their wrapper class
0431: * objects.
0432: * <p>
0433: * The object returned has the class as specified through
0434: * {@link #setReturnType(Class)}.
0435: * <p>
0436: * This method is thread-safe.
0437: *
0438: * @param parameterValues The concrete parameter values.
0439: */
0440: public Object evaluate(Object[] parameterValues)
0441: throws InvocationTargetException {
0442: return this .evaluate(0, parameterValues);
0443: }
0444:
0445: /**
0446: * Returns the loaded {@link java.lang.reflect.Method}.
0447: * <p>
0448: * This method must only be called after {@link #cook(Scanner)}.
0449: * <p>
0450: * This method must not be called for instances of derived classes.
0451: */
0452: public Method getMethod() {
0453: return this .getMethod(0);
0454: }
0455:
0456: /**
0457: * Define whether the methods implementing each script should be STATIC or not. By default
0458: * all scripts are compiled into STATIC methods.
0459: */
0460: public void setStaticMethod(boolean[] staticMethod) {
0461: this .optionalStaticMethod = (boolean[]) staticMethod.clone();
0462: }
0463:
0464: /**
0465: * Define the return types of the scripts. By default all scripts have VOID return type.
0466: */
0467: public void setReturnTypes(Class[] returnTypes) {
0468: this .optionalReturnTypes = (Class[]) returnTypes.clone();
0469: }
0470:
0471: /**
0472: * Define the names of the generated methods. By default the methods have distinct and
0473: * implementation-specific names.
0474: * <p>
0475: * If two scripts have the same name, then they must have different parameter types
0476: * (see {@link #setParameters(String[][], Class[][])}).
0477: */
0478: public void setMethodNames(String[] methodNames) {
0479: this .optionalMethodNames = (String[]) methodNames.clone();
0480: }
0481:
0482: /**
0483: * Define the names and types of the parameters of the generated methods.
0484: */
0485: public void setParameters(String[][] parameterNames,
0486: Class[][] parameterTypes) {
0487: this .optionalParameterNames = (String[][]) parameterNames
0488: .clone();
0489: this .optionalParameterTypes = (Class[][]) parameterTypes
0490: .clone();
0491: }
0492:
0493: /**
0494: * Define the exceptions that the generated methods may throw.
0495: */
0496: public void setThrownExceptions(Class[][] thrownExceptions) {
0497: this .optionalThrownExceptions = (Class[][]) thrownExceptions
0498: .clone();
0499: }
0500:
0501: /**
0502: * Like {@link #cook(Scanner)}, but cooks a <i>set</i> of scripts into one class. Notice that
0503: * if <i>any</i> of the scripts causes trouble, the entire compilation will fail. If you
0504: * need to report <i>which</i> of the scripts causes the exception, you may want to use the
0505: * <code>optionalFileName</code> argument of {@link Scanner#Scanner(String, Reader)} to
0506: * distinguish between the individual token sources.
0507: * <p>
0508: * On a 2 GHz Intel Pentium Core Duo under Windows XP with an IBM 1.4.2 JDK, compiling
0509: * 10000 expressions "a + b" (integer) takes about 4 seconds and 56 MB of main memory.
0510: * The generated class file is 639203 bytes large.
0511: * <p>
0512: * The number and the complexity of the scripts is restricted by the
0513: * <a href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#88659">Limitations
0514: * of the Java Virtual Machine</a>, where the most limiting factor is the 64K entries limit
0515: * of the constant pool. Since every method with a distinct name requires one entry there,
0516: * you can define at best 32K (very simple) scripts.
0517: *
0518: * If and only if the number of scanners is one, then that single script may contain leading
0519: * IMPORT directives.
0520: *
0521: * @throws IllegalStateException if any of the preceeding <code>set...()</code> had an array size different from that of <code>scanners</code>
0522: */
0523: public final void cook(Scanner[] scanners) throws CompileException,
0524: Parser.ParseException, Scanner.ScanException, IOException {
0525: if (scanners == null)
0526: throw new NullPointerException();
0527:
0528: // The "dimension" of this ScriptEvaluator, i.e. how many scripts are cooked at the same
0529: // time.
0530: int count = scanners.length;
0531:
0532: // Check array sizes.
0533: if (this .optionalMethodNames != null
0534: && this .optionalMethodNames.length != count)
0535: throw new IllegalStateException("methodName");
0536: if (this .optionalParameterNames != null
0537: && this .optionalParameterNames.length != count)
0538: throw new IllegalStateException("parameterNames");
0539: if (this .optionalParameterTypes != null
0540: && this .optionalParameterTypes.length != count)
0541: throw new IllegalStateException("parameterTypes");
0542: if (this .optionalReturnTypes != null
0543: && this .optionalReturnTypes.length != count)
0544: throw new IllegalStateException("returnTypes");
0545: if (this .optionalStaticMethod != null
0546: && this .optionalStaticMethod.length != count)
0547: throw new IllegalStateException("staticMethod");
0548: if (this .optionalThrownExceptions != null
0549: && this .optionalThrownExceptions.length != count)
0550: throw new IllegalStateException("thrownExceptions");
0551:
0552: this .setUpClassLoaders();
0553:
0554: // Create compilation unit.
0555: Java.CompilationUnit compilationUnit = this
0556: .makeCompilationUnit(count == 1 ? scanners[0] : null);
0557:
0558: // Create class declaration.
0559: Java.ClassDeclaration cd = this
0560: .addPackageMemberClassDeclaration(scanners[0]
0561: .location(), compilationUnit);
0562:
0563: // Determine method names.
0564: String[] methodNames;
0565: if (this .optionalMethodNames == null) {
0566: methodNames = new String[count];
0567: for (int i = 0; i < count; ++i)
0568: methodNames[i] = "eval" + i;
0569: } else {
0570: methodNames = this .optionalMethodNames;
0571: }
0572:
0573: // Create methods with one block each.
0574: for (int i = 0; i < count; ++i) {
0575: Scanner s = scanners[i];
0576:
0577: Java.Block block = this .makeBlock(i, scanners[i]);
0578:
0579: // Determine the following script properties AFTER the call to "makeBlock()",
0580: // because "makeBlock()" may modify these script properties on-the-fly.
0581: boolean staticMethod = this .optionalStaticMethod == null ? true
0582: : this .optionalStaticMethod[i];
0583: Class returnType = this .optionalReturnTypes == null ? this
0584: .getDefaultReturnType()
0585: : this .optionalReturnTypes[i];
0586: String[] parameterNames = this .optionalParameterNames == null ? new String[0]
0587: : this .optionalParameterNames[i];
0588: Class[] parameterTypes = this .optionalParameterTypes == null ? new Class[0]
0589: : this .optionalParameterTypes[i];
0590: Class[] thrownExceptions = this .optionalThrownExceptions == null ? new Class[0]
0591: : this .optionalThrownExceptions[i];
0592:
0593: cd.addDeclaredMethod(this .makeMethodDeclaration(s
0594: .location(), // location
0595: staticMethod, // staticMethod
0596: returnType, // returnType
0597: methodNames[i], // methodName
0598: parameterTypes, // parameterTypes
0599: parameterNames, // parameterNames
0600: thrownExceptions, // thrownExceptions
0601: block // optionalBody
0602: ));
0603: }
0604:
0605: // Compile and load the compilation unit.
0606: Class c = this .compileToClass(compilationUnit, // compilationUnit
0607: DebuggingInformation.DEFAULT_DEBUGGING_INFORMATION, // debuggingInformation
0608: this .className);
0609:
0610: // Find the script methods by name.
0611: this .result = new Method[count];
0612: if (count <= 10) {
0613: for (int i = 0; i < count; ++i) {
0614: try {
0615: this .result[i] = c
0616: .getDeclaredMethod(
0617: methodNames[i],
0618: this .optionalParameterTypes == null ? new Class[0]
0619: : this .optionalParameterTypes[i]);
0620: } catch (NoSuchMethodException ex) {
0621: throw new RuntimeException(
0622: "SNO: Loaded class does not declare method \""
0623: + methodNames[i] + "\"");
0624: }
0625: }
0626: } else {
0627: class MethodWrapper {
0628: private final String name;
0629: private final Class[] parameterTypes;
0630:
0631: MethodWrapper(String name, Class[] parameterTypes) {
0632: this .name = name;
0633: this .parameterTypes = parameterTypes;
0634: }
0635:
0636: public boolean equals(Object o) {
0637: if (!(o instanceof MethodWrapper))
0638: return false;
0639: MethodWrapper that = (MethodWrapper) o;
0640: if (!this .name.equals(that.name))
0641: return false;
0642: int cnt = this .parameterTypes.length;
0643: if (cnt != that.parameterTypes.length)
0644: return false;
0645: for (int i = 0; i < cnt; ++i) {
0646: if (!this .parameterTypes[i]
0647: .equals(that.parameterTypes[i]))
0648: return false;
0649: }
0650: return true;
0651: }
0652:
0653: public int hashCode() {
0654: int hc = this .name.hashCode();
0655: for (int i = 0; i < this .parameterTypes.length; ++i) {
0656: hc ^= this .parameterTypes[i].hashCode();
0657: }
0658: return hc;
0659: }
0660: }
0661: Method[] ma = c.getDeclaredMethods();
0662: Map dms = new HashMap(2 * count);
0663: for (int i = 0; i < ma.length; ++i) {
0664: Method m = ma[i];
0665: dms.put(new MethodWrapper(m.getName(), m
0666: .getParameterTypes()), m);
0667: }
0668: for (int i = 0; i < count; ++i) {
0669: Method m = (Method) dms
0670: .get(new MethodWrapper(
0671: methodNames[i],
0672: this .optionalParameterTypes == null ? new Class[0]
0673: : this .optionalParameterTypes[i]));
0674: if (m == null)
0675: throw new RuntimeException(
0676: "SNO: Loaded class does not declare method \""
0677: + methodNames[i] + "\"");
0678: this .result[i] = m;
0679: }
0680: }
0681: }
0682:
0683: public final void cook(Reader[] readers) throws CompileException,
0684: Parser.ParseException, Scanner.ScanException, IOException {
0685: this .cook(new String[readers.length], readers);
0686: }
0687:
0688: /**
0689: * @param optionalFileNames Used when reporting errors and warnings.
0690: */
0691: public final void cook(String[] optionalFileNames, Reader[] readers)
0692: throws CompileException, Parser.ParseException,
0693: Scanner.ScanException, IOException {
0694: Scanner[] scanners = new Scanner[readers.length];
0695: for (int i = 0; i < readers.length; ++i)
0696: scanners[i] = new Scanner(optionalFileNames[i], readers[i]);
0697: this .cook(scanners);
0698: }
0699:
0700: /**
0701: * Cook tokens from {@link java.lang.String}s.
0702: */
0703: public final void cook(String[] strings) throws CompileException,
0704: Parser.ParseException, Scanner.ScanException {
0705: Reader[] readers = new Reader[strings.length];
0706: for (int i = 0; i < strings.length; ++i)
0707: readers[i] = new StringReader(strings[i]);
0708: try {
0709: this .cook(readers);
0710: } catch (IOException ex) {
0711: throw new RuntimeException(
0712: "SNO: IOException despite StringReader");
0713: }
0714: }
0715:
0716: protected Class getDefaultReturnType() {
0717: return void.class;
0718: }
0719:
0720: /**
0721: * Fill the given <code>block</code> by parsing statements until EOF and adding
0722: * them to the block.
0723: */
0724: protected Java.Block makeBlock(int idx, Scanner scanner)
0725: throws ParseException, ScanException, IOException {
0726: Java.Block block = new Java.Block(scanner.location());
0727: Parser parser = new Parser(scanner);
0728: while (!scanner.peek().isEOF()) {
0729: block.addStatement(parser.parseBlockStatement());
0730: }
0731:
0732: return block;
0733: }
0734:
0735: protected void compileToMethods(
0736: Java.CompilationUnit compilationUnit, String[] methodNames,
0737: Class[][] parameterTypes) throws CompileException {
0738:
0739: // Compile and load the compilation unit.
0740: Class c = this .compileToClass(compilationUnit, // compilationUnit
0741: DebuggingInformation.DEFAULT_DEBUGGING_INFORMATION, // debuggingInformation
0742: this .className);
0743:
0744: // Find the script method by name.
0745: this .result = new Method[methodNames.length];
0746: for (int i = 0; i < this .result.length; ++i) {
0747: try {
0748: this .result[i] = c.getMethod(methodNames[i],
0749: parameterTypes[i]);
0750: } catch (NoSuchMethodException ex) {
0751: throw new RuntimeException(
0752: "SNO: Loaded class does not declare method \""
0753: + this .optionalMethodNames[i] + "\"");
0754: }
0755: }
0756: }
0757:
0758: /**
0759: * To the given {@link Java.ClassDeclaration}, add
0760: * <ul>
0761: * <li>A public method declaration with the given return type, name, parameter
0762: * names and values and thrown exceptions
0763: * <li>A block
0764: * </ul>
0765: *
0766: * @param returnType Return type of the declared method
0767: */
0768: protected Java.MethodDeclarator makeMethodDeclaration(
0769: Location location, boolean staticMethod, Class returnType,
0770: String methodName, Class[] parameterTypes,
0771: String[] parameterNames, Class[] thrownExceptions,
0772: Java.Block optionalBody) {
0773: if (parameterNames.length != parameterTypes.length)
0774: throw new RuntimeException(
0775: "Lengths of \"parameterNames\" ("
0776: + parameterNames.length
0777: + ") and \"parameterTypes\" ("
0778: + parameterTypes.length + ") do not match");
0779:
0780: Java.FunctionDeclarator.FormalParameter[] fps = new Java.FunctionDeclarator.FormalParameter[parameterNames.length];
0781: for (int i = 0; i < fps.length; ++i) {
0782: fps[i] = new Java.FunctionDeclarator.FormalParameter(
0783: location, // location
0784: true, // finaL
0785: this .classToType(location, parameterTypes[i]), // type
0786: parameterNames[i] // name
0787: );
0788: }
0789:
0790: return new Java.MethodDeclarator(location, // location
0791: null, // optionalDocComment
0792: ( // modifiers
0793: staticMethod ? (short) (Mod.PUBLIC | Mod.STATIC)
0794: : (short) Mod.PUBLIC), this .classToType(
0795: location, returnType), // type
0796: methodName, // name
0797: fps, // formalParameters
0798: this .classesToTypes(location, thrownExceptions), // thrownExceptions
0799: optionalBody // optionalBody
0800: );
0801: }
0802:
0803: /**
0804: * Simplified version of
0805: * {@link #createFastScriptEvaluator(Scanner, Class, String[], ClassLoader)}.
0806: *
0807: * @param script Contains the sequence of script tokens
0808: * @param interfaceToImplement Must declare exactly the one method that defines the expression's signature
0809: * @param parameterNames The expression references the parameters through these names
0810: * @return an object that implements the given interface and extends the <code>optionalExtendedType</code>
0811: */
0812: public static Object createFastScriptEvaluator(String script,
0813: Class interfaceToImplement, String[] parameterNames)
0814: throws CompileException, Parser.ParseException,
0815: Scanner.ScanException {
0816: ScriptEvaluator se = new ScriptEvaluator();
0817: return ScriptEvaluator.createFastEvaluator(se, script,
0818: parameterNames, interfaceToImplement);
0819: }
0820:
0821: /**
0822: * If the parameter and return types of the expression are known at compile time,
0823: * then a "fast" script evaluator can be instantiated through this method.
0824: * <p>
0825: * Script evaluation is faster than through {@link #evaluate(Object[])}, because
0826: * it is not done through reflection but through direct method invocation.
0827: * <p>
0828: * Example:
0829: * <pre>
0830: * public interface Foo {
0831: * int bar(int a, int b);
0832: * }
0833: * ...
0834: * Foo f = (Foo) ScriptEvaluator.createFastScriptEvaluator(
0835: * new Scanner(null, new StringReader("return a + b;")),
0836: * Foo.class,
0837: * new String[] { "a", "b" },
0838: * (ClassLoader) null // Use current thread's context class loader
0839: * );
0840: * System.out.println("1 + 2 = " + f.bar(1, 2));
0841: * </pre>
0842: * Notice: The <code>interfaceToImplement</code> must either be declared <code>public</code>,
0843: * or with package scope in the root package (i.e. "no" package).
0844: *
0845: * @param scanner Source of script tokens
0846: * @param interfaceToImplement Must declare exactly one method
0847: * @param parameterNames
0848: * @param optionalParentClassLoader
0849: * @return an object that implements the given interface
0850: */
0851: public static Object createFastScriptEvaluator(Scanner scanner,
0852: Class interfaceToImplement, String[] parameterNames,
0853: ClassLoader optionalParentClassLoader)
0854: throws CompileException, Parser.ParseException,
0855: Scanner.ScanException, IOException {
0856: ScriptEvaluator se = new ScriptEvaluator();
0857: se.setParentClassLoader(optionalParentClassLoader);
0858: return ScriptEvaluator.createFastEvaluator(se, scanner,
0859: parameterNames, interfaceToImplement);
0860: }
0861:
0862: /**
0863: * Like {@link #createFastScriptEvaluator(Scanner, Class, String[], ClassLoader)},
0864: * but gives you more control over the generated class (rarely needed in practice).
0865: * <p>
0866: * Notice: The <code>interfaceToImplement</code> must either be declared <code>public</code>,
0867: * or with package scope in the same package as <code>className</code>.
0868: *
0869: * @param scanner Source of script tokens
0870: * @param className Name of generated class
0871: * @param optionalExtendedType Class to extend
0872: * @param interfaceToImplement Must declare exactly the one method that defines the expression's signature
0873: * @param parameterNames The expression references the parameters through these names
0874: * @param optionalParentClassLoader Used to load referenced classes, defaults to the current thread's "context class loader"
0875: * @return an object that implements the given interface and extends the <code>optionalExtendedType</code>
0876: */
0877: public static Object createFastScriptEvaluator(Scanner scanner,
0878: String className, Class optionalExtendedType,
0879: Class interfaceToImplement, String[] parameterNames,
0880: ClassLoader optionalParentClassLoader)
0881: throws CompileException, Parser.ParseException,
0882: Scanner.ScanException, IOException {
0883: ScriptEvaluator se = new ScriptEvaluator();
0884: se.setClassName(className);
0885: se.setExtendedType(optionalExtendedType);
0886: se.setParentClassLoader(optionalParentClassLoader);
0887: return ScriptEvaluator.createFastEvaluator(se, scanner,
0888: parameterNames, interfaceToImplement);
0889: }
0890:
0891: public static Object createFastScriptEvaluator(Scanner scanner,
0892: String[] optionalDefaultImports, String className,
0893: Class optionalExtendedType, Class interfaceToImplement,
0894: String[] parameterNames,
0895: ClassLoader optionalParentClassLoader)
0896: throws CompileException, Parser.ParseException,
0897: Scanner.ScanException, IOException {
0898: ScriptEvaluator se = new ScriptEvaluator();
0899: se.setClassName(className);
0900: se.setExtendedType(optionalExtendedType);
0901: se.setDefaultImports(optionalDefaultImports);
0902: se.setParentClassLoader(optionalParentClassLoader);
0903: return ScriptEvaluator.createFastEvaluator(se, scanner,
0904: parameterNames, interfaceToImplement);
0905: }
0906:
0907: public static Object createFastEvaluator(ScriptEvaluator se,
0908: String s, String[] parameterNames,
0909: Class interfaceToImplement) throws CompileException,
0910: Parser.ParseException, Scanner.ScanException {
0911: try {
0912: return ScriptEvaluator.createFastEvaluator(se, new Scanner(
0913: null, new StringReader(s)), parameterNames,
0914: interfaceToImplement);
0915: } catch (IOException ex) {
0916: throw new RuntimeException(
0917: "IOException despite StringReader");
0918: }
0919: }
0920:
0921: /**
0922: * Create and return an object that implements the exactly one method of the given
0923: * <code>interfaceToImplement</code>.
0924: *
0925: * @param se A pre-configured {@link ScriptEvaluator} object
0926: * @param scanner Source of tokens to read
0927: * @param parameterNames The names of the parameters of the one abstract method of <code>interfaceToImplement</code>
0928: * @param interfaceToImplement A type with exactly one abstract method
0929: * @return an instance of the created {@link Object}
0930: */
0931: public static Object createFastEvaluator(ScriptEvaluator se,
0932: Scanner scanner, String[] parameterNames,
0933: Class interfaceToImplement) throws CompileException,
0934: Parser.ParseException, Scanner.ScanException, IOException {
0935: if (!interfaceToImplement.isInterface())
0936: throw new RuntimeException("\"" + interfaceToImplement
0937: + "\" is not an interface");
0938:
0939: Method[] methods = interfaceToImplement.getDeclaredMethods();
0940: if (methods.length != 1)
0941: throw new RuntimeException("Interface \""
0942: + interfaceToImplement
0943: + "\" must declare exactly one method");
0944: Method methodToImplement = methods[0];
0945:
0946: se.setImplementedTypes(new Class[] { interfaceToImplement });
0947: se.setStaticMethod(false);
0948: se.setReturnType(methodToImplement.getReturnType());
0949: se.setMethodName(methodToImplement.getName());
0950: se.setParameters(parameterNames, methodToImplement
0951: .getParameterTypes());
0952: se.setThrownExceptions(methodToImplement.getExceptionTypes());
0953: se.cook(scanner);
0954: Class c = se.getMethod().getDeclaringClass();
0955: try {
0956: return c.newInstance();
0957: } catch (InstantiationException e) {
0958: // SNO - Declared class is always non-abstract.
0959: throw new RuntimeException(e.toString());
0960: } catch (IllegalAccessException e) {
0961: // SNO - interface methods are always PUBLIC.
0962: throw new RuntimeException(e.toString());
0963: }
0964: }
0965:
0966: /**
0967: * Calls the generated method with concrete parameter values.
0968: * <p>
0969: * Each parameter value must have the same type as specified through
0970: * the "parameterTypes" parameter of
0971: * {@link #setParameters(String[], Class[])}.
0972: * <p>
0973: * Parameters of primitive type must passed with their wrapper class
0974: * objects.
0975: * <p>
0976: * The object returned has the class as specified through
0977: * {@link #setReturnType(Class)}.
0978: * <p>
0979: * This method is thread-safe.
0980: *
0981: * @param idx The index of the script (0 ... <code>scripts.length - 1</code>)
0982: * @param parameterValues The concrete parameter values.
0983: */
0984: public Object evaluate(int idx, Object[] parameterValues)
0985: throws InvocationTargetException {
0986: if (this .result == null)
0987: throw new IllegalStateException(
0988: "Must only be called after \"cook()\"");
0989: try {
0990: return this .result[idx].invoke(null, parameterValues);
0991: } catch (IllegalAccessException ex) {
0992: throw new RuntimeException(ex.toString());
0993: }
0994: }
0995:
0996: /**
0997: * Returns the loaded {@link java.lang.reflect.Method}.
0998: * <p>
0999: * This method must only be called after {@link #cook(Scanner)}.
1000: * <p>
1001: * This method must not be called for instances of derived classes.
1002: *
1003: * @param idx The index of the script (0 ... <code>scripts.length - 1</code>)
1004: */
1005: public Method getMethod(int idx) {
1006: if (this .result == null)
1007: throw new IllegalStateException(
1008: "Must only be called after \"cook()\"");
1009: return this.result[idx];
1010: }
1011: }
|