Author: danielf Date: Mon Jan 3 13:38:37 2005 New Revision: 124027 URL: http://svn.apache.org/viewcvs?view=rev&rev=124027 Log: Iterator support for Jexl Added: cocoon/trunk/src/blocks/template/java/org/apache/cocoon/components/expression/jexl/JSIntrospector.java Modified: cocoon/trunk/src/blocks/template/java/org/apache/cocoon/components/expression/jexl/JexlExpression.java cocoon/trunk/src/blocks/template/test/org/apache/cocoon/components/expression/jexl/JexlTestCase.java
Added: cocoon/trunk/src/blocks/template/java/org/apache/cocoon/components/expression/jexl/JSIntrospector.java Url: http://svn.apache.org/viewcvs/cocoon/trunk/src/blocks/template/java/org/apache/cocoon/components/expression/jexl/JSIntrospector.java?view=auto&rev=124027 ============================================================================== --- (empty file) +++ cocoon/trunk/src/blocks/template/java/org/apache/cocoon/components/expression/jexl/JSIntrospector.java Mon Jan 3 13:38:37 2005 @@ -0,0 +1,325 @@ +/* + * Copyright 1999-2004 The Apache Software Foundation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.cocoon.components.expression.jexl; + +import java.util.Enumeration; +import java.util.Iterator; + +import org.apache.commons.jexl.util.introspection.Info; +import org.apache.commons.jexl.util.introspection.UberspectImpl; +import org.apache.commons.jexl.util.introspection.VelMethod; +import org.apache.commons.jexl.util.introspection.VelPropertyGet; +import org.apache.commons.jexl.util.introspection.VelPropertySet; +import org.apache.commons.lang.StringUtils; +import org.mozilla.javascript.*; + + +/** + * Jexl Introspector that supports Rhino JavaScript objects + * as well as Java Objects + */ +public class JSIntrospector extends UberspectImpl { + + static class JSMethod implements VelMethod { + + Scriptable scope; + String name; + + public JSMethod(Scriptable scope, String name) { + this.scope = scope; + this.name = name; + } + + public Object invoke(Object thisArg, Object[] args) throws Exception { + Context cx = Context.enter(); + try { + Object result; + Scriptable thisObj = !(thisArg instanceof Scriptable) ? + Context.toObject(thisArg, scope) : (Scriptable)thisArg; + result = ScriptableObject.getProperty(thisObj, name); + Object[] newArgs = null; + if (args != null) { + newArgs = new Object[args.length]; + int len = args.length; + for (int i = 0; i < len; i++) { + newArgs[i] = args[i]; + if (args[i] != null && + !(args[i] instanceof Number) && + !(args[i] instanceof Boolean) && + !(args[i] instanceof String) && + !(args[i] instanceof Scriptable)) { + newArgs[i] = Context.toObject(args[i], scope); + } + } + } + result = ScriptRuntime.call(cx, result, thisObj, newArgs, scope); + if (result == Undefined.instance || result == Scriptable.NOT_FOUND) { + result = null; + } else if (!(result instanceof NativeJavaClass)) { + while (result instanceof Wrapper) { + result = ((Wrapper)result).unwrap(); + } + } + return result; + } catch (JavaScriptException e) { + throw new java.lang.reflect.InvocationTargetException(e); + } finally { + Context.exit(); + } + } + + public boolean isCacheable() { + return false; + } + + public String getMethodName() { + return name; + } + + public Class getReturnType() { + return Object.class; + } + + } + + static class JSPropertyGet implements VelPropertyGet { + + Scriptable scope; + String name; + + public JSPropertyGet(Scriptable scope, String name) { + this.scope = scope; + this.name = name; + } + + public Object invoke(Object thisArg) throws Exception { + Context cx = Context.enter(); + try { + Scriptable thisObj = !(thisArg instanceof Scriptable) ? + Context.toObject(thisArg, scope) : (Scriptable)thisArg; + Object result = ScriptableObject.getProperty(thisObj, name); + if (result == Scriptable.NOT_FOUND) { + result = ScriptableObject.getProperty(thisObj, "get" + StringUtils.capitalize(name)); + if (result != Scriptable.NOT_FOUND && result instanceof Function) { + try { + result = ((Function)result).call( + cx, ScriptableObject.getTopLevelScope(thisObj), thisObj, new Object[] {}); + } catch (JavaScriptException exc) { + exc.printStackTrace(); + result = null; + } + } + } + if (result == Scriptable.NOT_FOUND || result == Undefined.instance) { + result = null; + } else if (result instanceof Wrapper && !(result instanceof NativeJavaClass)) { + result = ((Wrapper)result).unwrap(); + } + return result; + } finally { + Context.exit(); + } + } + + public boolean isCacheable() { + return false; + } + + public String getMethodName() { + return name; + } + } + + static class JSPropertySet implements VelPropertySet { + + Scriptable scope; + String name; + + public JSPropertySet(Scriptable scope, String name) { + this.scope = scope; + this.name = name; + } + + public Object invoke(Object thisArg, Object rhs) throws Exception { + Context.enter(); + try { + Scriptable thisObj; + Object arg = rhs; + if (!(thisArg instanceof Scriptable)) { + thisObj = Context.toObject(thisArg, scope); + } else { + thisObj = (Scriptable)thisArg; + } + if (arg != null && + !(arg instanceof Number) && + !(arg instanceof Boolean) && + !(arg instanceof String) && + !(arg instanceof Scriptable)) { + arg = Context.toObject(arg, scope); + } + ScriptableObject.putProperty(thisObj, name, arg); + return rhs; + } finally { + Context.exit(); + } + } + + public boolean isCacheable() { + return false; + } + + public String getMethodName() { + return name; + } + } + + public static class NativeArrayIterator implements Iterator { + + NativeArray arr; + int index; + + public NativeArrayIterator(NativeArray arr) { + this.arr = arr; + this.index = 0; + } + + public boolean hasNext() { + return index < (int)arr.jsGet_length(); + } + + public Object next() { + Context.enter(); + try { + Object result = arr.get(index++, arr); + if (result == Undefined.instance || + result == Scriptable.NOT_FOUND) { + result = null; + } else { + if (!(result instanceof NativeJavaClass)) { + while (result instanceof Wrapper) { + result = ((Wrapper)result).unwrap(); + } + } + } + return result; + } finally { + Context.exit(); + } + } + + public void remove() { + arr.delete(index); + } + } + + static class ScriptableIterator implements Iterator { + + Scriptable scope; + Object[] ids; + int index; + + public ScriptableIterator(Scriptable scope) { + this.scope = scope; + this.ids = scope.getIds(); + this.index = 0; + } + + public boolean hasNext() { + return index < ids.length; + } + + public Object next() { + Context.enter(); + try { + Object result = ScriptableObject.getProperty(scope, ids[index++].toString()); + if (result == Undefined.instance || result == Scriptable.NOT_FOUND) { + result = null; + } else if (!(result instanceof NativeJavaClass)) { + while (result instanceof Wrapper) { + result = ((Wrapper)result).unwrap(); + } + } + return result; + } finally { + Context.exit(); + } + } + + public void remove() { + Context.enter(); + try { + scope.delete(ids[index].toString()); + } finally { + Context.exit(); + } + } + } + + public Iterator getIterator(Object obj, Info i) throws Exception { + if (!(obj instanceof Scriptable)) { + // support Enumeration + /* + Booth Enumeration and Iterator are supported in + Uberspect. The only difference is that they emit a + rather long warning message to commons logging, telling + that Enumerations and Iterator not are resettable and + cannot be reused. + */ + if (obj instanceof Enumeration) { + final Enumeration e = (Enumeration)obj; + return new Iterator() { + + public boolean hasNext() { + return e.hasMoreElements(); + } + + public Object next() { + return e.nextElement(); + } + + public void remove() { + // no action + } + + }; + } + if (obj instanceof Iterator) { + // support Iterator + return (Iterator)obj; + } + return super.getIterator(obj, i); + } + if (obj instanceof NativeArray) { + return new NativeArrayIterator((NativeArray)obj); + } + return new ScriptableIterator((Scriptable)obj); + } + + public VelMethod getMethod(Object obj, String methodName, Object[] args, Info i) throws Exception { + return !(obj instanceof Scriptable) ? + super.getMethod(obj, methodName, args, i) : new JSMethod((Scriptable)obj, methodName); + } + + public VelPropertyGet getPropertyGet(Object obj, String identifier, Info i) throws Exception { + return !(obj instanceof Scriptable) ? + super.getPropertyGet(obj, identifier, i) : new JSPropertyGet((Scriptable)obj, identifier); + } + + public VelPropertySet getPropertySet(Object obj, String identifier, Object arg, Info i) throws Exception { + return !(obj instanceof Scriptable) ? + super.getPropertySet(obj, identifier, arg, i) : new JSPropertySet((Scriptable)obj, identifier); + } +} Modified: cocoon/trunk/src/blocks/template/java/org/apache/cocoon/components/expression/jexl/JexlExpression.java Url: http://svn.apache.org/viewcvs/cocoon/trunk/src/blocks/template/java/org/apache/cocoon/components/expression/jexl/JexlExpression.java?view=diff&rev=124027&p1=cocoon/trunk/src/blocks/template/java/org/apache/cocoon/components/expression/jexl/JexlExpression.java&r1=124026&p2=cocoon/trunk/src/blocks/template/java/org/apache/cocoon/components/expression/jexl/JexlExpression.java&r2=124027 ============================================================================== --- cocoon/trunk/src/blocks/template/java/org/apache/cocoon/components/expression/jexl/JexlExpression.java (original) +++ cocoon/trunk/src/blocks/template/java/org/apache/cocoon/components/expression/jexl/JexlExpression.java Mon Jan 3 13:38:37 2005 @@ -15,10 +15,13 @@ */ package org.apache.cocoon.components.expression.jexl; +import java.lang.reflect.Field; import java.util.Iterator; import java.util.Map; import org.apache.commons.jexl.JexlContext; +import org.apache.commons.jexl.util.Introspector; +import org.apache.commons.jexl.util.introspection.Info; import org.apache.cocoon.components.expression.Expression; import org.apache.cocoon.components.expression.ExpressionCompiler; import org.apache.cocoon.components.expression.ExpressionContext; @@ -52,11 +55,32 @@ public Iterator iterate(ExpressionContext context) throws ExpressionException { - return null; + Iterator iter = null; + Object result = evaluate(context); + if (result != null) { + /* The Info object is supposed to contain the script + location where the expression is invoked and use that + in a warning log message if no iterator can be + generated. This info is not available in the expression + object and might not be relevant either as it can be + used from a non script situation. + */ + try { + iter = Introspector.getUberspect().getIterator(result, new Info("Unknown", 0, 0)); + } catch (Exception e) { + throw new ExpressionException("Couldn't get an iterator from expression " + + getExpression(), e); + } + } + if (iter == null) { + iter = EMPTY_ITER; + } + return iter; } public void assign(ExpressionContext context, Object value) throws ExpressionException { + throw new UnsupportedOperationException("Assign is not yet implemented for Jexl"); } public String getExpression() { @@ -67,7 +91,7 @@ return this.language; } - static class ContextAdapter implements JexlContext { + private static class ContextAdapter implements JexlContext { private final ExpressionContext context; public ContextAdapter(ExpressionContext context) { this.context = context; @@ -79,6 +103,31 @@ public void setVars(Map map) { this.context.setVars(map); + } + } + + private static final Iterator EMPTY_ITER = new Iterator() { + public boolean hasNext() { + return false; + } + + public Object next() { + return null; + } + + public void remove() { + // EMPTY + } + }; + + static { + // Hack: there's no _nice_ way to add my introspector to Jexl right now + try { + Field field = Introspector.class.getDeclaredField("uberSpect"); + field.setAccessible(true); + field.set(null, new JSIntrospector()); + } catch (Exception e) { + e.printStackTrace(); } } } Modified: cocoon/trunk/src/blocks/template/test/org/apache/cocoon/components/expression/jexl/JexlTestCase.java Url: http://svn.apache.org/viewcvs/cocoon/trunk/src/blocks/template/test/org/apache/cocoon/components/expression/jexl/JexlTestCase.java?view=diff&rev=124027&p1=cocoon/trunk/src/blocks/template/test/org/apache/cocoon/components/expression/jexl/JexlTestCase.java&r1=124026&p2=cocoon/trunk/src/blocks/template/test/org/apache/cocoon/components/expression/jexl/JexlTestCase.java&r2=124027 ============================================================================== --- cocoon/trunk/src/blocks/template/test/org/apache/cocoon/components/expression/jexl/JexlTestCase.java (original) +++ cocoon/trunk/src/blocks/template/test/org/apache/cocoon/components/expression/jexl/JexlTestCase.java Mon Jan 3 13:38:37 2005 @@ -15,6 +15,8 @@ */ package org.apache.cocoon.components.expression.jexl; +import java.util.Iterator; + import junit.framework.TestCase; import org.apache.cocoon.components.expression.Expression; import org.apache.cocoon.components.expression.ExpressionCompiler; @@ -36,5 +38,17 @@ context.put("b", new Long(2)); Expression expression = compiler.compile("jexl", "a+b"); assertEquals(new Long(3), expression.evaluate(context)); + } + + public void testIterator() throws ExpressionException { + ExpressionCompiler compiler = new JexlCompiler(); + ExpressionContext context = new ExpressionContext(); + String[] arr = {"foo"}; + context.put("arr", arr); + Expression expression = compiler.compile("jexl", "arr"); + Iterator iter = expression.iterate(context); + assertTrue("hasNext", iter.hasNext()); + assertEquals("foo", iter.next()); + assertFalse("hasNext", iter.hasNext()); } }