blob: 2e40d9a078c66e726079ec6925d7365afa4ce969 [file] [log] [blame]
/*
* Copyright (C) 2010 Google 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.google.clearsilver.jsilver.compiler;
import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
import com.google.clearsilver.jsilver.autoescape.EscapeMode;
import com.google.clearsilver.jsilver.data.Data;
import com.google.clearsilver.jsilver.data.DataContext;
import com.google.clearsilver.jsilver.data.DefaultDataContext;
import com.google.clearsilver.jsilver.data.TypeConverter;
import com.google.clearsilver.jsilver.exceptions.ExceptionUtil;
import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException;
import com.google.clearsilver.jsilver.functions.FunctionExecutor;
import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
import com.google.clearsilver.jsilver.template.DefaultRenderingContext;
import com.google.clearsilver.jsilver.template.Macro;
import com.google.clearsilver.jsilver.template.RenderingContext;
import com.google.clearsilver.jsilver.template.Template;
import com.google.clearsilver.jsilver.template.TemplateLoader;
import com.google.clearsilver.jsilver.values.Value;
import java.io.IOException;
import java.util.Collections;
/**
* Base class providing help to generated templates.
*
* Note, many of the methods are public as they are also used by macros.
*/
public abstract class BaseCompiledTemplate implements Template {
private FunctionExecutor functionExecutor;
private String templateName;
private TemplateLoader templateLoader;
private EscapeMode escapeMode = EscapeMode.ESCAPE_NONE;
private AutoEscapeOptions autoEscapeOptions;
public void setFunctionExecutor(FunctionExecutor functionExecutor) {
this.functionExecutor = functionExecutor;
}
public void setTemplateName(String templateName) {
this.templateName = templateName;
}
public void setTemplateLoader(TemplateLoader templateLoader) {
this.templateLoader = templateLoader;
}
/**
* Set auto escaping options so they can be passed to the rendering context.
*
* @see AutoEscapeOptions
*/
public void setAutoEscapeOptions(AutoEscapeOptions autoEscapeOptions) {
this.autoEscapeOptions = autoEscapeOptions;
}
@Override
public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException {
render(createRenderingContext(data, out, resourceLoader));
}
@Override
public RenderingContext createRenderingContext(Data data, Appendable out,
ResourceLoader resourceLoader) {
DataContext dataContext = new DefaultDataContext(data);
return new DefaultRenderingContext(dataContext, resourceLoader, out, functionExecutor,
autoEscapeOptions);
}
@Override
public String getTemplateName() {
return templateName;
}
/**
* Sets the EscapeMode in which this template was generated.
*
* @param mode EscapeMode
*/
public void setEscapeMode(EscapeMode mode) {
this.escapeMode = mode;
}
@Override
public EscapeMode getEscapeMode() {
return escapeMode;
}
@Override
public String getDisplayName() {
return templateName;
}
/**
* Verify that the loop arguments are valid. If not, we will skip the loop.
*/
public static boolean validateLoopArgs(int start, int end, int increment) {
if (increment == 0) {
return false; // No increment. Avoid infinite loop.
}
if (increment > 0 && start > end) {
return false; // Incrementing the wrong way. Avoid infinite loop.
}
if (increment < 0 && start < end) {
return false; // Incrementing the wrong way. Avoid infinite loop.
}
return true;
}
public static boolean exists(Data data) {
return TypeConverter.exists(data);
}
public static int asInt(String value) {
return TypeConverter.asNumber(value);
}
public static int asInt(int value) {
return value;
}
public static int asInt(boolean value) {
return value ? 1 : 0;
}
public static int asInt(Value value) {
return value.asNumber();
}
public static int asInt(Data data) {
return TypeConverter.asNumber(data);
}
public static String asString(String value) {
return value;
}
public static String asString(int value) {
return Integer.toString(value);
}
public static String asString(boolean value) {
return value ? "1" : "0";
}
public static String asString(Value value) {
return value.asString();
}
public static String asString(Data data) {
return TypeConverter.asString(data);
}
public static Value asValue(String value) {
// Compiler mode does not use the Value's escapeMode or partiallyEscaped
// variables. TemplateTranslator uses other means to determine the proper
// escaping to apply. So just set the default escaping flags here.
return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false);
}
public static Value asValue(int value) {
// Compiler mode does not use the Value's escapeMode or partiallyEscaped
// variables. TemplateTranslator uses other means to determine the proper
// escaping to apply. So just set the default escaping flags here.
return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false);
}
public static Value asValue(boolean value) {
// Compiler mode does not use the Value's escapeMode or partiallyEscaped
// variables. TemplateTranslator uses other means to determine the proper
// escaping to apply. So just set the default escaping flags here.
return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false);
}
public static Value asValue(Value value) {
return value;
}
public static Value asVariableValue(String variableName, DataContext context) {
return Value.variableValue(variableName, context);
}
public static boolean asBoolean(boolean value) {
return value;
}
public static boolean asBoolean(String value) {
return TypeConverter.asBoolean(value);
}
public static boolean asBoolean(int value) {
return value != 0;
}
public static boolean asBoolean(Value value) {
return value.asBoolean();
}
public static boolean asBoolean(Data data) {
return TypeConverter.asBoolean(data);
}
/**
* Gets the name of the node for writing. Used by cs name command. Returns empty string if not
* found.
*/
public static String getNodeName(Data data) {
return data == null ? "" : data.getSymlink().getName();
}
/**
* Returns child nodes of parent. Parent may be null, in which case an empty iterable is returned.
*/
public Iterable<? extends Data> getChildren(Data parent) {
if (parent == null) {
return Collections.emptySet();
} else {
return parent.getChildren();
}
}
protected TemplateLoader getTemplateLoader() {
return templateLoader;
}
public abstract class CompiledMacro implements Macro {
private final String macroName;
private final String[] argumentsNames;
protected CompiledMacro(String macroName, String... argumentsNames) {
this.macroName = macroName;
this.argumentsNames = argumentsNames;
}
@Override
public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException {
render(createRenderingContext(data, out, resourceLoader));
}
@Override
public RenderingContext createRenderingContext(Data data, Appendable out,
ResourceLoader resourceLoader) {
return BaseCompiledTemplate.this.createRenderingContext(data, out, resourceLoader);
}
@Override
public String getTemplateName() {
return BaseCompiledTemplate.this.getTemplateName();
}
@Override
public String getMacroName() {
return macroName;
}
@Override
public String getArgumentName(int index) {
if (index >= argumentsNames.length) {
// TODO: Make sure this behavior of failing if too many
// arguments are passed to a macro is consistent with JNI / interpreter.
throw new JSilverInterpreterException("Too many arguments supplied to macro " + macroName);
}
return argumentsNames[index];
}
public int getArgumentCount() {
return argumentsNames.length;
}
protected TemplateLoader getTemplateLoader() {
return templateLoader;
}
@Override
public EscapeMode getEscapeMode() {
return BaseCompiledTemplate.this.getEscapeMode();
}
@Override
public String getDisplayName() {
return BaseCompiledTemplate.this.getDisplayName() + ":" + macroName;
}
}
/**
* Code common to all three include commands.
*
* @param templateName String representing name of file to include.
* @param ignoreMissingFile {@code true} if any FileNotFound error generated by the template
* loader should be ignored, {@code false} otherwise.
* @param context Rendering context to use for the included template.
*/
protected void include(String templateName, boolean ignoreMissingFile, RenderingContext context) {
if (!context.pushIncludeStackEntry(templateName)) {
throw new JSilverInterpreterException(createIncludeLoopErrorMessage(templateName, context
.getIncludedTemplateNames()));
}
loadAndRenderIncludedTemplate(templateName, ignoreMissingFile, context);
if (!context.popIncludeStackEntry(templateName)) {
// Include stack trace is corrupted
throw new IllegalStateException("Unable to find on include stack: " + templateName);
}
}
// This method should ONLY be called from include()
private void loadAndRenderIncludedTemplate(String templateName, boolean ignoreMissingFile,
RenderingContext context) {
Template template = null;
try {
template =
templateLoader.load(templateName, context.getResourceLoader(), context
.getAutoEscapeMode());
} catch (RuntimeException e) {
if (ignoreMissingFile && ExceptionUtil.isFileNotFoundException(e)) {
return;
} else {
throw e;
}
}
// Intepret loaded template.
try {
template.render(context);
} catch (IOException e) {
throw new JSilverInterpreterException(e.getMessage());
}
}
private String createIncludeLoopErrorMessage(String templateName, Iterable<String> includeStack) {
StringBuilder message = new StringBuilder();
message.append("File included twice: ");
message.append(templateName);
message.append(" Include stack:");
for (String fileName : includeStack) {
message.append("\n -> ");
message.append(fileName);
}
message.append("\n -> ");
message.append(templateName);
return message.toString();
}
}