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;
18
19import com.google.clearsilver.jsilver.autoescape.AutoEscapeOptions;
20import com.google.clearsilver.jsilver.autoescape.EscapeMode;
21import com.google.clearsilver.jsilver.compiler.TemplateCompiler;
22import com.google.clearsilver.jsilver.data.Data;
23import com.google.clearsilver.jsilver.data.DataFactory;
24import com.google.clearsilver.jsilver.data.HDFDataFactory;
25import com.google.clearsilver.jsilver.exceptions.JSilverBadSyntaxException;
26import com.google.clearsilver.jsilver.exceptions.JSilverException;
27import com.google.clearsilver.jsilver.functions.Function;
28import com.google.clearsilver.jsilver.functions.FunctionRegistry;
29import com.google.clearsilver.jsilver.functions.TextFilter;
30import com.google.clearsilver.jsilver.functions.bundles.ClearSilverCompatibleFunctions;
31import com.google.clearsilver.jsilver.functions.bundles.CoreOperators;
32import com.google.clearsilver.jsilver.interpreter.InterpretedTemplateLoader;
33import com.google.clearsilver.jsilver.interpreter.LoadingTemplateFactory;
34import com.google.clearsilver.jsilver.interpreter.OptimizerProvider;
35import com.google.clearsilver.jsilver.interpreter.OptimizingTemplateFactory;
36import com.google.clearsilver.jsilver.interpreter.TemplateFactory;
37import com.google.clearsilver.jsilver.output.InstanceOutputBufferProvider;
38import com.google.clearsilver.jsilver.output.OutputBufferProvider;
39import com.google.clearsilver.jsilver.output.ThreadLocalOutputBufferProvider;
40import com.google.clearsilver.jsilver.precompiler.PrecompiledTemplateLoader;
41import com.google.clearsilver.jsilver.resourceloader.ResourceLoader;
42import com.google.clearsilver.jsilver.syntax.DataCommandConsolidator;
43import com.google.clearsilver.jsilver.syntax.SyntaxTreeOptimizer;
44import com.google.clearsilver.jsilver.syntax.StructuralWhitespaceStripper;
45import com.google.clearsilver.jsilver.syntax.node.Switch;
46import com.google.clearsilver.jsilver.template.DelegatingTemplateLoader;
47import com.google.clearsilver.jsilver.template.HtmlWhiteSpaceStripper;
48import com.google.clearsilver.jsilver.template.Template;
49import com.google.clearsilver.jsilver.template.TemplateLoader;
50
51import java.io.IOException;
52import java.util.LinkedList;
53import java.util.List;
54
55/**
56 * JSilver templating system.
57 *
58 * <p>
59 * This is a pure Java version of ClearSilver.
60 * </p>
61 *
62 * <h2>Example Usage</h2>
63 *
64 * <pre>
65 * // Load resources (e.g. templates) from directory.
66 * JSilver jSilver = new JSilver(new FileResourceLoader("/path/to/templates"));
67 *
68 * // Set up some data.
69 * Data data = new Data();
70 * data.setValue("name.first", "Mr");
71 * data.setValue("name.last", "Man");
72 *
73 * // Render template to System.out. Writer output = ...;
74 * jSilver.render("say-hello", data, output);
75 * </pre>
76 *
77 * For example usage, see java/com/google/clearsilver/jsilver/examples.
78 *
79 * Additional options can be passed to the constructor using JSilverOptions.
80 *
81 * @see <a href="http://go/jsilver">JSilver Docs</a>
82 * @see <a href="http://clearsilver.net">ClearSilver Docs</a>
83 * @see JSilverOptions
84 * @see Data
85 * @see ResourceLoader
86 */
87public final class JSilver implements TemplateRenderer, DataLoader {
88
89  private final JSilverOptions options;
90
91  private final TemplateLoader templateLoader;
92
93  /**
94   * If caching enabled, the cached wrapper (otherwise null). Kept here so we can call clearCache()
95   * later.
96   */
97
98  private final FunctionRegistry globalFunctions = new ClearSilverCompatibleFunctions();
99
100  private final ResourceLoader defaultResourceLoader;
101
102  private final DataFactory dataFactory;
103
104  // Object used to return Appendable output buffers when needed.
105  private final OutputBufferProvider outputBufferProvider;
106  public static final String VAR_ESCAPE_MODE_KEY = "Config.VarEscapeMode";
107  public static final String AUTO_ESCAPE_KEY = "Config.AutoEscape";
108
109  /**
110   * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g.
111   *        directory, classpath, memory, etc.
112   * @param options Additional options.
113   * @see JSilverOptions
114   */
115  public JSilver(ResourceLoader defaultResourceLoader, JSilverOptions options) {
116    // To ensure that options cannot be changed externally, we clone them and
117    // use the frozen clone.
118    options = options.clone();
119
120    this.defaultResourceLoader = defaultResourceLoader;
121    this.dataFactory =
122        new HDFDataFactory(options.getIgnoreAttributes(), options.getStringInternStrategy());
123    this.options = options;
124
125    // Setup the output buffer provider either with a threadlocal pool
126    // or creating a new instance each time it is asked for.
127    int bufferSize = options.getInitialBufferSize();
128    if (options.getUseOutputBufferPool()) {
129      // Use a ThreadLocal to reuse StringBuilder objects.
130      outputBufferProvider = new ThreadLocalOutputBufferProvider(bufferSize);
131    } else {
132      // Create a new StringBuilder each time.
133      outputBufferProvider = new InstanceOutputBufferProvider(bufferSize);
134    }
135
136    // Loads the template from the resource loader, manipulating the AST as
137    // required for correctness.
138    TemplateFactory templateFactory = new LoadingTemplateFactory();
139
140    // Applies optimizations to improve performance.
141    // These steps are entirely optional, and are not required for correctness.
142    templateFactory = setupOptimizerFactory(templateFactory);
143
144    TemplateLoader templateLoader;
145    List<DelegatingTemplateLoader> delegatingTemplateLoaders =
146        new LinkedList<DelegatingTemplateLoader>();
147    AutoEscapeOptions autoEscapeOptions = new AutoEscapeOptions();
148    autoEscapeOptions.setPropagateEscapeStatus(options.getPropagateEscapeStatus());
149    autoEscapeOptions.setLogEscapedVariables(options.getLogEscapedVariables());
150    if (options.getCompileTemplates()) {
151      // Compiled templates.
152      TemplateCompiler compiler =
153          new TemplateCompiler(templateFactory, globalFunctions, autoEscapeOptions);
154      delegatingTemplateLoaders.add(compiler);
155      templateLoader = compiler;
156    } else {
157      // Walk parse tree every time.
158      InterpretedTemplateLoader interpreter =
159          new InterpretedTemplateLoader(templateFactory, globalFunctions, autoEscapeOptions);
160      delegatingTemplateLoaders.add(interpreter);
161      templateLoader = interpreter;
162    }
163
164    // Do we want to load precompiled Template class objects?
165    if (options.getPrecompiledTemplateMap() != null) {
166      // Load precompiled template classes.
167      PrecompiledTemplateLoader ptl =
168          new PrecompiledTemplateLoader(templateLoader, options.getPrecompiledTemplateMap(),
169              globalFunctions, autoEscapeOptions);
170      delegatingTemplateLoaders.add(ptl);
171      templateLoader = ptl;
172    }
173
174    for (DelegatingTemplateLoader loader : delegatingTemplateLoaders) {
175      loader.setTemplateLoaderDelegate(templateLoader);
176    }
177    this.templateLoader = templateLoader;
178  }
179
180  /**
181   * Applies optimizations to improve performance. These steps are entirely optional, and are not
182   * required for correctness.
183   */
184  private TemplateFactory setupOptimizerFactory(TemplateFactory templateFactory) {
185    // DataCommandConsolidator saves state so we need to create a new one
186    // every time we run it.
187    OptimizerProvider dataCommandConsolidatorProvider = new OptimizerProvider() {
188      public Switch getOptimizer() {
189        return new DataCommandConsolidator();
190      }
191    };
192
193    // SyntaxTreeOptimizer has no state so we can use the same object
194    // concurrently, but it is cheap to make so lets be consistent.
195    OptimizerProvider syntaxTreeOptimizerProvider = new OptimizerProvider() {
196      public Switch getOptimizer() {
197        return new SyntaxTreeOptimizer();
198      }
199    };
200
201    OptimizerProvider stripStructuralWhitespaceProvider = null;
202    if (options.getStripStructuralWhiteSpace()) {
203      // StructuralWhitespaceStripper has state so create a new one each time.
204      stripStructuralWhitespaceProvider = new OptimizerProvider() {
205        public Switch getOptimizer() {
206          return new StructuralWhitespaceStripper();
207        }
208      };
209    }
210
211    return new OptimizingTemplateFactory(templateFactory, dataCommandConsolidatorProvider,
212        syntaxTreeOptimizerProvider, stripStructuralWhitespaceProvider);
213  }
214
215  /**
216   * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g.
217   *        directory, classpath, memory, etc.
218   * @param cacheTemplates Whether to cache templates. Cached templates are much faster but do not
219   *        check the filesystem for updates. Use true in prod, false in dev.
220   * @deprecated Use {@link #JSilver(ResourceLoader, JSilverOptions)}.
221   */
222  @Deprecated
223  public JSilver(ResourceLoader defaultResourceLoader, boolean cacheTemplates) {
224    this(defaultResourceLoader, new JSilverOptions().setCacheTemplates(cacheTemplates));
225  }
226
227  /**
228   * Creates a JSilver instance with default options.
229   *
230   * @param defaultResourceLoader Where resources (templates, HDF files) should be loaded from. e.g.
231   *        directory, classpath, memory, etc.
232   * @see JSilverOptions
233   */
234  public JSilver(ResourceLoader defaultResourceLoader) {
235    this(defaultResourceLoader, new JSilverOptions());
236  }
237
238  /**
239   * Renders a given template and provided data, writing to an arbitrary output.
240   *
241   * @param templateName Name of template to load (e.g. "things/blah.cs").
242   * @param data Data to be used in template.
243   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
244   *        System.out/err), StringBuffer/StringBuilder or anything that implements Appendable
245   * @param resourceLoader How to find the template data to render and any included files it depends
246   *        on.
247   */
248  @Override
249  public void render(String templateName, Data data, Appendable output,
250      ResourceLoader resourceLoader) throws IOException, JSilverException {
251    EscapeMode escapeMode = getEscapeMode(data);
252    render(templateLoader.load(templateName, resourceLoader, escapeMode), data, output,
253        resourceLoader);
254  }
255
256  /**
257   * Renders a given template and provided data, writing to an arbitrary output.
258   *
259   * @param templateName Name of template to load (e.g. "things/blah.cs").
260   * @param data Data to be used in template.
261   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
262   *        System.out/err), StringBuffer/StringBuilder or anything that implements
263   */
264  @Override
265  public void render(String templateName, Data data, Appendable output) throws IOException,
266      JSilverException {
267    render(templateName, data, output, defaultResourceLoader);
268  }
269
270  /**
271   * Same as {@link TemplateRenderer#render(String, Data, Appendable)}, except returns rendered
272   * template as a String.
273   */
274  @Override
275  public String render(String templateName, Data data) throws IOException, JSilverException {
276    Appendable output = createAppendableBuffer();
277    try {
278      render(templateName, data, output);
279      return output.toString();
280    } finally {
281      releaseAppendableBuffer(output);
282    }
283  }
284
285  /**
286   * Renders a given template and provided data, writing to an arbitrary output.
287   *
288   * @param template Template to load.
289   * @param data Data to be used in template.
290   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
291   *        System.out/err), StringBuffer/StringBuilder or anything that implements
292   *        java.io.Appendable.
293   */
294  @Override
295  public void render(Template template, Data data, Appendable output, ResourceLoader resourceLoader)
296      throws IOException, JSilverException {
297    if (options.getStripHtmlWhiteSpace() && !(output instanceof HtmlWhiteSpaceStripper)) {
298      // Strip out whitespace from rendered HTML content.
299      output = new HtmlWhiteSpaceStripper(output);
300    }
301    template.render(data, output, resourceLoader);
302  }
303
304  /**
305   * Renders a given template and provided data, writing to an arbitrary output.
306   *
307   * @param template Template to load.
308   * @param data Data to be used in template.
309   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
310   *        System.out/err), StringBuffer/StringBuilder or anything that implements
311   *        java.io.Appendable.
312   */
313  @Override
314  public void render(Template template, Data data, Appendable output) throws IOException,
315      JSilverException {
316    render(template, data, output, defaultResourceLoader);
317  }
318
319  @Override
320  public String render(Template template, Data data) throws IOException, JSilverException {
321    Appendable output = createAppendableBuffer();
322    try {
323      render(template, data, output);
324      return output.toString();
325    } finally {
326      releaseAppendableBuffer(output);
327    }
328  }
329
330  /**
331   * Renders a given template from the content passed in. That is, the first parameter is the actual
332   * template content rather than the filename to load.
333   *
334   * @param content Content of template (e.g. "Hello &lt;cs var:name ?&gt;").
335   * @param data Data to be used in template.
336   * @param output Where template should be rendered to. This can be a Writer, PrintStream,
337   *        System.out/err), StringBuffer/StringBuilder or anything that implements
338   *        java.io.Appendable
339   */
340  @Override
341  public void renderFromContent(String content, Data data, Appendable output) throws IOException,
342      JSilverException {
343    EscapeMode escapeMode = getEscapeMode(data);
344    render(templateLoader.createTemp("[renderFromContent]", content, escapeMode), data, output);
345  }
346
347  /**
348   * Same as {@link #renderFromContent(String, Data, Appendable)}, except returns rendered template
349   * as a String.
350   */
351  @Override
352  public String renderFromContent(String content, Data data) throws IOException, JSilverException {
353    Appendable output = createAppendableBuffer();
354    try {
355      renderFromContent(content, data, output);
356      return output.toString();
357    } finally {
358      releaseAppendableBuffer(output);
359    }
360  }
361
362  /**
363   * Determine the escaping to apply based on Config variables in HDF. If there is no escaping
364   * specified in the HDF, check whether JSilverOptions has any escaping configured.
365   *
366   * @param data HDF Data to check
367   * @return EscapeMode
368   */
369  public EscapeMode getEscapeMode(Data data) {
370    EscapeMode escapeMode =
371        EscapeMode.computeEscapeMode(data.getValue(VAR_ESCAPE_MODE_KEY), data
372            .getBooleanValue(AUTO_ESCAPE_KEY));
373    if (escapeMode.equals(EscapeMode.ESCAPE_NONE)) {
374      escapeMode = options.getEscapeMode();
375    }
376
377    return escapeMode;
378  }
379
380  /**
381   * Override this to change the type of Appendable buffer used in {@link #render(String, Data)}.
382   */
383  public Appendable createAppendableBuffer() {
384    return outputBufferProvider.get();
385  }
386
387  public void releaseAppendableBuffer(Appendable buffer) {
388    outputBufferProvider.release(buffer);
389  }
390
391  /**
392   * Registers a global Function that can be used from any template.
393   */
394  public void registerGlobalFunction(String name, Function function) {
395    globalFunctions.registerFunction(name, function);
396  }
397
398  /**
399   * Registers a global TextFilter as function that can be used from any template.
400   */
401  public void registerGlobalFunction(String name, TextFilter textFilter) {
402    globalFunctions.registerFunction(name, textFilter);
403  }
404
405  /**
406   * Registers a global escaper. This also makes it available as a Function named with "_escape"
407   * suffix (e.g. "html_escape").
408   */
409  public void registerGlobalEscaper(String name, TextFilter escaper) {
410    globalFunctions.registerFunction(name + "_escape", escaper, true);
411    globalFunctions.registerEscapeMode(name, escaper);
412  }
413
414  /**
415   * Create new Data instance, ready to be populated.
416   */
417  public Data createData() {
418    return dataFactory.createData();
419  }
420
421  /**
422   * Loads data in Hierarchical Data Format (HDF) into an existing Data object.
423   */
424  @Override
425  public void loadData(String dataFileName, Data output) throws JSilverBadSyntaxException,
426      IOException {
427    dataFactory.loadData(dataFileName, defaultResourceLoader, output);
428  }
429
430  /**
431   * Loads data in Hierarchical Data Format (HDF) into a new Data object.
432   */
433  @Override
434  public Data loadData(String dataFileName) throws IOException {
435    return dataFactory.loadData(dataFileName, defaultResourceLoader);
436  }
437
438  /**
439   * Gets underlying ResourceLoader so you can access arbitrary files using the same mechanism as
440   * JSilver.
441   */
442  public ResourceLoader getResourceLoader() {
443    return defaultResourceLoader;
444  }
445
446  /**
447   * Force all cached templates to be cleared.
448   */
449  public void clearCache() {
450
451  }
452
453  /**
454   * Returns the TemplateLoader used by this JSilver template renderer. Needed for HDF/CS
455   * compatbility.
456   */
457  public TemplateLoader getTemplateLoader() {
458    return templateLoader;
459  }
460
461  /**
462   * Returns a copy of the JSilverOptions used by this JSilver instance.
463   */
464  public JSilverOptions getOptions() {
465    return options.clone();
466  }
467}
468