/* * 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 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 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(); } }