Repository: PebbleTemplates/pebble
Pull request #363 (2018-06-30 03:12:29)

#362: Bugfix loop.length and loop.revindex not of type Number
For #362 


Patch:
diff --git a/src/main/java/com/mitchellbosecke/pebble/node/ForNode.java b/src/main/java/com/mitchellbosecke/pebble/node/ForNode.java
index 29b16bb6..da30285e 100644
--- a/src/main/java/com/mitchellbosecke/pebble/node/ForNode.java
+++ b/src/main/java/com/mitchellbosecke/pebble/node/ForNode.java
@@ -8,22 +8,22 @@
  ******************************************************************************/
 package com.mitchellbosecke.pebble.node;
 
+import com.mitchellbosecke.pebble.error.PebbleException;
+import com.mitchellbosecke.pebble.extension.NodeVisitor;
+import com.mitchellbosecke.pebble.node.expression.Expression;
+import com.mitchellbosecke.pebble.node.fornode.LazyLength;
+import com.mitchellbosecke.pebble.node.fornode.LazyRevIndex;
+import com.mitchellbosecke.pebble.template.EvaluationContext;
+import com.mitchellbosecke.pebble.template.PebbleTemplateImpl;
+import com.mitchellbosecke.pebble.template.ScopeChain;
 import java.io.IOException;
 import java.io.Writer;
 import java.lang.reflect.Array;
-import java.util.Collection;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
-import com.mitchellbosecke.pebble.error.PebbleException;
-import com.mitchellbosecke.pebble.extension.NodeVisitor;
-import com.mitchellbosecke.pebble.node.expression.Expression;
-import com.mitchellbosecke.pebble.template.EvaluationContext;
-import com.mitchellbosecke.pebble.template.PebbleTemplateImpl;
-import com.mitchellbosecke.pebble.template.ScopeChain;
-
 /**
  * Represents a "for" loop within the template.
  *
@@ -39,18 +39,6 @@ public class ForNode extends AbstractRenderableNode {
 
     private final BodyNode elseBody;
 
-    class Control extends Object {
-
-        protected int value = -1;
-
-        public Control(int value) {
-            this.value = value;
-        }
-
-        public Control() {
-        }
-    }
-
     public ForNode(int lineNumber, String variableName, Expression<?> iterableExpression, BodyNode body,
             BodyNode elseBody) {
         super(lineNumber);
@@ -83,16 +71,7 @@ public class ForNode extends AbstractRenderableNode {
             ScopeChain scopeChain = context.getScopeChain();
             scopeChain.pushScope();
 
-            final Control length = new Control() {
-
-                @Override
-                public String toString() {
-                    if (this.value == -1) {
-                        this.value = getIteratorSize(iterableEvaluation);
-                    }
-                    return String.valueOf(value);
-                }
-            };
+            LazyLength length = new LazyLength(iterableEvaluation);
 
             int index = 0;
 
@@ -118,15 +97,7 @@ public class ForNode extends AbstractRenderableNode {
                     loop.put("first", false);
                 }
 
-                Control revindex = new Control(index) {
-
-                    @Override
-                    public String toString() {
-                        return String.valueOf(Integer.valueOf(length.toString()) - this.value - 1);
-                    }
-                };
-
-                loop.put("revindex", revindex);
+                loop.put("revindex", new LazyRevIndex(index, length));
                 loop.put("index", index++);
                 scopeChain.put("loop", loop);
                 scopeChain.put(this.variableName, iterator.next());
@@ -187,36 +158,6 @@ public class ForNode extends AbstractRenderableNode {
         return result;
     }
 
-    private int getIteratorSize(Object iterable) {
-        if (iterable == null) {
-            return 0;
-        }
-        if (iterable instanceof Collection) {
-            return ((Collection<?>) iterable).size();
-        } else if (iterable instanceof Map) {
-            return ((Map<?, ?>) iterable).size();
-        } else if (iterable.getClass().isArray()) {
-            return Array.getLength(iterable);
-        } else if (iterable instanceof Enumeration) {
-            Enumeration<?> enumeration = (Enumeration<?>) iterable;
-            int size = 0;
-            while (enumeration.hasMoreElements()) {
-                size++;
-                enumeration.nextElement();
-            }
-            return size;
-        }
-
-        // assumed to be of type Iterator
-        Iterator<?> it = ((Iterable<?>) iterable).iterator();
-        int size = 0;
-        while (it.hasNext()) {
-            size++;
-            it.next();
-        }
-        return size;
-    }
-
     /**
      * Adapts an array to an Iterable
      */
