1/* 2 * Copyright (C) 2010 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.google.clearsilver.jsilver.compiler; 18 19import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions; 20import com.google.clearsilver.jsilver.autoescape.EscapeMode; 21import com.google.clearsilver.jsilver.data.Data; 22import com.google.clearsilver.jsilver.data.DataContext; 23import com.google.clearsilver.jsilver.data.DefaultDataContext; 24import com.google.clearsilver.jsilver.data.TypeConverter; 25import com.google.clearsilver.jsilver.exceptions.ExceptionUtil; 26import com.google.clearsilver.jsilver.exceptions.JSilverInterpreterException; 27import com.google.clearsilver.jsilver.functions.FunctionExecutor; 28import com.google.clearsilver.jsilver.resourceloader.ResourceLoader; 29import com.google.clearsilver.jsilver.template.DefaultRenderingContext; 30import com.google.clearsilver.jsilver.template.Macro; 31import com.google.clearsilver.jsilver.template.RenderingContext; 32import com.google.clearsilver.jsilver.template.Template; 33import com.google.clearsilver.jsilver.template.TemplateLoader; 34import com.google.clearsilver.jsilver.values.Value; 35 36import java.io.IOException; 37import java.util.Collections; 38 39/** 40 * Base class providing help to generated templates. 41 * 42 * Note, many of the methods are public as they are also used by macros. 43 */ 44public abstract class BaseCompiledTemplate implements Template { 45 46 private FunctionExecutor functionExecutor; 47 private String templateName; 48 private TemplateLoader templateLoader; 49 private EscapeMode escapeMode = EscapeMode.ESCAPE_NONE; 50 private AutoEscapeOptions autoEscapeOptions; 51 52 public void setFunctionExecutor(FunctionExecutor functionExecutor) { 53 this.functionExecutor = functionExecutor; 54 } 55 56 public void setTemplateName(String templateName) { 57 this.templateName = templateName; 58 } 59 60 public void setTemplateLoader(TemplateLoader templateLoader) { 61 this.templateLoader = templateLoader; 62 } 63 64 /** 65 * Set auto escaping options so they can be passed to the rendering context. 66 * 67 * @see AutoEscapeOptions 68 */ 69 public void setAutoEscapeOptions(AutoEscapeOptions autoEscapeOptions) { 70 this.autoEscapeOptions = autoEscapeOptions; 71 } 72 73 @Override 74 public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException { 75 76 render(createRenderingContext(data, out, resourceLoader)); 77 } 78 79 @Override 80 public RenderingContext createRenderingContext(Data data, Appendable out, 81 ResourceLoader resourceLoader) { 82 DataContext dataContext = new DefaultDataContext(data); 83 return new DefaultRenderingContext(dataContext, resourceLoader, out, functionExecutor, 84 autoEscapeOptions); 85 } 86 87 @Override 88 public String getTemplateName() { 89 return templateName; 90 } 91 92 /** 93 * Sets the EscapeMode in which this template was generated. 94 * 95 * @param mode EscapeMode 96 */ 97 public void setEscapeMode(EscapeMode mode) { 98 this.escapeMode = mode; 99 } 100 101 @Override 102 public EscapeMode getEscapeMode() { 103 return escapeMode; 104 } 105 106 @Override 107 public String getDisplayName() { 108 return templateName; 109 } 110 111 /** 112 * Verify that the loop arguments are valid. If not, we will skip the loop. 113 */ 114 public static boolean validateLoopArgs(int start, int end, int increment) { 115 if (increment == 0) { 116 return false; // No increment. Avoid infinite loop. 117 } 118 if (increment > 0 && start > end) { 119 return false; // Incrementing the wrong way. Avoid infinite loop. 120 } 121 if (increment < 0 && start < end) { 122 return false; // Incrementing the wrong way. Avoid infinite loop. 123 } 124 return true; 125 } 126 127 128 public static boolean exists(Data data) { 129 return TypeConverter.exists(data); 130 } 131 132 public static int asInt(String value) { 133 return TypeConverter.asNumber(value); 134 } 135 136 public static int asInt(int value) { 137 return value; 138 } 139 140 public static int asInt(boolean value) { 141 return value ? 1 : 0; 142 } 143 144 public static int asInt(Value value) { 145 return value.asNumber(); 146 } 147 148 public static int asInt(Data data) { 149 return TypeConverter.asNumber(data); 150 } 151 152 public static String asString(String value) { 153 return value; 154 } 155 156 public static String asString(int value) { 157 return Integer.toString(value); 158 } 159 160 public static String asString(boolean value) { 161 return value ? "1" : "0"; 162 } 163 164 public static String asString(Value value) { 165 return value.asString(); 166 } 167 168 public static String asString(Data data) { 169 return TypeConverter.asString(data); 170 } 171 172 public static Value asValue(String value) { 173 // Compiler mode does not use the Value's escapeMode or partiallyEscaped 174 // variables. TemplateTranslator uses other means to determine the proper 175 // escaping to apply. So just set the default escaping flags here. 176 return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false); 177 } 178 179 public static Value asValue(int value) { 180 // Compiler mode does not use the Value's escapeMode or partiallyEscaped 181 // variables. TemplateTranslator uses other means to determine the proper 182 // escaping to apply. So just set the default escaping flags here. 183 return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false); 184 } 185 186 public static Value asValue(boolean value) { 187 // Compiler mode does not use the Value's escapeMode or partiallyEscaped 188 // variables. TemplateTranslator uses other means to determine the proper 189 // escaping to apply. So just set the default escaping flags here. 190 return Value.literalValue(value, EscapeMode.ESCAPE_NONE, false); 191 } 192 193 public static Value asValue(Value value) { 194 return value; 195 } 196 197 public static Value asVariableValue(String variableName, DataContext context) { 198 return Value.variableValue(variableName, context); 199 } 200 201 public static boolean asBoolean(boolean value) { 202 return value; 203 } 204 205 public static boolean asBoolean(String value) { 206 return TypeConverter.asBoolean(value); 207 } 208 209 public static boolean asBoolean(int value) { 210 return value != 0; 211 } 212 213 public static boolean asBoolean(Value value) { 214 return value.asBoolean(); 215 } 216 217 public static boolean asBoolean(Data data) { 218 return TypeConverter.asBoolean(data); 219 } 220 221 /** 222 * Gets the name of the node for writing. Used by cs name command. Returns empty string if not 223 * found. 224 */ 225 public static String getNodeName(Data data) { 226 return data == null ? "" : data.getSymlink().getName(); 227 } 228 229 /** 230 * Returns child nodes of parent. Parent may be null, in which case an empty iterable is returned. 231 */ 232 public Iterable<? extends Data> getChildren(Data parent) { 233 if (parent == null) { 234 return Collections.emptySet(); 235 } else { 236 return parent.getChildren(); 237 } 238 } 239 240 protected TemplateLoader getTemplateLoader() { 241 return templateLoader; 242 } 243 244 public abstract class CompiledMacro implements Macro { 245 246 private final String macroName; 247 private final String[] argumentsNames; 248 249 protected CompiledMacro(String macroName, String... argumentsNames) { 250 this.macroName = macroName; 251 this.argumentsNames = argumentsNames; 252 } 253 254 @Override 255 public void render(Data data, Appendable out, ResourceLoader resourceLoader) throws IOException { 256 render(createRenderingContext(data, out, resourceLoader)); 257 } 258 259 @Override 260 public RenderingContext createRenderingContext(Data data, Appendable out, 261 ResourceLoader resourceLoader) { 262 return BaseCompiledTemplate.this.createRenderingContext(data, out, resourceLoader); 263 } 264 265 @Override 266 public String getTemplateName() { 267 return BaseCompiledTemplate.this.getTemplateName(); 268 } 269 270 @Override 271 public String getMacroName() { 272 return macroName; 273 } 274 275 @Override 276 public String getArgumentName(int index) { 277 if (index >= argumentsNames.length) { 278 // TODO: Make sure this behavior of failing if too many 279 // arguments are passed to a macro is consistent with JNI / interpreter. 280 throw new JSilverInterpreterException("Too many arguments supplied to macro " + macroName); 281 } 282 return argumentsNames[index]; 283 } 284 285 public int getArgumentCount() { 286 return argumentsNames.length; 287 } 288 289 protected TemplateLoader getTemplateLoader() { 290 return templateLoader; 291 } 292 293 @Override 294 public EscapeMode getEscapeMode() { 295 return BaseCompiledTemplate.this.getEscapeMode(); 296 } 297 298 @Override 299 public String getDisplayName() { 300 return BaseCompiledTemplate.this.getDisplayName() + ":" + macroName; 301 } 302 } 303 304 /** 305 * Code common to all three include commands. 306 * 307 * @param templateName String representing name of file to include. 308 * @param ignoreMissingFile {@code true} if any FileNotFound error generated by the template 309 * loader should be ignored, {@code false} otherwise. 310 * @param context Rendering context to use for the included template. 311 */ 312 protected void include(String templateName, boolean ignoreMissingFile, RenderingContext context) { 313 if (!context.pushIncludeStackEntry(templateName)) { 314 throw new JSilverInterpreterException(createIncludeLoopErrorMessage(templateName, context 315 .getIncludedTemplateNames())); 316 } 317 318 loadAndRenderIncludedTemplate(templateName, ignoreMissingFile, context); 319 320 if (!context.popIncludeStackEntry(templateName)) { 321 // Include stack trace is corrupted 322 throw new IllegalStateException("Unable to find on include stack: " + templateName); 323 } 324 } 325 326 // This method should ONLY be called from include() 327 private void loadAndRenderIncludedTemplate(String templateName, boolean ignoreMissingFile, 328 RenderingContext context) { 329 Template template = null; 330 try { 331 template = 332 templateLoader.load(templateName, context.getResourceLoader(), context 333 .getAutoEscapeMode()); 334 } catch (RuntimeException e) { 335 if (ignoreMissingFile && ExceptionUtil.isFileNotFoundException(e)) { 336 return; 337 } else { 338 throw e; 339 } 340 } 341 // Intepret loaded template. 342 try { 343 template.render(context); 344 } catch (IOException e) { 345 throw new JSilverInterpreterException(e.getMessage()); 346 } 347 } 348 349 private String createIncludeLoopErrorMessage(String templateName, Iterable<String> includeStack) { 350 StringBuilder message = new StringBuilder(); 351 message.append("File included twice: "); 352 message.append(templateName); 353 354 message.append(" Include stack:"); 355 for (String fileName : includeStack) { 356 message.append("\n -> "); 357 message.append(fileName); 358 } 359 message.append("\n -> "); 360 message.append(templateName); 361 return message.toString(); 362 } 363} 364