
com.mitchellbosecke.pebble.node.ForNode Maven / Gradle / Ivy
Show all versions of pebble Show documentation
/*******************************************************************************
* 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;
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;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Array;
import java.util.*;
/**
* Represents a "for" loop within the template.
*
* @author mbosecke
*/
public class ForNode extends AbstractRenderableNode {
private final String variableName;
private final Expression> iterableExpression;
private final BodyNode body;
private final BodyNode elseBody;
public ForNode(int lineNumber, String variableName, Expression> iterableExpression, BodyNode body,
BodyNode elseBody) {
super(lineNumber);
this.variableName = variableName;
this.iterableExpression = iterableExpression;
this.body = body;
this.elseBody = elseBody;
}
@Override
public void render(PebbleTemplateImpl self, Writer writer, EvaluationContext context)
throws PebbleException, IOException {
Object iterableEvaluation = iterableExpression.evaluate(self, context);
Iterable> iterable = null;
if (iterableEvaluation == null) {
return;
}
iterable = toIterable(iterableEvaluation);
Iterator> iterator = iterable.iterator();
boolean newScope = false;
if (iterator.hasNext()) {
ScopeChain scopeChain = context.getScopeChain();
/*
* Only if there is a variable name conflict between one of the
* variables added by the for loop construct and an existing
* variable do we push another scope, otherwise we reuse the current
* scope for performance purposes.
*/
if (scopeChain.currentScopeContainsVariable("loop") || scopeChain
.currentScopeContainsVariable(variableName)) {
scopeChain.pushScope();
newScope = true;
}
int length = getIteratorSize(iterableEvaluation);
int index = 0;
Map loop = new HashMap<>();
boolean usingExecutorService = context.getExecutorService() != null;
while (iterator.hasNext()) {
/*
* If the user is using an executor service (i.e. parallel node), we
* must create a new map with every iteration instead of
* re-using the same one; it's imperative that each thread would
* get it's own distinct copy of the context.
*/
if (usingExecutorService) {
loop = new HashMap<>();
}
loop.put("last", index == length - 1);
loop.put("first", index == 0);
loop.put("revindex", length - index - 1);
loop.put("index", index++);
loop.put("length", length);
scopeChain.put("loop", loop);
scopeChain.put(variableName, iterator.next());
body.render(self, writer, context);
}
if (newScope) {
scopeChain.popScope();
}
} else if (elseBody != null) {
elseBody.render(self, writer, context);
}
}
@Override
public void accept(NodeVisitor visitor) {
visitor.visit(this);
}
public String getIterationVariable() {
return variableName;
}
public Expression> getIterable() {
return iterableExpression;
}
public BodyNode getBody() {
return body;
}
public BodyNode getElseBody() {
return elseBody;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private Iterable