diff --git a/src/main/java/com/mitchellbosecke/pebble/node/fornode/LazyLength.java b/src/main/java/com/mitchellbosecke/pebble/node/fornode/LazyLength.java
new file mode 100644
index 00000000..1cc8af1f
--- /dev/null
+++ b/src/main/java/com/mitchellbosecke/pebble/node/fornode/LazyLength.java
@@ -0,0 +1,79 @@
+package com.mitchellbosecke.pebble.node.fornode;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+
+public class LazyLength extends Number {
+
+  private final Object iterableEvaluation;
+  private int value = -1;
+
+  public LazyLength(Object iterableEvaluation) {
+    this.iterableEvaluation = iterableEvaluation;
+  }
+
+  @Override
+  public int intValue() {
+    return getValue();
+  }
+
+  @Override
+  public long longValue() {
+    return (long) getValue();
+  }
+
+  @Override
+  public float floatValue() {
+    return (float) getValue();
+  }
+
+  @Override
+  public double doubleValue() {
+    return (double) getValue();
+  }
+
+  @Override
+  public String toString() {
+    return String.valueOf(getValue());
+  }
+
+  private int getValue() {
+    if (this.value == -1) {
+      this.value = getIteratorSize(iterableEvaluation);
+    }
+    return value;
+  }
+
+  private int getIteratorSize(Object iterable) {
+    if (iterable == null) {
+      return 0;
+    }
+    if (iterable instanceof Collection) {
+      return ((Collection<?>) iterable).size();
+    } else if (iterable instanceof Map) {
+      return ((Map<?, ?>) iterable).size();
+    } else if (iterable.getClass().isArray()) {
+      return Array.getLength(iterable);
+    } else if (iterable instanceof Enumeration) {
+      Enumeration<?> enumeration = (Enumeration<?>) iterable;
+      int size = 0;
+      while (enumeration.hasMoreElements()) {
+        size++;
+        enumeration.nextElement();
+      }
+      return size;
+    }
+
+    // assumed to be of type Iterator
+    Iterator<?> it = ((Iterable<?>) iterable).iterator();
+    int size = 0;
+    while (it.hasNext()) {
+      size++;
+      it.next();
+    }
+    return size;
+  }
+}
diff --git a/src/main/java/com/mitchellbosecke/pebble/node/fornode/LazyRevIndex.java b/src/main/java/com/mitchellbosecke/pebble/node/fornode/LazyRevIndex.java
new file mode 100644
index 00000000..2abb0a22
--- /dev/null
+++ b/src/main/java/com/mitchellbosecke/pebble/node/fornode/LazyRevIndex.java
@@ -0,0 +1,40 @@
+package com.mitchellbosecke.pebble.node.fornode;
+
+public class LazyRevIndex extends Number {
+  private final int value;
+  private final LazyLength lazyLength;
+
+  public LazyRevIndex(int value, LazyLength lazyLength) {
+    this.value = value;
+    this.lazyLength = lazyLength;
+  }
+
+  @Override
+  public int intValue() {
+    return getValue();
+  }
+
+  @Override
+  public long longValue() {
+    return (long) getValue();
+  }
+
+  @Override
+  public float floatValue() {
+    return (float) getValue();
+  }
+
+  @Override
+  public double doubleValue() {
+    return (double) getValue();
+  }
+
+  @Override
+  public String toString() {
+    return String.valueOf(getValue());
+  }
+
+  private int getValue() {
+    return lazyLength.intValue() - this.value - 1;
+  }
+}


Test patch:
diff --git a/src/test/java/com/mitchellbosecke/pebble/ForTest.java b/src/test/java/com/mitchellbosecke/pebble/ForTest.java
new file mode 100644
index 00000000..f84532af
--- /dev/null
+++ b/src/test/java/com/mitchellbosecke/pebble/ForTest.java
@@ -0,0 +1,60 @@
+package com.mitchellbosecke.pebble;
+
+import static org.junit.Assert.assertEquals;
+
+import com.mitchellbosecke.pebble.loader.StringLoader;
+import com.mitchellbosecke.pebble.template.PebbleTemplate;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Test;
+
+public class ForTest {
+  @Test
+  public void testForLengthWithOperation() throws IOException {
+    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();
+
+    String source = "{% for user in users %}{% if loop.index < ( loop.length - 1) %}{{user.username}}{% endif %}{% endfor %}";
+    PebbleTemplate template = pebble.getTemplate(source);
+    Map<String, Object> context = new HashMap<>();
+    List<User> users = new ArrayList<>();
+    users.add(new User("Alex"));
+    users.add(new User("Bob"));
+    users.add(new User("John"));
+    context.put("users", users);
+
+    Writer writer = new StringWriter();
+    template.evaluate(writer, context);
+    assertEquals("AlexBob", writer.toString());
+  }
+
+  @Test
+  public void testForRevIndexWithOperation() throws IOException {
+    PebbleEngine pebble = new PebbleEngine.Builder().loader(new StringLoader()).strictVariables(false).build();
+
+    String source = "{% for user in users %}{% if (loop.revindex - 1) >= 0 %}{{user.username}}{% endif %}{% endfor %}";
+    PebbleTemplate template = pebble.getTemplate(source);
+    Map<String, Object> context = new HashMap<>();
+    List<User> users = new ArrayList<>();
+    users.add(new User("Alex"));
+    users.add(new User("Bob"));
+    users.add(new User("John"));
+    context.put("users", users);
+
+    Writer writer = new StringWriter();
+    template.evaluate(writer, context);
+    assertEquals("AlexBob", writer.toString());
+  }
+
+  public static class User {
+    public final String username;
+
+    public User(String username) {
+      this.username = username;
+    }
+  }
+}