Author: henrib Date: Thu Jan 7 17:16:09 2010 New Revision: 896931 URL: http://svn.apache.org/viewvc?rev=896931&view=rev Log: Moved JexlScriptObject into JexlScriptEngine; JEXL variable is 'reserved', does not appear in binding and shields user value; ie user value of that name must be accessed through 'context.JEXL' Made JEXL.err, JEXL.out, JEXL.in point to defaut engine scope objects writer/errwriter/reader; All JexlScripEngine instances share a unique JEXL engine instance and expression cache;
Removed: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptObject.java Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngine.java commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineOptionalTest.java commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineTest.java Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngine.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngine.java?rev=896931&r1=896930&r2=896931&view=diff ============================================================================== --- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngine.java (original) +++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngine.java Thu Jan 7 17:16:09 2010 @@ -18,9 +18,9 @@ package org.apache.commons.jexl2.scripting; import java.io.IOException; +import java.io.PrintWriter; import java.io.Reader; -import java.util.HashMap; -import java.util.Map; +import java.io.Writer; import javax.script.AbstractScriptEngine; import javax.script.Bindings; @@ -36,6 +36,9 @@ import org.apache.commons.jexl2.JexlEngine; import org.apache.commons.jexl2.Script; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + /** * Implements the Jexl ScriptEngine for JSF-223. * <p> @@ -56,19 +59,27 @@ * @since 2.0 */ public class JexlScriptEngine extends AbstractScriptEngine implements Compilable { + /** The logger. */ + private static final Log LOG = LogFactory.getLog(JexlScriptEngine.class); - /** Reserved key for JexlScriptObject. */ - public static final String JEXL_OBJECT_KEY = "JEXL"; + /** The shared expression cache size. */ + private static final int CACHE_SIZE = 512; /** Reserved key for context (mandated by JSR-223). */ public static final String CONTEXT_KEY = "context"; + /** Reserved key for JexlScriptObject. */ + public static final String JEXL_OBJECT_KEY = "JEXL"; + + /** The JexlScriptObject instance. */ + private final JexlScriptObject jexlObject; + /** The factory which created this instance. */ private final ScriptEngineFactory parentFactory; /** The JEXL EL engine. */ private final JexlEngine jexlEngine; - + /** * Default constructor. * <p> @@ -76,22 +87,92 @@ * Sets the factory to {...@link JexlScriptEngineFactory}. */ public JexlScriptEngine() { - this(SingletonHolder.DEFAULT_FACTORY); + this(FactorySingletonHolder.DEFAULT_FACTORY); } /** - * The set of functions exposed in the default namespace. + * Implements engine and engine context properties for use by JEXL scripts. + * Those properties are allways bound to the default engine scope context. + * <p> + * The following properties are defined: + * <ul> + * <li>in - refers to the engine scope reader that defaults to reading System.err</li> + * <li>out - refers the engine scope writer that defaults to writing in System.out</li> + * <li>err - refers to the engine scope writer that defaults to writing in System.err</li> + * <li>logger - the JexlScriptEngine logger</li> + * <li>System - the System.class</li> + * </ul> + * </p> + * @since 2.0 */ - public static final class JexlFunctions { + public class JexlScriptObject { + /** + * Gives access to the underlying JEXL engine shared between all ScriptEngine instances. + * <p>Although this allows to manipulate various engine flags (lenient, debug, cache...) + * for <strong>all</strong> JexlScriptEngine instances, you probably should only do so + * if you are in strict control and sole user of the Jexl scripting feature.</p> + * @return the shared underlying JEXL engine + */ + public JexlEngine getEngine() { + return jexlEngine; + } + /** - * Calls System.out.println. - * @param arg the argument + * Gives access to the engine scope output writer (defaults to System.out). + * @return the engine output writer */ - public void print(String arg) { - System.out.println(arg); + public PrintWriter getOut() { + final Writer out = context.getWriter(); + if (out instanceof PrintWriter) { + return (PrintWriter) out; + } else if (out != null) { + return new PrintWriter(out, true); + } else { + return null; + } + } + + /** + * Gives access to the engine scope error writer (defaults to System.err). + * @return the engine error writer + */ + public PrintWriter getErr() { + final Writer error = context.getErrorWriter(); + if (error instanceof PrintWriter) { + return (PrintWriter) error; + } else if (error != null) { + return new PrintWriter(error, true); + } else { + return null; + } + } + + /** + * Gives access to the engine scope input reader (defaults to System.in). + * @return the engine input reader + */ + public Reader getIn() { + return context.getReader(); + } + + /** + * Gives access to System class. + * @return System.class + */ + public Class<System> getSystem() { + return System.class; + } + + /** + * Gives access to the engine logger. + * @return the JexlScriptEngine logger + */ + public Log getLogger() { + return LOG; } } - + + /** * Create a scripting engine using the supplied factory. * @@ -103,13 +184,8 @@ throw new NullPointerException("ScriptEngineFactory must not be null"); } parentFactory = factory; - jexlEngine = new JexlEngine(); - // Add the jexl functions, ie print and escape - Map<String,Object> funcs = new HashMap<String,Object>(); - funcs.put(null, new JexlFunctions()); - jexlEngine.setFunctions(funcs); - // Add utility object - put(JEXL_OBJECT_KEY, new JexlScriptObject()); + jexlEngine = EngineSingletonHolder.DEFAULT_ENGINE; + jexlObject = new JexlScriptObject(); } /** {...@inheritdoc} */ @@ -118,24 +194,22 @@ } /** {...@inheritdoc} */ - public Object eval(Reader reader, ScriptContext context) throws ScriptException { + public Object eval(final Reader reader, final ScriptContext context) throws ScriptException { // This is mandated by JSR-223 (see SCR.5.5.2 Methods) if (reader == null || context == null) { throw new NullPointerException("script and context must be non-null"); } - return eval(readerToString(reader), context); } /** {...@inheritdoc} */ - public Object eval(String script, final ScriptContext context) throws ScriptException { + public Object eval(final String script, final ScriptContext context) throws ScriptException { // This is mandated by JSR-223 (see SCR.5.5.2 Methods) if (script == null || context == null) { throw new NullPointerException("script and context must be non-null"); } // This is mandated by JSR-223 (end of section SCR.4.3.4.1.2 - Script Execution) context.setAttribute(CONTEXT_KEY, context, ScriptContext.ENGINE_SCOPE); - try { Script jexlScript = jexlEngine.createScript(script); JexlContext ctxt = new JexlContextWrapper(context); @@ -151,7 +225,7 @@ } /** {...@inheritdoc} */ - public CompiledScript compile(String script) throws ScriptException { + public CompiledScript compile(final String script) throws ScriptException { // This is mandated by JSR-223 if (script == null) { throw new NullPointerException("script must be non-null"); @@ -165,7 +239,7 @@ } /** {...@inheritdoc} */ - public CompiledScript compile(Reader script) throws ScriptException { + public CompiledScript compile(final Reader script) throws ScriptException { // This is mandated by JSR-223 if (script == null) { throw new NullPointerException("script must be non-null"); @@ -179,7 +253,7 @@ * @return the script as a string * @throws ScriptException if an exception occurs during read */ - private String readerToString(Reader script) throws ScriptException { + private String readerToString(final Reader script) throws ScriptException { try { return JexlEngine.readerToString(script); } catch (IOException e) { @@ -190,47 +264,68 @@ /** * Holds singleton JexlScriptEngineFactory (IODH). */ - private static class SingletonHolder { + private static class FactorySingletonHolder { /** non instantiable. */ - private SingletonHolder() {} - /** The singleton instance. */ + private FactorySingletonHolder() {} + /** The engine factory singleton instance. */ private static final JexlScriptEngineFactory DEFAULT_FACTORY = new JexlScriptEngineFactory(); } /** + * Holds singleton JexlScriptEngine (IODH). + * <p>A single JEXL engine and Uberspect is shared by all instances of JexlScriptEngine.</p> + */ + private static class EngineSingletonHolder { + /** non instantiable. */ + private EngineSingletonHolder() {} + /** The JEXL engine singleton instance. */ + private static final JexlEngine DEFAULT_ENGINE = new JexlEngine(null, null, null, LOG) { + { + this.setCache(CACHE_SIZE); + } + }; + } + + /** * Wrapper to help convert a JSR-223 ScriptContext into a JexlContext. * * Current implementation only gives access to ENGINE_SCOPE binding. */ - private static final class JexlContextWrapper implements JexlContext { - /** The engine context. */ - private final ScriptContext engineContext; + private final class JexlContextWrapper implements JexlContext { + /** The wrapped script context. */ + private final ScriptContext scriptContext; /** - * Create the class. - * - * @param context the engine context. + * Creates a context wrapper. + * @param theContext the engine context. */ - private JexlContextWrapper (final ScriptContext context){ - engineContext = context; + private JexlContextWrapper (final ScriptContext theContext){ + scriptContext = theContext; } /** {...@inheritdoc} */ - public Object get(String name) { - return engineContext.getAttribute(name); + public Object get(final String name) { + final Object o = scriptContext.getAttribute(name); + if (JEXL_OBJECT_KEY.equals(name)) { + if (o != null) { + LOG.warn("JEXL is a reserved variable name, user defined value is ignored"); + } + return jexlObject; + } + return o; } /** {...@inheritdoc} */ - public void set(String name, Object value) { - int scope = engineContext.getAttributesScope(name); + public void set(final String name, final Object value) { + int scope = scriptContext.getAttributesScope(name); if (scope == -1) { // not found, default to engine scope = ScriptContext.ENGINE_SCOPE; } - engineContext.getBindings(scope).put(name , value); + scriptContext.getBindings(scope).put(name , value); } /** {...@inheritdoc} */ - public boolean has(String name) { - Bindings bnd = engineContext.getBindings(ScriptContext.ENGINE_SCOPE); + public boolean has(final String name) { + Bindings bnd = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE); return bnd.containsKey(name); } @@ -247,7 +342,7 @@ * Creates an instance. * @param theScript to wrap */ - private JexlCompiledScript(Script theScript) { + private JexlCompiledScript(final Script theScript) { script = theScript; } @@ -259,7 +354,7 @@ /** {...@inheritdoc} */ @Override - public Object eval(ScriptContext context) throws ScriptException { + public Object eval(final ScriptContext context) throws ScriptException { // This is mandated by JSR-223 (end of section SCR.4.3.4.1.2 - Script Execution) context.setAttribute(CONTEXT_KEY, context, ScriptContext.ENGINE_SCOPE); try { Modified: commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java?rev=896931&r1=896930&r2=896931&view=diff ============================================================================== --- commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java (original) +++ commons/proper/jexl/trunk/src/main/java/org/apache/commons/jexl2/scripting/JexlScriptEngineFactory.java Thu Jan 7 17:16:09 2010 @@ -98,9 +98,9 @@ /** {...@inheritdoc} */ public String getOutputStatement(String toDisplay) { if (toDisplay == null) { - return "print(null)"; + return "JEXL.out.print(null)"; } else { - return "print("+StringParser.escapeString(toDisplay)+")"; + return "JEXL.out.print("+StringParser.escapeString(toDisplay)+")"; } } @@ -117,8 +117,14 @@ } else if(key.equals(ScriptEngine.LANGUAGE_VERSION)) { return getLanguageVersion(); } else if (key.equals("THREADING")) { - return null;//"MULTITHREADED"; // TODO what is the correct value here? - } + /* + * To implement multithreading, the scripting engine context (inherited from AbstractScriptEngine) + * would need to be made thread-safe; so would the setContext/getContext methods. + * It is easier to share the underlying Uberspect and JEXL engine instance, especially + * with an expression cache. + */ + return null; + } return null; } Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineOptionalTest.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineOptionalTest.java?rev=896931&r1=896930&r2=896931&view=diff ============================================================================== --- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineOptionalTest.java (original) +++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineOptionalTest.java Thu Jan 7 17:16:09 2010 @@ -18,8 +18,8 @@ package org.apache.commons.jexl2.scripting; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; import javax.script.Compilable; import javax.script.CompiledScript; @@ -32,18 +32,23 @@ private final ScriptEngineManager manager = new ScriptEngineManager(); private final ScriptEngine engine = manager.getEngineByName("jexl"); - public void testScriptEngineOutput() throws Exception { + public void testOutput() throws Exception { String output = factory.getOutputStatement("foo\u00a9bar"); - assertEquals(output, "print('foo\\u00a9bar')"); + assertEquals("JEXL.out.print('foo\\u00a9bar')", output); // redirect output to capture evaluation result - final ByteArrayOutputStream outContent = new ByteArrayOutputStream(); - System.setOut(new PrintStream(outContent)); - try { - engine.eval(output); - } finally { - System.setOut(null); - } - assertEquals("foo\u00a9bar\n", outContent.toString()); + final StringWriter outContent = new StringWriter(); + engine.getContext().setWriter(outContent); + engine.eval(output); + assertEquals("foo\u00a9bar", outContent.toString()); + } + + public void testError() throws Exception { + String error = "JEXL.err.print('ERROR')"; + // redirect error to capture evaluation result + final StringWriter outContent = new StringWriter(); + engine.getContext().setErrorWriter(outContent); + engine.eval(error); + assertEquals("ERROR", outContent.toString()); } public void testCompilable() throws Exception { @@ -53,5 +58,4 @@ assertEquals(Integer.valueOf(42), script.eval()); assertEquals(Integer.valueOf(42), script.eval()); } - } Modified: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineTest.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineTest.java?rev=896931&r1=896930&r2=896931&view=diff ============================================================================== --- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineTest.java (original) +++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl2/scripting/JexlScriptEngineTest.java Thu Jan 7 17:16:09 2010 @@ -18,13 +18,9 @@ package org.apache.commons.jexl2.scripting; -import java.io.ByteArrayOutputStream; -import java.io.PrintStream; import java.io.Reader; import java.util.Arrays; import java.util.Map; -import javax.script.Compilable; -import javax.script.CompiledScript; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; @@ -72,9 +68,9 @@ assertEquals(newValue,engine.get("value")); assertEquals(engine.getContext(),engine.get(JexlScriptEngine.CONTEXT_KEY)); // Check behaviour of JEXL object - assertNotNull(engine.get("JEXL")); - assertEquals(System.out,engine.eval("JEXL.out")); - assertEquals(System.err,engine.eval("JEXL.err")); + assertEquals(engine.getContext().getReader(),engine.eval("JEXL.in")); + assertEquals(engine.getContext().getWriter(),engine.eval("JEXL.out")); + assertEquals(engine.getContext().getErrorWriter(),engine.eval("JEXL.err")); assertEquals(System.class,engine.eval("JEXL.System")); } @@ -98,10 +94,9 @@ } public void testEngineNames() throws Exception { - ScriptEngine engine; ScriptEngineManager manager = new ScriptEngineManager(); assertNotNull("Manager should not be null", manager); - engine = manager.getEngineByName("JEXL"); + ScriptEngine engine = manager.getEngineByName("JEXL"); assertNotNull("Engine should not be null (JEXL)", engine); engine = manager.getEngineByName("Jexl"); assertNotNull("Engine should not be null (Jexl)", engine); @@ -110,10 +105,9 @@ } public void testScopes() throws Exception { - ScriptEngine engine; ScriptEngineManager manager = new ScriptEngineManager(); assertNotNull("Manager should not be null", manager); - engine = manager.getEngineByName("JEXL"); + ScriptEngine engine = manager.getEngineByName("JEXL"); assertNotNull("Engine should not be null (JEXL)", engine); manager.put("global",Integer.valueOf(1)); engine.put("local", Integer.valueOf(10)); @@ -132,10 +126,9 @@ } public void testDottedNames() throws Exception { - ScriptEngine engine; ScriptEngineManager manager = new ScriptEngineManager(); assertNotNull("Manager should not be null", manager); - engine = manager.getEngineByName("JEXL"); + ScriptEngine engine = manager.getEngineByName("JEXL"); assertNotNull("Engine should not be null (JEXL)", engine); engine.eval("this.is.a.test=null"); assertNull(engine.get("this.is.a.test"));