/** * Copyright 2016 Nabarun Mondal * 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 com.noga.njexl.lang; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.logging.Logger; /** * Tests local variables. */ public class VarTest extends JexlTestCase { static final Logger LOGGER = Logger.getLogger(VarTest.class.getName()); public VarTest(String testName) { super(testName); } public void testStrict() throws Exception { JexlContext map = new MapContext(); JexlContext ctxt = new ReadonlyContext(new MapContext()); JEXL.setStrict(true); Script e; e = JEXL.createScript("x"); try { Object o = e.execute(ctxt); fail("should have thrown an unknown var exception"); } catch(JexlException xjexl) { // ok since we are strict and x does not exist } e = JEXL.createScript("x = 42"); try { Object o = e.execute(ctxt); fail("should have thrown a readonly context exception"); } catch(JexlException xjexl) { // ok since we are strict and context is readonly } map.set("x", "fourty-two"); e = JEXL.createScript("x.theAnswerToEverything()"); try { Object o = e.execute(ctxt); fail("should have thrown an unknown method exception"); } catch(JexlException xjexl) { // ok since we are strict and method does not exist } } public void testLocalBasic() throws Exception { Script e = JEXL.createScript("var x; x = 42"); Object o = e.execute(null); assertEquals("Result is not 42", new Integer(42), o); } public void testLocalSimple() throws Exception { Script e = JEXL.createScript("var x = 21; x + x"); Object o = e.execute(null); assertEquals("Result is not 42", new Integer(42), o); } public void testLocalFor() throws Exception { Script e = JEXL.createScript("var y = 0; for(var x : [5, 17, 20]) { y = y + x; } y;"); Object o = e.execute(null); assertEquals("Result is not 42", new Integer(42), o); } public static class NumbersContext extends MapContext implements NamespaceResolver { public Object resolveNamespace(String name) { return name == null ? this : null; } public Object numbers() { return new int[]{5, 17, 20}; } } public void testLocalForFunc() throws Exception { JexlContext jc = new NumbersContext(); Script e = JEXL.createScript("var y = 0; for(var x : numbers()) { y = y + x; } y;"); Object o = e.execute(jc); assertEquals("Result is not 42", new Integer(42), o); } public void testLocalForFuncReturn() throws Exception { JexlContext jc = new NumbersContext(); Script e = JEXL.createScript("var y = 42; for(var x : numbers()) { if (x > 10) return x } y;"); Object o = e.execute(jc); assertEquals("Result is not 17", new Integer(17), o); assertTrue(toString(e.getVariables()), e.getVariables().isEmpty()); } /** * Generate a string representation of Set<List&t;String>>, useful to dump script variables * @param refs the variable reference set * @return the string representation */ String toString(Set<List<String>> refs) { StringBuilder strb = new StringBuilder("{"); int r = 0; for (List<String> strs : refs) { if (r++ > 0) { strb.append(", "); } strb.append("{"); for (int s = 0; s < strs.size(); ++s) { if (s > 0) { strb.append(", "); } strb.append('"'); strb.append(strs.get(s)); strb.append('"'); } strb.append("}"); } strb.append("}"); return strb.toString(); } /** * Creates a variable reference set from an array of array of strings. * @param refs the variable reference set * @return the set of variables */ Set<List<String>> mkref(String[][] refs) { Set<List<String>> set = new HashSet<List<String>>(); for(String[] ref : refs) { set.add(Arrays.asList(ref)); } return set; } /** * Checks that two sets of variable references are equal * @param lhs the left set * @param rhs the right set * @return true if equal, false otherwise */ boolean eq(Set<List<String>> lhs, Set<List<String>> rhs) { if (lhs.size() != rhs.size()) { return false; } List<String> llhs = stringify(lhs); List<String> lrhs = stringify(rhs); for(int s = 0; s < llhs.size(); ++s) { String l = llhs.get(s); String r = lrhs.get(s); if (!l.equals(r)) { return false; } } return true; } List<String> stringify(Set<List<String>> sls) { List<String> ls = new ArrayList<String>(); for(List<String> l : sls) { StringBuilder strb = new StringBuilder(); for(String s : l) { strb.append(s); strb.append('|'); } ls.add(strb.toString()); } Collections.sort(ls); return ls; } public void testRefs() throws Exception { Script e; Set<List<String>> vars; Set<List<String>> expect; e = JEXL.createScript("e[f]"); vars = e.getVariables(); expect = mkref(new String[][]{{"e"},{"f"}}); assertTrue(eq(expect, vars)); e = JEXL.createScript("e[f][g]"); vars = e.getVariables(); expect = mkref(new String[][]{{"e"},{"f"},{"g"}}); assertTrue(eq(expect, vars)); e = JEXL.createScript("e['f'].goo"); vars = e.getVariables(); expect = mkref(new String[][]{{"e","f","goo"}}); assertTrue(eq(expect, vars)); e = JEXL.createScript("e['f']"); vars = e.getVariables(); expect = mkref(new String[][]{{"e","f"}}); assertTrue(eq(expect, vars)); e = JEXL.createScript("e[f]['g']"); vars = e.getVariables(); expect = mkref(new String[][]{{"e"},{"f"}}); assertTrue(eq(expect, vars)); e = JEXL.createScript("e['f']['g']"); vars = e.getVariables(); expect = mkref(new String[][]{{"e","f","g"}}); assertTrue(eq(expect, vars)); e = JEXL.createScript("a['b'].c['d'].e"); vars = e.getVariables(); expect = mkref(new String[][]{{"a", "b", "c", "d", "e"}}); assertTrue(eq(expect, vars)); e = JEXL.createScript("a + b.c + b.c.d + e['f']"); //LOGGER.info(flattenedStr(e)); vars = e.getVariables(); expect = mkref(new String[][]{{"a"}, {"b", "c"}, {"b", "c", "d"}, {"e", "f"}}); assertTrue(eq(expect, vars)); } public void testMix() throws Exception { Script e; // x is a parameter, y a context variable, z a local variable e = JEXL.createScript("if (x) { y } else { var z = 2 * x}", "x"); Set<List<String>> vars = e.getVariables(); String[] parms = e.getParameters(); String[] locals = e.getLocalVariables(); assertTrue(eq(mkref(new String[][]{{"y"}}), vars)); assertEquals(1, parms.length); assertEquals("x", parms[0]); assertEquals(1, locals.length); assertEquals("z", locals[0]); } public void testLiteral() throws Exception { Script e = JEXL.createScript("x.y[['z', 't']]"); Set<List<String>> vars = e.getVariables(); assertEquals(1, vars.size()); assertTrue(eq(mkref(new String[][]{{"x", "y", "[ 'z', 't' ]"}}), vars)); e = JEXL.createScript("x.y[{'z': 't'}]"); vars = e.getVariables(); assertEquals(1, vars.size()); assertTrue(eq(mkref(new String[][]{{"x", "y", "{ 'z' : 't' }"}}), vars)); e = JEXL.createScript("x.y.'{ \\'z\\' : \\'t\\' }'"); vars = e.getVariables(); assertEquals(1, vars.size()); assertTrue(eq(mkref(new String[][]{{"x", "y", "{ 'z' : 't' }"}}), vars)); } }