package org.fenixedu.bennu.portal.servlet;
import java.io.IOException;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collection;
import java.util.stream.Stream;
import com.mitchellbosecke.pebble.error.ParserException;
import com.mitchellbosecke.pebble.error.PebbleException;
import com.mitchellbosecke.pebble.extension.NodeVisitor;
import com.mitchellbosecke.pebble.lexer.Token;
import com.mitchellbosecke.pebble.lexer.TokenStream;
import com.mitchellbosecke.pebble.node.AbstractRenderableNode;
import com.mitchellbosecke.pebble.node.BodyNode;
import com.mitchellbosecke.pebble.node.RenderableNode;
import com.mitchellbosecke.pebble.node.expression.Expression;
import com.mitchellbosecke.pebble.parser.Parser;
import com.mitchellbosecke.pebble.template.EvaluationContext;
import com.mitchellbosecke.pebble.template.PebbleTemplateImpl;
import com.mitchellbosecke.pebble.template.ScopeChain;
import com.mitchellbosecke.pebble.tokenParser.AbstractTokenParser;
import com.mitchellbosecke.pebble.tokenParser.TokenParser;
/**
* {@link TokenParser} for the 'lazyFor' node.
*
* This node allow lazy iteration over a {@link Stream}, without
* invoking extraneous terminal operations. In practice, this node
* is a wrapper for {@link Stream#forEach(java.util.function.Consumer)}.
*
* Unlike the regular 'for' node, no extra variables are defined, only
* the iteration variable, named in the token's usage.
*
* Accepted arguments are instance of {@link Stream}, {@link Collection} or arrays.
*
* Example:
*
* <pre>
* {% lazyFor item in itemStream %}
* {{item.name}}
* {% endLazyFor %}
* </pre>
*
* @author João Carvalho (joao.pedro.carvalho@tecnico.ulisboa.pt)
*
* @since 3.3
*
*/
public class LazyForTokenParser extends AbstractTokenParser {
@Override
public RenderableNode parse(Token token, Parser parser) throws ParserException {
TokenStream stream = parser.getStream();
int lineNumber = token.getLineNumber();
// skip the 'lazyFor' token
stream.next();
// get the iteration variable
String iterationVariable = parser.getExpressionParser().parseNewVariableName();
stream.expect(Token.Type.NAME, "in");
// get the iterable variable
Expression<?> iterable = parser.getExpressionParser().parseExpression();
stream.expect(Token.Type.EXECUTE_END);
BodyNode body = parser.subparse(test -> test.test(Token.Type.NAME, "endLazyFor"));
// skip the 'endLazyFor' token
stream.next();
stream.expect(Token.Type.EXECUTE_END);
return new ForNode(lineNumber, iterationVariable, iterable, body);
}
private static final class ForNode extends AbstractRenderableNode {
private final String variableName;
private final Expression<?> iterableExpression;
private final BodyNode body;
public ForNode(int lineNumber, String variableName, Expression<?> iterableExpression, BodyNode body) {
super(lineNumber);
this.variableName = variableName;
this.iterableExpression = iterableExpression;
this.body = body;
}
@Override
public void render(PebbleTemplateImpl self, Writer writer, EvaluationContext context) throws PebbleException, IOException {
Object value = iterableExpression.evaluate(self, context);
if (value == null) {
return;
}
Stream<?> stream = getStream(value);
ScopeChain scopeChain = context.getScopeChain();
scopeChain.pushScope();
stream.forEach((obj) -> {
try {
scopeChain.put(variableName, obj);
body.render(self, writer, context);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
scopeChain.popScope();
}
private Stream<?> getStream(Object obj) {
if (obj instanceof Stream) {
return (Stream<?>) obj;
} else if (obj instanceof Collection) {
return ((Collection<?>) obj).stream();
} else if (obj.getClass().isArray()) {
return Arrays.stream((Object[]) obj);
} else {
throw new ClassCastException(obj + " cannot be cast to Stream");
}
}
@Override
public void accept(NodeVisitor visitor) {
visitor.visit(this);
}
}
@Override
public String getTag() {
return "lazyFor";
}
}