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