From 0d13fdd028ab636e2253e7139d1d43a19ebc6395 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mitchell=20B=C3=B6secke?= Date: Sun, 28 Jun 2015 12:34:30 -0600 Subject: [PATCH] Added a getter on AttributeNotFoundException to retrieve attribute name. Fixes #80. --- .../error/AttributeNotFoundException.java | 9 +- .../error/RootAttributeNotFoundException.java | 8 +- .../expression/GetAttributeExpression.java | 514 +++++------ .../pebble/ErrorReportingTest.java | 14 +- .../pebble/GetAttributeTest.java | 850 +++++++++--------- 5 files changed, 702 insertions(+), 693 deletions(-) diff --git a/src/main/java/com/mitchellbosecke/pebble/error/AttributeNotFoundException.java b/src/main/java/com/mitchellbosecke/pebble/error/AttributeNotFoundException.java index 48eb9568b..2530c2702 100644 --- a/src/main/java/com/mitchellbosecke/pebble/error/AttributeNotFoundException.java +++ b/src/main/java/com/mitchellbosecke/pebble/error/AttributeNotFoundException.java @@ -12,8 +12,15 @@ public class AttributeNotFoundException extends PebbleException { private static final long serialVersionUID = 3863732457312917327L; - public AttributeNotFoundException(Throwable cause, String message) { + private final String attributeName; + + public AttributeNotFoundException(Throwable cause, String message, String attributeName) { super(cause, message); + this.attributeName = attributeName; + } + + public String getAttributeName() { + return attributeName; } } diff --git a/src/main/java/com/mitchellbosecke/pebble/error/RootAttributeNotFoundException.java b/src/main/java/com/mitchellbosecke/pebble/error/RootAttributeNotFoundException.java index 5d2c3aa5a..89c351aed 100644 --- a/src/main/java/com/mitchellbosecke/pebble/error/RootAttributeNotFoundException.java +++ b/src/main/java/com/mitchellbosecke/pebble/error/RootAttributeNotFoundException.java @@ -10,10 +10,10 @@ public class RootAttributeNotFoundException extends AttributeNotFoundException { - private static final long serialVersionUID = 3863732457312917327L; + private static final long serialVersionUID = 3863732457312917327L; - public RootAttributeNotFoundException(Throwable cause, String message) { - super(cause, message); - } + public RootAttributeNotFoundException(Throwable cause, String message, String attributeName) { + super(cause, message, attributeName); + } } diff --git a/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java b/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java index d987f1c28..789e4e5f4 100644 --- a/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java +++ b/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java @@ -36,261 +36,263 @@ */ public class GetAttributeExpression implements Expression { - private final Expression node; - - private final String attributeName; - - private final ArgumentsNode args; - - /** - * Potentially cached on first evaluation. - */ - private final ConcurrentHashMap, Member> memberCache; - - public GetAttributeExpression(Expression node, String attributeName) { - this(node, attributeName, null); - } - - public GetAttributeExpression(Expression node, String attributeName, ArgumentsNode args) { - this.node = node; - this.attributeName = attributeName; - this.args = args; - - /* - * I dont imagine that users will often give different types to the same - * template so we will give this cache a pretty small initial capacity. - */ - this.memberCache = new ConcurrentHashMap<>(2, 0.9f, 1); - } - - @Override - public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throws PebbleException { - Object object = node.evaluate(self, context); - - Object result = null; - - Object[] argumentValues = null; - - Member member = object == null ? null : memberCache.get(object.getClass()); - - if (object != null && member == null) { - - /* - * If, and only if, no arguments were provided does it make sense to - * check maps/arrays/lists - */ - if (args == null) { - - // first we check maps - if (object instanceof Map && ((Map) object).containsKey(attributeName)) { - return ((Map) object).get(attributeName); - } - - try { - - // then we check arrays - if (object instanceof Object[]) { - Integer key = Integer.valueOf(attributeName); - Object[] arr = ((Object[]) object); - return arr[key]; - } - - // then lists - if (object instanceof List) { - Integer key = Integer.valueOf(attributeName); - @SuppressWarnings("unchecked") - List list = (List) object; - return list.get(key); - } - } catch (NumberFormatException ex) { - // do nothing - } - - } - - /* - * turn args into an array of types and an array of values in order - * to use them for our reflection calls - */ - argumentValues = getArgumentValues(self, context); - Class[] argumentTypes = new Class[argumentValues.length]; - - for (int i = 0; i < argumentValues.length; i++) { - argumentTypes[i] = argumentValues[i].getClass(); - } - - member = reflect(object, attributeName, argumentTypes); - if (member != null) { - memberCache.put(object.getClass(), member); - } - - } - - if (object != null && member != null) { - if (argumentValues == null) { - argumentValues = getArgumentValues(self, context); - } - result = invokeMember(object, member, argumentValues); - } else if (context.isStrictVariables()) { - if (object == null) { - final String rootPropertyName = ((ContextVariableExpression) node).getName(); - throw new RootAttributeNotFoundException(null, - String.format( - "Root attribute [%s] does not exist or can not be accessed and strict variables is set to true.", - rootPropertyName)); - } else { - throw new AttributeNotFoundException(null, - String.format( - "Attribute [%s] of [%s] does not exist or can not be accessed and strict variables is set to true.", - attributeName, object.getClass().getName())); - } - } - return result; - - } - - /** - * Invoke the "Member" that was found via reflection. - * - * @param object - * @param member - * @param argumentValues - * @return - */ - private Object invokeMember(Object object, Member member, Object[] argumentValues) { - Object result = null; - try { - if (member != null) { - - if (member instanceof Method) { - result = ((Method) member).invoke(object, argumentValues); - } else if (member instanceof Field) { - result = ((Field) member).get(object); - } - } - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - e.printStackTrace(); - - } - return result; - } - - /** - * Fully evaluates the individual arguments. - * - * @param self - * @param context - * @return - * @throws PebbleException - */ - private Object[] getArgumentValues(PebbleTemplateImpl self, EvaluationContext context) throws PebbleException { - - Object[] argumentValues; - - if (this.args == null) { - argumentValues = new Object[0]; - } else { - List args = this.args.getPositionalArgs(); - - argumentValues = new Object[args.size()]; - - int index = 0; - for (PositionalArgumentNode arg : args) { - Object argumentValue = arg.getValueExpression().evaluate(self, context); - argumentValues[index] = argumentValue; - index++; - } - } - return argumentValues; - } - - /** - * Performs the actual reflection to obtain a "Member" from a class. - * - * @param object - * @param attributeName - * @param parameterTypes - * @return - */ - private Member reflect(Object object, String attributeName, Class[] parameterTypes) { - - Class clazz = object.getClass(); - - boolean found = false; - Member result = null; - - // capitalize first letter of attribute for the following attempts - String attributeCapitalized = Character.toUpperCase(attributeName.charAt(0)) + attributeName.substring(1); - - // try { - - // check get method - if (!found) { - try { - result = clazz.getMethod("get" + attributeCapitalized, parameterTypes); - found = true; - } catch (NoSuchMethodException | SecurityException e) { - } - } - - // check is method - if (!found) { - try { - result = clazz.getMethod("is" + attributeCapitalized, parameterTypes); - found = true; - } catch (NoSuchMethodException | SecurityException e) { - } - } - - // check has method - if (!found) { - try { - result = clazz.getMethod("has" + attributeCapitalized, parameterTypes); - found = true; - } catch (NoSuchMethodException | SecurityException e) { - } - } - - // check if attribute is a public method - if (!found) { - try { - result = clazz.getMethod(attributeName, parameterTypes); - found = true; - } catch (NoSuchMethodException | SecurityException e) { - } - } - - // public field - if (!found) { - try { - result = clazz.getField(attributeName); - found = true; - } catch (NoSuchFieldException | SecurityException e) { - } - } - - if (result != null) { - ((AccessibleObject) result).setAccessible(true); - } - - return result; - } - - @Override - public void accept(NodeVisitor visitor) { - visitor.visit(this); - } - - public Expression getNode() { - return node; - } - - public String getAttribute() { - return attributeName; - } - - public ArgumentsNode getArgumentsNode() { - return args; - } + private final Expression node; + + private final String attributeName; + + private final ArgumentsNode args; + + /** + * Potentially cached on first evaluation. + */ + private final ConcurrentHashMap, Member> memberCache; + + public GetAttributeExpression(Expression node, String attributeName) { + this(node, attributeName, null); + } + + public GetAttributeExpression(Expression node, String attributeName, ArgumentsNode args) { + this.node = node; + this.attributeName = attributeName; + this.args = args; + + /* + * I dont imagine that users will often give different types to the same + * template so we will give this cache a pretty small initial capacity. + */ + this.memberCache = new ConcurrentHashMap<>(2, 0.9f, 1); + } + + @Override + public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throws PebbleException { + Object object = node.evaluate(self, context); + + Object result = null; + + Object[] argumentValues = null; + + Member member = object == null ? null : memberCache.get(object.getClass()); + + if (object != null && member == null) { + + /* + * If, and only if, no arguments were provided does it make sense to + * check maps/arrays/lists + */ + if (args == null) { + + // first we check maps + if (object instanceof Map && ((Map) object).containsKey(attributeName)) { + return ((Map) object).get(attributeName); + } + + try { + + // then we check arrays + if (object instanceof Object[]) { + Integer key = Integer.valueOf(attributeName); + Object[] arr = ((Object[]) object); + return arr[key]; + } + + // then lists + if (object instanceof List) { + Integer key = Integer.valueOf(attributeName); + @SuppressWarnings("unchecked") + List list = (List) object; + return list.get(key); + } + } catch (NumberFormatException ex) { + // do nothing + } + + } + + /* + * turn args into an array of types and an array of values in order + * to use them for our reflection calls + */ + argumentValues = getArgumentValues(self, context); + Class[] argumentTypes = new Class[argumentValues.length]; + + for (int i = 0; i < argumentValues.length; i++) { + argumentTypes[i] = argumentValues[i].getClass(); + } + + member = reflect(object, attributeName, argumentTypes); + if (member != null) { + memberCache.put(object.getClass(), member); + } + + } + + if (object != null && member != null) { + if (argumentValues == null) { + argumentValues = getArgumentValues(self, context); + } + result = invokeMember(object, member, argumentValues); + } else if (context.isStrictVariables()) { + if (object == null) { + final String rootPropertyName = ((ContextVariableExpression) node).getName(); + throw new RootAttributeNotFoundException(null, + String.format( + "Root attribute [%s] does not exist or can not be accessed and strict variables is set to true.", + rootPropertyName), + rootPropertyName); + } else { + throw new AttributeNotFoundException(null, + String.format( + "Attribute [%s] of [%s] does not exist or can not be accessed and strict variables is set to true.", + attributeName, object.getClass().getName()), + attributeName); + } + } + return result; + + } + + /** + * Invoke the "Member" that was found via reflection. + * + * @param object + * @param member + * @param argumentValues + * @return + */ + private Object invokeMember(Object object, Member member, Object[] argumentValues) { + Object result = null; + try { + if (member != null) { + + if (member instanceof Method) { + result = ((Method) member).invoke(object, argumentValues); + } else if (member instanceof Field) { + result = ((Field) member).get(object); + } + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + e.printStackTrace(); + + } + return result; + } + + /** + * Fully evaluates the individual arguments. + * + * @param self + * @param context + * @return + * @throws PebbleException + */ + private Object[] getArgumentValues(PebbleTemplateImpl self, EvaluationContext context) throws PebbleException { + + Object[] argumentValues; + + if (this.args == null) { + argumentValues = new Object[0]; + } else { + List args = this.args.getPositionalArgs(); + + argumentValues = new Object[args.size()]; + + int index = 0; + for (PositionalArgumentNode arg : args) { + Object argumentValue = arg.getValueExpression().evaluate(self, context); + argumentValues[index] = argumentValue; + index++; + } + } + return argumentValues; + } + + /** + * Performs the actual reflection to obtain a "Member" from a class. + * + * @param object + * @param attributeName + * @param parameterTypes + * @return + */ + private Member reflect(Object object, String attributeName, Class[] parameterTypes) { + + Class clazz = object.getClass(); + + boolean found = false; + Member result = null; + + // capitalize first letter of attribute for the following attempts + String attributeCapitalized = Character.toUpperCase(attributeName.charAt(0)) + attributeName.substring(1); + + // try { + + // check get method + if (!found) { + try { + result = clazz.getMethod("get" + attributeCapitalized, parameterTypes); + found = true; + } catch (NoSuchMethodException | SecurityException e) { + } + } + + // check is method + if (!found) { + try { + result = clazz.getMethod("is" + attributeCapitalized, parameterTypes); + found = true; + } catch (NoSuchMethodException | SecurityException e) { + } + } + + // check has method + if (!found) { + try { + result = clazz.getMethod("has" + attributeCapitalized, parameterTypes); + found = true; + } catch (NoSuchMethodException | SecurityException e) { + } + } + + // check if attribute is a public method + if (!found) { + try { + result = clazz.getMethod(attributeName, parameterTypes); + found = true; + } catch (NoSuchMethodException | SecurityException e) { + } + } + + // public field + if (!found) { + try { + result = clazz.getField(attributeName); + found = true; + } catch (NoSuchFieldException | SecurityException e) { + } + } + + if (result != null) { + ((AccessibleObject) result).setAccessible(true); + } + + return result; + } + + @Override + public void accept(NodeVisitor visitor) { + visitor.visit(this); + } + + public Expression getNode() { + return node; + } + + public String getAttribute() { + return attributeName; + } + + public ArgumentsNode getArgumentsNode() { + return args; + } } diff --git a/src/test/java/com/mitchellbosecke/pebble/ErrorReportingTest.java b/src/test/java/com/mitchellbosecke/pebble/ErrorReportingTest.java index c06ef0936..eb0ae0f83 100644 --- a/src/test/java/com/mitchellbosecke/pebble/ErrorReportingTest.java +++ b/src/test/java/com/mitchellbosecke/pebble/ErrorReportingTest.java @@ -64,21 +64,21 @@ public void testLineNumberErrorReportingDuringEvaluation() throws PebbleExceptio throw ex; } } - + @Test(expected = RootAttributeNotFoundException.class) public void testMissingRootPropertyInStrictMode() throws PebbleException, IOException { try { - pebble.setStrictVariables(true); + pebble.setStrictVariables(true); PebbleTemplate template = pebble.getTemplate("template.errorReportingMissingRootProperty.peb"); template.evaluate(new StringWriter()); } catch (PebbleException ex) { String message = ex.getMessage(); - assertEquals(message, "Root attribute [root] does not exist or can not be accessed and strict variables is set to true. (?:?)"); + assertEquals("root", ((RootAttributeNotFoundException) ex).getAttributeName()); + assertEquals(message, + "Root attribute [root] does not exist or can not be accessed and strict variables is set to true. (?:?)"); throw ex; - } - finally - { - pebble.setStrictVariables(false); + } finally { + pebble.setStrictVariables(false); } } diff --git a/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java b/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java index 109909a4e..46e87a619 100644 --- a/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java +++ b/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java @@ -29,464 +29,464 @@ public class GetAttributeTest extends AbstractTest { - @Test - public void testOneLayerAttributeNesting() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - Map context = new HashMap<>(); - context.put("object", new SimpleObject()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello Steve", writer.toString()); - } - - @Test - public void testAttributeCacheHitting() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}{{ object.name }}"); - Map context = new HashMap<>(); - context.put("object", new SimpleObject()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - } - - @Test - public void testMultiLayerAttributeNesting() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.simpleObject2.simpleObject.name }}"); - Map context = new HashMap<>(); - context.put("object", new SimpleObject3()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello Steve", writer.toString()); - } - - @Test - public void testHashmapAttribute() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - Map context = new HashMap<>(); - Map map = new HashMap<>(); - map.put("name", "Steve"); - context.put("object", map); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello Steve", writer.toString()); - } - - @Test - public void testMethodAttribute() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - Map context = new HashMap<>(); - context.put("object", new SimpleObject4()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello Steve", writer.toString()); - } - - /** - * The GetAttribute expression involves caching, we test with different - * objects to make sure that the caching doesnt have any negative side - * effects. - * - * @throws PebbleException - * @throws IOException - */ - @Test - public void testMethodAttributeWithDifferentObjects() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - - Map context1 = new HashMap<>(); - context1.put("object", new CustomizableObject("Alex")); - Writer writer1 = new StringWriter(); - template.evaluate(writer1, context1); - assertEquals("hello Alex", writer1.toString()); - - Map context2 = new HashMap<>(); - context2.put("object", new CustomizableObject("Steve")); - Writer writer2 = new StringWriter(); - template.evaluate(writer2, context2); - assertEquals("hello Steve", writer2.toString()); - } - - @Test - public void testBeanMethodWithArgument() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name('Steve') }}"); - Map context = new HashMap<>(); - context.put("object", new BeanWithMethodsThatHaveArguments()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello Steve", writer.toString()); - } - - @Test - public void testBeanMethodWithLongArgument() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.number(2) }}"); - Map context = new HashMap<>(); - context.put("object", new BeanWithMethodsThatHaveArguments()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello 2", writer.toString()); - } - - @Test - public void testBeanMethodWithOverloadedArgument() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.number(2.0) }}"); - Map context = new HashMap<>(); - context.put("object", new BeanWithMethodsThatHaveArguments()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello 4.0", writer.toString()); - } - - @Test - public void testBeanMethodWithTwoArguments() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.multiply(2, 3) }}"); - Map context = new HashMap<>(); - context.put("object", new BeanWithMethodsThatHaveArguments()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello 6", writer.toString()); - } - - @Test - public void testGetMethodAttribute() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - Map context = new HashMap<>(); - context.put("object", new SimpleObject5()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello Steve", writer.toString()); - } - - @Test - public void testHasMethodAttribute() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - Map context = new HashMap<>(); - context.put("object", new SimpleObject9()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello Steve", writer.toString()); - } - - @Test - public void testIsMethodAttribute() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - Map context = new HashMap<>(); - context.put("object", new SimpleObject6()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello Steve", writer.toString()); - } - - @Test - public void testComplexNestedAttributes() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - - String source = - "hello {{ object.map.SimpleObject2.simpleObject.name }}. My name is {{ object.map.SimpleObject6.name }}."; - PebbleTemplate template = pebble.getTemplate(source); - Map context = new HashMap<>(); - context.put("object", new ComplexObject()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello Steve. My name is Steve.", writer.toString()); - } - - @Test(expected = RootAttributeNotFoundException.class) - public void testAttributeOfNullObjectWithStrictVariables() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - - Writer writer = new StringWriter(); - template.evaluate(writer); - } - - @Test - public void testAttributeOfNullObjectWithoutStrictVariables() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(false); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - - Map context = new HashMap<>(); - context.put("object", null); - Writer writer = new StringWriter(); - template.evaluate(writer, context); - - assertEquals("hello ", writer.toString()); - } - - @Test - public void testNonExistingAttributeWithoutStrictVariables() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(false); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - Map context = new HashMap<>(); - context.put("object", new Object()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello ", writer.toString()); - } - - @Test(expected = AttributeNotFoundException.class) - public void testNonExistingAttributeWithStrictVariables() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - Map context = new HashMap<>(); - context.put("object", new Object()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello ", writer.toString()); - } - - @Test - public void testNullAttributeWithoutStrictVariables() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - Map context = new HashMap<>(); - context.put("object", new SimpleObject7()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello ", writer.toString()); - - } - - /** - * Should behave the same as it does with strictVariables = false. - * - * @throws PebbleException - * @throws IOException - */ - @Test - public void testNullAttributeWithStrictVariables() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - pebble.setStrictVariables(true); - - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - Map context = new HashMap<>(); - context.put("object", new SimpleObject7()); - - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello ", writer.toString()); - - } - - @Test() - public void testPrimitiveAttribute() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); + @Test + public void testOneLayerAttributeNesting() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + Map context = new HashMap<>(); + context.put("object", new SimpleObject()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello Steve", writer.toString()); + } + + @Test + public void testAttributeCacheHitting() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}{{ object.name }}"); + Map context = new HashMap<>(); + context.put("object", new SimpleObject()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + } + + @Test + public void testMultiLayerAttributeNesting() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.simpleObject2.simpleObject.name }}"); + Map context = new HashMap<>(); + context.put("object", new SimpleObject3()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello Steve", writer.toString()); + } + + @Test + public void testHashmapAttribute() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + Map context = new HashMap<>(); + Map map = new HashMap<>(); + map.put("name", "Steve"); + context.put("object", map); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello Steve", writer.toString()); + } + + @Test + public void testMethodAttribute() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + Map context = new HashMap<>(); + context.put("object", new SimpleObject4()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello Steve", writer.toString()); + } + + /** + * The GetAttribute expression involves caching, we test with different + * objects to make sure that the caching doesnt have any negative side + * effects. + * + * @throws PebbleException + * @throws IOException + */ + @Test + public void testMethodAttributeWithDifferentObjects() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + + Map context1 = new HashMap<>(); + context1.put("object", new CustomizableObject("Alex")); + Writer writer1 = new StringWriter(); + template.evaluate(writer1, context1); + assertEquals("hello Alex", writer1.toString()); + + Map context2 = new HashMap<>(); + context2.put("object", new CustomizableObject("Steve")); + Writer writer2 = new StringWriter(); + template.evaluate(writer2, context2); + assertEquals("hello Steve", writer2.toString()); + } + + @Test + public void testBeanMethodWithArgument() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name('Steve') }}"); + Map context = new HashMap<>(); + context.put("object", new BeanWithMethodsThatHaveArguments()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello Steve", writer.toString()); + } + + @Test + public void testBeanMethodWithLongArgument() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.number(2) }}"); + Map context = new HashMap<>(); + context.put("object", new BeanWithMethodsThatHaveArguments()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello 2", writer.toString()); + } + + @Test + public void testBeanMethodWithOverloadedArgument() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.number(2.0) }}"); + Map context = new HashMap<>(); + context.put("object", new BeanWithMethodsThatHaveArguments()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello 4.0", writer.toString()); + } + + @Test + public void testBeanMethodWithTwoArguments() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.multiply(2, 3) }}"); + Map context = new HashMap<>(); + context.put("object", new BeanWithMethodsThatHaveArguments()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello 6", writer.toString()); + } + + @Test + public void testGetMethodAttribute() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + Map context = new HashMap<>(); + context.put("object", new SimpleObject5()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello Steve", writer.toString()); + } + + @Test + public void testHasMethodAttribute() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + Map context = new HashMap<>(); + context.put("object", new SimpleObject9()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello Steve", writer.toString()); + } + + @Test + public void testIsMethodAttribute() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + Map context = new HashMap<>(); + context.put("object", new SimpleObject6()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello Steve", writer.toString()); + } + + @Test + public void testComplexNestedAttributes() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + + String source = "hello {{ object.map.SimpleObject2.simpleObject.name }}. My name is {{ object.map.SimpleObject6.name }}."; + PebbleTemplate template = pebble.getTemplate(source); + Map context = new HashMap<>(); + context.put("object", new ComplexObject()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello Steve. My name is Steve.", writer.toString()); + } + + @Test(expected = RootAttributeNotFoundException.class) + public void testAttributeOfNullObjectWithStrictVariables() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + + Writer writer = new StringWriter(); + template.evaluate(writer); + } + + @Test + public void testAttributeOfNullObjectWithoutStrictVariables() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(false); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + + Map context = new HashMap<>(); + context.put("object", null); + Writer writer = new StringWriter(); + template.evaluate(writer, context); + + assertEquals("hello ", writer.toString()); + } + + @Test + public void testNonExistingAttributeWithoutStrictVariables() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(false); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + Map context = new HashMap<>(); + context.put("object", new Object()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello ", writer.toString()); + } + + @Test(expected = AttributeNotFoundException.class) + public void testNonExistingAttributeWithStrictVariables() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + Map context = new HashMap<>(); + context.put("object", new Object()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello ", writer.toString()); + } + + @Test + public void testNullAttributeWithoutStrictVariables() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + Map context = new HashMap<>(); + context.put("object", new SimpleObject7()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello ", writer.toString()); + + } + + /** + * Should behave the same as it does with strictVariables = false. + * + * @throws PebbleException + * @throws IOException + */ + @Test + public void testNullAttributeWithStrictVariables() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + pebble.setStrictVariables(true); + + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + Map context = new HashMap<>(); + context.put("object", new SimpleObject7()); + + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello ", writer.toString()); + + } + + @Test() + public void testPrimitiveAttribute() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); - PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); - Map context = new HashMap<>(); - context.put("object", new SimpleObject8()); + PebbleTemplate template = pebble.getTemplate("hello {{ object.name }}"); + Map context = new HashMap<>(); + context.put("object", new SimpleObject8()); - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("hello true", writer.toString()); - } - - @Test - public void testArrayIndexAttribute() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); - - PebbleTemplate template = pebble.getTemplate("{{ arr[2] }}"); - Map context = new HashMap<>(); - String[] data = new String[3]; - data[0] = "Zero"; - data[1] = "One"; - data[2] = "Two"; - context.put("arr", data); + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("hello true", writer.toString()); + } + + @Test + public void testArrayIndexAttribute() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); + + PebbleTemplate template = pebble.getTemplate("{{ arr[2] }}"); + Map context = new HashMap<>(); + String[] data = new String[3]; + data[0] = "Zero"; + data[1] = "One"; + data[2] = "Two"; + context.put("arr", data); - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("Two", writer.toString()); - } + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("Two", writer.toString()); + } - @Test - public void testListIndexAttribute() throws PebbleException, IOException { - Loader stringLoader = new StringLoader(); - PebbleEngine pebble = new PebbleEngine(stringLoader); + @Test + public void testListIndexAttribute() throws PebbleException, IOException { + Loader stringLoader = new StringLoader(); + PebbleEngine pebble = new PebbleEngine(stringLoader); - PebbleTemplate template = pebble.getTemplate("{{ arr[2] }}"); - Map context = new HashMap<>(); - List data = new ArrayList<>(); - data.add("Zero"); - data.add("One"); - data.add("Two"); - context.put("arr", data); + PebbleTemplate template = pebble.getTemplate("{{ arr[2] }}"); + Map context = new HashMap<>(); + List data = new ArrayList<>(); + data.add("Zero"); + data.add("One"); + data.add("Two"); + context.put("arr", data); - Writer writer = new StringWriter(); - template.evaluate(writer, context); - assertEquals("Two", writer.toString()); - } + Writer writer = new StringWriter(); + template.evaluate(writer, context); + assertEquals("Two", writer.toString()); + } - public class SimpleObject { + public class SimpleObject { - public final String name = "Steve"; - } + public final String name = "Steve"; + } - public class SimpleObject2 { + public class SimpleObject2 { - public final SimpleObject simpleObject = new SimpleObject(); - } + public final SimpleObject simpleObject = new SimpleObject(); + } - public class SimpleObject3 { + public class SimpleObject3 { - public final SimpleObject2 simpleObject2 = new SimpleObject2(); - } + public final SimpleObject2 simpleObject2 = new SimpleObject2(); + } - public class SimpleObject4 { + public class SimpleObject4 { - public String name() { - return "Steve"; - } - } + public String name() { + return "Steve"; + } + } - public class SimpleObject5 { + public class SimpleObject5 { - public String getName() { - return "Steve"; - } - } + public String getName() { + return "Steve"; + } + } - public class SimpleObject6 { + public class SimpleObject6 { - public String isName() { - return "Steve"; - } - } + public String isName() { + return "Steve"; + } + } - public class SimpleObject7 { + public class SimpleObject7 { - public String name = null; - } + public String name = null; + } - public class SimpleObject8 { + public class SimpleObject8 { - public boolean name = true; - } + public boolean name = true; + } - public class SimpleObject9 { + public class SimpleObject9 { - public String hasName() { - return "Steve"; - } - } + public String hasName() { + return "Steve"; + } + } - public class BeanWithMethodsThatHaveArguments { + public class BeanWithMethodsThatHaveArguments { - public String getName(String name) { - return name; - } + public String getName(String name) { + return name; + } - public Double getNumber(Double number) { - return number * 2; - } + public Double getNumber(Double number) { + return number * 2; + } - public Long getNumber(Long number) { - return number; - } + public Long getNumber(Long number) { + return number; + } - public Long multiply(Long one, Long two) { - return one * two; - } - } + public Long multiply(Long one, Long two) { + return one * two; + } + } - public class ComplexObject { + public class ComplexObject { - public final Map map = new HashMap<>(); + public final Map map = new HashMap<>(); - { - map.put("SimpleObject2", new SimpleObject2()); - map.put("SimpleObject6", new SimpleObject6()); - } - } + { + map.put("SimpleObject2", new SimpleObject2()); + map.put("SimpleObject6", new SimpleObject6()); + } + } - public class CustomizableObject { - private final String name; + public class CustomizableObject { - public CustomizableObject(String name) { - this.name = name; - } + private final String name; - public String getName() { - return this.name; - } - } + public CustomizableObject(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + } }