diff --git a/CHANGELOG.md b/CHANGELOG.md index 86b02fc13..49d0b52a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,97 +1,106 @@ -## v1.0.0 -- Some code cleanup and fixed an incorrect unit test - -## v0.4.0-beta -- No more code generation, all nodes of the AST are rendered during template evaluation phase. - -## v0.3.0-beta -- Autoescaping, more escaping strategies, autoescape tag, and raw filter. -- Extensions can now provide node visitors to traverse the AST. -- Macros can have default argument values. -- Implemented dynamic inheritance. -- Renamed 'message' function to 'i18n' -- Fixed issue where compilation failed in JBoss. -- Code cleanup and misc small bugs - -## v0.2.0-beta (2014-02-08) -- Implemented named arguments. -- Added dependency on google guava for template cache. -- Split the default loader class into multiple discrete loaders. -- Added the `title` filter. -- Fixed issue where compilation mutex might not have been released. -- Fixed parsing issues if variable names were prefixed with operator names. -- Fixed issue where included templates didn't have access to context. -- Fixed issue where `if` tag could not be used directly on a boolean variable. -- Removed the `format` filter. -- Fixed misc other smaller bugs. - -## v0.1.5-beta (2014-01-27) -- Fixed major bug from v0.1.4 that prevented macros from being invoked more than once. - -## v0.1.4-beta (2014-01-27) -- The i18n extension is now enabled by default. -- Improved exception handling (storing cause where applicable). -- PebbleEngine now returns a PebbleTemplate interface with a small subset of original methods. -- Refactored function/filter/test interfaces into functional interfaces (preparation for Java 8). - -## v0.1.3-beta (2014-01-25) -- More unit tests and minor bug fixes. -- Fixed issue where child templates were being inappropriately cached. -- All core filters now perform null checking. -- Performance optimization with variable attributes. -- Renamed the number_format filter to numberformat - -## v0.1.2-beta (2014-01-19) -- Fixed issue where parent block didn’t have access to context. -- Macros no longer have access to context (only local vars). -- Fixed issue where macro output coudn’t be filtered/tested. -- Refactored how blocks and macros are implemented . -- Renamed number filter to number_format. -- Added a cache interface for user’s to provide their own cache. Also removed the “cacheTemplates” setting. -- Default cache is now thread safe. -- Templates can now be evaluated concurrently. -- Users can now safely attempt a concurrent compilation. -- Fixed issue where provided writer was being closed by pebble engine. -- Fixed memory leak in file manager. -- Removed json filter. -- Removed some third party dependencies. -- Added parallel tag. -- More unit tests and misc code cleanup. - -## v0.1.1-beta (2014-01-02) -- Fixed issue where templates of same name but different path were overriding each other in main template cache. -- Made sure byte code stored in memory in InMemoryJavaFileManager is cleared when no longer required. -- Removed caching of Reader objects from PebbleDefaultLoader which was causing more harm than good. This can be added back later if it is deemed necessary. -- Completely changed how operators are compiled into Java due to a bunch of bugs regarding operand types. -- Changed the behaviour of the == operator and added the equals operator as an alias. -- Extensions can now provide custom functions. -- Added source, min, and max functions. -- The setting, cacheTemplates, now defaults to true. -- Renamed the main entry points into the main Engine from “loadTemplate/render” to “compile/evaluate”. -- Added i18n extension (disabled by default) and a default locale setting on the main pebble engine. The extension adds one new function: message() -- Small performance improvements when looking up variable attributes. - -## v0.1.0-beta (2013-12-27) -- Refined PebbleEngine’s available public methods. -- Added “strictVariables” setting to PebbleEngine. -- Cleaned up how pebble-spring is to be configured. -- More bug fixes and unit tests. - -## v0.0.3-alpha (2013-11-17) -- Configuration changes in order to have the project hosted in the Maven Central Repository. - -## v0.0.2-alpha (2013-11-16) -- Dedicated website with documentation. -- Code refactoring, more unit tests, bug fixes. -- Conditional (ternary) operator. -- Escape filter. -- Macro overloading. - -## v0.0.1-alpha (2013-09-30) -This is the first functioning version of Pebble. The following has been implemented: -- tags: block, extends, for, if, import, include, macro, set -- filters: abbreviate, capitalize, date, default, format, json, lower, number, trim, upper, urlencode -- functions: block, parent -- tests: empty, even, null, odd, iterable, equalTo -- operators: in, is, is not, +, -, /, *, %, and, or, (), ==, !=, <, >, <=, >=, |, . -- unit tests +## v1.1.0 +- The ability to call bean methods that require arguments. +- For loop now works with primitive arrays (i.e. no longer just Iterable objects). +- Added "subscript syntax" support for accessing attributes. +- Continuous integration with travis-ci. +- Fixed NPE occurring in ternary expressions. +- Fixed issue with if-then-else expressions +- General code and testing improvements. + +## v1.0.0 +- Some code cleanup and fixed an incorrect unit test. + +## v0.4.0-beta +- No more code generation, all nodes of the AST are rendered during template evaluation phase. + +## v0.3.0-beta +- Autoescaping, more escaping strategies, autoescape tag, and raw filter. +- Extensions can now provide node visitors to traverse the AST. +- Macros can have default argument values. +- Implemented dynamic inheritance. +- Renamed 'message' function to 'i18n' +- Fixed issue where compilation failed in JBoss. +- Code cleanup and misc small bugs + +## v0.2.0-beta (2014-02-08) +- Implemented named arguments. +- Added dependency on google guava for template cache. +- Split the default loader class into multiple discrete loaders. +- Added the `title` filter. +- Fixed issue where compilation mutex might not have been released. +- Fixed parsing issues if variable names were prefixed with operator names. +- Fixed issue where included templates didn't have access to context. +- Fixed issue where `if` tag could not be used directly on a boolean variable. +- Removed the `format` filter. +- Fixed misc other smaller bugs. + +## v0.1.5-beta (2014-01-27) +- Fixed major bug from v0.1.4 that prevented macros from being invoked more than once. + +## v0.1.4-beta (2014-01-27) +- The i18n extension is now enabled by default. +- Improved exception handling (storing cause where applicable). +- PebbleEngine now returns a PebbleTemplate interface with a small subset of original methods. +- Refactored function/filter/test interfaces into functional interfaces (preparation for Java 8). + +## v0.1.3-beta (2014-01-25) +- More unit tests and minor bug fixes. +- Fixed issue where child templates were being inappropriately cached. +- All core filters now perform null checking. +- Performance optimization with variable attributes. +- Renamed the number_format filter to numberformat + +## v0.1.2-beta (2014-01-19) +- Fixed issue where parent block didn’t have access to context. +- Macros no longer have access to context (only local vars). +- Fixed issue where macro output coudn’t be filtered/tested. +- Refactored how blocks and macros are implemented . +- Renamed number filter to number_format. +- Added a cache interface for user’s to provide their own cache. Also removed the “cacheTemplates” setting. +- Default cache is now thread safe. +- Templates can now be evaluated concurrently. +- Users can now safely attempt a concurrent compilation. +- Fixed issue where provided writer was being closed by pebble engine. +- Fixed memory leak in file manager. +- Removed json filter. +- Removed some third party dependencies. +- Added parallel tag. +- More unit tests and misc code cleanup. + +## v0.1.1-beta (2014-01-02) +- Fixed issue where templates of same name but different path were overriding each other in main template cache. +- Made sure byte code stored in memory in InMemoryJavaFileManager is cleared when no longer required. +- Removed caching of Reader objects from PebbleDefaultLoader which was causing more harm than good. This can be added back later if it is deemed necessary. +- Completely changed how operators are compiled into Java due to a bunch of bugs regarding operand types. +- Changed the behaviour of the == operator and added the equals operator as an alias. +- Extensions can now provide custom functions. +- Added source, min, and max functions. +- The setting, cacheTemplates, now defaults to true. +- Renamed the main entry points into the main Engine from “loadTemplate/render” to “compile/evaluate”. +- Added i18n extension (disabled by default) and a default locale setting on the main pebble engine. The extension adds one new function: message() +- Small performance improvements when looking up variable attributes. + +## v0.1.0-beta (2013-12-27) +- Refined PebbleEngine’s available public methods. +- Added “strictVariables” setting to PebbleEngine. +- Cleaned up how pebble-spring is to be configured. +- More bug fixes and unit tests. + +## v0.0.3-alpha (2013-11-17) +- Configuration changes in order to have the project hosted in the Maven Central Repository. + +## v0.0.2-alpha (2013-11-16) +- Dedicated website with documentation. +- Code refactoring, more unit tests, bug fixes. +- Conditional (ternary) operator. +- Escape filter. +- Macro overloading. + +## v0.0.1-alpha (2013-09-30) +This is the first functioning version of Pebble. The following has been implemented: +- tags: block, extends, for, if, import, include, macro, set +- filters: abbreviate, capitalize, date, default, format, json, lower, number, trim, upper, urlencode +- functions: block, parent +- tests: empty, even, null, odd, iterable, equalTo +- operators: in, is, is not, +, -, /, *, %, and, or, (), ==, !=, <, >, <=, >=, |, . +- unit tests 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 1770ce9da..255883c4c 100644 --- a/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java +++ b/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java @@ -1,197 +1,197 @@ -/******************************************************************************* - * This file is part of Pebble. - * - * Copyright (c) 2014 by Mitchell Bösecke - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - ******************************************************************************/ -package com.mitchellbosecke.pebble.node.expression; - -import java.lang.reflect.AccessibleObject; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import com.mitchellbosecke.pebble.error.AttributeNotFoundException; -import com.mitchellbosecke.pebble.error.PebbleException; -import com.mitchellbosecke.pebble.extension.NodeVisitor; -import com.mitchellbosecke.pebble.node.ArgumentsNode; -import com.mitchellbosecke.pebble.node.PositionalArgumentNode; -import com.mitchellbosecke.pebble.template.EvaluationContext; -import com.mitchellbosecke.pebble.template.PebbleTemplateImpl; - -/** - * Used to get an attribute from an object. It will look up attributes in the - * following order: map entry, get method, is method, has method, public method, - * public field. It current only supports zero-argument methods. - * - * @author Mitchell - * - */ -public class GetAttributeExpression implements Expression { - - private final Expression node; - private final String attributeName; - private final ArgumentsNode args; - - 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; - } - - @Override - public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throws PebbleException { - Object object = node.evaluate(self, context); - - Object result = null; - boolean found = false; - - if (object != null) { - - // first we check maps, as they are a bit of an exception - if (args == null) { - if (object instanceof Map && ((Map) object).containsKey(attributeName)) { - result = ((Map) object).get(attributeName); - found = true; - } - } - - if (!found) { - - /* - * turn args into an array of types and an array of values in - * order to use them for our reflection calls - */ - List> parameterTypes = new ArrayList<>(); - List parameterValues = new ArrayList<>(); - if (this.args != null) { - for (PositionalArgumentNode arg : this.args.getPositionalArgs()) { - Object parameterValue = arg.getValueExpression().evaluate(self, context); - parameterTypes.add(parameterValue.getClass()); - parameterValues.add(parameterValue); - } - } - Class[] parameterTypesArray = parameterTypes.toArray(new Class[parameterTypes.size()]); - Object[] parameterValuesArray = parameterValues.toArray(new Object[parameterValues.size()]); - - Member member = null; - try { - member = findMember(object, attributeName, parameterTypesArray); - if (member != null) { - - if (member instanceof Method) { - result = ((Method) member).invoke(object, parameterValuesArray); - } else if (member instanceof Field) { - result = ((Field) member).get(object); - } - found = true; - } - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - e.printStackTrace(); - throw new PebbleException(e, "Could not access attribute [" + attributeName + "]"); - } - - } - } - - if (!found && context.isStrictVariables()) { - 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; - - } - - @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 Member findMember(Object object, String attributeName, Class[] parameterTypes) - throws IllegalAccessException { - - 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); - - // 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; - } - -} +/******************************************************************************* + * This file is part of Pebble. + * + * Copyright (c) 2014 by Mitchell Bösecke + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + ******************************************************************************/ +package com.mitchellbosecke.pebble.node.expression; + +import java.lang.reflect.AccessibleObject; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.mitchellbosecke.pebble.error.AttributeNotFoundException; +import com.mitchellbosecke.pebble.error.PebbleException; +import com.mitchellbosecke.pebble.extension.NodeVisitor; +import com.mitchellbosecke.pebble.node.ArgumentsNode; +import com.mitchellbosecke.pebble.node.PositionalArgumentNode; +import com.mitchellbosecke.pebble.template.EvaluationContext; +import com.mitchellbosecke.pebble.template.PebbleTemplateImpl; + +/** + * Used to get an attribute from an object. It will look up attributes in the + * following order: map entry, get method, is method, has method, public method, + * public field. + * + * @author Mitchell + * + */ +public class GetAttributeExpression implements Expression { + + private final Expression node; + private final String attributeName; + private final ArgumentsNode args; + + 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; + } + + @Override + public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throws PebbleException { + Object object = node.evaluate(self, context); + + Object result = null; + boolean found = false; + + if (object != null) { + + // first we check maps, as they are a bit of an exception + if (args == null) { + if (object instanceof Map && ((Map) object).containsKey(attributeName)) { + result = ((Map) object).get(attributeName); + found = true; + } + } + + if (!found) { + + /* + * turn args into an array of types and an array of values in + * order to use them for our reflection calls + */ + List> parameterTypes = new ArrayList<>(); + List parameterValues = new ArrayList<>(); + if (this.args != null) { + for (PositionalArgumentNode arg : this.args.getPositionalArgs()) { + Object parameterValue = arg.getValueExpression().evaluate(self, context); + parameterTypes.add(parameterValue.getClass()); + parameterValues.add(parameterValue); + } + } + Class[] parameterTypesArray = parameterTypes.toArray(new Class[parameterTypes.size()]); + Object[] parameterValuesArray = parameterValues.toArray(new Object[parameterValues.size()]); + + Member member = null; + try { + member = findMember(object, attributeName, parameterTypesArray); + if (member != null) { + + if (member instanceof Method) { + result = ((Method) member).invoke(object, parameterValuesArray); + } else if (member instanceof Field) { + result = ((Field) member).get(object); + } + found = true; + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + e.printStackTrace(); + throw new PebbleException(e, "Could not access attribute [" + attributeName + "]"); + } + + } + } + + if (!found && context.isStrictVariables()) { + 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; + + } + + @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 Member findMember(Object object, String attributeName, Class[] parameterTypes) + throws IllegalAccessException { + + 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); + + // 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; + } + +}