/* * FreeMarker: a tool that allows Java programs to generate HTML * output using templates. * Copyright (C) 1998-2005 Benjamin Geer * Email: beroul@users.sourceforge.net * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package freemarker.template; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamField; import java.io.PrintWriter; import java.io.Reader; import java.io.Serializable; import java.io.Writer; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import freemarker.template.compiler.LinkedListTemplateBuilder; import freemarker.template.compiler.ParseException; import freemarker.template.compiler.StandardTemplateParser; import freemarker.template.instruction.TemplateFunctionModel; /** * <p> * An application or servlet can instantiate a <code>Template</code> to compile * and process an HTML template. * </p> * * <p> * You can pass the filename of the template to the constructor, in which case * it is compiled immediately. Once compiled, the template is stored in an an * efficient data structure for later use. * </p> * * <p> * To process the template and produce HTML, call the * {@link #process(TemplateWriteableHashModel, Writer)} method, which takes a * tree of {@link TemplateModel} objects as its data model. The root node of the * tree must be a {@link TemplateWriteableHashModel}. * </p> * * <p> * Any error messages from exceptions thrown by the data model, or generated by * the <code>Template</code> during compilation or processing, will be included * as HTML comments in the output. * </p> * * <p> * To facilitate multithreading, <code>Template</code> objects are immutable; if * you need to recompile a template, you must make a new <code>Template</code> * object. In most cases, it will be sufficient to let a {@link TemplateCache} * do this for you. * </p> * * @see TemplateCache * @version $Id: Template.java 1144 2005-10-09 06:31:56Z run2000 $ */ public class Template extends AbstractTemplate implements FunctionTemplateProcessor, Serializable { /** The root node of the compiled template. */ protected TemplateProcessor compiledTemplate; /** A mapping of all function models in this compiled template. */ protected Map functions; /** A cached copy of any parser exception thrown during compilation. */ protected transient ParseException buildError; /** Serialization UUID for this class. */ private static final long serialVersionUID = 2146772088091073346L; /** * Serialized form is a TemplateProcessor holding the parsed template tree, * followed by an array of FunctionInstructions. The array contains * functions to be evaluated at runtime if any functions are called. This is * done primarily for type correctness, and avoids serializing a Map object. * * @serialField * compiledTemplate TemplateProcessor object holding the * entire compiled template parse tree * @serialField * functionNames String[] an array of function names * @serialField * functions TemplateFunctionModel[] an array of function * models associated with the names */ private static final ObjectStreamField[] serialPersistentFields = { new ObjectStreamField("compiledTemplate", TemplateProcessor.class), new ObjectStreamField("functionNames", String[].class), new ObjectStreamField("functions", TemplateFunctionModel[].class) }; /** * Constructs an empty template. */ public Template() { functions = new HashMap(); } /** * Constructs a template by compiling it from an InputSource. Calls * <code>compile()</code>. * * @param source * the source of the template file to be compiled. */ public Template(InputSource source) throws IOException { super(source); } /** * Constructs a template by compiling it from a file. Calls * <code>compileFromFile()</code>. * * @param filePath * the absolute path of the template file to be compiled. * @deprecated use the {@link InputSource} contructor to supply source * streams to the template compiler */ public Template(String filePath) throws IOException { super(filePath); } /** * Constructs a template by compiling it from a file. Calls * <code>compileFromFile()</code>. * * @param file * a <code>File</code> representing the template file to be * compiled. * @deprecated use the {@link InputSource} contructor to supply source * streams to the template compiler */ public Template(File file) throws IOException { super(file); } /** * Constructs a template by compiling it from an <code>InputStream</code>. * Calls <code>compileFromStream()</code>. * * @param stream * an <tt>InputStream</tt> from which the template can be read. * @deprecated use the {@link InputSource} contructor to supply source * streams to the template compiler */ public Template(InputStream stream) throws IOException { super(stream); } /** * Constructs a template by compiling it from an <code>Reader</code>. Calls * <code>compileFromStream()</code>. * * @param stream * a <code>Reader</code> from which the template can be read. * @deprecated use the {@link InputSource} contructor to supply source * streams to the template compiler */ public Template(Reader stream) throws IOException { super(stream); } /** * Compiles the template from an <code>InputSource</code>. If the template * has already been compiled, this method does nothing. Calls * {@link #compileFromStream(java.io.Reader)} to perform parsing. * * @param source * an <code>InputSource</code> from which the template can be * read. */ public void compile(InputSource source) throws IOException, IllegalArgumentException { synchronized (this) { if (compiledTemplate != null) { return; } if (functions == null) { functions = new HashMap(); } } TemplateProcessor newTemplate = compileText(source); synchronized (this) { compiledTemplate = newTemplate; } } /** * Compiles the template text using the standard parser and builder classes. * To provide different compilation behavior, subclasses need only override * this method. * * @param source * the text to compile. * @return a <code>TemplateProcessor</code> representing the compiled * template. */ protected TemplateProcessor compileText(InputSource source) throws IOException { StandardTemplateParser parser = new StandardTemplateParser(this, source); LinkedListTemplateBuilder builder = new LinkedListTemplateBuilder(this, parser); try { return builder.build(); } catch (ParseException e) { buildError = e; return null; } } /** * Adds a function to the template. Called by the * <code>TemplateBuilder</code> at compile-time. * * @param name * the name of the function to be stored * @param function * the function to be stored by the template * @throws IllegalStateException * the method has been called after the template has been * compiled */ public synchronized void addFunction(String name, TemplateFunctionModel function) throws IllegalStateException { if (compiledTemplate != null) { throw new IllegalStateException("Template has already been compiled"); } functions.put(name, function); } /** * Retrieves a function from the template. Called by * <code>CallInstruction</code>s and <code>IncludeInstruction</code>s at * run-time. * * @param name * the name of the function to be retrieved */ public TemplateFunctionModel getFunction(String name) { return (TemplateFunctionModel) functions.get(name); } /** * Retrieve a <code>Set</code> of function names for this template. * * @return a <code>Set</code> of function names (<code>String</code> * objects) that have been defined for this template. */ public Set getFunctionNames() { return functions.keySet(); } /** * Processes the contents of this {@link TemplateProcessor} and outputs the * resulting text to a <code>PrintWriter</code>. * * @param modelRoot * the root node of the data model. * @param out * a <code>PrintWriter</code> to send the output to. * @param eventHandler * a <code>TemplateEventAdapter</code> for handling any events * that occur during processing. */ public void process(TemplateWriteableHashModel modelRoot, PrintWriter out, TemplateRuntimeHandler eventHandler) { try { process(modelRoot, (Writer) out, eventHandler); } catch (IOException e) { // Do nothing, since PrintWriter cannot throw IOException } } /** * Processes the template, using data from a template model, and outputs the * resulting text to a <code>PrintWriter</code>. * * @param modelRoot * the root node of the data model. If <code>null</code>, an * empty data model is used. * @param out * a <code>PrintWriter</code> to send the output to. */ public void process(TemplateWriteableHashModel modelRoot, PrintWriter out) { try { process(modelRoot, (Writer) out); } catch (IOException e) { // Do nothing, since PrintWriter cannot throw IOException } } /** * Processes the template, using an empty data model, and outputs the * resulting text to a <code>PrintWriter</code>. * * @param out * a <code>PrintWriter</code> to send the output to. */ public void process(PrintWriter out) { try { process((Writer) out); } catch (IOException e) { // Do nothing, since PrintWriter cannot throw IOException } } /** * Processes the contents of this {@link TemplateProcessor} and outputs the * resulting text to a <code>Writer</code>. * * @param modelRoot * the root node of the data model. * @param out * a <code>Writer</code> to send the output to. * @param eventHandler * a <code>TemplateEventAdapter</code> for handling any events * that occur during processing. * @return a zero or positive value if template processing was successful, * otherwise a negative number to indicate that no template has been * compiled * @throws IOException * an IO error occurred with the <code>Writer</code> during * processing */ public short process(TemplateWriteableHashModel modelRoot, Writer out, TemplateRuntimeHandler eventHandler) throws IOException { if (compiledTemplate != null) { if (modelRoot == null) { modelRoot = new FastHash(); } if ((functions != null) && (!functions.isEmpty())) { try { copyFunctions(this, modelRoot); } catch (TemplateModelException e) { eventHandler.fireExceptionThrown(this, new TemplateException("Couldn't copy functions into the model", e), out, "freemarker.template.Template.process", TemplateRuntimeHandler.SEVERITY_ERROR); } } return compiledTemplate.process(modelRoot, out, eventHandler); } else if (buildError != null) { eventHandler.fireExceptionThrown(this, buildError, out, "freemarker.template.Template.process", TemplateRuntimeHandler.SEVERITY_ERROR); } return TemplateProcessor.UNCOMPILED_TEMPLATE; } /** * Processes the template, using data from a template model, and outputs the * resulting text to a <code>Writer</code>. * * @param modelRoot * the root node of the data model. If <code>null</code>, an * empty data model is used. * @param out * a <code>Writer</code> to output the text to. */ public void process(TemplateWriteableHashModel modelRoot, Writer out) throws IOException { process(modelRoot, out, TemplateEventAdapter.DefaultEventAdapter); } /** * Processes the template, using an empty data model, and outputs the * resulting text to a <code>Writer</code>. * * @param out * a <code>Writer</code> to output the text to. */ public void process(Writer out) throws IOException { TemplateWriteableHashModel modelRoot = new FastHash(); process(modelRoot, out, TemplateEventAdapter.DefaultEventAdapter); } /** * <p> * Clones the current template. A shallow clone is performed, meaning that * changes to the underlying structure of one will affect the structure of * the other. As Templates are immutable anyway, this shouldn't be an issue. * </p> * * <p> * Cloning is used in {@link freemarker.template.cache.Cache}s, whenever we * need to create a new template: rather than simply creating a new * <code>Template</code>, we ask a * {@link freemarker.template.cache.TemplateRegistry} to create one for us. * <code>TemplateRegistry</code> uses the clone function to take an existing * template, copy it, and return the copy to the cache, where it is then * populated. * </p> * * @return a shallow clone of the current template. */ public Object clone() { Template newTemplate = (Template) super.clone(); if (functions != null) { newTemplate.functions = (Map) ((HashMap) functions).clone(); } return newTemplate; } /** * Copies functions from a template into a data model. This lets functions * from parent and child templates be available in both contexts. * * @param template * the template from which function instructions will be copied * @param modelRoot * the template model where functions will be copied to * @throws TemplateModelException * the model could not accept the function */ protected static void copyFunctions(FunctionTemplateProcessor template, TemplateWriteableHashModel modelRoot) throws TemplateModelException { Set functionNames = template.getFunctionNames(); Iterator iterator = functionNames.iterator(); while (iterator.hasNext()) { String functionName = (String) iterator.next(); TemplateFunctionModel function = template.getFunction(functionName); modelRoot.put(functionName, function); } } /** * For serialization, write this object a TemplateProcessor containing the * compiled template, and an arrays. The array contains the * FunctionInstructions that may be called from the template. * * @param stream * the output stream to write this object to */ private void writeObject(ObjectOutputStream stream) throws IOException { ObjectOutputStream.PutField fields = stream.putFields(); int size = (functions == null) ? 0 : functions.size(); TemplateFunctionModel[] functionArray = new TemplateFunctionModel[size]; String[] nameArray = new String[size]; if (functions != null) { Iterator iValues = functions.keySet().iterator(); int i = 0; while (iValues.hasNext()) { String name = (String) iValues.next(); nameArray[i] = name; functionArray[i] = (TemplateFunctionModel) functions.get(name); i++; } } fields.put("compiledTemplate", compiledTemplate); fields.put("functionNames", nameArray); fields.put("functions", functionArray); stream.writeFields(); } /** * For serialization, read this object a TemplateProcessor containing the * compiled template, and an array of FunctionInstructions. These may be * called from the template at runtime if any functions were defined in the * template source. Check whether the function list is null. * * @param stream * the input stream to read serialized objects from */ private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { ObjectInputStream.GetField fields = stream.readFields(); compiledTemplate = (TemplateProcessor) fields.get("compiledTemplate", null); String[] functionNames = (String[]) fields.get("functionNames", null); TemplateFunctionModel[] functionValues = (TemplateFunctionModel[]) fields.get("functions", null); if (functionValues == null) { throw new InvalidObjectException("Cannot create a Template with a null function list"); } functions = new HashMap(); int size = functionValues.length; for (int i = 0; i < size; i++) { String name = functionNames[i]; TemplateFunctionModel functionValue = functionValues[i]; functions.put(name, functionValue); } } /** * Returns a string representation of the object. The value returned * represents the parse tree of the template. * * @return a string representation of the object. */ public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("FreeMarker template, "); buffer.append(compiledTemplate); return buffer.toString(); } }