001: /**
002: * InstantJ
003: *
004: * Copyright (C) 2002 Nils Meier
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or (at your option) any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */package instantj.expression;
017:
018: import instantj.compile.CompilationFailedException;
019: import instantj.compile.CompiledClass;
020: import instantj.compile.Compiler;
021: import instantj.compile.Source;
022: import instantj.reflect.IllegalPropertyException;
023: import instantj.reflect.InaccessiblePropertyException;
024: import instantj.reflect.ReflectAccess;
025:
026: import java.io.File;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.io.InputStreamReader;
031: import java.io.PrintWriter;
032: import java.io.Serializable;
033: import java.util.ArrayList;
034: import java.util.Arrays;
035: import java.util.Collection;
036: import java.util.HashMap;
037: import java.util.Iterator;
038: import java.util.Map;
039:
040: /**
041: * <p>
042: * An Expression is a piece of Java-code that evaluates to a result
043: * of a primitive- or Object-type. Therefor it doesn't have any
044: * return-statements and does not end with ';'. Think of an expression
045: * as something you can assign to a variable like this
046: * <pre>
047: * Object foo = <font color="red">theExpression</font>;
048: * </pre>
049: * (Note that <i>Object foo =</i> and <i>;</i> are NOT part of the expression).
050: * Examples for expression are
051: * <pre>
052: * "Hello World"
053: * account.balance - 100
054: * 2 * Math.PI
055: * count--; count<0
056: * </pre>
057: * The last one is treated a little bit different - this expression consists
058: * of an instruction <i>count--;</i> and the end-result <i>count<0</i>.
059: * During evaluation the instruction(s) is/are executed first and the last
060: * piece of the expression is returned as the result.
061: * </p>
062: *
063: * <p>
064: * A java-expression can evaluate to a Object- or primitive-type. In
065: * the latter case the evaluation of the Expression-object will wrap
066: * the primitive result in a Java-type (e.g. Integer).
067: * </p>
068: *
069: * <p>
070: * This type not only wraps the expression's body but alos its environment
071: * properties. The properties are kept in a map of name/Class that keeps track
072: * of those the expression can use.(e.g. String.class, Account.class or
073: * Integer.TYPE). The expression can reference properties in its environment
074: * as local variables (see above).
075: * </p>
076: *
077: * <p>
078: * Once you have an instance of an expression you can evaluate it in the
079: * environment of property values that you provide. The values are mapped
080: * to the property-names and assigned to the expression's local variables
081: * before execution.
082: * </p>
083: *
084: * <p>
085: * Example 1:
086: * <pre>
087: * // Our expression body
088: * String body = " 3*4 ";
089: *
090: * // Creating the Expression
091: * Expression expression = new Expression(body);
092: *
093: * // Evaluate it
094: * Object result = expression.evaluate();
095: * </pre>
096: * The result of this expression is
097: * <blockquote>
098: * (java.lang.Integer) 12
099: * </blockquote>
100: * <font size=-1>Note: there's no exception-handling in this snippet</font>
101: * </p>
102: *
103: * <p>
104: * Example 2:
105: * <pre>
106: * // The properties we want to use inside the expression
107: * Hashtable properties = new Hashtable();
108: * properties.put("count", Integer.TYPE);
109: * properties.put("msg" , String.class);
110: *
111: * // Our expression body
112: * String body = " count++; msg+new java.util.Date() ";
113: *
114: * // Creating the Expression
115: * Expression expression = new Expression(body,properties);
116: *
117: * // The values we give to the evaluation
118: * Map values = new HashMap();
119: * values.put("count", new Integer(3));
120: * values.put("msg" , "Guybrush was here on " );
121: *
122: * // Evaluate it
123: * Object result = expression.getInstance(values).evaluate();
124: * </pre>
125: * The result of this expression is
126: * <blockquote>
127: * (java.lang.String) Guybrush was here on Dec 17 11:33:31 EST 2000
128: * </blockquote>
129: * As an alternative to passing a value map into getInstance() you
130: * can also provide the values for properties programmatically:
131: * <pre>
132: * // Evaluate it
133: * Object result = expression.getInstance().set("count", new Integer(3)).set("msg", "Guybrush was here on").evaluate();
134: * </pre>
135: * <font size=-1>Note: there's no exception-handling in this snippet; primitive-types
136: * have to wrapped when provided; the time varies :)</font>
137: * </p>
138: *
139: * @author <A href="mailto:nils@meiers.net">Nils Meier</A>
140: */
141: public class Expression implements Serializable {
142:
143: /** the default packages that will be imported for the expression */
144: private static Collection defaultImports = Arrays
145: .asList(new Object[] { "java.text.*", "java.util.*",
146: "java.io.*", "java.net.*" });
147:
148: /** a VM flag that will turn on output of generated expressions in specified directory */
149: public final static String PARAM_EXPRESSION_DIR = "instantj.expression.output";
150:
151: /** the directory we store expressions into */
152: private final static String expressionDir = System
153: .getProperty(PARAM_EXPRESSION_DIR);
154:
155: /** internal enumeration of generated expressions */
156: private static long count = 0;
157:
158: /** the properties we've been told to prepare for the expression */
159: private Map properties;
160:
161: /** the compiled class for the instance of the expression */
162: private CompiledClass compiled;
163:
164: /**
165: * Constructor for de-serialization
166: */
167: protected Expression() {
168: }
169:
170: /**
171: * Constructor for a simple expression without properties.
172: * For more information about what an expression is, look at the
173: * {@link Expression class description}
174: *
175: * @param body the expression Java body
176: * @exception CompilationFailedException when the expression couldn't be compiled
177: */
178: public Expression(String body) throws CompilationFailedException {
179: this (body, new HashMap());
180: }
181:
182: /**
183: * Constructor for an expression with properties.
184: * For more information about what an expression is, look at the
185: * {@link Expression class description}
186: *
187: * @param body the expression body (Java)
188: * @param properties the property-names mapped to types (java.lang.Class or java.lang.String). These will be available to the evaluated expression.
189: * @exception CompilationFailedException when the expression couldn't be compiled
190: * @exception IllegalArgumentException when passed Hashtable does not map names (String) to types (Class)
191: */
192: public Expression(String body, Map properties)
193: throws CompilationFailedException {
194: this (body, properties, new ArrayList());
195: }
196:
197: /**
198: * Constructor for an expression with properties.
199: * For more information about what an expression is, look at the
200: * {@link Expression class description}
201: *
202: * @param body the expression body (Java)
203: * @param props the property-names mapped to types (java.lang.Class or java.lang.String). These will be available to the evaluated expression.
204: * @param imports imports to use (e.g java.util.*, java.sql.*)
205: * @exception CompilationFailedException when the expression couldn't be compiled
206: * @exception IllegalArgumentException when passed Hashtable does not map names (String) to types (Class)
207: */
208: public Expression(String body, Map props, Collection imports)
209: throws CompilationFailedException {
210: this (body, props, imports, null);
211: }
212:
213: /**
214: * Constructor for an expression with properties and a library.
215: * For more information about what an expression is, look at the
216: * {@link Expression class description}
217: *
218: * @param body the expression body (Java)
219: * @param props the property-names mapped to types (java.lang.Class or java.lang.String). These will be available to the evaluated expression.
220: * @param imports imports to use (e.g java.util.*, java.sql.*)
221: * @param library of CompiledSources to use
222: * @exception CompilationFailedException when the expression couldn't be compiled
223: * @exception IllegalArgumentException when passed Hashtable does not map names (String) to types (Class)
224: */
225: public Expression(String body, Map props, Collection imports,
226: Collection library) throws CompilationFailedException {
227: init(body, props, imports, library);
228: }
229:
230: /**
231: * Initialization
232: *
233: * @param body the expression body (Java)
234: * @param props the property-names mapped to types (java.lang.Class or java.lang.String). These will be available to the evaluated expression.
235: * @param imports imports to use
236: * @exception CompilationFailedException when the expression couldn't be compiled
237: * @exception IllegalArgumentException when passed Hashtable does not map names (String) to types (Class)
238: */
239: private void init(String body, Map props, Collection imports,
240: Collection library) throws CompilationFailedException {
241:
242: // Do we have imports or will we fall back to the defaults?
243: if (imports == null || imports.isEmpty()) {
244: imports = defaultImports;
245: }
246:
247: // Remember the properties
248: properties = new HashMap(props.size());
249: Iterator ps = props.keySet().iterator();
250: while (ps.hasNext()) {
251:
252: // Here's the property name and type
253: String p = (String) ps.next();
254: Object c = props.get(p);
255:
256: // .. might be a type or a type-name
257: if (!(c instanceof Class)) {
258: if (!(c instanceof String)) {
259: throw new IllegalArgumentException(
260: "props has to map String to type (java.lang.Class) or type-name (java.lang.String)");
261: }
262: c = ReflectAccess.getInstance().calcClassFromName(
263: (String) c);
264: }
265: properties.put(p, c);
266: }
267:
268: // increase internal counter
269: count++;
270:
271: // Generate the well known source of the Expression
272: Source s = generateSource(body, properties, imports);
273:
274: // Compile (for now only to byte-code)
275: compiled = Compiler.compile(s, true, new ArrayList(), library);
276:
277: // Done for now
278: }
279:
280: /**
281: * Constructor for a simple expression without properties.
282: * For more information about what an expression is, look at the
283: * {@link Expression class description}
284: *
285: * @param body the expression's body (Java)
286: * @exception CompilationFailedException when the expression couldn't be compiled
287: */
288: public Expression(InputStream body)
289: throws CompilationFailedException, IOException {
290: this (body, new HashMap());
291: }
292:
293: /**
294: * Constructor for an expression with properties.
295: * For more information about what an expression is, look at the
296: * {@link Expression class description}
297: *
298: * @param body the expression body (Java)
299: * @param properties the property-names mapped to types (java.lang.Class or java.lang.String). These will be available to the evaluated expression.
300: * @exception CompilationFailedException when the expression couldn't be compiled
301: * @exception IllegalArgumentException when passed Hashtable does not map names (String) to types (Class)
302: */
303: public Expression(InputStream body, Map properties)
304: throws CompilationFailedException, IOException {
305: this (body, properties, new ArrayList());
306: }
307:
308: /**
309: * Constructor for an expression with properties.
310: * For more information about what an expression is, look at the
311: * {@link Expression class description}
312: *
313: * @param body the expression body (Java)
314: * @param properties the property-names mapped to types (java.lang.Class or java.lang.String). These will be available to the evaluated expression.
315: * @param imports imports to use
316: * @exception CompilationFailedException when the expression couldn't be compiled
317: * @exception IllegalArgumentException when passed Hashtable does not map names (String) to types (Class)
318: */
319: public Expression(InputStream body, Map properties,
320: Collection imports) throws CompilationFailedException,
321: IOException {
322: this (body, properties, imports, null);
323: }
324:
325: /**
326: * Constructor for an expression with properties.
327: * For more information about what an expression is, look at the
328: * {@link Expression class description}
329: *
330: * @param body the expression body (Java)
331: * @param properties the property-names mapped to types (java.lang.Class or java.lang.String). These will be available to the evaluated expression.
332: * @param imports imports to use
333: * @exception CompilationFailedException when the expression couldn't be compiled
334: * @exception IllegalArgumentException when passed Hashtable does not map names (String) to types (Class)
335: */
336: public Expression(InputStream body, Map properties,
337: Collection imports, Collection library)
338: throws CompilationFailedException, IOException {
339: init(load(body), properties, imports, library);
340: }
341:
342: /**
343: * Helper that loads text from given InputStream
344: */
345: private static String load(InputStream in) throws IOException {
346:
347: InputStreamReader reader = new InputStreamReader(in);
348:
349: // We read from in
350: char[] buffer = new char[4096];
351: int length = reader.read(buffer, 0, buffer.length);
352:
353: // Done
354: return new String(buffer, 0, length);
355: }
356:
357: /**
358: * Returns an instance of the expression for thread-private use
359: */
360: public ExpressionInstance getInstance() {
361:
362: try {
363: return (ExpressionInstance) compiled.getType()
364: .newInstance();
365: } catch (Throwable t) {
366: throw new RuntimeException(
367: "Unexpected failure while creating ExpressionInstance");
368: }
369: }
370:
371: /**
372: * Returns an ExpressionInstance with its properties' preset to values
373: * in given context.
374: *
375: * @param context a context for the evaluation of the expression. It
376: * maps property-names to -values. All properties specified in
377: * the expression's constructor will be preset with values from
378: * the map if possible.
379: * Either provide values of according property-type or values
380: * that can be used in a one-argument-constructor of the according
381: * property-type.
382: * @exception IllegalPropertyException when a property doesn't match the expression's properties
383: * @return the result
384: */
385: public ExpressionInstance getInstance(Map context)
386: throws IllegalPropertyException {
387:
388: // Create a new Expression
389: ExpressionInstance instance = getInstance();
390:
391: // Prepare the values by looking at what properties where
392: // specified in the constructor
393: try {
394: Iterator propertyKeys = properties.keySet().iterator();
395: while (propertyKeys.hasNext()) {
396: String p = (String) propertyKeys.next();
397: Object v = context.get(p);
398:
399: instance.set(p, v);
400: }
401: } catch (InaccessiblePropertyException e) {
402: // ignored
403: }
404:
405: // Done
406: return instance;
407:
408: }
409:
410: /**
411: * Creates the source for given expression classname, body & properties
412: */
413: private Source generateSource(String body, Map properties,
414: Collection imports) {
415:
416: // Create a memory writing destination
417: StringBuffer buffer = new StringBuffer(4096);
418:
419: // .. and the source comes here
420: generateSourceHeader(buffer, imports);
421: generateSourceFields(buffer, properties);
422: generateSourceBody(buffer, body);
423: generateSourceFooter(buffer);
424:
425: // Debugging
426: debug(buffer.toString());
427:
428: // Done
429: return new Source(buffer.toString());
430: }
431:
432: /**
433: * Creates the source's header
434: */
435: private void generateSourceHeader(StringBuffer buffer,
436: Collection imports) {
437:
438: // Here comes the class
439: Iterator is = imports.iterator();
440: while (is.hasNext()) {
441: buffer.append("import ");
442: buffer.append(is.next().toString());
443: buffer.append(";");
444: }
445:
446: buffer.append("public class Expression extends "
447: + ExpressionInstance.class.getName() + "{");
448:
449: // Done
450: }
451:
452: /**
453: * Creates the source's members
454: */
455: private void generateSourceFields(StringBuffer buffer,
456: Map properties) {
457:
458: // Then the fields
459: try {
460: Iterator ps = properties.keySet().iterator();
461: while (ps.hasNext()) {
462: String name = (String) ps.next();
463: Class type = (Class) properties.get(name);
464: buffer.append("public " + type.getName() + " " + name
465: + ";");
466: }
467: } catch (ClassCastException clastcastexception) {
468: throw new IllegalArgumentException(
469: "Mappings in properties have to be of String/Class");
470: }
471:
472: // Done
473: }
474:
475: /**
476: * Creastes the source's methods
477: */
478: private void generateSourceBody(StringBuffer buffer, String body) {
479:
480: // And the evaluation method
481: buffer.append("protected Object internalEvaluate(){");
482:
483: // .. instruction lines
484: // Note: this is hard to figure out without lots of knowledge
485: // about the expression syntax :( I've tried to search for
486: // ';' AND '}' to catch the case where the evaluatable comes
487: // after a code-block (1) - in that case placing objectify
488: // behind works nice. But in case (2) objectify has to be placed
489: // before that :(
490: //
491: // (1)
492: // if (true) { System.out("Foo"); } "Hello World"
493: // -> ^^^
494: // if (true) { System.out("Foo"); } objectify("Hello World");
495: //
496: // (2)
497: // new Object[]{ "a", "b", "c" }
498: // -> ^^^
499: // objectify( new Object[]{ "a", "b", "c" } );
500: //
501: // For now we require that '}' in instructions are closed off
502: // with ';'
503: int instructions = body.lastIndexOf(';');
504: if (instructions >= 0) {
505: buffer.append(body.substring(0, instructions + 1));
506: body = body.substring(instructions + 1);
507: }
508: // .. and the (last and final) expression which is 'objectified'
509: buffer.append("return objectify(" + body + ");}");
510:
511: // Done
512: }
513:
514: /**
515: * Creates the source's footer
516: */
517: private void generateSourceFooter(StringBuffer buffer) {
518: // Simple!
519: buffer.append('}');
520: // Done
521: }
522:
523: /**
524: * A helper that debugs the ready-to-use expression's source
525: */
526: private void debug(String source) {
527:
528: // Anything to do?
529: if (expressionDir == null) {
530: return;
531: }
532:
533: // Write it
534: try {
535:
536: File file = File.createTempFile("Expression", ".java",
537: new File(expressionDir));
538: PrintWriter out = new PrintWriter(
539: new FileOutputStream(file));
540: out.print(source.toString());
541: out.close();
542:
543: } catch (IOException e) {
544: }
545:
546: // Done
547: }
548:
549: }
|