Repository: PebbleTemplates/pebble
Pull request #341 (2018-05-09 11:47:11)

Add options for literal number types & method finding strategy (for issue #268)
changes are:
1. support `L` type suffix for literal number which will be treated as Long, such as '123L'. 
2. Add `literalDecimalTreatedAsInteger` option for toggling to enable/disable literal decimal treated as integer. 
3. Add 'greedyMatchMethod' option for toggling to enable/disable greedy matching mode for finding java method. Reduce the limit of the parameter type, try to find other method which has compatible parameter types.

For issue #268 , there are 2 solutions：
- Set literalDecimalTreatedAsInteger=true, add 'L' suffix for other number which is Long.
- Or set greedyMatchMethod=true 

Plz see the test case for more detail.


Patch:
diff --git a/.travis.yml b/.travis.yml
index b5d7507a..44e588c7 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,4 +10,4 @@ jdk:
 
 after_success:
   - export VERSION=$(echo "cat //*[local-name()='project']/*[local-name()='version']/text()" | xmllint --nocdata --shell pom.xml | sed '1d;$d')
-  - 'if [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [[ "$VERSION" =~ \-SNAPSHOT$ ]]; then mvn --settings travis-settings.xml -DsonatypeUsername=$SONATYPE_USERNAME -DsonatypePassword=$SONATYPE_PASSWORD -DskipTests=true -B deploy; fi'
\ No newline at end of file
+  - 'if [ "$TRAVIS_BRANCH" = "master" ] && [ "$TRAVIS_PULL_REQUEST" = "false" ] && [[ "$VERSION" =~ \-SNAPSHOT$ ]]; then cp ./travis-settings.xml $HOME/.m2/settings.xml && mvn -DskipTests=true -B deploy; fi'
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 952dd4a8..016b0c23 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@
 ## v2.6.0 (xxxx-xx-xx)
 - Add support for 'as' and 'from' in import statement (#271)
 - Add support for spring-boot-2 (#340)
+- Supports empty collection for first filter (#342)
 
 ## v2.5.1 (2018-05-04)
 - Bugfix introduced when accessing getClass which will always return null (#338)
diff --git a/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java b/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java
index 04e4b615..d0aae4a3 100644
--- a/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java
+++ b/src/main/java/com/mitchellbosecke/pebble/PebbleEngine.java
@@ -31,6 +31,8 @@ import com.mitchellbosecke.pebble.loader.Loader;
 import com.mitchellbosecke.pebble.node.RootNode;
 import com.mitchellbosecke.pebble.parser.Parser;
 import com.mitchellbosecke.pebble.parser.ParserImpl;
+import com.mitchellbosecke.pebble.parser.ParserOptions;
+import com.mitchellbosecke.pebble.template.EvaluationOptions;
 import com.mitchellbosecke.pebble.template.PebbleTemplate;
 import com.mitchellbosecke.pebble.template.PebbleTemplateImpl;
 
@@ -67,7 +69,9 @@ public class PebbleEngine {
 
     private final ExtensionRegistry extensionRegistry;
 
-    private final boolean allowGetClass;
+    private final ParserOptions parserOptions;
+
+    private final EvaluationOptions evaluationOptions;
 
     /**
      * Constructor for the Pebble Engine given an instantiated Loader. This
@@ -85,7 +89,8 @@ public class PebbleEngine {
                          Cache<Object, PebbleTemplate> templateCache,
                          ExecutorService executorService,
                          Collection<? extends Extension> extensions,
-                         boolean allowGetClass) {
+                         ParserOptions parserOptions,
+                         EvaluationOptions evaluationOptions) {
 
         this.loader = loader;
         this.syntax = syntax;
@@ -95,7 +100,8 @@ public class PebbleEngine {
         this.executorService = executorService;
         this.templateCache = templateCache;
         this.extensionRegistry = new ExtensionRegistry(extensions);
-        this.allowGetClass = allowGetClass;
+        this.parserOptions = parserOptions;
+        this.evaluationOptions = evaluationOptions;
     }
 
     /**
@@ -135,7 +141,7 @@ public class PebbleEngine {
                     TokenStream tokenStream = lexer.tokenize(templateReader, templateName);
 
                     Parser parser = new ParserImpl(extensionRegistry.getUnaryOperators(),
-                            extensionRegistry.getBinaryOperators(), extensionRegistry.getTokenParsers());
+                            extensionRegistry.getBinaryOperators(), extensionRegistry.getTokenParsers(), parserOptions);
                     RootNode root = parser.parse(tokenStream);
 
                     PebbleTemplateImpl instance = new PebbleTemplateImpl(self, root, templateName);
@@ -252,15 +258,6 @@ public class PebbleEngine {
         return this.tagCache;
     }
 
-    /**
-     * Returns toggle to enable/disable getClass access
-     *
-     * @return toggle to enable/disable getClass access
-     */
-    public boolean isAllowGetClass() {
-        return this.allowGetClass;
-    }
-
     /**
      * A builder to configure and construct an instance of a PebbleEngine.
      */
@@ -290,6 +287,10 @@ public class PebbleEngine {
 
         private boolean allowGetClass = true;
 
+        private boolean literalDecimalTreatedAsInteger = false;
+
+        private boolean greedyMatchMethod = false;
+
         /**
          * Creates the builder.
          */
@@ -480,6 +481,46 @@ public class PebbleEngine {
             return this;
         }
 
+        /**
+         * Enable/disable treat literal decimal as Integer.
+         * Default is disabled, treated as Long.
+         *
+         * @param literalDecimalTreatedAsInteger toggle to enable/disable
+         *            literal decimal treated as integer
+         * @return This builder object
+         */
+        public Builder literalDecimalTreatedAsInteger(boolean literalDecimalTreatedAsInteger) {
+            this.literalDecimalTreatedAsInteger = literalDecimalTreatedAsInteger;
+            return this;
+        }
+
+        /**
+         * Enable/disable greedy matching mode for finding java method. Default is disabled. <br/>
+         * If enabled, <br/>
+         * when can not find perfect method (method name, parameter length and parameter type are all satisfied),
+         * reduce the limit of the parameter type, try to find other method which has compatible parameter types.
+         *
+         * For example,
+         * <pre> {{ obj.number(2) }} </pre>
+         * the expression can match all of below methods.
+         * <pre>
+         *   public Long getNumber(Long v) {...}
+         *   public Integer getNumber(Integer v) {...}
+         *   public Short getNumber(Short v) {...}
+         *   public Byte getNumber(Byte v) {...}
+         *   ...
+         * </pre>
+         *
+         * @see com.mitchellbosecke.pebble.utils.TypeUtils#compatibleCast(Object, Class)
+         * @param greedyMatchMethod toggle to enable/disable
+         *            greedy match method
+         * @return This builder object
+         */
+        public Builder greedyMatchMethod(boolean greedyMatchMethod) {
+            this.greedyMatchMethod = greedyMatchMethod;
+            return this;
+        }
+
         /**
          * Creates the PebbleEngine instance.
          *
@@ -507,7 +548,6 @@ public class PebbleEngine {
                 defaultLocale = Locale.getDefault();
             }
 
-
             if (cacheActive) {
                 // default caches
                 if (templateCache == null) {
@@ -526,8 +566,19 @@ public class PebbleEngine {
                 syntax = new Syntax.Builder().setEnableNewLineTrimming(enableNewLineTrimming).build();
             }
 
+            ParserOptions parserOptions = new ParserOptions();
+            parserOptions.setLiteralDecimalTreatedAsInteger(literalDecimalTreatedAsInteger);
+
+            EvaluationOptions evaluationOptions = new EvaluationOptions();
+            evaluationOptions.setAllowGetClass(allowGetClass);
+            evaluationOptions.setGreedyMatchMethod(greedyMatchMethod);
+
             return new PebbleEngine(loader, syntax, strictVariables, defaultLocale, tagCache, templateCache,
-                    executorService, extensions, allowGetClass);
+                    executorService, extensions, parserOptions, evaluationOptions);
         }
     }
+
+    public EvaluationOptions getEvaluationOptions() {
+        return evaluationOptions;
+    }
 }
diff --git a/src/main/java/com/mitchellbosecke/pebble/extension/core/FirstFilter.java b/src/main/java/com/mitchellbosecke/pebble/extension/core/FirstFilter.java
index e365f5f5..7b03ac6e 100644
--- a/src/main/java/com/mitchellbosecke/pebble/extension/core/FirstFilter.java
+++ b/src/main/java/com/mitchellbosecke/pebble/extension/core/FirstFilter.java
@@ -8,13 +8,14 @@
  ******************************************************************************/
 package com.mitchellbosecke.pebble.extension.core;
 
+import com.mitchellbosecke.pebble.extension.Filter;
+
 import java.lang.reflect.Array;
 import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 
-import com.mitchellbosecke.pebble.extension.Filter;
-
 /**
  * Returns the first element of a collection
  * 
@@ -45,6 +46,11 @@ public class FirstFilter implements Filter {
         }
         
         Collection<?> inputCollection = (Collection<?>) input;
-        return inputCollection.iterator().next();
+
+        Iterator<?> iterator = inputCollection.iterator();
+        if (iterator.hasNext()) {
+            return iterator.next();
+        }
+        return null;
     }
 }
diff --git a/src/main/java/com/mitchellbosecke/pebble/lexer/LexerImpl.java b/src/main/java/com/mitchellbosecke/pebble/lexer/LexerImpl.java
index b45a63b7..a8e09669 100644
--- a/src/main/java/com/mitchellbosecke/pebble/lexer/LexerImpl.java
+++ b/src/main/java/com/mitchellbosecke/pebble/lexer/LexerImpl.java
@@ -90,6 +90,8 @@ public final class LexerImpl implements Lexer {
      */
     private static final Pattern REGEX_NAME = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*");
 
+    private static final Pattern REGEX_LONG = Pattern.compile("^[0-9]+L");
+
     private static final Pattern REGEX_NUMBER = Pattern.compile("^[0-9]+(\\.[0-9]+)?");
 
     // the negative lookbehind assertion is used to ignore escaped quotation
@@ -359,6 +361,15 @@ public final class LexerImpl implements Lexer {
             return;
         }
 
+        // long
+        matcher = REGEX_LONG.matcher(source);
+        if (matcher.lookingAt()) {
+            token = source.substring(matcher.end()-1);
+            pushToken(Token.Type.LONG, token);
+            source.advance(matcher.end());
+            return;
+        }
+
         // numbers
         matcher = REGEX_NUMBER.matcher(source);
         if (matcher.lookingAt()) {
diff --git a/src/main/java/com/mitchellbosecke/pebble/lexer/Token.java b/src/main/java/com/mitchellbosecke/pebble/lexer/Token.java
index 4153ab60..a24828e0 100644
--- a/src/main/java/com/mitchellbosecke/pebble/lexer/Token.java
+++ b/src/main/java/com/mitchellbosecke/pebble/lexer/Token.java
@@ -19,7 +19,7 @@ public class Token {
     private int lineNumber;
 
     public enum Type {
-        EOF, TEXT, EXECUTE_START, EXECUTE_END, PRINT_START, PRINT_END, NAME, NUMBER, STRING, OPERATOR, PUNCTUATION
+        EOF, TEXT, EXECUTE_START, EXECUTE_END, PRINT_START, PRINT_END, NAME, NUMBER, LONG, STRING, OPERATOR, PUNCTUATION
     }
 
     public Token(Type type, String value, int lineNumber) {
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 0c407b53..695658f8 100644
--- a/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java
+++ b/src/main/java/com/mitchellbosecke/pebble/node/expression/GetAttributeExpression.java
@@ -23,8 +23,10 @@ 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.EvaluationOptions;
 import com.mitchellbosecke.pebble.template.MacroAttributeProvider;
 import com.mitchellbosecke.pebble.template.PebbleTemplateImpl;
+import com.mitchellbosecke.pebble.utils.TypeUtils;
 
 import java.lang.reflect.AccessibleObject;
 import java.lang.reflect.Array;
@@ -32,6 +34,7 @@ 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 java.util.concurrent.ConcurrentHashMap;
@@ -180,7 +183,7 @@ public class GetAttributeExpression implements Expression<Object> {
                 }
             }
 
-            member = this.reflect(object, attributeName, argumentTypes, context.isAllowGetClass());
+            member = this.reflect(object, attributeName, argumentTypes, context.getEvaluationOptions());
             if (member != null) {
                 this.memberCache.put(new MemberCacheKey(object.getClass(), attributeName), member);
             }
@@ -255,6 +258,7 @@ public class GetAttributeExpression implements Expression<Object> {
         Object result = null;
         try {
             if (member instanceof Method) {
+                argumentValues = TypeUtils.compatibleCast(argumentValues, ((Method) member).getParameterTypes());
                 result = ((Method) member).invoke(object, argumentValues);
             } else if (member instanceof Field) {
                 result = ((Field) member).get(object);
@@ -302,7 +306,7 @@ public class GetAttributeExpression implements Expression<Object> {
      * @param parameterTypes
      * @return
      */
-    private Member reflect(Object object, String attributeName, Class<?>[] parameterTypes, boolean allowGetClass) {
+    private Member reflect(Object object, String attributeName, Class<?>[] parameterTypes, EvaluationOptions evaluationOptions) {
 
         Class<?> clazz = object.getClass();
 
@@ -312,21 +316,21 @@ public class GetAttributeExpression implements Expression<Object> {
         String attributeCapitalized = Character.toUpperCase(attributeName.charAt(0)) + attributeName.substring(1);
 
         // check get method
-        result = this.findMethod(clazz, "get" + attributeCapitalized, parameterTypes, allowGetClass);
+        result = this.findMethod(clazz, "get" + attributeCapitalized, parameterTypes, evaluationOptions);
 
         // check is method
         if (result == null) {
-            result = this.findMethod(clazz, "is" + attributeCapitalized, parameterTypes, allowGetClass);
+            result = this.findMethod(clazz, "is" + attributeCapitalized, parameterTypes, evaluationOptions);
         }
 
         // check has method
         if (result == null) {
-            result = this.findMethod(clazz, "has" + attributeCapitalized, parameterTypes, allowGetClass);
+            result = this.findMethod(clazz, "has" + attributeCapitalized, parameterTypes, evaluationOptions);
         }
 
         // check if attribute is a public method
         if (result == null) {
-            result = this.findMethod(clazz, attributeName, parameterTypes, allowGetClass);
+            result = this.findMethod(clazz, attributeName, parameterTypes, evaluationOptions);
         }
 
         // public field
@@ -353,27 +357,17 @@ public class GetAttributeExpression implements Expression<Object> {
      * @param requiredTypes
      * @return
      */
-    private Method findMethod(Class<?> clazz, String name, Class<?>[] requiredTypes, boolean allowGetClass) {
-        if (!allowGetClass && name.equals("getClass")) {
+    private Method findMethod(Class<?> clazz, String name, Class<?>[] requiredTypes, EvaluationOptions evaluationOptions) {
+        if (!evaluationOptions.isAllowGetClass() && name.equals("getClass")) {
             throw new ClassAccessException(this.lineNumber, this.filename);
         }
 
-        Method result = null;
-
-        Method[] candidates = clazz.getMethods();
+        List<Method> candidates = getCandidates(clazz, name, requiredTypes);
 
+        // perfect match
         for (Method candidate : candidates) {
-            if (!candidate.getName().equalsIgnoreCase(name)) {
-                continue;
-            }
-
-            Class<?>[] types = candidate.getParameterTypes();
-
-            if (types.length != requiredTypes.length) {
-                continue;
-            }
-
             boolean compatibleTypes = true;
+            Class<?>[] types = candidate.getParameterTypes();
             for (int i = 0; i < types.length; i++) {
                 if (requiredTypes[i] != null && !this.widen(types[i]).isAssignableFrom(requiredTypes[i])) {
                     compatibleTypes = false;
@@ -382,11 +376,29 @@ public class GetAttributeExpression implements Expression<Object> {
             }
 
             if (compatibleTypes) {
-                result = candidate;
-                break;
+                return candidate;
             }
         }
-        return result;
+
+        // greedy match
+        if (evaluationOptions.isGreedyMatchMethod()) {
+            for (Method candidate : candidates) {
+                boolean compatibleTypes = true;
+                Class<?>[] types = candidate.getParameterTypes();
+                for (int i = 0; i < types.length; i++) {
+                    if (requiredTypes[i] != null && !isCompatibleType(types[i], requiredTypes[i])) {
+                        compatibleTypes = false;
+                        break;
+                    }
+                }
+
+                if (compatibleTypes) {
+                    return candidate;
+                }
+            }
+        }
+
+        return null;
     }
 
     /**
@@ -396,23 +408,28 @@ public class GetAttributeExpression implements Expression<Object> {
      * @return
      */
     private Class<?> widen(Class<?> clazz) {
-        Class<?> result = clazz;
         if (clazz == int.class) {
-            result = Integer.class;
-        } else if (clazz == long.class) {
-            result = Long.class;
-        } else if (clazz == double.class) {
-            result = Double.class;
-        } else if (clazz == float.class) {
-            result = Float.class;
-        } else if (clazz == short.class) {
-            result = Short.class;
-        } else if (clazz == byte.class) {
-            result = Byte.class;
-        } else if (clazz == boolean.class) {
-            result = Boolean.class;
+            return Integer.class;
         }
-        return result;
+        if (clazz == long.class) {
+            return Long.class;
+        }
+        if (clazz == double.class) {
+            return Double.class;
+        }
+        if (clazz == float.class) {
+            return Float.class;
+        }
+        if (clazz == short.class) {
+            return Short.class;
+        }
+        if (clazz == byte.class) {
+            return Byte.class;
+        }
+        if (clazz == boolean.class) {
+            return Boolean.class;
+        }
+        return clazz;
     }
 
     private class MemberCacheKey {
@@ -466,4 +483,25 @@ public class GetAttributeExpression implements Expression<Object> {
         return this.lineNumber;
     }
 
+    private List<Method> getCandidates(Class<?> clazz, String name, Object[] requiredTypes) {
+        List<Method> candidates = new ArrayList<>();
+        Method[] methods = clazz.getMethods();
+        for (Method m : methods) {
+            if (!m.getName().equalsIgnoreCase(name)) {
+                continue;
+            }
+
+            Class<?>[] types = m.getParameterTypes();
+            if (types.length != requiredTypes.length) {
+                continue;
+            }
+            candidates.add(m);
+        }
+        return candidates;
+    }
+
+    private boolean isCompatibleType(Class<?> type1, Class<?> type2) {
+        Class<?> widenType = widen(type1);
+        return Number.class.isAssignableFrom(widenType) && Number.class.isAssignableFrom(type2);
+    }
 }
diff --git a/src/main/java/com/mitchellbosecke/pebble/node/expression/LiteralIntegerExpression.java b/src/main/java/com/mitchellbosecke/pebble/node/expression/LiteralIntegerExpression.java
new file mode 100644
index 00000000..142a995c
--- /dev/null
+++ b/src/main/java/com/mitchellbosecke/pebble/node/expression/LiteralIntegerExpression.java
@@ -0,0 +1,40 @@
+/*******************************************************************************
+ * 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 com.mitchellbosecke.pebble.extension.NodeVisitor;
+import com.mitchellbosecke.pebble.template.EvaluationContext;
+import com.mitchellbosecke.pebble.template.PebbleTemplateImpl;
+
+public class LiteralIntegerExpression implements Expression<Integer> {
+
+    private final Integer value;
+    private final int lineNumber;
+
+    public LiteralIntegerExpression(Integer value, int lineNumber) {
+        this.value = value;
+        this.lineNumber = lineNumber;
+    }
+
+    @Override
+    public void accept(NodeVisitor visitor) {
+        visitor.visit(this);
+    }
+
+    @Override
+    public Integer evaluate(PebbleTemplateImpl self, EvaluationContext context) {
+        return value;
+    }
+
+    @Override
+    public int getLineNumber() {
+        return this.lineNumber;
+    }
+
+}
diff --git a/src/main/java/com/mitchellbosecke/pebble/parser/ExpressionParser.java b/src/main/java/com/mitchellbosecke/pebble/parser/ExpressionParser.java
index df35727e..10aea1a1 100644
--- a/src/main/java/com/mitchellbosecke/pebble/parser/ExpressionParser.java
+++ b/src/main/java/com/mitchellbosecke/pebble/parser/ExpressionParser.java
@@ -27,6 +27,7 @@ import com.mitchellbosecke.pebble.node.expression.FunctionOrMacroInvocationExpre
 import com.mitchellbosecke.pebble.node.expression.GetAttributeExpression;
 import com.mitchellbosecke.pebble.node.expression.LiteralBooleanExpression;
 import com.mitchellbosecke.pebble.node.expression.LiteralDoubleExpression;
+import com.mitchellbosecke.pebble.node.expression.LiteralIntegerExpression;
 import com.mitchellbosecke.pebble.node.expression.LiteralLongExpression;
 import com.mitchellbosecke.pebble.node.expression.LiteralNullExpression;
 import com.mitchellbosecke.pebble.node.expression.LiteralStringExpression;
@@ -63,6 +64,8 @@ public class ExpressionParser {
 
     private Map<String, UnaryOperator> unaryOperators;
 
+    private ParserOptions parserOptions;
+
     /**
      * Constructor
      *
@@ -74,10 +77,11 @@ public class ExpressionParser {
      *            All the unary operators
      */
     public ExpressionParser(Parser parser, Map<String, BinaryOperator> binaryOperators,
-                            Map<String, UnaryOperator> unaryOperators) {
+                            Map<String, UnaryOperator> unaryOperators, ParserOptions parserOptions) {
         this.parser = parser;
         this.binaryOperators = binaryOperators;
         this.unaryOperators = unaryOperators;
+        this.parserOptions = parserOptions;
     }
 
     /**
@@ -293,12 +297,21 @@ public class ExpressionParser {
                 }
                 break;
 
+            case LONG:
+                final String longValue = token.getValue();
+                node = new LiteralLongExpression(Long.valueOf(longValue), token.getLineNumber());
+                break;
+
             case NUMBER:
                 final String numberValue = token.getValue();
                 if (numberValue.contains(".")) {
                     node = new LiteralDoubleExpression(Double.valueOf(numberValue), token.getLineNumber());
                 } else {
-                    node = new LiteralLongExpression(Long.valueOf(numberValue), token.getLineNumber());
+                    if (parserOptions.isLiteralDecimalTreatedAsInteger()) {
+                        node = new LiteralIntegerExpression(Integer.valueOf(numberValue), token.getLineNumber());
+                    } else {
+                        node = new LiteralLongExpression(Long.valueOf(numberValue), token.getLineNumber());
+                    }
                 }
 
                 break;
diff --git a/src/main/java/com/mitchellbosecke/pebble/parser/ParserImpl.java b/src/main/java/com/mitchellbosecke/pebble/parser/ParserImpl.java
index 21d5294d..1ada92b6 100644
--- a/src/main/java/com/mitchellbosecke/pebble/parser/ParserImpl.java
+++ b/src/main/java/com/mitchellbosecke/pebble/parser/ParserImpl.java
@@ -63,6 +63,11 @@ public class ParserImpl implements Parser {
      */
     private LinkedList<String> blockStack;
 
+    /**
+     * parser options
+     */
+    private ParserOptions parserOptions;
+
     /**
      * Constructor
      *
@@ -71,17 +76,18 @@ public class ParserImpl implements Parser {
      * @param tokenParsers    A map of token parsers
      */
     public ParserImpl(Map<String, UnaryOperator> unaryOperators, Map<String, BinaryOperator> binaryOperators,
-                      Map<String, TokenParser> tokenParsers) {
+                      Map<String, TokenParser> tokenParsers, ParserOptions parserOptions) {
         this.binaryOperators = binaryOperators;
         this.unaryOperators = unaryOperators;
         this.tokenParsers = tokenParsers;
+        this.parserOptions = parserOptions;
     }
 
     @Override
     public RootNode parse(TokenStream stream) {
 
         // expression parser
-        this.expressionParser = new ExpressionParser(this, binaryOperators, unaryOperators);
+        this.expressionParser = new ExpressionParser(this, binaryOperators, unaryOperators, parserOptions);
 
         this.stream = stream;
 
diff --git a/src/main/java/com/mitchellbosecke/pebble/parser/ParserOptions.java b/src/main/java/com/mitchellbosecke/pebble/parser/ParserOptions.java
new file mode 100644
index 00000000..0ce3abc2
--- /dev/null
+++ b/src/main/java/com/mitchellbosecke/pebble/parser/ParserOptions.java
@@ -0,0 +1,20 @@
+package com.mitchellbosecke.pebble.parser;
+
+/**
+ * Parser options.
+ * 
+ * @author yanxiyue
+ * 
+ */
+public class ParserOptions {
+    private boolean literalDecimalTreatedAsInteger;
+
+    public boolean isLiteralDecimalTreatedAsInteger() {
+        return literalDecimalTreatedAsInteger;
+    }
+
+    public ParserOptions setLiteralDecimalTreatedAsInteger(boolean literalDecimalTreatedAsInteger) {
+        this.literalDecimalTreatedAsInteger = literalDecimalTreatedAsInteger;
+        return this;
+    }
+}
diff --git a/src/main/java/com/mitchellbosecke/pebble/template/EvaluationContext.java b/src/main/java/com/mitchellbosecke/pebble/template/EvaluationContext.java
index 9d280860..5a53f6cd 100644
--- a/src/main/java/com/mitchellbosecke/pebble/template/EvaluationContext.java
+++ b/src/main/java/com/mitchellbosecke/pebble/template/EvaluationContext.java
@@ -80,9 +80,9 @@ public class EvaluationContext {
     private final Map<String, PebbleTemplateImpl> namedImportedTemplates;
 
     /**
-     * toggle to enable/disable getClass access
+     * evaluation options
      */
-    private final boolean allowGetClass;
+    private final EvaluationOptions evaluationOptions;
 
     /**
      * Constructor used to provide all final variables.
@@ -100,7 +100,7 @@ public class EvaluationContext {
                              ExtensionRegistry extensionRegistry, Cache<CacheKey, Object> tagCache,
                              ExecutorService executorService, List<PebbleTemplateImpl> importedTemplates,
                              Map<String, PebbleTemplateImpl> namedImportedTemplates, ScopeChain scopeChain,
-                             Hierarchy hierarchy, boolean allowGetClass) {
+                             Hierarchy hierarchy, EvaluationOptions evaluationOptions) {
 
         if (hierarchy == null) {
             hierarchy = new Hierarchy(self);
@@ -115,7 +115,7 @@ public class EvaluationContext {
         this.namedImportedTemplates = namedImportedTemplates;
         this.scopeChain = scopeChain;
         this.hierarchy = hierarchy;
-        this.allowGetClass = allowGetClass;
+        this.evaluationOptions = evaluationOptions;
     }
 
     /**
@@ -127,7 +127,7 @@ public class EvaluationContext {
      */
     public EvaluationContext shallowCopyWithoutInheritanceChain(PebbleTemplateImpl self) {
         EvaluationContext result = new EvaluationContext(self, strictVariables, locale, extensionRegistry, tagCache,
-                executorService, importedTemplates, namedImportedTemplates, scopeChain, null, allowGetClass);
+                executorService, importedTemplates, namedImportedTemplates, scopeChain, null, evaluationOptions);
         return result;
     }
 
@@ -141,7 +141,7 @@ public class EvaluationContext {
      */
     public EvaluationContext threadSafeCopy(PebbleTemplateImpl self) {
         EvaluationContext result = new EvaluationContext(self, strictVariables, locale, extensionRegistry, tagCache, executorService,
-                new ArrayList<>(importedTemplates), new HashMap<>(namedImportedTemplates), scopeChain.deepCopy(), hierarchy, allowGetClass);
+                new ArrayList<>(importedTemplates), new HashMap<>(namedImportedTemplates), scopeChain.deepCopy(), hierarchy, evaluationOptions);
         return result;
     }
 
@@ -236,12 +236,11 @@ public class EvaluationContext {
     }
 
     /**
-     * Returns toggle to enable/disable getClass access
-     *
-     * @return toggle to enable/disable getClass access
+     * Returns the evaluation options.
+     * @return the evaluation options
      */
-    public boolean isAllowGetClass() {
-        return this.allowGetClass;
+    public EvaluationOptions getEvaluationOptions() {
+        return evaluationOptions;
     }
 
 }
diff --git a/src/main/java/com/mitchellbosecke/pebble/template/EvaluationOptions.java b/src/main/java/com/mitchellbosecke/pebble/template/EvaluationOptions.java
new file mode 100644
index 00000000..0b6f92fa
--- /dev/null
+++ b/src/main/java/com/mitchellbosecke/pebble/template/EvaluationOptions.java
@@ -0,0 +1,37 @@
+package com.mitchellbosecke.pebble.template;
+
+/**
+ * Evaluation options.
+ * 
+ * @author yanxiyue
+ * 
+ */
+public class EvaluationOptions {
+    /**
+     * toggle to enable/disable getClass access
+     */
+    private boolean allowGetClass;
+    
+    /**
+     * toggle to enable/disable greedy matching mode for finding java method
+     */
+    private boolean greedyMatchMethod;
+
+    public boolean isAllowGetClass() {
+        return allowGetClass;
+    }
+
+    public EvaluationOptions setAllowGetClass(boolean allowGetClass) {
+        this.allowGetClass = allowGetClass;
+        return this;
+    }
+
+    public boolean isGreedyMatchMethod() {
+        return greedyMatchMethod;
+    }
+
+    public EvaluationOptions setGreedyMatchMethod(boolean greedyMatchMethod) {
+        this.greedyMatchMethod = greedyMatchMethod;
+        return this;
+    }
+}
diff --git a/src/main/java/com/mitchellbosecke/pebble/template/MacroAttributeProvider.java b/src/main/java/com/mitchellbosecke/pebble/template/MacroAttributeProvider.java
index 1f671f0e..eeec6381 100644
--- a/src/main/java/com/mitchellbosecke/pebble/template/MacroAttributeProvider.java
+++ b/src/main/java/com/mitchellbosecke/pebble/template/MacroAttributeProvider.java
@@ -1,6 +1,5 @@
 package com.mitchellbosecke.pebble.template;
 
-import com.mitchellbosecke.pebble.error.PebbleException;
 import com.mitchellbosecke.pebble.node.ArgumentsNode;
 
 
diff --git a/src/main/java/com/mitchellbosecke/pebble/template/PebbleTemplateImpl.java b/src/main/java/com/mitchellbosecke/pebble/template/PebbleTemplateImpl.java
index 8894c38d..0b2289d8 100644
--- a/src/main/java/com/mitchellbosecke/pebble/template/PebbleTemplateImpl.java
+++ b/src/main/java/com/mitchellbosecke/pebble/template/PebbleTemplateImpl.java
@@ -174,9 +174,9 @@ public class PebbleTemplateImpl implements PebbleTemplate {
         // global vars provided from extensions
         scopeChain.pushScope(this.engine.getExtensionRegistry().getGlobalVariables());
 
-        return new EvaluationContext(this, this.engine.isStrictVariables(), locale,
-                this.engine.getExtensionRegistry(), this.engine.getTagCache(), this.engine.getExecutorService(),
-                new ArrayList<PebbleTemplateImpl>(), new HashMap<String, PebbleTemplateImpl>(), scopeChain, null, this.engine.isAllowGetClass());
+        return new EvaluationContext(this, this.engine.isStrictVariables(), locale, this.engine.getExtensionRegistry(), this.engine.getTagCache(),
+                        this.engine.getExecutorService(), new ArrayList<PebbleTemplateImpl>(), new HashMap<String, PebbleTemplateImpl>(), scopeChain,
+                        null, this.engine.getEvaluationOptions());
     }
 
     /**
diff --git a/src/main/java/com/mitchellbosecke/pebble/utils/TypeUtils.java b/src/main/java/com/mitchellbosecke/pebble/utils/TypeUtils.java
new file mode 100644
index 00000000..90559675
--- /dev/null
+++ b/src/main/java/com/mitchellbosecke/pebble/utils/TypeUtils.java
@@ -0,0 +1,63 @@
+package com.mitchellbosecke.pebble.utils;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+
+/**
+ * A small utility class to handle type operation.
+ * 
+ * @author yanxiyue
+ */
+public class TypeUtils {
+    
+    public static Object[] compatibleCast(Object[] argumentValues, Class<?>[] parameterTypes) {
+        if (argumentValues == null || parameterTypes == null) {
+            return argumentValues;
+        }
+        
+        Object[] result = new Object[argumentValues.length];
+        for (int i=0; i<result.length; i++) {
+            result[i] = compatibleCast(argumentValues[i], parameterTypes[i]);
+        }
+        
+        return result;
+    }
+    
+    public static Object compatibleCast(Object value, Class<?> type) {
+        if (value == null || type == null || type.isAssignableFrom(value.getClass())) {
+            return value;
+        }
+        if (value instanceof Number) {
+            Number number = (Number) value;
+            if (type == byte.class || type == Byte.class) {
+                return number.byteValue();
+            }
+            if (type == short.class || type == Short.class) {
+                return number.shortValue();
+            }
+            if (type == int.class || type == Integer.class) {
+                return number.intValue();
+            }
+            if (type == long.class || type == Long.class) {
+                return number.longValue();
+            }
+            if (type == float.class || type == Float.class) {
+                return number.floatValue();
+            }
+            if (type == double.class || type == Double.class) {
+                return number.doubleValue();
+            }
+            if (type == BigInteger.class) {
+                return BigInteger.valueOf(number.longValue());
+            }
+            if (type == BigDecimal.class) {
+                return BigDecimal.valueOf(number.doubleValue());
+            }
+            if (type == Date.class) {
+                return new Date(number.longValue());
+            }
+        }
+        return value;
+    }
+}
diff --git a/travis-settings.xml b/travis-settings.xml
index ab3c72c0..7b7b8c37 100644
--- a/travis-settings.xml
+++ b/travis-settings.xml
@@ -5,8 +5,8 @@
   <servers>
     <server>
       <id>ossrh</id>
-      <username>${sonatypeUsername}</username>
-      <password>${sonatypePassword}</password>
+      <username>${env.SONATYPE_USERNAME}</username>
+      <password>${env.SONATYPE_PASSWORD}</password>
     </server>
   </servers>
 </settings>
\ No newline at end of file


Test patch:
diff --git a/src/test/java/com/mitchellbosecke/pebble/CoreFiltersTest.java b/src/test/java/com/mitchellbosecke/pebble/CoreFiltersTest.java
index 5efd2ffb..a818254b 100755
--- a/src/test/java/com/mitchellbosecke/pebble/CoreFiltersTest.java
+++ b/src/test/java/com/mitchellbosecke/pebble/CoreFiltersTest.java
@@ -14,6 +14,7 @@ import com.mitchellbosecke.pebble.extension.core.LengthFilter;
 import com.mitchellbosecke.pebble.extension.core.ReplaceFilter;
 import com.mitchellbosecke.pebble.loader.StringLoader;
 import com.mitchellbosecke.pebble.template.PebbleTemplate;
+
 import org.junit.Test;
 
 import java.io.IOException;
@@ -22,8 +23,16 @@ import java.io.Writer;
 import java.text.DateFormat;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
-import java.util.*;
-
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import static java.util.Collections.emptyList;
 import static org.junit.Assert.assertEquals;
 
 public class CoreFiltersTest extends AbstractTest {
@@ -512,6 +521,20 @@ public class CoreFiltersTest extends AbstractTest {
         assertEquals("A", writer.toString());
     }
 
+    @Test
+    public void testFirstWithEmptyCollection() throws PebbleException, IOException {
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();
+
+        PebbleTemplate template = pebble.getTemplate("{{ names | first }}");
+
+        Map<String, Object> context = new HashMap<>();
+        context.put("names", emptyList());
+
+        Writer writer = new StringWriter();
+        template.evaluate(writer, context);
+        assertEquals("", writer.toString());
+    }
+
     @Test
     public void testJoin() throws PebbleException, IOException {
         PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();
diff --git a/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java b/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java
index f0a1a1ab..b72e57d8 100644
--- a/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java
+++ b/src/test/java/com/mitchellbosecke/pebble/GetAttributeTest.java
@@ -28,6 +28,7 @@ import java.util.List;
 import java.util.Map;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
 
 public class GetAttributeTest extends AbstractTest {
 
@@ -305,6 +306,65 @@ public class GetAttributeTest extends AbstractTest {
         assertEquals("hello 2", writer.toString());
     }
 
+    @Test
+    public void testBeanMethodWithLongArgument2() throws PebbleException, IOException {
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(true).build();
+
+        PebbleTemplate template = pebble.getTemplate("hello {{ object.number(2L) }}");
+        Map<String, Object> context = new HashMap<>();
+        context.put("object", new BeanWithMethodsThatHaveArguments());
+
+        Writer writer = new StringWriter();
+        template.evaluate(writer, context);
+        assertEquals("hello 2", writer.toString());
+    }
+
+    @Test
+    public void testBeanMethodWithTreatLiteralDecimalAsLong() throws PebbleException, IOException {
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(true).literalDecimalTreatedAsInteger(false)
+                        .greedyMatchMethod(false).build();
+
+        PebbleTemplate template = pebble.getTemplate("hello {{ object.integer(2) }}");
+        Map<String, Object> context = new HashMap<>();
+        context.put("object", new BeanWithMethodsThatHaveArguments());
+
+        try {
+          Writer writer = new StringWriter();
+          template.evaluate(writer, context);
+          fail("expected PebbleException");
+        } catch (PebbleException e) {
+          assertEquals(e.getLineNumber(), (Integer) 1);
+          assertEquals(e.getClass(), AttributeNotFoundException.class);
+        }
+    }
+
+    @Test
+    public void testBeanMethodWithTreatNumberAsInteger() throws PebbleException, IOException {
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(true).literalDecimalTreatedAsInteger(true)
+                        .build();
+
+        PebbleTemplate template = pebble.getTemplate("hello {{ object.integer(2) }}");
+        Map<String, Object> context = new HashMap<>();
+        context.put("object", new BeanWithMethodsThatHaveArguments());
+
+        Writer writer = new StringWriter();
+        template.evaluate(writer, context);
+        assertEquals("hello 2", writer.toString());
+    }
+
+    @Test
+    public void testBeanMethodWithGreedyMatchArgument() throws PebbleException, IOException {
+        PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(true).greedyMatchMethod(true).build();
+
+        PebbleTemplate template = pebble.getTemplate("hello {{ object.integer(2) }} {{ object.short(2) }}");
+        Map<String, Object> context = new HashMap<>();
+        context.put("object", new BeanWithMethodsThatHaveArguments());
+
+        Writer writer = new StringWriter();
+        template.evaluate(writer, context);
+        assertEquals("hello 2 2", writer.toString());
+    }
+
     @Test
     public void testBeanMethodWithOverloadedArgument() throws PebbleException, IOException {
         PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(true).build();
@@ -655,6 +715,14 @@ public class GetAttributeTest extends AbstractTest {
             return number;
         }
 
+        public Integer getInteger(Integer number) {
+            return number;
+        }
+
+        public Short getShort(short number) {
+            return number;
+        }
+
         public Long multiply(Long one, Long two) {
             return one * two;
         }