Skip to content
Permalink
Browse files
Merge pull request #94 from apache/JEXL-368
JEXL-368: refactored namespace resolution and cache functor properly
  • Loading branch information
henrib committed May 3, 2022
2 parents d5621f7 + 0a5417c commit bb04b40c71f381e8fe1e3b012c91db58010157d0
Showing 4 changed files with 227 additions and 82 deletions.
@@ -189,74 +189,92 @@ protected Object resolveNamespace(final String prefix, final JexlNode node) {
throw new JexlException(node, "no such function namespace " + prefix, null);
}
}
// shortcut if ns is known to be not-a-functor
final boolean cacheable = cache;
final Object cached = cacheable ? node.jjtGetValue() : null;
if (cached != JexlContext.NamespaceFunctor.class) {
// allow namespace to instantiate a functor with context if possible, not an error otherwise
Object functor = null;
if (namespace instanceof JexlContext.NamespaceFunctor) {
functor = ((JexlContext.NamespaceFunctor) namespace).createFunctor(context);
} else if (namespace instanceof Class<?> || namespace instanceof String) {
// attempt to reuse last ctor cached in volatile JexlNode.value
if (cached instanceof JexlMethod) {
try {
final Object eval = ((JexlMethod) cached).tryInvoke(null, context);
if (JexlEngine.TRY_FAILED != eval) {
functor = eval;
}
} catch (final JexlException.TryFailed xtry) {
throw new JexlException(node, "unable to instantiate namespace " + prefix, xtry.getCause());
}
Object functor = null;
// class or string (*1)
if (namespace instanceof Class<?> || namespace instanceof String) {
// the namespace(d) identifier
final ASTIdentifier nsNode = (ASTIdentifier) node.jjtGetChild(0);
final boolean cacheable = cache && prefix != null;
final Object cached = cacheable ? nsNode.jjtGetValue() : null;
// we know the class is used as namespace of static methods, no functor
if (cached instanceof Class<?>) {
return (Class<?>) cached;
}
// attempt to reuse last cached constructor
if (cached instanceof JexlContext.NamespaceFunctor) {
Object eval = ((JexlContext.NamespaceFunctor) cached).createFunctor(context);
if (JexlEngine.TRY_FAILED != eval) {
functor = eval;
namespace = cached;
}
// find a ctor with that context class
if (functor == null) {
JexlMethod ctor = uberspect.getConstructor(namespace, context);
}
if (functor == null) {
// find a constructor with that context as argument or without
for (int tried = 0; tried < 2; ++tried) {
final boolean withContext = tried == 0;
final JexlMethod ctor = withContext
? uberspect.getConstructor(namespace, context)
: uberspect.getConstructor(namespace);
if (ctor != null) {
try {
functor = ctor.invoke(namespace, context);
if (cacheable && ctor.isCacheable()) {
node.jjtSetValue(ctor);
functor = withContext
? ctor.invoke(namespace, context)
: ctor.invoke(namespace);
// defensive
if (functor != null) {
// wrap the namespace in a NamespaceFunctor to shield us from the actual
// number of arguments to call it with.
final Object ns = namespace;
// make it a class (not a lambda!) so instanceof (see *2) will catch it
namespace = new JexlContext.NamespaceFunctor() {
@Override
public Object createFunctor(JexlContext context) {
return withContext
? ctor.tryInvoke(null, ns, context)
: ctor.tryInvoke(null, ns);
}
};
if (cacheable && ctor.isCacheable()) {
nsNode.jjtSetValue(namespace);
}
break; // we found a constructor that did create a functor
}
} catch (final Exception xinst) {
throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
}
}
// try again; find a ctor with no arg
if (functor == null) {
ctor = uberspect.getConstructor(namespace);
if (ctor != null) {
try {
functor = ctor.invoke(namespace);
} catch (final Exception xinst) {
throw new JexlException(node, "unable to instantiate namespace " + prefix, xinst);
}
}
// try again; use a class, namespace of static methods
}
// did not, will not create a functor instance; use a class, namespace of static methods
if (functor == null) {
try {
// try to find a class with that name
if (functor == null && namespace instanceof String) {
try {
namespace = uberspect.getClassLoader().loadClass((String) namespace);
} catch (final ClassNotFoundException xignore) {
// not a class
namespace = null;
}
} // we know it's a class
if (namespace instanceof String) {
namespace = uberspect.getClassLoader().loadClass((String) namespace);
}
// we know it's a class in all cases (see *1)
if (cacheable) {
nsNode.jjtSetValue(namespace);
}
} catch (final ClassNotFoundException xignore) {
// not a class
throw new JexlException(node, "no such class namespace " + prefix, null);
}
}
}
// got a functor, store it and return it
if (functor != null) {
synchronized (this) {
if (functors == null) {
functors = new HashMap<>();
}
functors.put(prefix, functor);
}
// if a namespace functor, instantiate the functor (if not done already) and store it (*2)
if (functor == null && namespace instanceof JexlContext.NamespaceFunctor) {
functor = ((JexlContext.NamespaceFunctor) namespace).createFunctor(context);
}
// got a functor, store it and return it
if (functor != null) {
synchronized (this) {
if (functors == null) {
functors = new HashMap<>();
}
return functor;
functors.put(prefix, functor);
}
// use the NamespaceFunctor class to tag this node as not-a-functor
node.jjtSetValue(JexlContext.NamespaceFunctor.class);
return functor;
}
return namespace;
}
@@ -79,25 +79,28 @@ public Object invoke(final Object obj, final Object... params) throws Exception
}

@Override
public Object tryInvoke(final String name, final Object obj, final Object... params) {
try {
final Class<?> ctorClass = ctor.getDeclaringClass();
boolean invoke = true;
if (obj != null) {
if (obj instanceof Class<?>) {
invoke = ctorClass.equals(obj);
} else {
invoke = ctorClass.getName().equals(obj.toString());
public Object tryInvoke(final String name, final Object obj, final Object... args) {
// dont try to invoke if no parameter but call has arguments
if (ctor.getParameterCount() > 0 || args.length == 0) {
try {
final Class<?> ctorClass = ctor.getDeclaringClass();
boolean invoke = true;
if (obj != null) {
if (obj instanceof Class<?>) {
invoke = ctorClass.equals(obj);
} else {
invoke = ctorClass.getName().equals(obj.toString());
}
}
invoke &= name == null || ctorClass.getName().equals(name);
if (invoke) {
return ctor.newInstance(args);
}
} catch (InstantiationException | IllegalArgumentException | IllegalAccessException xinstance) {
return Uberspect.TRY_FAILED;
} catch (final InvocationTargetException xinvoke) {
throw JexlException.tryFailed(xinvoke); // throw
}
invoke &= name == null || ctorClass.getName().equals(name);
if (invoke) {
return ctor.newInstance(params);
}
} catch (InstantiationException | IllegalArgumentException | IllegalAccessException xinstance) {
return Uberspect.TRY_FAILED;
} catch (final InvocationTargetException xinvoke) {
throw JexlException.tryFailed(xinvoke); // throw
}
return Uberspect.TRY_FAILED;
}
@@ -19,6 +19,8 @@
import org.junit.Assert;
import org.junit.Test;

import java.util.Arrays;

/**
* Test cases for assignment.
*
@@ -72,10 +74,9 @@ public AssignTest() {
/**
* Make sure bean assignment works
*
* @throws Exception on any error
*/
@Test
public void testAntish() throws Exception {
public void testAntish() {
final JexlExpression assign = JEXL.createExpression("froboz.value = 10");
final JexlExpression check = JEXL.createExpression("froboz.value");
final JexlContext jc = new MapContext();
@@ -86,7 +87,7 @@ public void testAntish() throws Exception {
}

@Test
public void testAntishInteger() throws Exception {
public void testAntishInteger() {
final JexlExpression assign = JEXL.createExpression("froboz.0 = 10");
final JexlExpression check = JEXL.createExpression("froboz.0");
final JexlContext jc = new MapContext();
@@ -97,7 +98,7 @@ public void testAntishInteger() throws Exception {
}

@Test
public void testBeanish() throws Exception {
public void testBeanish() {
final JexlExpression assign = JEXL.createExpression("froboz.value = 10");
final JexlExpression check = JEXL.createExpression("froboz.value");
final JexlContext jc = new MapContext();
@@ -110,7 +111,7 @@ public void testBeanish() throws Exception {
}

@Test
public void testAmbiguous() throws Exception {
public void testAmbiguous() {
final JexlExpression assign = JEXL.createExpression("froboz.nosuchbean = 10");
final JexlContext jc = new MapContext();
final Froboz froboz = new Froboz(-169);
@@ -129,7 +130,7 @@ public void testAmbiguous() throws Exception {
}

@Test
public void testArray() throws Exception {
public void testArray() {
final JexlExpression assign = JEXL.createExpression("froboz[\"value\"] = 10");
final JexlExpression check = JEXL.createExpression("froboz[\"value\"]");
final JexlContext jc = new MapContext();
@@ -142,7 +143,7 @@ public void testArray() throws Exception {
}

@Test
public void testMini() throws Exception {
public void testMini() {
final JexlContext jc = new MapContext();
final JexlExpression assign = JEXL.createExpression("quux = 10");
final Object o = assign.evaluate(jc);
@@ -151,7 +152,7 @@ public void testMini() throws Exception {
}

@Test
public void testMore() throws Exception {
public void testMore() {
final JexlContext jc = new MapContext();
jc.set("quuxClass", Quux.class);
final JexlExpression create = JEXL.createExpression("quux = new(quuxClass, 'xuuq', 100)");
@@ -167,7 +168,7 @@ public void testMore() throws Exception {
}

@Test
public void testUtil() throws Exception {
public void testUtil() {
final Quux quux = JEXL.newInstance(Quux.class, "xuuq", Integer.valueOf(100));
Assert.assertNotNull(quux);
JEXL.setProperty(quux, "froboz.value", Integer.valueOf(100));
@@ -179,7 +180,7 @@ public void testUtil() throws Exception {
}

@Test
public void testRejectLocal() throws Exception {
public void testRejectLocal() {
final JexlContext jc = new MapContext();
JexlScript assign = JEXL.createScript("var quux = null; quux.froboz.value = 10");
try {
@@ -194,4 +195,53 @@ public void testRejectLocal() throws Exception {
final Object o = assign.execute(jc);
Assert.assertEquals(10, o);
}
}

@Test
public void testPropertyInError0() {
JexlScript script;
for(String op : Arrays.asList(
" = ", "+= ", " -= "," *= "," /= "," %= ",
" &= ", " |= ", " ^= ",
" <<= ", " >>= ", " >>>= ")) {
script = JEXL.createScript("x -> x.y " +op+ "42");
try {
script.execute(null, "the_x_value");
} catch (final JexlException.Property xprop) {
Assert.assertEquals("y", xprop.getProperty());
}
}
script = JEXL.createScript("x -> x.y ");
try {
script.execute(null, "the_x_value");
} catch (final JexlException.Property xprop) {
Assert.assertEquals("y", xprop.getProperty());
}
}

@Test
public void testSetInError1() {
try {
JEXL.setProperty("the_x_value", "y", 42);
} catch (final JexlException.Property xprop) {
Assert.assertEquals("y", xprop.getProperty());
}
try {
JEXL.setProperty(null, "y", 42);
} catch (final JexlException xprop) {
//
}
}
@Test
public void testGetInError1() {
try {
JEXL.getProperty("the_x_value", "y");
} catch (final JexlException.Property xprop) {
Assert.assertEquals("y", xprop.getProperty());
}
try {
JEXL.getProperty(null, "y");
} catch (final JexlException xprop) {
//
}
}
}

0 comments on commit bb04b40

Please sign in to comment.