📄 environment.java
字号:
/*
* Copyright (c) 2003 The Visigoth Software Society. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowledgement:
* "This product includes software developed by the
* Visigoth Software Society (http://www.visigoths.org/)."
* Alternately, this acknowledgement may appear in the software itself,
* if and wherever such third-party acknowledgements normally appear.
*
* 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
* project contributors may be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact visigoths@visigoths.org.
*
* 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
* nor may "FreeMarker" or "Visigoth" appear in their names
* without prior written permission of the Visigoth Software Society.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Visigoth Software Society. For more
* information on the Visigoth Software Society, please see
* http://www.visigoths.org/
*/
package freemarker.core;
import java.io.*;
import java.text.*;
import java.util.*;
import freemarker.ext.beans.BeansWrapper;
import freemarker.log.Logger;
import freemarker.template.*;
import freemarker.template.utility.UndeclaredThrowableException;
/**
* Object that represents the runtime environment during template processing.
* For every invocation of a <tt>Template.process()</tt> method, a new instance
* of this object is created, and then discarded when <tt>process()</tt> returns.
* This object stores the set of temporary variables created by the template,
* the value of settings set by the template, the reference to the data model root,
* etc. Everything that is needed to fulfill the template processing job.
*
* <p>Data models that need to access the <tt>Environment</tt>
* object that represents the template processing on the current thread can use
* the {@link #getCurrentEnvironment()} method.
*
* <p>If you need to modify or read this object before or after the <tt>process</tt>
* call, use {@link Template#createProcessingEnvironment(Object rootMap, Writer out, ObjectWrapper wrapper)}
*
* @author <a href="mailto:jon@revusky.com">Jonathan Revusky</a>
* @author Attila Szegedi
*/
public final class Environment extends Configurable {
private static final ThreadLocal threadEnv = new ThreadLocal();
private static final Logger logger = Logger.getLogger("freemarker.runtime");
private static final Map localizedNumberFormats = new HashMap();
private static final Map localizedDateFormats = new HashMap();
private final TemplateHashModel rootDataModel;
private final ArrayList elementStack = new ArrayList();
private final ArrayList recoveredErrorStack = new ArrayList();
private NumberFormat numberFormat;
private Map numberFormats;
private DateFormat timeFormat, dateFormat, dateTimeFormat;
private Map[] dateFormats;
private Collator collator;
private Writer out;
private Macro.Context currentMacroContext;
private ArrayList localContextStack;
private Namespace mainNamespace, currentNamespace, globalNamespace;
private HashMap loadedLibs;
private Throwable lastThrowable;
private TemplateModel lastReturnValue;
private HashMap macroToNamespaceLookup = new HashMap();
private TemplateNodeModel currentVisitorNode;
private TemplateSequenceModel nodeNamespaces;
// Things we keep track of for the fallback mechanism.
private int nodeNamespaceIndex;
private String currentNodeName, currentNodeNS;
private String cachedURLEscapingCharset;
private boolean urlEscapingCharsetCached;
/**
* Retrieves the environment object associated with the current
* thread. Data model implementations that need access to the
* environment can call this method to obtain the environment object
* that represents the template processing that is currently running
* on the current thread.
*/
public static Environment getCurrentEnvironment()
{
return (Environment)threadEnv.get();
}
public Environment(Template template, final TemplateHashModel rootDataModel, Writer out)
{
super(template);
this.globalNamespace = new Namespace(null);
this.currentNamespace = mainNamespace = new Namespace(template);
this.out = out;
this.rootDataModel = rootDataModel;
importMacros(template);
}
/**
* Retrieves the currently processed template.
*/
public Template getTemplate()
{
return (Template)getParent();
}
/**
* Deletes cached values that meant to be valid only during a single
* template execution.
*/
private void clearCachedValues() {
numberFormats = null;
numberFormat = null;
dateFormats = null;
collator = null;
cachedURLEscapingCharset = null;
urlEscapingCharsetCached = false;
}
/**
* Processes the template to which this environment belongs.
*/
public void process() throws TemplateException, IOException {
Object savedEnv = threadEnv.get();
threadEnv.set(this);
try {
// Cached values from a previous execution are possibly outdated.
clearCachedValues();
try {
visit(getTemplate().getRootTreeNode());
// Do not flush if there was an exception.
out.flush();
} finally {
// It's just to allow the GC to free memory...
clearCachedValues();
}
} finally {
threadEnv.set(savedEnv);
}
}
/**
* "Visit" the template element.
*/
void visit(TemplateElement element)
throws TemplateException, IOException
{
pushElement(element);
try {
element.accept(this);
}
catch (TemplateException te) {
handleTemplateException(te);
}
finally {
popElement();
}
}
/**
* "Visit" the template element, passing the output
* through a TemplateTransformModel
* @param element the element to visit through a transform
* @param transform the transform to pass the element output
* through
* @param args optional arguments fed to the transform
*/
void visit(TemplateElement element,
TemplateTransformModel transform,
Map args)
throws TemplateException, IOException
{
try {
Writer tw = transform.getWriter(out, args);
if (tw == null) tw = EMPTY_BODY_WRITER;
TransformControl tc =
tw instanceof TransformControl
? (TransformControl)tw
: null;
Writer prevOut = out;
out = tw;
try {
if(tc == null || tc.onStart() != TransformControl.SKIP_BODY) {
do {
if(element != null) {
visit(element);
}
} while(tc != null && tc.afterBody() == TransformControl.REPEAT_EVALUATION);
}
}
catch(Throwable t) {
try {
if(tc != null) {
tc.onError(t);
}
else {
throw t;
}
}
catch(TemplateException e) {
throw e;
}
catch(IOException e) {
throw e;
}
catch(RuntimeException e) {
throw e;
}
catch(Error e) {
throw e;
}
catch(Throwable e) {
throw new UndeclaredThrowableException(e);
}
}
finally {
out = prevOut;
tw.close();
}
}
catch(TemplateException te) {
handleTemplateException(te);
}
}
/**
* Visit a block using buffering/recovery
*/
void visit(TemplateElement attemptBlock, TemplateElement recoveryBlock)
throws TemplateException, IOException {
Writer prevOut = this.out;
StringWriter sw = new StringWriter();
this.out = sw;
TemplateException thrownException = null;
try {
visit(attemptBlock);
} catch (TemplateException te) {
thrownException = te;
} finally {
this.out = prevOut;
}
if (thrownException != null) {
if (logger.isErrorEnabled()) {
String msg = "Error in attempt block " + attemptBlock.getStartLocation();
logger.error(msg, thrownException);
}
try {
recoveredErrorStack.add(thrownException.getMessage());
visit(recoveryBlock);
} finally {
recoveredErrorStack.remove(recoveredErrorStack.size() -1);
}
} else {
out.write(sw.toString());
}
}
String getCurrentRecoveredErrorMesssage() throws TemplateException {
if(recoveredErrorStack.isEmpty()) {
throw new TemplateException(
".error is not available outside of a <#recover> block", this);
}
return (String) recoveredErrorStack.get(recoveredErrorStack.size() -1);
}
void visit(BodyInstruction.Context bctxt) throws TemplateException, IOException {
Macro.Context invokingMacroContext = getCurrentMacroContext();
ArrayList prevLocalContextStack = localContextStack;
TemplateElement body = invokingMacroContext.body;
if (body != null) {
this.currentMacroContext = invokingMacroContext.prevMacroContext;
currentNamespace = invokingMacroContext.bodyNamespace;
Configurable prevParent = getParent();
setParent(currentNamespace.getTemplate());
this.localContextStack = invokingMacroContext.prevLocalContextStack;
if (invokingMacroContext.bodyParameterNames != null) {
pushLocalContext(bctxt);
}
try {
visit(body);
}
finally {
if (invokingMacroContext.bodyParameterNames != null) {
popLocalContext();
}
this.currentMacroContext = invokingMacroContext;
currentNamespace = getMacroNamespace(invokingMacroContext.getMacro());
setParent(prevParent);
this.localContextStack = prevLocalContextStack;
}
}
}
/**
* "visit" an IteratorBlock
*/
void visit(IteratorBlock.Context ictxt)
throws TemplateException, IOException
{
pushLocalContext(ictxt);
try {
ictxt.runLoop(this);
}
catch (BreakInstruction.Break br) {
}
catch (TemplateException te) {
handleTemplateException(te);
}
finally {
popLocalContext();
}
}
/**
* "Visit" A TemplateNodeModel
*/
void visit(TemplateNodeModel node, TemplateSequenceModel namespaces)
throws TemplateException, IOException
{
if (nodeNamespaces == null) {
SimpleSequence ss = new SimpleSequence(1);
ss.add(currentNamespace);
nodeNamespaces = ss;
}
int prevNodeNamespaceIndex = this.nodeNamespaceIndex;
String prevNodeName = this.currentNodeName;
String prevNodeNS = this.currentNodeNS;
TemplateSequenceModel prevNodeNamespaces = nodeNamespaces;
TemplateNodeModel prevVisitorNode = currentVisitorNode;
currentVisitorNode = node;
if (namespaces != null) {
this.nodeNamespaces = namespaces;
}
try {
TemplateModel macroOrTransform = getNodeProcessor(node);
if (macroOrTransform instanceof Macro) {
visit((Macro) macroOrTransform, null, null, null, null);
}
else if (macroOrTransform instanceof TemplateTransformModel) {
visit(null, (TemplateTransformModel) macroOrTransform, null);
}
else {
String nodeType = node.getNodeType();
if (nodeType != null) {
// If the node's type is 'text', we just output it.
if ((nodeType.equals("text") && node instanceof TemplateScalarModel))
{
out.write(((TemplateScalarModel) node).getAsString());
}
else if (nodeType.equals("document")) {
recurse(node, namespaces);
}
// We complain here, unless the node's type is 'pi', or "comment" or "document_type", in which case
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -