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