com.fizzed.rocker.compiler.JavaGenerator Maven / Gradle / Ivy
/*
* Copyright 2015 Fizzed Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.fizzed.rocker.compiler;
import com.fizzed.rocker.ContentType;
import com.fizzed.rocker.Generated;
import com.fizzed.rocker.RockerContent;
import com.fizzed.rocker.model.*;
import com.fizzed.rocker.runtime.*;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.file.Path;
import java.util.*;
import static com.fizzed.rocker.compiler.RockerUtil.*;
public class JavaGenerator {
static private final Logger log = LoggerFactory.getLogger(JavaGenerator.class);
static public final String CRLF = "\r\n";
static public final String TAB = " ";
// utf-8 constant string in jvm spec uses short for length -- since chars
// that require lots of space (e.g. euro symbol) make sure chunk length can be expanded by 4
static public final int PLAIN_TEXT_CHUNK_LENGTH = 16384;
private final RockerConfiguration configuration;
//private File outputDirectory;
private PlainTextStrategy plainTextStrategy;
public JavaGenerator(RockerConfiguration configuration) {
this.configuration = configuration;
//this.outputDirectory = RockerConfiguration.getInstance().getOutputDirectory();
//this.plainTextStrategy = PlainTextStrategy.STATIC_FINAL_STRINGS;
this.plainTextStrategy = PlainTextStrategy.STATIC_BYTE_ARRAYS_VIA_UNLOADED_CLASS;
}
public RockerConfiguration getConfiguration() {
return configuration;
}
public PlainTextStrategy getPlainTextStrategy() {
return plainTextStrategy;
}
public void setPlainTextStrategy(PlainTextStrategy plainTextStrategy) {
this.plainTextStrategy = plainTextStrategy;
}
public File generate(TemplateModel model) throws GeneratorException, IOException {
if (configuration.getOutputDirectory() == null) {
throw new NullPointerException("Output dir was null");
}
if (model == null) {
throw new NullPointerException("Model was null");
}
Path outputPath = configuration.getOutputDirectory().toPath();
// append package path
Path packagePath = RockerUtil.packageNameToPath(model.getPackageName());
if (packagePath != null) {
outputPath = outputPath.resolve(packagePath);
}
File buildDir = outputPath.toFile();
if (!buildDir.exists()) {
buildDir.mkdirs();
}
File outputFile = new File(buildDir, model.getName() + ".java");
try (Writer w = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFile), "UTF-8"))) {
createSourceTemplate(model, w);
w.flush();
}
return outputFile;
}
public Writer tab(Writer w, int count) throws IOException {
for (int i = 0; i < count; i++) {
w.append(TAB);
}
return w;
}
public String sourceRef(TemplateUnit unit) {
return new StringBuilder()
.append("@ ")
.append("[").append(unit.getSourceRef().getBegin().toString()).append("]")
.toString();
}
public String sourceRefLineCommaPosInLine(TemplateUnit unit) {
return new StringBuilder()
.append(unit.getSourceRef().getBegin().getLineNumber())
.append(", ")
.append(unit.getSourceRef().getBegin().getPosInLine())
.toString();
}
public void appendCommentAndSourcePositionUpdate(Writer w, int tab, TemplateUnit unit) throws IOException {
String unitName = unqualifiedClassName(unit);
//tab(w, tab).append("// ").append(unitName).append(sourceRef(unit)).append(CRLF);
tab(w, tab).append("// ").append(unitName).append(" ").append(sourceRef(unit)).append(CRLF);
tab(w, tab)
.append("__internal.aboutToExecutePosInTemplate(")
.append(sourceRefLineCommaPosInLine(unit))
.append(");")
.append(CRLF);
}
public boolean isForIteratorType(String type) {
return type != null &&
(type.equals("ForIterator") || type.equals(com.fizzed.rocker.ForIterator.class.getName()));
}
public void appendArgumentMembers(TemplateModel model, Writer w, String access, boolean finalModifier, int indent) throws IOException {
// arguments (including possible RockerBody)
if (model.getArguments().size() > 0) {
w.append(CRLF);
for (Argument arg : model.getArguments()) {
tab(w, indent).append("// argument ").append(sourceRef(arg)).append(CRLF);
tab(w, indent)
.append(access).append(" ")
.append((finalModifier ? "final " : ""))
.append(arg.getExternalType()).append(" " + arg.getName()).append(";").append(CRLF);
}
}
}
// TODO: square's JavaWriter looks like a possible replacement
private void createSourceTemplate(TemplateModel model, Writer w) throws GeneratorException, IOException {
if ( model.getOptions().getPostProcessing() != null ) {
// allow post-processors to transform the model
try {
model = postProcess( model );
} catch ( PostProcessorException ppe ) {
throw new GeneratorException("Error during post-processing of model.", ppe);
}
}
// Used to register any withstatements we encounter, so we can generate all dynamic consumers at the end.
final WithStatementConsumerGenerator withStatementConsumerGenerator = new WithStatementConsumerGenerator();
// simple increment to help create unique var names
int varCounter = -1;
if (model.getPackageName() != null && !model.getPackageName().equals("")) {
w.append("package ").append(model.getPackageName()).append(";").append(CRLF);
}
// imports regardless of template
w.append(CRLF);
w.append("import ").append(java.io.IOException.class.getName()).append(";").append(CRLF);
w.append("import ").append(com.fizzed.rocker.ForIterator.class.getName()).append(";").append(CRLF);
w.append("import ").append(com.fizzed.rocker.RenderingException.class.getName()).append(";").append(CRLF);
w.append("import ").append(com.fizzed.rocker.RockerContent.class.getName()).append(";").append(CRLF);
w.append("import ").append(com.fizzed.rocker.RockerOutput.class.getName()).append(";").append(CRLF);
w.append("import ").append(com.fizzed.rocker.runtime.DefaultRockerTemplate.class.getName()).append(";").append(CRLF);
w.append("import ").append(com.fizzed.rocker.runtime.PlainTextUnloadedClassLoader.class.getName()).append(";").append(CRLF);
// template imports
if (model.getImports().size() > 0) {
for (JavaImport i : model.getImports()) {
w.append("// import ").append(sourceRef(i)).append(CRLF);
w.append("import ").append(i.getStatement()).append(";").append(CRLF);
}
}
w.append(CRLF);
w.append("/*").append(CRLF);
w.append(" * Auto generated code to render template ")
.append(model.getPackageName().replace('.', '/'))
.append("/")
.append(model.getTemplateName()).append(CRLF);
w.append(" * Do not edit this file. Changes will eventually be overwritten by Rocker parser!").append(CRLF);
w.append(" */").append(CRLF);
int indent = 0;
// MODEL CLASS
// annotations (https://github.com/fizzed/rocker/issues/59)
tab(w, indent).append("@SuppressWarnings(\"unused\")")
.append(CRLF);
if (model.getOptions().getMarkAsGenerated()) {
tab(w, indent).append('@').append(Generated.class.getCanonicalName()).append(CRLF);
}
// class definition
tab(w, indent).append("public class ")
.append(model.getName())
.append(" extends ")
.append(model.getOptions().getExtendsModelClass())
.append(" {").append(CRLF);
indent++;
w.append(CRLF);
// static info about this template
tab(w, indent).append("static public ")
.append(ContentType.class.getCanonicalName()).append(" getContentType() { return ")
.append(ContentType.class.getCanonicalName()).append(".").append(model.getContentType().toString()).append("; }").append(CRLF);
tab(w, indent).append("static public String getTemplateName() { return \"").append(model.getTemplateName()).append("\"; }").append(CRLF);
tab(w, indent).append("static public String getTemplatePackageName() { return \"").append(model.getPackageName()).append("\"; }").append(CRLF);
tab(w, indent).append("static public String getHeaderHash() { return \"").append(model.createHeaderHash()+"").append("\"; }").append(CRLF);
// Don't include getModifiedAt header when optimized compiler is used since this implicitly disables hot reloading anyhow
if (!model.getOptions().getOptimize()) {
tab(w, indent).append("static public long getModifiedAt() { return ").append(model.getModifiedAt() + "").append("L; }").append(CRLF);
}
tab(w, indent).append("static public String[] getArgumentNames() { return new String[] {");
StringBuilder argNameList = new StringBuilder();
for (Argument arg : model.getArgumentsWithoutRockerBody()) {
if (argNameList.length() > 0) { argNameList.append(","); }
argNameList.append(" \"").append(arg.getExternalName()).append("\"");
}
w.append(argNameList).append(" }; }").append(CRLF);
// model arguments as members of model class
appendArgumentMembers(model, w, "private", false, indent);
// model setters & getters with builder-style pattern
// special case for the RockerBody argument which sorta "hides" its getter/setter
if (model.getArguments().size() > 0) {
for (Argument arg : model.getArguments()) {
// setter
w.append(CRLF);
tab(w, indent)
.append("public ").append(model.getName()).append(" ").append(arg.getExternalName())
.append("(" + arg.getExternalType()).append(" ").append(arg.getName())
.append(") {").append(CRLF);
tab(w, indent+1).append("this.").append(arg.getName()).append(" = ").append(arg.getName()).append(";").append(CRLF);
tab(w, indent+1).append("return this;").append(CRLF);
tab(w, indent).append("}").append(CRLF);
// getter
w.append(CRLF);
tab(w, indent).append("public ").append(arg.getExternalType()).append(" ").append(arg.getExternalName()).append("() {").append(CRLF);
tab(w, indent+1).append("return this.").append(arg.getName()).append(";").append(CRLF);
tab(w, indent).append("}").append(CRLF);
}
}
w.append(CRLF);
//
// model "template" static builder
//
tab(w, indent).append("static public ").append(model.getName()).append(" template(");
if (model.getArguments().size() > 0) {
int i = 0;
// RockerBody is NOT included (it is passed via a closure block in other templates)
// so we only care about the other arguments
for (Argument arg : model.getArgumentsWithoutRockerBody()) {
if (i != 0) { w.append(", "); }
w.append(arg.getType()).append(" ").append(arg.getName());
i++;
}
}
w.append(") {").append(CRLF);
tab(w, indent+1).append("return new ").append(model.getName()).append("()");
if (model.getArguments().size() > 0) {
int i = 0;
for (Argument arg : model.getArgumentsWithoutRockerBody()) {
w.append(CRLF);
tab(w, indent+2).append(".").append(arg.getName()).append("(").append(arg.getName()).append(")");
i++;
}
}
w.append(";").append(CRLF);
tab(w, indent).append("}").append(CRLF);
//
// render of model
//
w.append(CRLF);
tab(w, indent).append("@Override").append(CRLF);
tab(w, indent).append("protected DefaultRockerTemplate buildTemplate() throws RenderingException {").append(CRLF);
if (model.getOptions().getOptimize()) {
// model "template" static builder (not reloading support, fastest performance)
tab(w, indent+1).append("// optimized for performance (via rocker.optimize flag; no auto reloading)").append(CRLF);
tab(w, indent+1).append("return new Template(this);").append(CRLF);
//tab(w, indent+1).append("return template.__render(context);").append(CRLF);
} else {
tab(w, indent+1).append("// optimized for convenience (runtime auto reloading enabled if rocker.reloading=true)").append(CRLF);
// use bootstrap to create underlying template
tab(w, indent+1)
.append("return ")
.append(RockerRuntime.class.getCanonicalName())
.append(".getInstance().getBootstrap().template(this.getClass(), this);").append(CRLF);
//tab(w, indent+1).append("return template.__render(context);").append(CRLF);
}
tab(w, indent).append("}").append(CRLF);
//
// TEMPLATE CLASS
//
w.append(CRLF);
if (model.getOptions().getMarkAsGenerated()) {
tab(w, indent).append('@').append(Generated.class.getCanonicalName()).append(CRLF);
}
// class definition
tab(w, indent)
.append("static public class Template extends ")
.append(model.getOptions().getExtendsClass());
w.append(" {").append(CRLF);
indent++;
// plain text -> map of chunks of text (Java only supports 2-byte length of string constant)
LinkedHashMap> plainTextMap
= model.createPlainTextMap(PLAIN_TEXT_CHUNK_LENGTH);
if (!plainTextMap.isEmpty()) {
w.append(CRLF);
for (String plainText : plainTextMap.keySet()) {
// include static text as comments in source (limit to 500)
tab(w, indent).append("// ")
.append(StringUtils.abbreviate(RockerUtil.ESCAPE_JAVA.translate(plainText), 500)).append(CRLF);
for (Map.Entry chunk : plainTextMap.get(plainText).entrySet()) {
if (this.plainTextStrategy == PlainTextStrategy.STATIC_STRINGS) {
tab(w, indent).append("static private final String ")
.append(chunk.getKey())
.append(" = \"")
.append(StringEscapeUtils.escapeJava(chunk.getValue()))
.append("\";")
.append(CRLF);
}
else if (this.plainTextStrategy == PlainTextStrategy.STATIC_BYTE_ARRAYS_VIA_UNLOADED_CLASS) {
tab(w, indent).append("static private final byte[] ")
.append(chunk.getKey())
.append(";")
.append(CRLF);
}
}
}
// generate the static initializer
if (this.plainTextStrategy == PlainTextStrategy.STATIC_BYTE_ARRAYS_VIA_UNLOADED_CLASS) {
w.append(CRLF);
tab(w, indent).append("static {").append(CRLF);
String loaderClassName = unqualifiedClassName(PlainTextUnloadedClassLoader.class);
tab(w, indent+1).append(loaderClassName)
.append(" loader = ")
.append(loaderClassName)
.append(".tryLoad(")
.append(model.getName())
.append(".class.getClassLoader(), ")
.append(model.getName())
.append(".class.getName()")
.append(" + \"$PlainText\", \"")
.append(model.getOptions().getTargetCharset())
.append("\");")
.append(CRLF);
for (String plainText : plainTextMap.keySet()) {
for (Map.Entry chunk : plainTextMap.get(plainText).entrySet()) {
if (this.plainTextStrategy == PlainTextStrategy.STATIC_BYTE_ARRAYS_VIA_UNLOADED_CLASS) {
tab(w, indent+1).append(chunk.getKey())
.append(" = loader.tryGet(\"")
.append(chunk.getKey())
.append("\");")
.append(CRLF);
}
}
}
tab(w, indent).append("}").append(CRLF);
}
}
// arguments as members of template class
appendArgumentMembers(model, w, "protected", true, indent);
w.append(CRLF);
// constructor
tab(w, indent).append("public Template(").append(model.getName()).append(" model) {").append(CRLF);
tab(w, indent+1).append("super(model);").append(CRLF);
tab(w, indent+1).append("__internal.setCharset(\"").append(model.getOptions().getTargetCharset()).append("\");").append(CRLF);
tab(w, indent+1).append("__internal.setContentType(getContentType());").append(CRLF);
tab(w, indent+1).append("__internal.setTemplateName(getTemplateName());").append(CRLF);
tab(w, indent+1).append("__internal.setTemplatePackageName(getTemplatePackageName());").append(CRLF);
// each model argument passed along as well
for (Argument arg : model.getArguments()) {
tab(w, indent+1).append("this.").append(arg.getName()).append(" = model.").append(arg.getExternalName()).append("();").append(CRLF);
}
tab(w, indent).append("}").append(CRLF);
w.append(CRLF);
tab(w, indent).append("@Override").append(CRLF);
tab(w, indent).append("protected void __doRender() throws IOException, RenderingException {").append(CRLF);
// build rendering code
int depth = 1;
Deque blockEnd = new ArrayDeque<>();
for (TemplateUnit unit : model.getUnits()) {
if (unit instanceof Comment) {
continue;
}
// something like
// IfBeginBlock
// __internal.aboutToExecutePosInSourceTemplate(5, 10);
if(unit.supportsSourceJournaling()){
appendCommentAndSourcePositionUpdate(w, depth+indent, unit);
}
if (unit instanceof PlainText) {
PlainText plain = (PlainText)unit;
LinkedHashMap chunks = plainTextMap.get(plain.getText());
for (String chunkName : chunks.keySet()) {
tab(w, depth+indent)
.append("__internal.writeValue(").append(chunkName).append(");").append(CRLF);
}
}
else if (unit instanceof ValueExpression) {
ValueExpression value = (ValueExpression)unit;
tab(w, depth+indent)
.append("__internal.renderValue(")
.append(value.getExpression())
.append(", ").append(""+value.isNullSafe())
.append(");").append(CRLF);
}
else if (unit instanceof NullTernaryExpression) {
NullTernaryExpression nullTernary = (NullTernaryExpression)unit;
tab(w, depth+indent)
.append("{").append(CRLF);
tab(w, depth+indent+1)
.append("final Object __v = ").append(nullTernary.getLeftExpression()).append(";").append(CRLF);
tab(w, depth+indent+1)
.append("if (!__internal.renderValue(__v, true)) {").append(CRLF);
if (nullTernary.getRightExpression() != null) {
tab(w, depth+indent+2)
.append("__internal.renderValue(")
.append(nullTernary.getRightExpression())
.append(", true);").append(CRLF);
}
tab(w, depth+indent+1)
.append("}").append(CRLF);
tab(w, depth+indent)
.append("}").append(CRLF);
}
else if (unit instanceof ValueClosureBegin) {
ValueClosureBegin closure = (ValueClosureBegin)unit;
tab(w, depth+indent)
.append("__internal.renderValue(")
.append(closure.getExpression())
.append(".__body(");
// Java 1.8+ use lambda
if (isJava8Plus(model)) {
w.append("() -> {").append(CRLF);
depth++;
blockEnd.push("}), false);");
}
// Java 1.7- uses anonymous inner class
else {
w.append("new ")
.append(unqualifiedClassName(RockerContent.class))
.append("() {").append(CRLF);
depth++;
blockEnd.push("}), false);");
tab(w, depth+indent)
.append("@Override")
.append(CRLF);
tab(w, depth+indent)
.append("public void render() throws IOException, RenderingException {")
.append(CRLF);
depth++;
blockEnd.push("}");
}
}
else if (unit instanceof ValueClosureEnd) {
// Java 1.8+ use lambda
if (isJava8Plus(model)) {
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(" // value closure end ").append(sourceRef(unit)).append(CRLF);
}
// Java 1.7- uses anonymous inner class
else {
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(CRLF);
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(" // value closure end ").append(sourceRef(unit)).append(CRLF);
}
}
else if (unit instanceof ContentClosureBegin) {
ContentClosureBegin closure = (ContentClosureBegin)unit;
tab(w, depth+indent)
.append("RockerContent ")
.append(closure.getIdentifier())
.append(" = ");
// Java 1.8+ use lambda
if (isJava8Plus(model)) {
w.append("() -> {").append(CRLF);
depth++;
blockEnd.push("};");
}
// Java 1.7- uses anonymous inner class
else {
w.append("new ")
.append(unqualifiedClassName(com.fizzed.rocker.RockerContent.class))
.append("() {").append(CRLF);
depth++;
blockEnd.push("};");
tab(w, depth+indent)
.append("@Override")
.append(CRLF);
tab(w, depth+indent)
.append("public void render() throws IOException, RenderingException {")
.append(CRLF);
depth++;
blockEnd.push("}");
}
}
else if (unit instanceof ContentClosureEnd) {
// Java 1.8+ use lambda
if (isJava8Plus(model)) {
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(" // content closure end ").append(sourceRef(unit)).append(CRLF);
}
// Java 1.7- uses anonymous inner class
else {
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(CRLF);
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(" // content closure end ").append(sourceRef(unit)).append(CRLF);
}
}
else if (unit instanceof IfBlockBegin) {
IfBlockBegin block = (IfBlockBegin)unit;
tab(w, depth+indent)
.append("if ")
.append(block.getExpression())
.append(" {").append(CRLF);
blockEnd.push("}");
depth++;
}
else if(unit instanceof IfBlockElseIf) {
final IfBlockElseIf block = (IfBlockElseIf) unit;
depth--;
// This keeps else-if nicely formatted in generated code.
tab(w, depth+indent)
.append("} else if ")
.append(block.getExpression())
.append(" {").append(CRLF);
depth++;
}
else if (unit instanceof IfBlockElse) {
depth--;
tab(w, depth+indent)
.append("} else {")
.append(" // else ").append(sourceRef(unit)).append(CRLF);
depth++;
}
else if (unit instanceof IfBlockEnd) {
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(" // if end ").append(sourceRef(unit)).append(CRLF);
}
else if (unit instanceof WithBlockBegin) {
WithBlockBegin block = (WithBlockBegin)unit;
WithStatement stmt = block.getStatement();
String statementConsumerName = withStatementConsumerGenerator.register(stmt);
final List variables = stmt.getVariables();
if (isJava8Plus(model)) {
tab(w, depth+indent)
.append(variables.size() == 1 ? qualifiedClassName(WithBlock.class) : WithStatementConsumerGenerator.WITH_BLOCKS_GENERATED_CLASS_NAME)
.append(".with(");
// All expressions
for(int i = 0; i < variables.size(); i++) {
final WithStatement.VariableWithExpression var = variables.get(i);
if(i > 0) {
w.append(", ");
}
w.append(var.getValueExpression());
}
w.append(", ").append(stmt.isNullSafe()+"")
.append(", (");
for(int i = 0; i < variables.size(); i++) {
final WithStatement.VariableWithExpression var = variables.get(i);
if(i > 0) {
w.append(", ");
}
w.append(var.getVariable().getName());
}
w.append(") -> {").append(CRLF);
depth++;
blockEnd.push("});");
}
else {
tab(w, depth+indent)
// Note for standard 1 variable with block we use the predefined consumers.
// Otherwise we fallback to the generated ones.
.append(variables.size() == 1 ? qualifiedClassName(WithBlock.class) : WithStatementConsumerGenerator.WITH_BLOCKS_GENERATED_CLASS_NAME)
.append(".with(");
// All expressions
for(int i = 0; i < variables.size(); i++) {
final WithStatement.VariableWithExpression var = variables.get(i);
if(i > 0) {
w.append(", ");
}
w.append(var.getValueExpression());
}
w.append(", ").append(stmt.isNullSafe()+"")
.append(", (new ").append(statementConsumerName).append('<');
// Types for the .with(..)
for(int i = 0; i < variables.size(); i++) {
final JavaVariable variable = variables.get(i).getVariable();
if(i > 0) {
w.append(", ");
}
w.append(variable.getType());
}
w.append(">() {").append(CRLF);
tab(w, depth+indent+1)
.append("@Override public void accept(");
for(int i = 0; i < variables.size(); i++) {
final JavaVariable variable = variables.get(i).getVariable();
if(i > 0) {
w.append(", ");
}
w.append("final ").append(variable.toString());
}
w.append(") throws IOException {").append(CRLF);
depth++;
blockEnd.push("}}));");
}
}
else if (unit instanceof WithBlockElse) {
depth--;
if (isJava8Plus(model)) {
tab(w, depth+indent)
.append("}, () -> {").append(CRLF);
} else {
tab(w, depth+indent)
.append("}}), (new ").append(qualifiedClassName(WithBlock.Consumer0.class)).append("() { ")
.append("@Override public void accept() throws IOException {").append(CRLF);
}
depth++;
}
else if (unit instanceof WithBlockEnd) {
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(" // with end ").append(sourceRef(unit)).append(CRLF);
}
else if (unit instanceof ForBlockBegin) {
ForBlockBegin block = (ForBlockBegin)unit;
ForStatement stmt = block.getStatement();
// break support via try and catch mechanism (works across lambdas!)
tab(w, depth+indent)
.append("try {").append(CRLF);
depth++;
if (stmt.getForm() == ForStatement.Form.GENERAL) {
// print out raw statement including parentheses
tab(w, depth+indent)
.append("for ")
.append(block.getExpression())
.append(" {").append(CRLF);
blockEnd.push("}");
}
else if (stmt.getForm() == ForStatement.Form.ENHANCED) {
// Java 1.8+ (use lambdas)
if (stmt.hasAnyUntypedArguments() &&
isJava8Plus(model)) {
// build list of lambda vars
String localVars = "";
for (JavaVariable arg : stmt.getArguments()) {
if (localVars.length() != 0) { localVars += ","; }
localVars += arg.getName();
}
tab(w, depth+indent)
.append(Java8Iterator.class.getName())
.append(".forEach(")
.append(stmt.getValueExpression())
.append(", (").append(localVars).append(") -> {").append(CRLF);
blockEnd.push("});");
}
else {
// is the first argument a "ForIterator" ?
boolean forIterator = isForIteratorType(stmt.getArguments().get(0).getType());
int collectionCount = (forIterator ? 2 : 1);
int mapCount = (forIterator ? 3 : 2);
// type and value we are going to iterate thru
String iterateeType = null;
String valueExpression = null;
if (stmt.getArguments().size() == collectionCount) {
iterateeType = stmt.getArguments().get(collectionCount - 1).getTypeAsNonPrimitiveType();
valueExpression = stmt.getValueExpression();
}
else if (stmt.getArguments().size() == mapCount) {
iterateeType = "java.util.Map.Entry<"
+ stmt.getArguments().get(mapCount - 2).getTypeAsNonPrimitiveType()
+ ","
+ stmt.getArguments().get(mapCount - 1).getTypeAsNonPrimitiveType()
+ ">";
valueExpression = stmt.getValueExpression() + ".entrySet()";
}
// create unique variable name for iterator
String forIteratorVarName = "__forIterator" + (++varCounter);
// ForIterator for collection and make it final to assure nested anonymous
// blocks can access it as well.
tab(w, depth+indent)
.append("final ")
.append(com.fizzed.rocker.runtime.IterableForIterator.class.getName())
.append("<").append(iterateeType).append(">")
.append(" ")
.append(forIteratorVarName)
.append(" = new ")
.append(com.fizzed.rocker.runtime.IterableForIterator.class.getName())
.append("<").append(iterateeType).append(">")
.append("(")
.append(valueExpression)
.append(");")
.append(CRLF);
// for loop same regardless of map vs. collection
tab(w, depth+indent)
.append("while (")
.append(forIteratorVarName)
.append(".hasNext()) {")
.append(CRLF);
// if forIterator request assign to local var and make it final to assure nested anonymous
// blocks can access it as well.
if (forIterator) {
tab(w, depth+indent+1)
.append("final ")
.append(com.fizzed.rocker.ForIterator.class.getName())
.append(" ")
.append(stmt.getArguments().get(0).getName())
.append(" = ")
.append(forIteratorVarName)
.append(";")
.append(CRLF);
}
if (stmt.getArguments().size() == collectionCount) {
// assign item to local var and make it final to assure nested anonymous
// blocks can access it as well.
tab(w, depth+indent+1)
.append("final ")
.append(stmt.getArguments().get(collectionCount - 1).toString())
.append(" = ")
.append(forIteratorVarName)
.append(".next();")
.append(CRLF);
}
else if (stmt.getArguments().size() == mapCount) {
// create unique variable name for iterator
String entryVarName = "__entry" + (++varCounter);
// assign map entry to local var
tab(w, depth+indent+1)
.append("final ")
.append(iterateeType)
.append(" ")
.append(entryVarName)
.append(" = ")
.append(forIteratorVarName)
.append(".next();")
.append(CRLF);
// assign entry to local values make it final to assure nested anonymous
// blocks can access it as well.
tab(w, depth+indent+1)
.append("final ")
.append(stmt.getArguments().get(mapCount - 2).toString())
.append(" = ").append(entryVarName).append(".getKey();").append(CRLF);
tab(w, depth+indent+1)
.append("final ")
.append(stmt.getArguments().get(mapCount - 1).toString())
.append(" = ").append(entryVarName).append(".getValue();").append(CRLF);
}
else {
throw new GeneratorException("Unsupported number of arguments for for loop");
}
blockEnd.push("}");
}
}
depth++;
// continue support via try and catch mechanism (works across lambdas!)
tab(w, depth+indent)
.append("try {").append(CRLF);
depth++;
}
else if (unit instanceof ForBlockEnd) {
depth--;
// continue support via try and catch mechanism (works across lambdas!)
tab(w, depth+indent)
.append("} catch (").append(ContinueException.class.getCanonicalName()).append(" e) {") .append(CRLF);
tab(w, depth+indent+1)
.append("// support for continuing for loops").append(CRLF);
tab(w, depth+indent)
.append("}").append(CRLF);
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(" // for end ").append(sourceRef(unit)).append(CRLF);
depth--;
// break support via try and catch mechanism (works across lambdas!)
tab(w, depth+indent)
.append("} catch (").append(BreakException.class.getCanonicalName()).append(" e) {") .append(CRLF);
tab(w, depth+indent+1)
.append("// support for breaking for loops").append(CRLF);
tab(w, depth+indent)
.append("}").append(CRLF);
}
else if (unit instanceof BreakStatement) {
tab(w, depth+indent)
.append("__internal.throwBreakException();").append(CRLF);
}
else if (unit instanceof ContinueStatement) {
tab(w, depth+indent)
.append("__internal.throwContinueException();").append(CRLF);
}
else if(unit instanceof SwitchBlock){
SwitchBlock block = (SwitchBlock) unit;
// break support via try and catch mechanism (works across lambdas!)
tab(w, depth+indent)
.append("try {").append(CRLF);
depth++;
tab(w, depth+indent)
.append("switch ")
.append(block.getExpression())
.append(" {").append(CRLF);
blockEnd.push("}");
depth++;
} else if (unit instanceof SwitchBlockEnd) {
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(" // switch end ").append(sourceRef(unit)).append(CRLF);
depth--;
// break support via try and catch mechanism (works across lambdas!)
tab(w, depth+indent)
.append("} catch (").append(BreakException.class.getCanonicalName()).append(" e) {") .append(CRLF);
tab(w, depth+indent+1)
.append("// support for breaking switch statements").append(CRLF);
tab(w, depth+indent)
.append("}").append(CRLF);
} else if (unit instanceof SwitchCaseBlock) {
SwitchCaseBlock block = (SwitchCaseBlock) unit;
tab(w, depth+indent)
.append("case ")
.append(block.getExpression())
.append(": ")
.append(" {").append(CRLF);
blockEnd.push("}");
depth++;
} else if (unit instanceof SwitchCaseBlockEnd) {
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(" // case end ").append(sourceRef(unit)).append(CRLF);
}else if (unit instanceof SwitchDefaultBlock) {
tab(w, depth+indent)
.append("default ")
.append(": ")
.append(" {").append(CRLF);
blockEnd.push("}");
depth++;
}
else if (unit instanceof SwitchDefaultBlockEnd) {
depth--;
tab(w, depth+indent)
.append(blockEnd.pop())
.append(" // default end ").append(sourceRef(unit)).append(CRLF);
}
//log.info(" src (@ {}): [{}]", unit.getSourceRef(), unit.getSourceRef().getConsoleFriendlyText());
}
// end of render()
tab(w, indent).append("}").append(CRLF);
indent--;
// end of template class
tab(w, indent).append("}").append(CRLF);
// Generate class with all gathered consumer interfaces for all withblocks
withStatementConsumerGenerator.generate(this, w);
if (this.plainTextStrategy == PlainTextStrategy.STATIC_BYTE_ARRAYS_VIA_UNLOADED_CLASS &&
!plainTextMap.isEmpty()) {
w.append(CRLF);
if (model.getOptions().getMarkAsGenerated()) {
tab(w, indent).append('@').append(Generated.class.getCanonicalName()).append(CRLF);
}
tab(w, indent).append("private static class PlainText {").append(CRLF);
w.append(CRLF);
for (String plainText : plainTextMap.keySet()) {
for (Map.Entry chunk : plainTextMap.get(plainText).entrySet()) {
tab(w, indent+1).append("static private final String ")
.append(chunk.getKey())
.append(" = \"")
.append(StringEscapeUtils.escapeJava(chunk.getValue()))
.append("\";")
.append(CRLF);
}
}
w.append(CRLF);
tab(w, indent).append("}").append(CRLF);
}
w.append(CRLF);
w.append("}").append(CRLF);
}
/**
* Execute all {@link TemplateModelPostProcessor}s as they were configured globally through
* Maven's pom.xml, and through a per-template option. If both were given, execute the global
* post-processors first, and then the per-template post-processors.
* Generation of Java code will continue with the TemplateModel returned by the last
* post-processor in the chain.
*
* @param templateModel the {@link TemplateModel} to run the post-processing on.
*
* @return a {@link TemplateModel} with all post-processing transformations applied. Only this
* resulting TemplateModel will be used for further Java-code generation.
* @throws PostProcessorException if a post-processor cannot be instantiated, or if any of the
* post-processors throws an exception during processing of the model.
*/
private TemplateModel postProcess(TemplateModel templateModel) throws PostProcessorException {
// create a list of post-processor class names. by setting up this list with copies of the
// configured class names before any post-processors run, no changes made by post-processors
// to the templateModel's list of post-processors will be honoured.
List postProcessorClassNames = new ArrayList<>();
// consider global list of post-processors from Maven's pom.xml first.
if ( getConfiguration().getOptions().getPostProcessing() != null ) {
postProcessorClassNames.addAll( Arrays.asList(getConfiguration().getOptions().getPostProcessing() ) );
}
// appened per-template post-processors
postProcessorClassNames.addAll( Arrays.asList(templateModel.getOptions().getPostProcessing()));
for ( int i = 0; i < postProcessorClassNames.size(); i ++ ) {
String ppClassName = postProcessorClassNames.get(i);
try {
Class ppClass = (Class) Class.forName(ppClassName);
TemplateModelPostProcessor postProcessor = ppClass.newInstance();
log.debug("Running post-processor {} on template {} at index {}.", postProcessor.getClass().getName(), templateModel.getName(), i );
templateModel = postProcessor.process(templateModel, i);
} catch (ClassNotFoundException e) {
throw new PostProcessorException("Post-Processor class not found (" + ppClassName + ").", e);
} catch (InstantiationException e) {
throw new PostProcessorException("Could not instantiate Post-Processor (" + ppClassName + ").", e);
} catch (IllegalAccessException e) {
throw new PostProcessorException("Illegal access for Post-Processor (" + ppClassName + ").", e);
}
}
return templateModel;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy