/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package freemarker.core;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.Collator;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import freemarker.cache.TemplateNameFormat;
import freemarker.cache._CacheAPI;
import freemarker.ext.beans.BeansWrapper;
import freemarker.log.Logger;
import freemarker.template.Configuration;
import freemarker.template.MalformedTemplateNameException;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleHash;
import freemarker.template.SimpleSequence;
import freemarker.template.Template;
import freemarker.template.TemplateCollectionModel;
import freemarker.template.TemplateDateModel;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateHashModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateModelIterator;
import freemarker.template.TemplateNodeModel;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.TemplateTransformModel;
import freemarker.template.TransformControl;
import freemarker.template._TemplateAPI;
import freemarker.template.utility.DateUtil;
import freemarker.template.utility.DateUtil.DateToISO8601CalendarFactory;
import freemarker.template.utility.NullWriter;
import freemarker.template.utility.StringUtil;
import freemarker.template.utility.UndeclaredThrowableException;

Object that represents the runtime environment during template processing. For every invocation of a Template.process() method, a new instance of this object is created, and then discarded when process() 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.

Data models that need to access the Environment object that represents the template processing on the current thread can use the getCurrentEnvironment() method.

If you need to modify or read this object before or after the process call, use Template.createProcessingEnvironment(Object rootMap, Writer out, ObjectWrapper wrapper)

/** * 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)} */
public final class Environment extends Configurable { private static final ThreadLocal threadEnv = new ThreadLocal(); private static final Logger LOG = Logger.getLogger("freemarker.runtime"); private static final Logger ATTEMPT_LOGGER = Logger.getLogger("freemarker.runtime.attempt"); // Do not use this object directly; clone it first! DecimalFormat isn't // thread-safe. private static final DecimalFormat C_NUMBER_FORMAT = new DecimalFormat( "0.################", new DecimalFormatSymbols(Locale.US)); static { C_NUMBER_FORMAT.setGroupingUsed(false); C_NUMBER_FORMAT.setDecimalSeparatorAlwaysShown(false); } private final Configuration configuration; private final boolean incompatibleImprovementsGE2328; private final TemplateHashModel rootDataModel; private TemplateElement[] instructionStack = new TemplateElement[16]; private int instructionStackSize = 0; private final ArrayList recoveredErrorStack = new ArrayList(); private TemplateNumberFormat cachedTemplateNumberFormat; private Map<String, TemplateNumberFormat> cachedTemplateNumberFormats;
Stores the date/time/date-time formatters that are used when no format is explicitly given at the place of formatting. That is, in situations like ${lastModified} or even ${lastModified?date}, but not in situations like ${lastModified?string.iso}.

The index of the array is calculated from what kind of formatter we want (see getTemplateDateFormatCacheArrayIndex(int, boolean, boolean)):
Zoned input: 0: U, 1: T, 2: D, 3: DT
Zoneless input: 4: U, 5: T, 6: D, 7: DT
SQL D T TZ + Zoned input: 8: U, 9: T, 10: D, 11: DT
SQL D T TZ + Zoneless input: 12: U, 13: T, 14: D, 15: DT

This is a lazily filled cache. It starts out as null, then when first needed the array will be created. The array elements also start out as null-s, and they are filled as the particular kind of formatter is first needed.

/** * Stores the date/time/date-time formatters that are used when no format is explicitly given at the place of * formatting. That is, in situations like ${lastModified} or even ${lastModified?date}, but not in situations like * ${lastModified?string.iso}. * * <p> * The index of the array is calculated from what kind of formatter we want (see * {@link #getTemplateDateFormatCacheArrayIndex(int, boolean, boolean)}):<br> * Zoned input: 0: U, 1: T, 2: D, 3: DT<br> * Zoneless input: 4: U, 5: T, 6: D, 7: DT<br> * SQL D T TZ + Zoned input: 8: U, 9: T, 10: D, 11: DT<br> * SQL D T TZ + Zoneless input: 12: U, 13: T, 14: D, 15: DT * * <p> * This is a lazily filled cache. It starts out as {@code null}, then when first needed the array will be created. * The array elements also start out as {@code null}-s, and they are filled as the particular kind of formatter is * first needed. */
private TemplateDateFormat[] cachedTempDateFormatArray;
Similar to cachedTempDateFormatArray, but used when a formatting string was specified.
/** Similar to {@link #cachedTempDateFormatArray}, but used when a formatting string was specified. */
private HashMap<String, TemplateDateFormat>[] cachedTempDateFormatsByFmtStrArray; private static final int CACHED_TDFS_ZONELESS_INPUT_OFFS = 4; private static final int CACHED_TDFS_SQL_D_T_TZ_OFFS = CACHED_TDFS_ZONELESS_INPUT_OFFS * 2; private static final int CACHED_TDFS_LENGTH = CACHED_TDFS_SQL_D_T_TZ_OFFS * 2; /** Caches the result of {@link #isSQLDateAndTimeTimeZoneSameAsNormal()}. */ private Boolean cachedSQLDateAndTimeTimeZoneSameAsNormal; private NumberFormat cNumberFormat;
Used by the "iso_" built-ins to accelerate formatting.
See Also:
  • getISOBuiltInCalendarFactory()
/** * Used by the "iso_" built-ins to accelerate formatting. * * @see #getISOBuiltInCalendarFactory() */
private DateToISO8601CalendarFactory isoBuiltInCalendarFactory; private Collator cachedCollator; private Writer out; private Macro.Context currentMacroContext; private LocalContextStack localContextStack; private final Namespace mainNamespace; private Namespace currentNamespace, globalNamespace; private HashMap<String, Namespace> loadedLibs; private Configurable legacyParent; private boolean inAttemptBlock; 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 cachedURLEscapingCharsetSet; private boolean fastInvalidReferenceExceptions;
Retrieves the environment object associated with the current thread, or null if there's no template processing going on in this 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.
/** * Retrieves the environment object associated with the current thread, or {@code null} if there's no template * processing going on in this 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(); } static void setCurrentEnvironment(Environment env) { threadEnv.set(env); } public Environment(Template template, final TemplateHashModel rootDataModel, Writer out) { super(template); configuration = template.getConfiguration(); incompatibleImprovementsGE2328 = configuration.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_28; this.globalNamespace = new Namespace(null); this.currentNamespace = mainNamespace = new Namespace(template); this.out = out; this.rootDataModel = rootDataModel; importMacros(template); }
Despite its name it just returns Configurable.getParent(). If Configuration.getIncompatibleImprovements() is at least 2.3.22, then that will be the same as getMainTemplate(). Otherwise the returned value follows the Environment parent switchings that occur at #include/#import and #nested directive calls, that is, it's not very meaningful outside FreeMarker internals.
Deprecated:Use getMainTemplate() instead (or getCurrentNamespace() and then Namespace.getTemplate()); the value returned by this method is often not what you expect when it comes to macro/function invocations.
/** * Despite its name it just returns {@link #getParent()}. If {@link Configuration#getIncompatibleImprovements()} is * at least 2.3.22, then that will be the same as {@link #getMainTemplate()}. Otherwise the returned value follows * the {@link Environment} parent switchings that occur at {@code #include}/{@code #import} and {@code #nested} * directive calls, that is, it's not very meaningful outside FreeMarker internals. * * @deprecated Use {@link #getMainTemplate()} instead (or {@link #getCurrentNamespace()} and then * {@link Namespace#getTemplate()}); the value returned by this method is often not what you expect when * it comes to macro/function invocations. */
@Deprecated public Template getTemplate() { return (Template) getParent(); }
Returns the same value as pre-IcI 2.3.22 getTemplate() did.
/** Returns the same value as pre-IcI 2.3.22 getTemplate() did. */
Template getTemplate230() { Template legacyParent = (Template) this.legacyParent; return legacyParent != null ? legacyParent : getTemplate(); }
Returns the topmost Template, with other words, the one for which this Environment was created. That template will never change, like #include or macro calls don't change it. This method never returns null.
See Also:
Since:2.3.22
/** * Returns the topmost {@link Template}, with other words, the one for which this {@link Environment} was created. * That template will never change, like {@code #include} or macro calls don't change it. This method never returns * {@code null}. * * @see #getCurrentNamespace() * * @since 2.3.22 */
public Template getMainTemplate() { return mainNamespace.getTemplate(); }
Returns the Template that we are "lexically" inside at the moment. This template will change when entering an #include or calling a macro or function in another template, or returning to yet another template with #nested. When you are calling a directive that's implemented in Java or a Java method from a template, the current template will be the last current template, not null. This method never returns null.
See Also:
Since:2.3.23
/** * Returns the {@link Template} that we are "lexically" inside at the moment. This template will change when * entering an {@code #include} or calling a macro or function in another template, or returning to yet another * template with {@code #nested}. When you are calling a directive that's implemented in Java or a Java method * from a template, the current template will be the last current template, not {@code null}. This method never * returns {@code null}. * * @see #getMainTemplate() * @see #getCurrentNamespace() * * @since 2.3.23 */
@SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "False alarm") public Template getCurrentTemplate() { int ln = instructionStackSize; return ln == 0 ? getMainTemplate() : instructionStack[ln - 1].getTemplate(); }
Gets the currently executing custom directive's call place information, or null if there's no executing custom directive. This currently only works for calls made from templates with the <@...> syntax. This should only be called from the TemplateDirectiveModel that was invoked with <@...>, otherwise its return value is not defined by this API (it's usually null).
Since:2.3.22
/** * Gets the currently executing <em>custom</em> directive's call place information, or {@code null} if there's no * executing custom directive. This currently only works for calls made from templates with the {@code <@...>} * syntax. This should only be called from the {@link TemplateDirectiveModel} that was invoked with {@code <@...>}, * otherwise its return value is not defined by this API (it's usually {@code null}). * * @since 2.3.22 */
@SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "False alarm") public DirectiveCallPlace getCurrentDirectiveCallPlace() { int ln = instructionStackSize; if (ln == 0) return null; TemplateElement te = instructionStack[ln - 1]; if (te instanceof UnifiedCall) return (UnifiedCall) te; if (te instanceof Macro && ln > 1 && instructionStack[ln - 2] instanceof UnifiedCall) { return (UnifiedCall) instructionStack[ln - 2]; } return null; }
Deletes cached values that meant to be valid only during a single template execution.
/** * Deletes cached values that meant to be valid only during a single template execution. */
private void clearCachedValues() { cachedTemplateNumberFormats = null; cachedTemplateNumberFormat = null; cachedTempDateFormatArray = null; cachedTempDateFormatsByFmtStrArray = null; cachedCollator = null; cachedURLEscapingCharset = null; cachedURLEscapingCharsetSet = false; }
Processes the template to which this environment belongs to.
/** * Processes the template to which this environment belongs to. */
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 { doAutoImportsAndIncludes(this); visit(getTemplate().getRootTreeNode()); // It's here as we must not flush if there was an exception. if (getAutoFlush()) { out.flush(); } } finally { // It's just to allow the GC to free memory... clearCachedValues(); } } finally { threadEnv.set(savedEnv); } }
"Visit" the template element.
/** * "Visit" the template element. */
void visit(TemplateElement element) throws IOException, TemplateException { // ATTENTION: This method body is manually "inlined" into visit(TemplateElement[]); keep them in sync! pushElement(element); try { TemplateElement[] templateElementsToVisit = element.accept(this); if (templateElementsToVisit != null) { for (TemplateElement el : templateElementsToVisit) { if (el == null) { break; // Skip unused trailing buffer capacity } visit(el); } } } catch (TemplateException te) { handleTemplateException(te); } finally { popElement(); } // ATTENTION: This method body above is manually "inlined" into visit(TemplateElement[]); keep them in sync! }
Params:
  • elementBuffer – The elements to visit; might contains trailing null-s. Can be null.
Since:2.3.24
/** * @param elementBuffer * The elements to visit; might contains trailing {@code null}-s. Can be {@code null}. * * @since 2.3.24 */
final void visit(TemplateElement[] elementBuffer) throws IOException, TemplateException { if (elementBuffer == null) { return; } for (TemplateElement element : elementBuffer) { if (element == null) { break; // Skip unused trailing buffer capacity } // ATTENTION: This part is the manually "inlining" of visit(TemplateElement[]); keep them in sync! // We don't just let Hotspot to do it, as we want a hard guarantee regarding maximum stack usage. pushElement(element); try { TemplateElement[] templateElementsToVisit = element.accept(this); if (templateElementsToVisit != null) { for (TemplateElement el : templateElementsToVisit) { if (el == null) { break; // Skip unused trailing buffer capacity } visit(el); } } } catch (TemplateException te) { handleTemplateException(te); } finally { popElement(); } // ATTENTION: This part above is the manually "inlining" of visit(TemplateElement[]); keep them in sync! } }
Visits the elements while temporarily using the parameter output Writer.
Since:2.3.27
/** * Visits the elements while temporarily using the parameter output {@link Writer}. * * @since 2.3.27 */
final void visit(TemplateElement[] elementBuffer, Writer out) throws IOException, TemplateException { Writer prevOut = this.out; this.out = out; try { visit(elementBuffer); } finally { this.out = prevOut; } } @SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "Not called when stack is empty") private TemplateElement replaceTopElement(TemplateElement element) { return instructionStack[instructionStackSize - 1] = element; } private static final TemplateModel[] NO_OUT_ARGS = new TemplateModel[0];
Deprecated:Should be internal API
/** * @deprecated Should be internal API */
@Deprecated public void visit(final TemplateElement element, TemplateDirectiveModel directiveModel, Map args, final List bodyParameterNames) throws TemplateException, IOException { visit(new TemplateElement[] { element }, directiveModel, args, bodyParameterNames); } void visit(final TemplateElement[] childBuffer, TemplateDirectiveModel directiveModel, Map args, final List bodyParameterNames) throws TemplateException, IOException { TemplateDirectiveBody nested; if (childBuffer == null) { nested = null; } else { nested = new NestedElementTemplateDirectiveBody(childBuffer); } final TemplateModel[] outArgs; if (bodyParameterNames == null || bodyParameterNames.isEmpty()) { outArgs = NO_OUT_ARGS; } else { outArgs = new TemplateModel[bodyParameterNames.size()]; } if (outArgs.length > 0) { pushLocalContext(new LocalContext() { public TemplateModel getLocalVariable(String name) { int index = bodyParameterNames.indexOf(name); return index != -1 ? outArgs[index] : null; } public Collection getLocalVariableNames() { return bodyParameterNames; } }); } try { directiveModel.execute(this, args, outArgs, nested); } catch (FlowControlException e) { throw e; } catch (TemplateException e) { throw e; } catch (IOException e) { // For backward compatibility, we assume that this is because the output Writer has thrown it. throw e; } catch (Exception e) { if (EvalUtil.shouldWrapUncheckedException(e, this)) { throw new _MiscTemplateException( e, this, "Directive has thrown an unchecked exception; see the cause exception."); } else if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { throw new UndeclaredThrowableException(e); } } finally { if (outArgs.length > 0) { localContextStack.pop(); } } }
"Visit" the template element, passing the output through a TemplateTransformModel
Params:
  • elementBuffer – the element to visit through a transform; might contains trailing null-s
  • transform – the transform to pass the element output through
  • args – optional arguments fed to the transform
/** * "Visit" the template element, passing the output through a TemplateTransformModel * * @param elementBuffer * the element to visit through a transform; might contains trailing {@code null}-s * @param transform * the transform to pass the element output through * @param args * optional arguments fed to the transform */
void visitAndTransform(TemplateElement[] elementBuffer, 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 { visit(elementBuffer); } while (tc != null && tc.afterBody() == TransformControl.REPEAT_EVALUATION); } } catch (Throwable t) { try { if (tc != null && !(t instanceof FlowControlException && getConfiguration().getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_27)) { tc.onError(t); } else { throw t; } } catch (TemplateException e) { throw e; } catch (IOException e) { throw e; } catch (Error e) { throw e; } catch (Throwable e) { if (EvalUtil.shouldWrapUncheckedException(e, this)) { throw new _MiscTemplateException( e, this, "Transform has thrown an unchecked exception; see the cause exception."); } else if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { throw new UndeclaredThrowableException(e); } } } finally { out = prevOut; if (prevOut != tw) { tw.close(); } } } catch (TemplateException te) { handleTemplateException(te); } }
Visit a block using buffering/recovery
/** * Visit a block using buffering/recovery */
void visitAttemptRecover( AttemptBlock attemptBlock, TemplateElement attemptedSection, RecoveryBlock recoverySection) throws TemplateException, IOException { Writer prevOut = this.out; StringWriter sw = new StringWriter(); this.out = sw; TemplateException thrownException = null; boolean lastFIRE = setFastInvalidReferenceExceptions(false); boolean lastInAttemptBlock = inAttemptBlock; try { inAttemptBlock = true; visit(attemptedSection); } catch (TemplateException te) { thrownException = te; } finally { inAttemptBlock = lastInAttemptBlock; setFastInvalidReferenceExceptions(lastFIRE); this.out = prevOut; } if (thrownException != null) { if (ATTEMPT_LOGGER.isDebugEnabled()) { ATTEMPT_LOGGER.debug("Error in attempt block " + attemptBlock.getStartLocationQuoted(), thrownException); } try { recoveredErrorStack.add(thrownException); visit(recoverySection); } finally { recoveredErrorStack.remove(recoveredErrorStack.size() - 1); } } else { out.write(sw.toString()); } } String getCurrentRecoveredErrorMessage() throws TemplateException { if (recoveredErrorStack.isEmpty()) { throw new _MiscTemplateException(this, ".error is not available outside of a #recover block"); } return ((Throwable) recoveredErrorStack.get(recoveredErrorStack.size() - 1)).getMessage(); }
Tells if we are inside an #attempt block (but before #recover). This can be useful for TemplateExceptionHandler-s, as then they may don't want to print the error to the output, as #attempt will roll it back anyway.
Since:2.3.20
/** * Tells if we are inside an <tt>#attempt</tt> block (but before <tt>#recover</tt>). This can be useful for * {@link TemplateExceptionHandler}-s, as then they may don't want to print the error to the output, as * <tt>#attempt</tt> will roll it back anyway. * * @since 2.3.20 */
public boolean isInAttemptBlock() { return inAttemptBlock; }
Used for #nested.
/** * Used for {@code #nested}. */
void invokeNestedContent(BodyInstruction.Context bodyCtx) throws TemplateException, IOException { Macro.Context invokingMacroContext = getCurrentMacroContext(); LocalContextStack prevLocalContextStack = localContextStack; TemplateObject callPlace = invokingMacroContext.callPlace; TemplateElement[] nestedContentBuffer = callPlace instanceof TemplateElement ? ((TemplateElement) callPlace).getChildBuffer() : null; if (nestedContentBuffer != null) { this.currentMacroContext = invokingMacroContext.prevMacroContext; currentNamespace = invokingMacroContext.nestedContentNamespace; final Configurable prevParent; final boolean parentReplacementOn = isBeforeIcI2322(); prevParent = getParent(); if (parentReplacementOn) { setParent(currentNamespace.getTemplate()); } else { legacyParent = currentNamespace.getTemplate(); } this.localContextStack = invokingMacroContext.prevLocalContextStack; if (invokingMacroContext.nestedContentParameterNames != null) { pushLocalContext(bodyCtx); } try { visit(nestedContentBuffer); } finally { if (invokingMacroContext.nestedContentParameterNames != null) { localContextStack.pop(); } this.currentMacroContext = invokingMacroContext; currentNamespace = getMacroNamespace(invokingMacroContext.getMacro()); if (parentReplacementOn) { setParent(prevParent); } else { legacyParent = prevParent; } this.localContextStack = prevLocalContextStack; } } }
"visit" an IteratorBlock
/** * "visit" an IteratorBlock */
boolean visitIteratorBlock(IteratorBlock.IterationContext ictxt) throws TemplateException, IOException { pushLocalContext(ictxt); try { return ictxt.accept(this); } catch (TemplateException te) { handleTemplateException(te); return true; } finally { localContextStack.pop(); } }
Used for #visit and #recurse.
/** * Used for {@code #visit} and {@code #recurse}. */
void invokeNodeHandlerFor(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) { invoke((Macro) macroOrTransform, null, null, null, null); } else if (macroOrTransform instanceof TemplateTransformModel) { visitAndTransform(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 // we just ignore it. else if (!nodeType.equals("pi") && !nodeType.equals("comment") && !nodeType.equals("document_type")) { throw new _MiscTemplateException( this, noNodeHandlerDefinedDescription(node, node.getNodeNamespace(), nodeType)); } } else { throw new _MiscTemplateException( this, noNodeHandlerDefinedDescription(node, node.getNodeNamespace(), "default")); } } } finally { this.currentVisitorNode = prevVisitorNode; this.nodeNamespaceIndex = prevNodeNamespaceIndex; this.currentNodeName = prevNodeName; this.currentNodeNS = prevNodeNS; this.nodeNamespaces = prevNodeNamespaces; } } private Object[] noNodeHandlerDefinedDescription( TemplateNodeModel node, String ns, String nodeType) throws TemplateModelException { String nsPrefix; if (ns != null) { if (ns.length() > 0) { nsPrefix = " and namespace "; } else { nsPrefix = " and no namespace"; } } else { nsPrefix = ""; ns = ""; } return new Object[] { "No macro or directive is defined for node named ", new _DelayedJQuote(node.getNodeName()), nsPrefix, ns, ", and there is no fallback handler called @", nodeType, " either." }; } void fallback() throws TemplateException, IOException { TemplateModel macroOrTransform = getNodeProcessor(currentNodeName, currentNodeNS, nodeNamespaceIndex); if (macroOrTransform instanceof Macro) { invoke((Macro) macroOrTransform, null, null, null, null); } else if (macroOrTransform instanceof TemplateTransformModel) { visitAndTransform(null, (TemplateTransformModel) macroOrTransform, null); } }
Calls the macro or function with the given arguments and nested block.
/** * Calls the macro or function with the given arguments and nested block. */
void invoke(Macro macro, Map namedArgs, List positionalArgs, List bodyParameterNames, TemplateObject callPlace) throws TemplateException, IOException { if (macro == Macro.DO_NOTHING_MACRO) { return; } boolean elementPushed; if (!incompatibleImprovementsGE2328) { // Doing this so early is wrong, as now the arguments will be evaluated while the called macro/function is // in the element stack. Thus .current_template_name will be wrong for example. pushElement(macro); elementPushed = true; } else { elementPushed = false; } try { final Macro.Context macroCtx = macro.new Context(this, callPlace, bodyParameterNames); // Causes the evaluation of argument expressions: setMacroContextLocalsFromArguments(macroCtx, macro, namedArgs, positionalArgs); if (!elementPushed) { // When incompatibleImprovements >= 2.3.28 pushElement(macro); elementPushed = true; } final Macro.Context prevMacroCtx = currentMacroContext; currentMacroContext = macroCtx; final LocalContextStack prevLocalContextStack = localContextStack; localContextStack = null; final Namespace prevNamespace = currentNamespace; currentNamespace = (Namespace) macroToNamespaceLookup.get(macro); try { macroCtx.sanityCheck(this); visit(macro.getChildBuffer()); } catch (ReturnInstruction.Return re) { // Not an error, just a <#return> } catch (TemplateException te) { handleTemplateException(te); } finally { currentMacroContext = prevMacroCtx; localContextStack = prevLocalContextStack; currentNamespace = prevNamespace; } } finally { if (elementPushed) { popElement(); } } }
Sets the local variables corresponding to the macro call arguments in the macro context.
/** * Sets the local variables corresponding to the macro call arguments in the macro context. */
private void setMacroContextLocalsFromArguments( final Macro.Context macroCtx, final Macro macro, final Map namedArgs, final List positionalArgs) throws TemplateException, _MiscTemplateException { String catchAllParamName = macro.getCatchAll(); if (namedArgs != null) { final SimpleHash catchAllParamValue; if (catchAllParamName != null) { catchAllParamValue = new SimpleHash((ObjectWrapper) null); macroCtx.setLocalVar(catchAllParamName, catchAllParamValue); } else { catchAllParamValue = null; } for (Iterator it = namedArgs.entrySet().iterator(); it.hasNext();) { final Map.Entry argNameAndValExp = (Map.Entry) it.next(); final String argName = (String) argNameAndValExp.getKey(); final boolean isArgNameDeclared = macro.hasArgNamed(argName); if (isArgNameDeclared || catchAllParamName != null) { Expression argValueExp = (Expression) argNameAndValExp.getValue(); TemplateModel argValue = argValueExp.eval(this); if (isArgNameDeclared) { macroCtx.setLocalVar(argName, argValue); } else { catchAllParamValue.put(argName, argValue); } } else { throw new _MiscTemplateException(this, (macro.isFunction() ? "Function " : "Macro "), new _DelayedJQuote(macro.getName()), " has no parameter with name ", new _DelayedJQuote(argName), "."); } } } else if (positionalArgs != null) { final SimpleSequence catchAllParamValue; if (catchAllParamName != null) { catchAllParamValue = new SimpleSequence((ObjectWrapper) null); macroCtx.setLocalVar(catchAllParamName, catchAllParamValue); } else { catchAllParamValue = null; } String[] argNames = macro.getArgumentNamesInternal(); final int argsCnt = positionalArgs.size(); if (argNames.length < argsCnt && catchAllParamName == null) { throw new _MiscTemplateException(this, (macro.isFunction() ? "Function " : "Macro "), new _DelayedJQuote(macro.getName()), " only accepts ", new _DelayedToString(argNames.length), " parameters, but got ", new _DelayedToString(argsCnt), "."); } for (int i = 0; i < argsCnt; i++) { Expression argValueExp = (Expression) positionalArgs.get(i); TemplateModel argValue = argValueExp.eval(this); try { if (i < argNames.length) { String argName = argNames[i]; macroCtx.setLocalVar(argName, argValue); } else { catchAllParamValue.add(argValue); } } catch (RuntimeException re) { throw new _MiscTemplateException(re, this); } } } }
Defines the given macro in the current namespace (doesn't call it).
/** * Defines the given macro in the current namespace (doesn't call it). */
void visitMacroDef(Macro macro) { macroToNamespaceLookup.put(macro, currentNamespace); currentNamespace.put(macro.getName(), macro); } Namespace getMacroNamespace(Macro macro) { return (Namespace) macroToNamespaceLookup.get(macro); } void recurse(TemplateNodeModel node, TemplateSequenceModel namespaces) throws TemplateException, IOException { if (node == null) { node = this.getCurrentVisitorNode(); if (node == null) { throw new _TemplateModelException( "The target node of recursion is missing or null."); } } TemplateSequenceModel children = node.getChildNodes(); if (children == null) { return; } int size = children.size(); for (int i = 0; i < size; i++) { TemplateNodeModel child = (TemplateNodeModel) children.get(i); if (child != null) { invokeNodeHandlerFor(child, namespaces); } } } Macro.Context getCurrentMacroContext() { return currentMacroContext; } private void handleTemplateException(TemplateException templateException) throws TemplateException { if (templateException instanceof TemplateModelException && ((TemplateModelException) templateException).getReplaceWithCause() && templateException.getCause() instanceof TemplateException) { templateException = (TemplateException) templateException.getCause(); } // Logic to prevent double-handling of the exception in // nested visit() calls. if (lastThrowable == templateException) { throw templateException; } lastThrowable = templateException; if (getLogTemplateExceptions() && LOG.isErrorEnabled() && !isInAttemptBlock() /* because then the AttemptExceptionReporter will report this */) { LOG.error("Error executing FreeMarker template", templateException); } try { // Stop exception is not passed to the handler, but // explicitly rethrown. if (templateException instanceof StopException) { throw templateException; } // Finally, pass the exception to the handler getTemplateExceptionHandler().handleTemplateException(templateException, this, out); } catch (TemplateException e) { // Note that if the TemplateExceptionHandler doesn't rethrow the exception, we don't get in there. if (isInAttemptBlock()) { this.getAttemptExceptionReporter().report(templateException, this); } throw e; } } @Override public void setTemplateExceptionHandler(TemplateExceptionHandler templateExceptionHandler) { super.setTemplateExceptionHandler(templateExceptionHandler); lastThrowable = null; } @Override public void setLocale(Locale locale) { Locale prevLocale = getLocale(); super.setLocale(locale); if (!locale.equals(prevLocale)) { cachedTemplateNumberFormats = null; if (cachedTemplateNumberFormat != null && cachedTemplateNumberFormat.isLocaleBound()) { cachedTemplateNumberFormat = null; } if (cachedTempDateFormatArray != null) { for (int i = 0; i < CACHED_TDFS_LENGTH; i++) { final TemplateDateFormat f = cachedTempDateFormatArray[i]; if (f != null && f.isLocaleBound()) { cachedTempDateFormatArray[i] = null; } } } cachedTempDateFormatsByFmtStrArray = null; cachedCollator = null; } } @Override public void setTimeZone(TimeZone timeZone) { TimeZone prevTimeZone = getTimeZone(); super.setTimeZone(timeZone); if (!timeZone.equals(prevTimeZone)) { if (cachedTempDateFormatArray != null) { for (int i = 0; i < CACHED_TDFS_SQL_D_T_TZ_OFFS; i++) { TemplateDateFormat f = cachedTempDateFormatArray[i]; if (f != null && f.isTimeZoneBound()) { cachedTempDateFormatArray[i] = null; } } } if (cachedTempDateFormatsByFmtStrArray != null) { for (int i = 0; i < CACHED_TDFS_SQL_D_T_TZ_OFFS; i++) { cachedTempDateFormatsByFmtStrArray[i] = null; } } cachedSQLDateAndTimeTimeZoneSameAsNormal = null; } } @Override public void setSQLDateAndTimeTimeZone(TimeZone timeZone) { TimeZone prevTimeZone = getSQLDateAndTimeTimeZone(); super.setSQLDateAndTimeTimeZone(timeZone); if (!nullSafeEquals(timeZone, prevTimeZone)) { if (cachedTempDateFormatArray != null) { for (int i = CACHED_TDFS_SQL_D_T_TZ_OFFS; i < CACHED_TDFS_LENGTH; i++) { TemplateDateFormat format = cachedTempDateFormatArray[i]; if (format != null && format.isTimeZoneBound()) { cachedTempDateFormatArray[i] = null; } } } if (cachedTempDateFormatsByFmtStrArray != null) { for (int i = CACHED_TDFS_SQL_D_T_TZ_OFFS; i < CACHED_TDFS_LENGTH; i++) { cachedTempDateFormatsByFmtStrArray[i] = null; } } cachedSQLDateAndTimeTimeZoneSameAsNormal = null; } } // Replace with Objects.equals in Java 7 private static boolean nullSafeEquals(Object o1, Object o2) { if (o1 == o2) return true; if (o1 == null || o2 == null) return false; return o1.equals(o2); }
Tells if the same concrete time zone is used for SQL date-only and time-only values as for other date/time/date-time values.
/** * Tells if the same concrete time zone is used for SQL date-only and time-only values as for other * date/time/date-time values. */
boolean isSQLDateAndTimeTimeZoneSameAsNormal() { if (cachedSQLDateAndTimeTimeZoneSameAsNormal == null) { cachedSQLDateAndTimeTimeZoneSameAsNormal = Boolean.valueOf( getSQLDateAndTimeTimeZone() == null || getSQLDateAndTimeTimeZone().equals(getTimeZone())); } return cachedSQLDateAndTimeTimeZoneSameAsNormal.booleanValue(); } @Override public void setURLEscapingCharset(String urlEscapingCharset) { cachedURLEscapingCharsetSet = false; super.setURLEscapingCharset(urlEscapingCharset); } /* * Note that altough it's not allowed to set this setting with the <tt>setting</tt> directive, it still must be * allowed to set it from Java code while the template executes, since some frameworks allow templates to actually * change the output encoding on-the-fly. */ @Override public void setOutputEncoding(String outputEncoding) { cachedURLEscapingCharsetSet = false; super.setOutputEncoding(outputEncoding); }
Returns the name of the charset that should be used for URL encoding. This will be null if the information is not available. The function caches the return value, so it's quick to call it repeatedly.
/** * Returns the name of the charset that should be used for URL encoding. This will be <code>null</code> if the * information is not available. The function caches the return value, so it's quick to call it repeatedly. */
String getEffectiveURLEscapingCharset() { if (!cachedURLEscapingCharsetSet) { cachedURLEscapingCharset = getURLEscapingCharset(); if (cachedURLEscapingCharset == null) { cachedURLEscapingCharset = getOutputEncoding(); } cachedURLEscapingCharsetSet = true; } return cachedURLEscapingCharset; } Collator getCollator() { if (cachedCollator == null) { cachedCollator = Collator.getInstance(getLocale()); } return cachedCollator; }
Compares two TemplateModel-s according the rules of the FTL "==" operator.
Since:2.3.20
/** * Compares two {@link TemplateModel}-s according the rules of the FTL "==" operator. * * @since 2.3.20 */
public boolean applyEqualsOperator(TemplateModel leftValue, TemplateModel rightValue) throws TemplateException { return EvalUtil.compare(leftValue, EvalUtil.CMP_OP_EQUALS, rightValue, this); }
Compares two TemplateModel-s according the rules of the FTL "==" operator, except that if the two types are incompatible, they are treated as non-equal instead of throwing an exception. Comparing dates of different types (date-only VS time-only VS date-time) will still throw an exception, however.
Since:2.3.20
/** * Compares two {@link TemplateModel}-s according the rules of the FTL "==" operator, except that if the two types * are incompatible, they are treated as non-equal instead of throwing an exception. Comparing dates of different * types (date-only VS time-only VS date-time) will still throw an exception, however. * * @since 2.3.20 */
public boolean applyEqualsOperatorLenient(TemplateModel leftValue, TemplateModel rightValue) throws TemplateException { return EvalUtil.compareLenient(leftValue, EvalUtil.CMP_OP_EQUALS, rightValue, this); }
Compares two TemplateModel-s according the rules of the FTL "<" operator.
Since:2.3.20
/** * Compares two {@link TemplateModel}-s according the rules of the FTL "&lt;" operator. * * @since 2.3.20 */
public boolean applyLessThanOperator(TemplateModel leftValue, TemplateModel rightValue) throws TemplateException { return EvalUtil.compare(leftValue, EvalUtil.CMP_OP_LESS_THAN, rightValue, this); }
Compares two TemplateModel-s according the rules of the FTL "<" operator.
Since:2.3.20
/** * Compares two {@link TemplateModel}-s according the rules of the FTL "&lt;" operator. * * @since 2.3.20 */
public boolean applyLessThanOrEqualsOperator(TemplateModel leftValue, TemplateModel rightValue) throws TemplateException { return EvalUtil.compare(leftValue, EvalUtil.CMP_OP_LESS_THAN_EQUALS, rightValue, this); }
Compares two TemplateModel-s according the rules of the FTL ">" operator.
Since:2.3.20
/** * Compares two {@link TemplateModel}-s according the rules of the FTL "&gt;" operator. * * @since 2.3.20 */
public boolean applyGreaterThanOperator(TemplateModel leftValue, TemplateModel rightValue) throws TemplateException { return EvalUtil.compare(leftValue, EvalUtil.CMP_OP_GREATER_THAN, rightValue, this); }
Compares two TemplateModel-s according the rules of the FTL ">=" operator.
Since:2.3.20
/** * Compares two {@link TemplateModel}-s according the rules of the FTL "&gt;=" operator. * * @since 2.3.20 */
public boolean applyWithGreaterThanOrEqualsOperator(TemplateModel leftValue, TemplateModel rightValue) throws TemplateException { return EvalUtil.compare(leftValue, EvalUtil.CMP_OP_GREATER_THAN_EQUALS, rightValue, this); } public void setOut(Writer out) { this.out = out; } public Writer getOut() { return out; } @Override public void setNumberFormat(String formatName) { super.setNumberFormat(formatName); cachedTemplateNumberFormat = null; }
Format number with the default number format.
Params:
  • exp – The blamed expression if an error occurs; it's only needed for better error messages
/** * Format number with the default number format. * * @param exp * The blamed expression if an error occurs; it's only needed for better error messages */
String formatNumberToPlainText(TemplateNumberModel number, Expression exp, boolean useTempModelExc) throws TemplateException { return formatNumberToPlainText(number, getTemplateNumberFormat(exp, useTempModelExc), exp, useTempModelExc); }
Format number with the number format specified as the parameter, with the current locale.
Params:
  • exp – The blamed expression if an error occurs; it's only needed for better error messages
/** * Format number with the number format specified as the parameter, with the current locale. * * @param exp * The blamed expression if an error occurs; it's only needed for better error messages */
String formatNumberToPlainText( TemplateNumberModel number, TemplateNumberFormat format, Expression exp, boolean useTempModelExc) throws TemplateException { try { return EvalUtil.assertFormatResultNotNull(format.formatToPlainText(number)); } catch (TemplateValueFormatException e) { throw _MessageUtil.newCantFormatNumberException(format, exp, e, useTempModelExc); } }
Format number with the number format specified as the parameter, with the current locale.
Params:
  • exp – The blamed expression if an error occurs; it's only needed for better error messages
/** * Format number with the number format specified as the parameter, with the current locale. * * @param exp * The blamed expression if an error occurs; it's only needed for better error messages */
String formatNumberToPlainText(Number number, BackwardCompatibleTemplateNumberFormat format, Expression exp) throws TemplateModelException, _MiscTemplateException { try { return format.format(number); } catch (UnformattableValueException e) { throw new _MiscTemplateException(exp, e, this, "Failed to format number with ", new _DelayedJQuote(format.getDescription()), ": ", e.getMessage()); } }
Returns the current number format (Configurable.getNumberFormat()) as TemplateNumberFormat.

Performance notes: The result is stored for reuse, so calling this method frequently is usually not a problem. However, at least as of this writing (2.3.24), changing the current locale setLocale(Locale) or changing the current number format (setNumberFormat(String)) will drop the stored value, so it will have to be recalculated.

Since:2.3.24
/** * Returns the current number format ({@link #getNumberFormat()}) as {@link TemplateNumberFormat}. * * <p> * Performance notes: The result is stored for reuse, so calling this method frequently is usually not a problem. * However, at least as of this writing (2.3.24), changing the current locale {@link #setLocale(Locale)} or changing * the current number format ({@link #setNumberFormat(String)}) will drop the stored value, so it will have to be * recalculated. * * @since 2.3.24 */
public TemplateNumberFormat getTemplateNumberFormat() throws TemplateValueFormatException { TemplateNumberFormat format = cachedTemplateNumberFormat; if (format == null) { format = getTemplateNumberFormat(getNumberFormat(), false); cachedTemplateNumberFormat = format; } return format; }
Returns the number format as TemplateNumberFormat for the given format string and the current locale. (The current locale is the locale returned by Configurable.getLocale().) Note that the result will be cached in the Environment instance (though at least in 2.3.24 the cache will be flushed if the current locale of the Environment is changed).
Params:
  • formatString – A string that you could also use as the value of the numberFormat configuration setting. Can't be null.
Since:2.3.24
/** * Returns the number format as {@link TemplateNumberFormat} for the given format string and the current locale. * (The current locale is the locale returned by {@link #getLocale()}.) Note that the result will be cached in the * {@link Environment} instance (though at least in 2.3.24 the cache will be flushed if the current locale of the * {@link Environment} is changed). * * @param formatString * A string that you could also use as the value of the {@code numberFormat} configuration setting. Can't * be {@code null}. * * @since 2.3.24 */
public TemplateNumberFormat getTemplateNumberFormat(String formatString) throws TemplateValueFormatException { return getTemplateNumberFormat(formatString, true); }
Returns the number format as TemplateNumberFormat, for the given format string and locale. To get a number format for the current locale, use getTemplateNumberFormat(String) instead.

Note on performance (which was true at least for 2.3.24): Unless the locale happens to be equal to the current locale, the Environment-level format cache can't be used, so the format string has to be parsed and the matching factory has to be get an invoked, which is much more expensive than getting the format from the cache. Thus the returned format should be stored by the caller for later reuse (but only within the current thread and in relation to the current Environment), if it will be needed frequently.

Params:
  • formatString – A string that you could also use as the value of the numberFormat configuration setting.
  • locale – The locale of the number format; not null.
Since:2.3.24
/** * Returns the number format as {@link TemplateNumberFormat}, for the given format string and locale. To get a * number format for the current locale, use {@link #getTemplateNumberFormat(String)} instead. * * <p> * Note on performance (which was true at least for 2.3.24): Unless the locale happens to be equal to the current * locale, the {@link Environment}-level format cache can't be used, so the format string has to be parsed and the * matching factory has to be get an invoked, which is much more expensive than getting the format from the cache. * Thus the returned format should be stored by the caller for later reuse (but only within the current thread and * in relation to the current {@link Environment}), if it will be needed frequently. * * @param formatString * A string that you could also use as the value of the {@code numberFormat} configuration setting. * @param locale * The locale of the number format; not {@code null}. * * @since 2.3.24 */
public TemplateNumberFormat getTemplateNumberFormat(String formatString, Locale locale) throws TemplateValueFormatException { if (locale.equals(getLocale())) { getTemplateNumberFormat(formatString); } return getTemplateNumberFormatWithoutCache(formatString, locale); }
Convenience wrapper around getTemplateNumberFormat() to be called during expression evaluation.
/** * Convenience wrapper around {@link #getTemplateNumberFormat()} to be called during expression evaluation. */
TemplateNumberFormat getTemplateNumberFormat(Expression exp, boolean useTempModelExc) throws TemplateException { TemplateNumberFormat format; try { format = getTemplateNumberFormat(); } catch (TemplateValueFormatException e) { _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder( "Failed to get number format object for the current number format string, ", new _DelayedJQuote(getNumberFormat()), ": ", e.getMessage()) .blame(exp); throw useTempModelExc ? new _TemplateModelException(e, this, desc) : new _MiscTemplateException(e, this, desc); } return format; }
Convenience wrapper around getTemplateNumberFormat(String) to be called during expression evaluation.
Params:
  • exp – The blamed expression if an error occurs; it's only needed for better error messages
/** * Convenience wrapper around {@link #getTemplateNumberFormat(String)} to be called during expression evaluation. * * @param exp * The blamed expression if an error occurs; it's only needed for better error messages */
TemplateNumberFormat getTemplateNumberFormat(String formatString, Expression exp, boolean useTempModelExc) throws TemplateException { TemplateNumberFormat format; try { format = getTemplateNumberFormat(formatString); } catch (TemplateValueFormatException e) { _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder( "Failed to get number format object for the ", new _DelayedJQuote(formatString), " number format string: ", e.getMessage()) .blame(exp); throw useTempModelExc ? new _TemplateModelException(e, this, desc) : new _MiscTemplateException(e, this, desc); } return format; }
Gets the TemplateNumberFormat for the current locale.
Params:
  • formatString – Not null
  • cacheResult – If the results should stored in the Environment-level cache. It will still try to get the result from the cache regardless of this parameter.
/** * Gets the {@link TemplateNumberFormat} <em>for the current locale</em>. * * @param formatString * Not {@code null} * @param cacheResult * If the results should stored in the {@link Environment}-level cache. It will still try to get the * result from the cache regardless of this parameter. */
private TemplateNumberFormat getTemplateNumberFormat(String formatString, boolean cacheResult) throws TemplateValueFormatException { if (cachedTemplateNumberFormats == null) { if (cacheResult) { cachedTemplateNumberFormats = new HashMap<String, TemplateNumberFormat>(); } } else { TemplateNumberFormat format = cachedTemplateNumberFormats.get(formatString); if (format != null) { return format; } } TemplateNumberFormat format = getTemplateNumberFormatWithoutCache(formatString, getLocale()); if (cacheResult) { cachedTemplateNumberFormats.put(formatString, format); } return format; }
Returns the TemplateNumberFormat for the given parameters without using the Environment-level cache. Of course, the TemplateNumberFormatFactory involved might still uses its own cache.
Params:
  • formatString – Not null
  • locale – Not null
/** * Returns the {@link TemplateNumberFormat} for the given parameters without using the {@link Environment}-level * cache. Of course, the {@link TemplateNumberFormatFactory} involved might still uses its own cache. * * @param formatString * Not {@code null} * @param locale * Not {@code null} */
private TemplateNumberFormat getTemplateNumberFormatWithoutCache(String formatString, Locale locale) throws TemplateValueFormatException { int formatStringLen = formatString.length(); if (formatStringLen > 1 && formatString.charAt(0) == '@' && (isIcI2324OrLater() || hasCustomFormats()) && Character.isLetter(formatString.charAt(1))) { final String name; final String params; { int endIdx; findParamsStart: for (endIdx = 1; endIdx < formatStringLen; endIdx++) { char c = formatString.charAt(endIdx); if (c == ' ' || c == '_') { break findParamsStart; } } name = formatString.substring(1, endIdx); params = endIdx < formatStringLen ? formatString.substring(endIdx + 1) : ""; } TemplateNumberFormatFactory formatFactory = getCustomNumberFormat(name); if (formatFactory == null) { throw new UndefinedCustomFormatException( "No custom number format was defined with name " + StringUtil.jQuote(name)); } return formatFactory.get(params, locale, this); } else { return JavaTemplateNumberFormatFactory.INSTANCE.get(formatString, locale, this); } }
Returns the NumberFormat used for the c built-in. This is always US English "0.################", without grouping and without superfluous decimal separator.
/** * Returns the {@link NumberFormat} used for the <tt>c</tt> built-in. This is always US English * <code>"0.################"</code>, without grouping and without superfluous decimal separator. */
public NumberFormat getCNumberFormat() { // It can't be cached in a static field, because DecimalFormat-s aren't // thread-safe. if (cNumberFormat == null) { cNumberFormat = (DecimalFormat) C_NUMBER_FORMAT.clone(); } return cNumberFormat; } @Override public void setTimeFormat(String timeFormat) { String prevTimeFormat = getTimeFormat(); super.setTimeFormat(timeFormat); if (!timeFormat.equals(prevTimeFormat)) { if (cachedTempDateFormatArray != null) { for (int i = 0; i < CACHED_TDFS_LENGTH; i += CACHED_TDFS_ZONELESS_INPUT_OFFS) { cachedTempDateFormatArray[i + TemplateDateModel.TIME] = null; } } } } @Override public void setDateFormat(String dateFormat) { String prevDateFormat = getDateFormat(); super.setDateFormat(dateFormat); if (!dateFormat.equals(prevDateFormat)) { if (cachedTempDateFormatArray != null) { for (int i = 0; i < CACHED_TDFS_LENGTH; i += CACHED_TDFS_ZONELESS_INPUT_OFFS) { cachedTempDateFormatArray[i + TemplateDateModel.DATE] = null; } } } } @Override public void setDateTimeFormat(String dateTimeFormat) { String prevDateTimeFormat = getDateTimeFormat(); super.setDateTimeFormat(dateTimeFormat); if (!dateTimeFormat.equals(prevDateTimeFormat)) { if (cachedTempDateFormatArray != null) { for (int i = 0; i < CACHED_TDFS_LENGTH; i += CACHED_TDFS_ZONELESS_INPUT_OFFS) { cachedTempDateFormatArray[i + TemplateDateModel.DATETIME] = null; } } } } public Configuration getConfiguration() { return configuration; } TemplateModel getLastReturnValue() { return lastReturnValue; } void setLastReturnValue(TemplateModel lastReturnValue) { this.lastReturnValue = lastReturnValue; } void clearLastReturnValue() { this.lastReturnValue = null; }
Params:
  • tdmSourceExpr – The blamed expression if an error occurs; only used for error messages.
/** * @param tdmSourceExpr * The blamed expression if an error occurs; only used for error messages. */
String formatDateToPlainText(TemplateDateModel tdm, Expression tdmSourceExpr, boolean useTempModelExc) throws TemplateException { TemplateDateFormat format = getTemplateDateFormat(tdm, tdmSourceExpr, useTempModelExc); try { return EvalUtil.assertFormatResultNotNull(format.formatToPlainText(tdm)); } catch (TemplateValueFormatException e) { throw _MessageUtil.newCantFormatDateException(format, tdmSourceExpr, e, useTempModelExc); } }
Params:
  • blamedDateSourceExp – The blamed expression if an error occurs; only used for error messages.
  • blamedFormatterExp – The blamed expression if an error occurs; only used for error messages.
/** * @param blamedDateSourceExp * The blamed expression if an error occurs; only used for error messages. * @param blamedFormatterExp * The blamed expression if an error occurs; only used for error messages. */
String formatDateToPlainText(TemplateDateModel tdm, String formatString, Expression blamedDateSourceExp, Expression blamedFormatterExp, boolean useTempModelExc) throws TemplateException { Date date = EvalUtil.modelToDate(tdm, blamedDateSourceExp); TemplateDateFormat format = getTemplateDateFormat( formatString, tdm.getDateType(), date.getClass(), blamedDateSourceExp, blamedFormatterExp, useTempModelExc); try { return EvalUtil.assertFormatResultNotNull(format.formatToPlainText(tdm)); } catch (TemplateValueFormatException e) { throw _MessageUtil.newCantFormatDateException(format, blamedDateSourceExp, e, useTempModelExc); } }
Gets a TemplateDateFormat using the date/time/datetime format settings and the current locale and time zone. (The current locale is the locale returned by Configurable.getLocale(). The current time zone is Configurable.getTimeZone() or Configurable.getSQLDateAndTimeTimeZone()).
Params:
/** * Gets a {@link TemplateDateFormat} using the date/time/datetime format settings and the current locale and time * zone. (The current locale is the locale returned by {@link #getLocale()}. The current time zone is * {@link #getTimeZone()} or {@link #getSQLDateAndTimeTimeZone()}). * * @param dateType * The FTL date type; see the similar parameter of * {@link TemplateDateFormatFactory#get(String, int, Locale, TimeZone, boolean, Environment)} * @param dateClass * The exact {@link Date} class, like {@link java.sql.Date} or {@link java.sql.Time}; this can influences * time zone selection. See also: {@link #setSQLDateAndTimeTimeZone(TimeZone)} */
public TemplateDateFormat getTemplateDateFormat(int dateType, Class<? extends Date> dateClass) throws TemplateValueFormatException { boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass); return getTemplateDateFormat(dateType, shouldUseSQLDTTimeZone(isSQLDateOrTime), isSQLDateOrTime); }
Gets a TemplateDateFormat for the specified format string and the current locale and time zone. (The current locale is the locale returned by Configurable.getLocale(). The current time zone is Configurable.getTimeZone() or Configurable.getSQLDateAndTimeTimeZone()).

Note on performance: The result will be cached in the Environment instance. However, at least in 2.3.24 the cached entries that depend on the current locale or the current time zone or the current date/time/datetime format of the Environment will be lost when those settings are changed.

Params:
  • formatString – Like "iso m" or "dd.MM.yyyy HH:mm" or "@somethingCustom" or "@somethingCustom params"
Since:2.3.24
/** * Gets a {@link TemplateDateFormat} for the specified format string and the current locale and time zone. (The * current locale is the locale returned by {@link #getLocale()}. The current time zone is {@link #getTimeZone()} or * {@link #getSQLDateAndTimeTimeZone()}). * * <p> * Note on performance: The result will be cached in the {@link Environment} instance. However, at least in 2.3.24 * the cached entries that depend on the current locale or the current time zone or the current date/time/datetime * format of the {@link Environment} will be lost when those settings are changed. * * @param formatString * Like {@code "iso m"} or {@code "dd.MM.yyyy HH:mm"} or {@code "@somethingCustom"} or * {@code "@somethingCustom params"} * * @since 2.3.24 */
public TemplateDateFormat getTemplateDateFormat( String formatString, int dateType, Class<? extends Date> dateClass) throws TemplateValueFormatException { boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass); return getTemplateDateFormat( formatString, dateType, shouldUseSQLDTTimeZone(isSQLDateOrTime), isSQLDateOrTime, true); }
Like getTemplateDateFormat(String, int, Class<? extends Date>), but allows you to use a different locale than the current one. If you want to use the current locale, use getTemplateDateFormat(String, int, Class<? extends Date>) instead.

Performance notes regarding the locale and time zone parameters of getTemplateDateFormat(String, int, Locale, TimeZone, boolean) apply.

Params:
See Also:
Since:2.4
/** * Like {@link #getTemplateDateFormat(String, int, Class)}, but allows you to use a different locale than the * current one. If you want to use the current locale, use {@link #getTemplateDateFormat(String, int, Class)} * instead. * * <p> * Performance notes regarding the locale and time zone parameters of * {@link #getTemplateDateFormat(String, int, Locale, TimeZone, boolean)} apply. * * @param locale * Can't be {@code null}; See the similar parameter of * {@link TemplateDateFormatFactory#get(String, int, Locale, TimeZone, boolean, Environment)} * * @see #getTemplateDateFormat(String, int, Class) * * @since 2.4 */
public TemplateDateFormat getTemplateDateFormat( String formatString, int dateType, Class<? extends Date> dateClass, Locale locale) throws TemplateValueFormatException { boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass); boolean useSQLDTTZ = shouldUseSQLDTTimeZone(isSQLDateOrTime); return getTemplateDateFormat( formatString, dateType, locale, useSQLDTTZ ? getSQLDateAndTimeTimeZone() : getTimeZone(), isSQLDateOrTime); }
Like getTemplateDateFormat(String, int, Class<? extends Date>), but allows you to use a different locale and time zone than the current one. If you want to use the current locale and time zone, use getTemplateDateFormat(String, int, Class<? extends Date>) instead.

Performance notes regarding the locale and time zone parameters of getTemplateDateFormat(String, int, Locale, TimeZone, boolean) apply.

Params:
  • timeZone – The TimeZone used if dateClass is not an SQL date-only or time-only type. Can't be null.
  • sqlDateAndTimeTimeZone – The TimeZone used if dateClass is an SQL date-only or time-only type. Can't be null.
See Also:
Since:2.4
/** * Like {@link #getTemplateDateFormat(String, int, Class)}, but allows you to use a different locale and time zone * than the current one. If you want to use the current locale and time zone, use * {@link #getTemplateDateFormat(String, int, Class)} instead. * * <p> * Performance notes regarding the locale and time zone parameters of * {@link #getTemplateDateFormat(String, int, Locale, TimeZone, boolean)} apply. * * @param timeZone * The {@link TimeZone} used if {@code dateClass} is not an SQL date-only or time-only type. Can't be * {@code null}. * @param sqlDateAndTimeTimeZone * The {@link TimeZone} used if {@code dateClass} is an SQL date-only or time-only type. Can't be * {@code null}. * * @see #getTemplateDateFormat(String, int, Class) * * @since 2.4 */
public TemplateDateFormat getTemplateDateFormat( String formatString, int dateType, Class<? extends Date> dateClass, Locale locale, TimeZone timeZone, TimeZone sqlDateAndTimeTimeZone) throws TemplateValueFormatException { boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass); boolean useSQLDTTZ = shouldUseSQLDTTimeZone(isSQLDateOrTime); return getTemplateDateFormat( formatString, dateType, locale, useSQLDTTZ ? sqlDateAndTimeTimeZone : timeZone, isSQLDateOrTime); }
Gets a TemplateDateFormat for the specified parameters. This is mostly meant to be used by TemplateDateFormatFactory implementations to delegate to a format based on a specific format string. It works well for that, as its parameters are the same low level values as the parameters of TemplateDateFormatFactory.get(String, int, Locale, TimeZone, boolean, Environment). For other tasks consider the other overloads of this method.

Note on performance (which was true at least for 2.3.24): Unless the locale happens to be equal to the current locale and the time zone with one of the current time zones (Configurable.getTimeZone() or Configurable.getSQLDateAndTimeTimeZone()), the Environment-level format cache can't be used, so the format string has to be parsed and the matching factory has to be get an invoked, which is much more expensive than getting the format from the cache. Thus the returned format should be stored by the caller for later reuse (but only within the current thread and in relation to the current Environment), if it will be needed frequently.

Params:
Since:2.3.24
/** * Gets a {@link TemplateDateFormat} for the specified parameters. This is mostly meant to be used by * {@link TemplateDateFormatFactory} implementations to delegate to a format based on a specific format string. It * works well for that, as its parameters are the same low level values as the parameters of * {@link TemplateDateFormatFactory#get(String, int, Locale, TimeZone, boolean, Environment)}. For other tasks * consider the other overloads of this method. * * <p> * Note on performance (which was true at least for 2.3.24): Unless the locale happens to be equal to the current * locale and the time zone with one of the current time zones ({@link #getTimeZone()} or * {@link #getSQLDateAndTimeTimeZone()}), the {@link Environment}-level format cache can't be used, so the format * string has to be parsed and the matching factory has to be get an invoked, which is much more expensive than * getting the format from the cache. Thus the returned format should be stored by the caller for later reuse (but * only within the current thread and in relation to the current {@link Environment}), if it will be needed * frequently. * * @param formatString * Like {@code "iso m"} or {@code "dd.MM.yyyy HH:mm"} or {@code "@somethingCustom"} or * {@code "@somethingCustom params"} * @param dateType * The FTL date type; see the similar parameter of * {@link TemplateDateFormatFactory#get(String, int, Locale, TimeZone, boolean, Environment)} * @param timeZone * Not {@code null}; See the similar parameter of * {@link TemplateDateFormatFactory#get(String, int, Locale, TimeZone, boolean, Environment)} * @param locale * Not {@code null}; See the similar parameter of * {@link TemplateDateFormatFactory#get(String, int, Locale, TimeZone, boolean, Environment)} * @param zonelessInput * See the similar parameter of * {@link TemplateDateFormatFactory#get(String, int, Locale, TimeZone, boolean, Environment)} * * @since 2.3.24 */
public TemplateDateFormat getTemplateDateFormat( String formatString, int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput) throws TemplateValueFormatException { Locale currentLocale = getLocale(); if (locale.equals(currentLocale)) { int equalCurrentTZ; TimeZone currentTimeZone = getTimeZone(); if (timeZone.equals(currentTimeZone)) { equalCurrentTZ = 1; } else { TimeZone currentSQLDTTimeZone = getSQLDateAndTimeTimeZone(); if (timeZone.equals(currentSQLDTTimeZone)) { equalCurrentTZ = 2; } else { equalCurrentTZ = 0; } } if (equalCurrentTZ != 0) { return getTemplateDateFormat(formatString, dateType, equalCurrentTZ == 2, zonelessInput, true); } // Falls through } return getTemplateDateFormatWithoutCache(formatString, dateType, locale, timeZone, zonelessInput); } TemplateDateFormat getTemplateDateFormat(TemplateDateModel tdm, Expression tdmSourceExpr, boolean useTempModelExc) throws TemplateModelException, TemplateException { Date date = EvalUtil.modelToDate(tdm, tdmSourceExpr); TemplateDateFormat format = getTemplateDateFormat( tdm.getDateType(), date.getClass(), tdmSourceExpr, useTempModelExc); return format; }
Same as getTemplateDateFormat(int, Class<? extends Date>), but translates the exceptions to TemplateException-s.
/** * Same as {@link #getTemplateDateFormat(int, Class)}, but translates the exceptions to {@link TemplateException}-s. */
TemplateDateFormat getTemplateDateFormat( int dateType, Class<? extends Date> dateClass, Expression blamedDateSourceExp, boolean useTempModelExc) throws TemplateException { try { return getTemplateDateFormat(dateType, dateClass); } catch (UnknownDateTypeFormattingUnsupportedException e) { throw _MessageUtil.newCantFormatUnknownTypeDateException(blamedDateSourceExp, e); } catch (TemplateValueFormatException e) { String settingName; String settingValue; switch (dateType) { case TemplateDateModel.TIME: settingName = Configurable.TIME_FORMAT_KEY; settingValue = getTimeFormat(); break; case TemplateDateModel.DATE: settingName = Configurable.DATE_FORMAT_KEY; settingValue = getDateFormat(); break; case TemplateDateModel.DATETIME: settingName = Configurable.DATETIME_FORMAT_KEY; settingValue = getDateTimeFormat(); break; default: settingName = "???"; settingValue = "???"; } _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder( "The value of the \"", settingName, "\" FreeMarker configuration setting is a malformed date/time/datetime format string: ", new _DelayedJQuote(settingValue), ". Reason given: ", e.getMessage()); throw useTempModelExc ? new _TemplateModelException(e, desc) : new _MiscTemplateException(e, desc); } } /** * Same as {@link #getTemplateDateFormat(String, int, Class)}, but translates the exceptions to * {@link TemplateException}-s. */ TemplateDateFormat getTemplateDateFormat( String formatString, int dateType, Class<? extends Date> dateClass, Expression blamedDateSourceExp, Expression blamedFormatterExp, boolean useTempModelExc) throws TemplateException { try { return getTemplateDateFormat(formatString, dateType, dateClass); } catch (UnknownDateTypeFormattingUnsupportedException e) { throw _MessageUtil.newCantFormatUnknownTypeDateException(blamedDateSourceExp, e); } catch (TemplateValueFormatException e) { _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder( "Can't create date/time/datetime format based on format string ", new _DelayedJQuote(formatString), ". Reason given: ", e.getMessage()) .blame(blamedFormatterExp); throw useTempModelExc ? new _TemplateModelException(e, desc) : new _MiscTemplateException(e, desc); } }
Used to get the TemplateDateFormat according the date/time/datetime format settings, for the current locale and time zone. See getTemplateDateFormat(String, int, Locale, TimeZone, boolean) for the meaning of some of the parameters.
/** * Used to get the {@link TemplateDateFormat} according the date/time/datetime format settings, for the current * locale and time zone. See {@link #getTemplateDateFormat(String, int, Locale, TimeZone, boolean)} for the meaning * of some of the parameters. */
private TemplateDateFormat getTemplateDateFormat(int dateType, boolean useSQLDTTZ, boolean zonelessInput) throws TemplateValueFormatException { if (dateType == TemplateDateModel.UNKNOWN) { throw new UnknownDateTypeFormattingUnsupportedException(); } int cacheIdx = getTemplateDateFormatCacheArrayIndex(dateType, zonelessInput, useSQLDTTZ); TemplateDateFormat[] cachedTemplateDateFormats = this.cachedTempDateFormatArray; if (cachedTemplateDateFormats == null) { cachedTemplateDateFormats = new TemplateDateFormat[CACHED_TDFS_LENGTH]; this.cachedTempDateFormatArray = cachedTemplateDateFormats; } TemplateDateFormat format = cachedTemplateDateFormats[cacheIdx]; if (format == null) { final String formatString; switch (dateType) { case TemplateDateModel.TIME: formatString = getTimeFormat(); break; case TemplateDateModel.DATE: formatString = getDateFormat(); break; case TemplateDateModel.DATETIME: formatString = getDateTimeFormat(); break; default: throw new IllegalArgumentException("Invalid date type enum: " + Integer.valueOf(dateType)); } format = getTemplateDateFormat(formatString, dateType, useSQLDTTZ, zonelessInput, false); cachedTemplateDateFormats[cacheIdx] = format; } return format; }
Used to get the TemplateDateFormat for the specified parameters, using the Environment-level cache. As the Environment-level cache currently only stores formats for the current locale and time zone, there's no parameter to specify those.
Params:
  • cacheResult – If the results should stored in the Environment-level cache. It will still try to get the result from the cache regardless of this parameter.
/** * Used to get the {@link TemplateDateFormat} for the specified parameters, using the {@link Environment}-level * cache. As the {@link Environment}-level cache currently only stores formats for the current locale and time zone, * there's no parameter to specify those. * * @param cacheResult * If the results should stored in the {@link Environment}-level cache. It will still try to get the * result from the cache regardless of this parameter. */
private TemplateDateFormat getTemplateDateFormat( String formatString, int dateType, boolean useSQLDTTimeZone, boolean zonelessInput, boolean cacheResult) throws TemplateValueFormatException { HashMap<String, TemplateDateFormat> cachedFormatsByFormatString; readFromCache: do { HashMap<String, TemplateDateFormat>[] cachedTempDateFormatsByFmtStrArray = this.cachedTempDateFormatsByFmtStrArray; if (cachedTempDateFormatsByFmtStrArray == null) { if (cacheResult) { cachedTempDateFormatsByFmtStrArray = new HashMap[CACHED_TDFS_LENGTH]; this.cachedTempDateFormatsByFmtStrArray = cachedTempDateFormatsByFmtStrArray; } else { cachedFormatsByFormatString = null; break readFromCache; } } TemplateDateFormat format; { int cacheArrIdx = getTemplateDateFormatCacheArrayIndex(dateType, zonelessInput, useSQLDTTimeZone); cachedFormatsByFormatString = cachedTempDateFormatsByFmtStrArray[cacheArrIdx]; if (cachedFormatsByFormatString == null) { if (cacheResult) { cachedFormatsByFormatString = new HashMap<String, TemplateDateFormat>(4); cachedTempDateFormatsByFmtStrArray[cacheArrIdx] = cachedFormatsByFormatString; format = null; } else { break readFromCache; } } else { format = cachedFormatsByFormatString.get(formatString); } } if (format != null) { return format; } // Cache miss; falls through } while (false); TemplateDateFormat format = getTemplateDateFormatWithoutCache( formatString, dateType, getLocale(), useSQLDTTimeZone ? getSQLDateAndTimeTimeZone() : getTimeZone(), zonelessInput); if (cacheResult) { // We know here that cachedFormatsByFormatString != null cachedFormatsByFormatString.put(formatString, format); } return format; }
Returns the TemplateDateFormat for the given parameters without using the Environment-level cache. Of course, the TemplateDateFormatFactory involved might still uses its own cache, which can be global (class-loader-level) or Environment-level.
Params:
/** * Returns the {@link TemplateDateFormat} for the given parameters without using the {@link Environment}-level * cache. Of course, the {@link TemplateDateFormatFactory} involved might still uses its own cache, which can be * global (class-loader-level) or {@link Environment}-level. * * @param formatString * See the similar parameter of {@link TemplateDateFormatFactory#get} * @param dateType * See the similar parameter of {@link TemplateDateFormatFactory#get} * @param zonelessInput * See the similar parameter of {@link TemplateDateFormatFactory#get} */
private TemplateDateFormat getTemplateDateFormatWithoutCache( String formatString, int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput) throws TemplateValueFormatException { final int formatStringLen = formatString.length(); final String formatParams; TemplateDateFormatFactory formatFactory; char firstChar = formatStringLen != 0 ? formatString.charAt(0) : 0; // As of Java 8, 'x' and 'i' (lower case) are illegal date format letters, so this is backward-compatible. if (firstChar == 'x' && formatStringLen > 1 && formatString.charAt(1) == 's') { formatFactory = XSTemplateDateFormatFactory.INSTANCE; formatParams = formatString; // for speed, we don't remove the prefix } else if (firstChar == 'i' && formatStringLen > 2 && formatString.charAt(1) == 's' && formatString.charAt(2) == 'o') { formatFactory = ISOTemplateDateFormatFactory.INSTANCE; formatParams = formatString; // for speed, we don't remove the prefix } else if (firstChar == '@' && formatStringLen > 1 && (isIcI2324OrLater() || hasCustomFormats()) && Character.isLetter(formatString.charAt(1))) { final String name; { int endIdx; findParamsStart: for (endIdx = 1; endIdx < formatStringLen; endIdx++) { char c = formatString.charAt(endIdx); if (c == ' ' || c == '_') { break findParamsStart; } } name = formatString.substring(1, endIdx); formatParams = endIdx < formatStringLen ? formatString.substring(endIdx + 1) : ""; } formatFactory = getCustomDateFormat(name); if (formatFactory == null) { throw new UndefinedCustomFormatException( "No custom date format was defined with name " + StringUtil.jQuote(name)); } } else { formatParams = formatString; formatFactory = JavaTemplateDateFormatFactory.INSTANCE; } return formatFactory.get(formatParams, dateType, locale, timeZone, zonelessInput, this); } boolean shouldUseSQLDTTZ(Class dateClass) { // Attention! If you update this method, update all overloads of it! return dateClass != Date.class // This pre-condition is only for speed && !isSQLDateAndTimeTimeZoneSameAsNormal() && isSQLDateOrTimeClass(dateClass); } private boolean shouldUseSQLDTTimeZone(boolean sqlDateOrTime) { // Attention! If you update this method, update all overloads of it! return sqlDateOrTime && !isSQLDateAndTimeTimeZoneSameAsNormal(); }
Tells if the given class is or is subclass of Date or Time.
/** * Tells if the given class is or is subclass of {@link java.sql.Date} or {@link java.sql.Time}. */
private static boolean isSQLDateOrTimeClass(Class dateClass) { // We do shortcuts for the most common cases. return dateClass != java.util.Date.class && (dateClass == java.sql.Date.class || dateClass == Time.class || (dateClass != Timestamp.class && (java.sql.Date.class.isAssignableFrom(dateClass) || Time.class.isAssignableFrom(dateClass)))); } private int getTemplateDateFormatCacheArrayIndex(int dateType, boolean zonelessInput, boolean sqlDTTZ) { return dateType + (zonelessInput ? CACHED_TDFS_ZONELESS_INPUT_OFFS : 0) + (sqlDTTZ ? CACHED_TDFS_SQL_D_T_TZ_OFFS : 0); } /** * Returns the {@link DateToISO8601CalendarFactory} used by the the "iso_" built-ins. Be careful when using this; it * should only by used with * {@link DateUtil#dateToISO8601String(Date, boolean, boolean, boolean, int, TimeZone, DateToISO8601CalendarFactory)} * and {@link DateUtil#dateToXSString(Date, boolean, boolean, boolean, int, TimeZone, DateToISO8601CalendarFactory)} * . */ DateToISO8601CalendarFactory getISOBuiltInCalendarFactory() { if (isoBuiltInCalendarFactory == null) { isoBuiltInCalendarFactory = new DateUtil.TrivialDateToISO8601CalendarFactory(); } return isoBuiltInCalendarFactory; } TemplateTransformModel getTransform(Expression exp) throws TemplateException { TemplateTransformModel ttm = null; TemplateModel tm = exp.eval(this); if (tm instanceof TemplateTransformModel) { ttm = (TemplateTransformModel) tm; } else if (exp instanceof Identifier) { tm = configuration.getSharedVariable(exp.toString()); if (tm instanceof TemplateTransformModel) { ttm = (TemplateTransformModel) tm; } } return ttm; }
Returns the loop or macro local variable corresponding to this variable name. Possibly null. (Note that the misnomer is kept for backward compatibility: loop variables are not local variables according to our terminology.)
/** * Returns the loop or macro local variable corresponding to this variable name. Possibly null. (Note that the * misnomer is kept for backward compatibility: loop variables are not local variables according to our * terminology.) */
public TemplateModel getLocalVariable(String name) throws TemplateModelException { if (localContextStack != null) { for (int i = localContextStack.size() - 1; i >= 0; i--) { LocalContext lc = localContextStack.get(i); TemplateModel tm = lc.getLocalVariable(name); if (tm != null) { return tm; } } } return currentMacroContext == null ? null : currentMacroContext.getLocalVariable(name); }
Returns the variable that is visible in this context, or null if the variable is not found. This is the correspondent to an FTL top-level variable reading expression. That is, it tries to find the the variable in this order:
  1. An loop variable (if we're in a loop or user defined directive body) such as foo_has_next
  2. A local variable (if we're in a macro)
  3. A variable defined in the current namespace (say, via <#assign ...>)
  4. A variable defined globally (say, via <#global ....>)
  5. Variable in the data model:
    1. A variable in the root hash that was exposed to this rendering environment in the Template.process(...) call
    2. A shared variable set in the configuration via a call to Configuration.setSharedVariable(...)
/** * Returns the variable that is visible in this context, or {@code null} if the variable is not found. This is the * correspondent to an FTL top-level variable reading expression. That is, it tries to find the the variable in this * order: * <ol> * <li>An loop variable (if we're in a loop or user defined directive body) such as foo_has_next * <li>A local variable (if we're in a macro) * <li>A variable defined in the current namespace (say, via &lt;#assign ...&gt;) * <li>A variable defined globally (say, via &lt;#global ....&gt;) * <li>Variable in the data model: * <ol> * <li>A variable in the root hash that was exposed to this rendering environment in the Template.process(...) call * <li>A shared variable set in the configuration via a call to Configuration.setSharedVariable(...) * </ol> * </li> * </ol> */
public TemplateModel getVariable(String name) throws TemplateModelException { TemplateModel result = getLocalVariable(name); if (result == null) { result = currentNamespace.get(name); } if (result == null) { result = getGlobalVariable(name); } return result; }
Returns the globally visible variable of the given name (or null). This is correspondent to FTL .globals.name. This will first look at variables that were assigned globally via: <#global ...> and then at the data model exposed to the template.
/** * Returns the globally visible variable of the given name (or null). This is correspondent to FTL * <code>.globals.<i>name</i></code>. This will first look at variables that were assigned globally via: &lt;#global * ...&gt; and then at the data model exposed to the template. */
public TemplateModel getGlobalVariable(String name) throws TemplateModelException { TemplateModel result = globalNamespace.get(name); if (result == null) { result = rootDataModel.get(name); } if (result == null) { result = configuration.getSharedVariable(name); } return result; }
Sets a variable that is visible globally. This is correspondent to FTL <#global name=model>. This can be considered a convenient shorthand for: getGlobalNamespace().put(name, model)
/** * Sets a variable that is visible globally. This is correspondent to FTL * <code>&lt;#global <i>name</i>=<i>model</i>&gt;</code>. This can be considered a convenient shorthand for: * getGlobalNamespace().put(name, model) */
public void setGlobalVariable(String name, TemplateModel model) { globalNamespace.put(name, model); }
Sets a variable in the current namespace. This is correspondent to FTL <#assign name=model>. This can be considered a convenient shorthand for: getCurrentNamespace().put(name, model)
/** * Sets a variable in the current namespace. This is correspondent to FTL * <code>&lt;#assign <i>name</i>=<i>model</i>&gt;</code>. This can be considered a convenient shorthand for: * getCurrentNamespace().put(name, model) */
public void setVariable(String name, TemplateModel model) { currentNamespace.put(name, model); }
Sets a local variable (one effective only during a macro invocation). This is correspondent to FTL <#local name=model>.
Params:
  • name – the identifier of the variable
  • model – the value of the variable.
Throws:
/** * Sets a local variable (one effective only during a macro invocation). This is correspondent to FTL * <code>&lt;#local <i>name</i>=<i>model</i>&gt;</code>. * * @param name * the identifier of the variable * @param model * the value of the variable. * @throws IllegalStateException * if the environment is not executing a macro body. */
public void setLocalVariable(String name, TemplateModel model) { if (currentMacroContext == null) { throw new IllegalStateException("Not executing macro body"); } currentMacroContext.setLocalVar(name, model); }
Returns a set of variable names that are known at the time of call. This includes names of all shared variables in the Configuration, names of all global variables that were assigned during the template processing, names of all variables in the current name-space, names of all local variables and loop variables. If the passed root data model implements the TemplateHashModelEx interface, then all names it retrieves through a call to TemplateHashModelEx.keys() method are returned as well. The method returns a new Set object on each call that is completely disconnected from the Environment. That is, modifying the set will have no effect on the Environment object.
/** * Returns a set of variable names that are known at the time of call. This includes names of all shared variables * in the {@link Configuration}, names of all global variables that were assigned during the template processing, * names of all variables in the current name-space, names of all local variables and loop variables. If the passed * root data model implements the {@link TemplateHashModelEx} interface, then all names it retrieves through a call * to {@link TemplateHashModelEx#keys()} method are returned as well. The method returns a new Set object on each * call that is completely disconnected from the Environment. That is, modifying the set will have no effect on the * Environment object. */
public Set getKnownVariableNames() throws TemplateModelException { // shared vars. Set set = configuration.getSharedVariableNames(); // root hash if (rootDataModel instanceof TemplateHashModelEx) { TemplateModelIterator rootNames = ((TemplateHashModelEx) rootDataModel).keys().iterator(); while (rootNames.hasNext()) { set.add(((TemplateScalarModel) rootNames.next()).getAsString()); } } // globals for (TemplateModelIterator tmi = globalNamespace.keys().iterator(); tmi.hasNext();) { set.add(((TemplateScalarModel) tmi.next()).getAsString()); } // current name-space for (TemplateModelIterator tmi = currentNamespace.keys().iterator(); tmi.hasNext();) { set.add(((TemplateScalarModel) tmi.next()).getAsString()); } // locals and loop vars if (currentMacroContext != null) { set.addAll(currentMacroContext.getLocalVariableNames()); } if (localContextStack != null) { for (int i = localContextStack.size() - 1; i >= 0; i--) { LocalContext lc = localContextStack.get(i); set.addAll(lc.getLocalVariableNames()); } } return set; }
Prints the current FTL stack trace. Useful for debugging. TemplateExceptions incorporate this information in their stack traces.
/** * Prints the current FTL stack trace. Useful for debugging. {@link TemplateException}s incorporate this information * in their stack traces. */
public void outputInstructionStack(PrintWriter pw) { outputInstructionStack(getInstructionStackSnapshot(), false, pw); pw.flush(); } private static final int TERSE_MODE_INSTRUCTION_STACK_TRACE_LIMIT = 10;
Prints an FTL stack trace based on a stack trace snapshot.
Params:
See Also:
Since:2.3.21
/** * Prints an FTL stack trace based on a stack trace snapshot. * * @param w * If it's a {@link PrintWriter}, {@link PrintWriter#println()} will be used for line-breaks. * @see #getInstructionStackSnapshot() * @since 2.3.21 */
static void outputInstructionStack( TemplateElement[] instructionStackSnapshot, boolean terseMode, Writer w) { final PrintWriter pw = (PrintWriter) (w instanceof PrintWriter ? w : null); try { if (instructionStackSnapshot != null) { final int totalFrames = instructionStackSnapshot.length; int framesToPrint = terseMode ? (totalFrames <= TERSE_MODE_INSTRUCTION_STACK_TRACE_LIMIT ? totalFrames : TERSE_MODE_INSTRUCTION_STACK_TRACE_LIMIT - 1) : totalFrames; boolean hideNestringRelatedFrames = terseMode && framesToPrint < totalFrames; int nestingRelatedFramesHidden = 0; int trailingFramesHidden = 0; int framesPrinted = 0; for (int frameIdx = 0; frameIdx < totalFrames; frameIdx++) { TemplateElement stackEl = instructionStackSnapshot[frameIdx]; final boolean nestingRelatedElement = (frameIdx > 0 && stackEl instanceof BodyInstruction) || (frameIdx > 1 && instructionStackSnapshot[frameIdx - 1] instanceof BodyInstruction); if (framesPrinted < framesToPrint) { if (!nestingRelatedElement || !hideNestringRelatedFrames) { w.write(frameIdx == 0 ? "\t- Failed at: " : (nestingRelatedElement ? "\t~ Reached through: " : "\t- Reached through: ")); w.write(instructionStackItemToString(stackEl)); if (pw != null) pw.println(); else w.write('\n'); framesPrinted++; } else { nestingRelatedFramesHidden++; } } else { trailingFramesHidden++; } } boolean hadClosingNotes = false; if (trailingFramesHidden > 0) { w.write("\t... (Had "); w.write(String.valueOf(trailingFramesHidden + nestingRelatedFramesHidden)); w.write(" more, hidden for tersenes)"); hadClosingNotes = true; } if (nestingRelatedFramesHidden > 0) { if (hadClosingNotes) { w.write(' '); } else { w.write('\t'); } w.write("(Hidden " + nestingRelatedFramesHidden + " \"~\" lines for terseness)"); if (pw != null) pw.println(); else w.write('\n'); hadClosingNotes = true; } if (hadClosingNotes) { if (pw != null) pw.println(); else w.write('\n'); } } else { w.write("(The stack was empty)"); if (pw != null) pw.println(); else w.write('\n'); } } catch (IOException e) { LOG.error("Failed to print FTL stack trace", e); } }
Returns the snapshot of what would be printed as FTL stack trace.
Since:2.3.20
/** * Returns the snapshot of what would be printed as FTL stack trace. * * @since 2.3.20 */
TemplateElement[] getInstructionStackSnapshot() { int requiredLength = 0; int ln = instructionStackSize; for (int i = 0; i < ln; i++) { TemplateElement stackEl = instructionStack[i]; if (i == ln - 1 || stackEl.isShownInStackTrace()) { requiredLength++; } } if (requiredLength == 0) return null; TemplateElement[] result = new TemplateElement[requiredLength]; int dstIdx = requiredLength - 1; for (int i = 0; i < ln; i++) { TemplateElement stackEl = instructionStack[i]; if (i == ln - 1 || stackEl.isShownInStackTrace()) { result[dstIdx--] = stackEl; } } return result; } static String instructionStackItemToString(TemplateElement stackEl) { StringBuilder sb = new StringBuilder(); appendInstructionStackItem(stackEl, sb); return sb.toString(); } static void appendInstructionStackItem(TemplateElement stackEl, StringBuilder sb) { sb.append(_MessageUtil.shorten(stackEl.getDescription(), 40)); sb.append(" ["); Macro enclosingMacro = getEnclosingMacro(stackEl); if (enclosingMacro != null) { sb.append(_MessageUtil.formatLocationForEvaluationError( enclosingMacro, stackEl.beginLine, stackEl.beginColumn)); } else { sb.append(_MessageUtil.formatLocationForEvaluationError( stackEl.getTemplate(), stackEl.beginLine, stackEl.beginColumn)); } sb.append("]"); } static private Macro getEnclosingMacro(TemplateElement stackEl) { while (stackEl != null) { if (stackEl instanceof Macro) return (Macro) stackEl; stackEl = stackEl.getParentElement(); } return null; } private void pushLocalContext(LocalContext localContext) { if (localContextStack == null) { localContextStack = new LocalContextStack(); } localContextStack.push(localContext); } LocalContextStack getLocalContextStack() { return localContextStack; }
Returns the name-space for the name if exists, or null.
Params:
  • name – the template path that you have used with the import directive or importLib(String, String) call, in normalized form. That is, the path must be an absolute path, and it must not contain "/../" or "/./". The leading "/" is optional.
/** * Returns the name-space for the name if exists, or null. * * @param name * the template path that you have used with the <code>import</code> directive or * {@link #importLib(String, String)} call, in normalized form. That is, the path must be an absolute * path, and it must not contain "/../" or "/./". The leading "/" is optional. */
public Namespace getNamespace(String name) { if (name.startsWith("/")) name = name.substring(1); if (loadedLibs != null) { return loadedLibs.get(name); } else { return null; } }
Returns the main namespace. This corresponds to the FTL .main hash.
/** * Returns the main namespace. This corresponds to the FTL {@code .main} hash. */
public Namespace getMainNamespace() { return mainNamespace; }
Returns the current namespace. This corresponds to the FTL .namespace hash. Initially, the current name space is the main namespace, but when inside an #import-ed template, it will change to the namespace of that import. Note that #include doesn't affect the namespace, so if you are in an #import-ed template and then from there do an #include, the current namespace will remain the namespace of the #import.
/** * Returns the current namespace. This corresponds to the FTL {@code .namespace} hash. Initially, the current name * space is the main namespace, but when inside an {@code #import}-ed template, it will change to the namespace of * that import. Note that {@code #include} doesn't affect the namespace, so if you are in an {@code #import}-ed * template and then from there do an {@code #include}, the current namespace will remain the namespace of the * {@code #import}. */
public Namespace getCurrentNamespace() { return currentNamespace; }
Returns the name-space that contains the globally visible non-data-model variables (usually created with &lt;#global ...&gt;).
/** * Returns the name-space that contains the globally visible non-data-model variables (usually created with * {@code &lt;#global ...&gt;}). */
public Namespace getGlobalNamespace() { return globalNamespace; }
Returns a view of the data-model (also known as the template context in some other template engines) that falls back to shared variables.
/** * Returns a view of the data-model (also known as the template context in some other template engines) * that falls back to {@linkplain Configuration#setSharedVariable(String, TemplateModel) shared variables}. */
public TemplateHashModel getDataModel() { return rootDataModel instanceof TemplateHashModelEx ? new TemplateHashModelEx() { public boolean isEmpty() throws TemplateModelException { return false; } public TemplateModel get(String key) throws TemplateModelException { TemplateModel value = rootDataModel.get(key); return value != null ? value : configuration.getSharedVariable(key); } // NB: The methods below do not take into account // configuration shared variables even though // the hash will return them, if only for BWC reasons public TemplateCollectionModel values() throws TemplateModelException { return ((TemplateHashModelEx) rootDataModel).values(); } public TemplateCollectionModel keys() throws TemplateModelException { return ((TemplateHashModelEx) rootDataModel).keys(); } public int size() throws TemplateModelException { return ((TemplateHashModelEx) rootDataModel).size(); } } : new TemplateHashModel() { public boolean isEmpty() { return false; } public TemplateModel get(String key) throws TemplateModelException { TemplateModel value = rootDataModel.get(key); return value != null ? value : configuration.getSharedVariable(key); } }; }
Returns the read-only hash of globally visible variables. This is the correspondent of FTL .globals hash. That is, you see the variables created with <#global ...>, and the variables of the data-model. To create new global variables, use setGlobalVariable.
/** * Returns the read-only hash of globally visible variables. This is the correspondent of FTL <code>.globals</code> * hash. That is, you see the variables created with <code>&lt;#global ...&gt;</code>, and the variables of the * data-model. To create new global variables, use {@link #setGlobalVariable setGlobalVariable}. */
public TemplateHashModel getGlobalVariables() { return new TemplateHashModel() { public boolean isEmpty() { return false; } public TemplateModel get(String key) throws TemplateModelException { TemplateModel result = globalNamespace.get(key); if (result == null) { result = rootDataModel.get(key); } if (result == null) { result = configuration.getSharedVariable(key); } return result; } }; } private void pushElement(TemplateElement element) { final int newSize = ++instructionStackSize; TemplateElement[] instructionStack = this.instructionStack; if (newSize > instructionStack.length) { final TemplateElement[] newInstructionStack = new TemplateElement[newSize * 2]; for (int i = 0; i < instructionStack.length; i++) { newInstructionStack[i] = instructionStack[i]; } instructionStack = newInstructionStack; this.instructionStack = instructionStack; } instructionStack[newSize - 1] = element; } private void popElement() { instructionStackSize--; } void replaceElementStackTop(TemplateElement instr) { instructionStack[instructionStackSize - 1] = instr; } public TemplateNodeModel getCurrentVisitorNode() { return currentVisitorNode; }
sets TemplateNodeModel as the current visitor node. .current_node
/** * sets TemplateNodeModel as the current visitor node. <tt>.current_node</tt> */
public void setCurrentVisitorNode(TemplateNodeModel node) { currentVisitorNode = node; } TemplateModel getNodeProcessor(TemplateNodeModel node) throws TemplateException { String nodeName = node.getNodeName(); if (nodeName == null) { throw new _MiscTemplateException(this, "Node name is null."); } TemplateModel result = getNodeProcessor(nodeName, node.getNodeNamespace(), 0); if (result == null) { String type = node.getNodeType(); /* DD: Original version: */ if (type == null) { type = "default"; } result = getNodeProcessor("@" + type, null, 0); /* * DD: Jonathan's non-BC version and IMHO otherwise wrong version: if (type != null) { result = * getNodeProcessor("@" + type, null, 0); } if (result == null) { result = getNodeProcessor("@default", * null, 0); } */ } return result; } private TemplateModel getNodeProcessor(final String nodeName, final String nsURI, int startIndex) throws TemplateException { TemplateModel result = null; int i; int size = nodeNamespaces.size(); for (i = startIndex; i < size; i++) { Namespace ns = null; try { ns = (Namespace) nodeNamespaces.get(i); } catch (ClassCastException cce) { throw new _MiscTemplateException(this, "A \"using\" clause should contain a sequence of namespaces or strings that indicate the " + "location of importable macro libraries."); } result = getNodeProcessor(ns, nodeName, nsURI); if (result != null) break; } if (result != null) { this.nodeNamespaceIndex = i + 1; this.currentNodeName = nodeName; this.currentNodeNS = nsURI; } return result; } private TemplateModel getNodeProcessor(Namespace ns, String localName, String nsURI) throws TemplateException { TemplateModel result = null; if (nsURI == null) { result = ns.get(localName); if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) { result = null; } } else { Template template = ns.getTemplate(); String prefix = template.getPrefixForNamespace(nsURI); if (prefix == null) { // The other template cannot handle this node // since it has no prefix registered for the namespace return null; } if (prefix.length() > 0) { result = ns.get(prefix + ":" + localName); if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) { result = null; } } else { if (nsURI.length() == 0) { result = ns.get(Template.NO_NS_PREFIX + ":" + localName); if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) { result = null; } } if (nsURI.equals(template.getDefaultNS())) { result = ns.get(Template.DEFAULT_NAMESPACE_PREFIX + ":" + localName); if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) { result = null; } } if (result == null) { result = ns.get(localName); if (!(result instanceof Macro) && !(result instanceof TemplateTransformModel)) { result = null; } } } } return result; }
Emulates include directive, except that name must be template root relative.

It's the same as include(getTemplateForInclusion(name, encoding, parse)). But, you may want to separately call these two methods, so you can determine the source of exceptions more precisely, and thus achieve more intelligent error handling.

See Also:
/** * Emulates <code>include</code> directive, except that <code>name</code> must be template root relative. * * <p> * It's the same as <code>include(getTemplateForInclusion(name, encoding, parse))</code>. But, you may want to * separately call these two methods, so you can determine the source of exceptions more precisely, and thus achieve * more intelligent error handling. * * @see #getTemplateForInclusion(String name, String encoding, boolean parse) * @see #include(Template includedTemplate) */
public void include(String name, String encoding, boolean parse) throws IOException, TemplateException { include(getTemplateForInclusion(name, encoding, parse)); }
Same as getTemplateForInclusion(String, String, boolean, boolean) with false ignoreMissing argument.
/** * Same as {@link #getTemplateForInclusion(String, String, boolean, boolean)} with {@code false} * {@code ignoreMissing} argument. */
public Template getTemplateForInclusion(String name, String encoding, boolean parse) throws IOException { return getTemplateForInclusion(name, encoding, parse, false); }
Gets a template for inclusion; used for implementing include(Template includedTemplate). The advantage over simply using config.getTemplate(...) is that it chooses the default encoding exactly as the include directive does, although that encoding selection mechanism is a historical baggage and considered to be harmful.
Params:
Throws:
Returns:Same as Configuration.getTemplate(String, Locale, String, boolean, boolean)
Since:2.3.21
/** * Gets a template for inclusion; used for implementing {@link #include(Template includedTemplate)}. The advantage * over simply using <code>config.getTemplate(...)</code> is that it chooses the default encoding exactly as the * <code>include</code> directive does, although that encoding selection mechanism is a historical baggage and * considered to be harmful. * * @param name * the name of the template, relatively to the template root directory (not the to the directory of the * currently executing template file). (Note that you can use * {@link freemarker.cache.TemplateCache#getFullTemplatePath} to convert paths to template root relative * paths.) For more details see the identical parameter of * {@link Configuration#getTemplate(String, Locale, String, boolean, boolean)} * * @param encoding * the charset of the obtained template. If {@code null}, the encoding of the top template that is * currently being processed in this {@link Environment} is used, which can lead to odd situations, so * using {@code null} is not recommended. In most applications, the value of * {@link Configuration#getEncoding(Locale)} (or {@link Configuration#getDefaultEncoding()}) should be * used here. * * @param parseAsFTL * See identical parameter of {@link Configuration#getTemplate(String, Locale, String, boolean, boolean)} * * @param ignoreMissing * See identical parameter of {@link Configuration#getTemplate(String, Locale, String, boolean, boolean)} * * @return Same as {@link Configuration#getTemplate(String, Locale, String, boolean, boolean)} * @throws IOException * Same as exceptions thrown by * {@link Configuration#getTemplate(String, Locale, String, boolean, boolean)} * * @since 2.3.21 */
public Template getTemplateForInclusion(String name, String encoding, boolean parseAsFTL, boolean ignoreMissing) throws IOException { return configuration.getTemplate( name, getLocale(), getIncludedTemplateCustomLookupCondition(), encoding != null ? encoding : getIncludedTemplateEncoding(), parseAsFTL, ignoreMissing); } private Object getIncludedTemplateCustomLookupCondition() { return getTemplate().getCustomLookupCondition(); } private String getIncludedTemplateEncoding() { String encoding; // This branch shouldn't exist, as it doesn't make much sense to inherit encoding. But we have to keep BC. encoding = getTemplate().getEncoding(); if (encoding == null) { encoding = configuration.getEncoding(this.getLocale()); } return encoding; }
Processes a Template in the context of this Environment, including its output in the Environment's Writer.
Params:
/** * Processes a Template in the context of this <code>Environment</code>, including its output in the * <code>Environment</code>'s Writer. * * @param includedTemplate * the template to process. Note that it does <em>not</em> need to be a template returned by * {@link #getTemplateForInclusion(String name, String encoding, boolean parse)}. */
public void include(Template includedTemplate) throws TemplateException, IOException { final Template prevTemplate; final boolean parentReplacementOn = isBeforeIcI2322(); prevTemplate = getTemplate(); if (parentReplacementOn) { setParent(includedTemplate); } else { legacyParent = includedTemplate; } importMacros(includedTemplate); try { visit(includedTemplate.getRootTreeNode()); } finally { if (parentReplacementOn) { setParent(prevTemplate); } else { legacyParent = prevTemplate; } } }
Emulates import directive, except that templateName must be template root relative.

It's the same as importLib(getTemplateForImporting(templateName), namespace). But, you may want to separately call these two methods, so you can determine the source of exceptions more precisely, and thus achieve more intelligent error handling.

If it will be a lazy or an eager import is decided by the value of Configurable.getLazyImports(). You can also directly control that aspect by using importLib(String, String, boolean) instead.

See Also:
Returns:Not null. This is possibly a lazily self-initializing namespace, which means that it will only try to get and process the imported template when you access its content.
/** * Emulates <code>import</code> directive, except that <code>templateName</code> must be template root relative. * * <p> * It's the same as <code>importLib(getTemplateForImporting(templateName), namespace)</code>. But, you may want to * separately call these two methods, so you can determine the source of exceptions more precisely, and thus achieve * more intelligent error handling. * * <p> * If it will be a lazy or an eager import is decided by the value of {@link #getLazyImports()}. You * can also directly control that aspect by using {@link #importLib(String, String, boolean)} instead. * * @return Not {@code null}. This is possibly a lazily self-initializing namespace, which means that it will only * try to get and process the imported template when you access its content. * * @see #getTemplateForImporting(String templateName) * @see #importLib(Template includedTemplate, String namespaceVarName) * @see #importLib(String, String, boolean) */
public Namespace importLib(String templateName, String targetNsVarName) throws IOException, TemplateException { return importLib(templateName, targetNsVarName, getLazyImports()); }
Does what the #import directive does, but with an already loaded template.
Params:
  • loadedTemplate – The template to import. Note that it does not need to be a template returned by getTemplateForImporting(String name). Not null.
  • targetNsVarName – The name of the FTL variable that will store the namespace. If null, the namespace won't be stored in a variable (but it's still returned).
See Also:
Returns:The namespace of the imported template, already initialized.
/** * Does what the <code>#import</code> directive does, but with an already loaded template. * * @param loadedTemplate * The template to import. Note that it does <em>not</em> need to be a template returned by * {@link #getTemplateForImporting(String name)}. Not {@code null}. * @param targetNsVarName * The name of the FTL variable that will store the namespace. If {@code null}, the namespace * won't be stored in a variable (but it's still returned). * * @return The namespace of the imported template, already initialized. * * @see #getTemplateForImporting(String name) * @see #importLib(Template includedTemplate, String namespaceVarName) */
public Namespace importLib(Template loadedTemplate, String targetNsVarName) throws IOException, TemplateException { return importLib(null, loadedTemplate, targetNsVarName); }
Like importLib(String, String), but you can specify if you want a lazy import or not.
Returns:Not null. This is possibly a lazily self-initializing namespace, which mean that it will only try to get and process the imported template when you access its content.
Since:2.3.25
/** * Like {@link #importLib(String, String)}, but you can specify if you want a * {@linkplain #setLazyImports(boolean) lazy import} or not. * * @return Not {@code null}. This is possibly a lazily self-initializing namespace, which mean that it will only try * to get and process the imported template when you access its content. * * @since 2.3.25 */
public Namespace importLib(String templateName, String targetNsVarName, boolean lazy) throws IOException, TemplateException { return lazy ? importLib(templateName, null, targetNsVarName) : importLib(null, getTemplateForImporting(templateName), targetNsVarName); }
Gets a template for importing; used with importLib(Template importedTemplate, String namespace). The advantage over simply using config.getTemplate(...) is that it chooses the encoding as the import directive does.
Params:
  • name – the name of the template, relatively to the template root directory (not the to the directory of the currently executing template file!). (Note that you can use TemplateCache.getFullTemplatePath to convert paths to template root relative paths.)
/** * Gets a template for importing; used with {@link #importLib(Template importedTemplate, String namespace)}. The * advantage over simply using <code>config.getTemplate(...)</code> is that it chooses the encoding as the * <code>import</code> directive does. * * @param name * the name of the template, relatively to the template root directory (not the to the directory of the * currently executing template file!). (Note that you can use * {@link freemarker.cache.TemplateCache#getFullTemplatePath} to convert paths to template root relative * paths.) */
public Template getTemplateForImporting(String name) throws IOException { return getTemplateForInclusion(name, null, true); }
Params:
  • templateName – Ignored if loadedTemaplate is set (so we do eager import), otherwise it can't be null. Assumed to be template root directory relative (not relative to the current template).
  • loadedTemplate – null exactly if we want a lazy import
/** * @param templateName * Ignored if {@code loadedTemaplate} is set (so we do eager import), otherwise it can't be {@code null}. * Assumed to be template root directory relative (not relative to the current template). * @param loadedTemplate * {@code null} exactly if we want a lazy import */
private Namespace importLib( String templateName, final Template loadedTemplate, final String targetNsVarName) throws IOException, TemplateException { final boolean lazyImport; if (loadedTemplate != null) { lazyImport = false; // As we have an already normalized name, we use it. 2.3.x note: We should use the template.sourceName as // namespace key, but historically we use the looked up name (template.name); check what lazy import does if // that will be fixed, as that can't do the template lookup, yet the keys must be the same. templateName = loadedTemplate.getName(); } else { lazyImport = true; // We can't cause a template lookup here (see TemplateLookupStrategy), as that can be expensive. We exploit // that (at least in 2.3.x) the name used for eager import namespace key isn't the template.sourceName, but // the looked up name (template.name), which we can get quickly: TemplateNameFormat tnf = getConfiguration().getTemplateNameFormat(); templateName = _CacheAPI.normalizeRootBasedName(tnf, templateName); } if (loadedLibs == null) { loadedLibs = new HashMap(); } final Namespace existingNamespace = loadedLibs.get(templateName); if (existingNamespace != null) { if (targetNsVarName != null) { setVariable(targetNsVarName, existingNamespace); if (isIcI2324OrLater() && currentNamespace == mainNamespace) { globalNamespace.put(targetNsVarName, existingNamespace); } } if (!lazyImport && existingNamespace instanceof LazilyInitializedNamespace) { ((LazilyInitializedNamespace) existingNamespace).ensureInitializedTME(); } } else { final Namespace newNamespace = lazyImport ? new LazilyInitializedNamespace(templateName) : new Namespace(loadedTemplate); loadedLibs.put(templateName, newNamespace); if (targetNsVarName != null) { setVariable(targetNsVarName, newNamespace); if (currentNamespace == mainNamespace) { globalNamespace.put(targetNsVarName, newNamespace); } } if (!lazyImport) { initializeImportLibNamespace(newNamespace, loadedTemplate); } } return loadedLibs.get(templateName); } private void initializeImportLibNamespace(final Namespace newNamespace, Template loadedTemplate) throws TemplateException, IOException { Namespace prevNamespace = this.currentNamespace; this.currentNamespace = newNamespace; Writer prevOut = out; this.out = NullWriter.INSTANCE; try { include(loadedTemplate); } finally { this.out = prevOut; this.currentNamespace = prevNamespace; } }
Resolves a reference to a template (like the one used in #include or #import), assuming a base name. This gives a root based, even if non-normalized and possibly non-absolute (but then relative to the root) template name, that could be used for Configuration.getTemplate(String). This is mostly used when a template refers to another template.

If you need to guarantee that the result is also an absolute path, then apply rootBasedToAbsoluteTemplateName(String) on it.

Params:
  • baseName – The name to which relative targetName-s are relative to. Maybe null (happens when resolving names in nameless templates), which means that the base is the root "directory", and so the targetName is returned without change. Assuming TemplateNameFormat.DEFAULT_2_3_0 or TemplateNameFormat.DEFAULT_2_4_0, the rules are as follows. If you want to specify a base directory here, it must end with "/". If it doesn't end with "/", it's parent directory will be used as the base path. Might starts with a scheme part (like "foo://", or with TemplateNameFormat.DEFAULT_2_4_0 even just with "foo:").
  • targetName – The name of the template, which is either a relative or absolute name. Assuming TemplateNameFormat.DEFAULT_2_3_0 or TemplateNameFormat.DEFAULT_2_4_0, the rules are as follows. If it starts with "/" or contains a scheme part separator ("://", also, with TemplateNameFormat.DEFAULT_2_4_0 a ":" with no "/" anywhere before it) then it's an absolute name, otherwise it's a relative path. Relative paths are interpreted relatively to the baseName. Absolute names are simply returned as is, ignoring the baseName, except, when the baseName has scheme part while the targetName doesn't have, then the schema of the baseName is prepended to the targetName.
Since:2.3.22
/** * Resolves a reference to a template (like the one used in {@code #include} or {@code #import}), assuming a base * name. This gives a root based, even if non-normalized and possibly non-absolute (but then relative to the root) * template name, that could be used for {@link Configuration#getTemplate(String)}. This is mostly used when a * template refers to another template. * <p> * If you need to guarantee that the result is also an absolute path, then apply * {@link #rootBasedToAbsoluteTemplateName(String)} on it. * * @param baseName * The name to which relative {@code targetName}-s are relative to. Maybe {@code null} (happens when * resolving names in nameless templates), which means that the base is the root "directory", and so the * {@code targetName} is returned without change. Assuming {@link TemplateNameFormat#DEFAULT_2_3_0} or * {@link TemplateNameFormat#DEFAULT_2_4_0}, the rules are as follows. If you want to specify a base * directory here, it must end with {@code "/"}. If it doesn't end with {@code "/"}, it's parent * directory will be used as the base path. Might starts with a scheme part (like {@code "foo://"}, or * with {@link TemplateNameFormat#DEFAULT_2_4_0} even just with {@code "foo:"}). * @param targetName * The name of the template, which is either a relative or absolute name. Assuming * {@link TemplateNameFormat#DEFAULT_2_3_0} or {@link TemplateNameFormat#DEFAULT_2_4_0}, the rules are as * follows. If it starts with {@code "/"} or contains a scheme part separator ({@code "://"}, also, with * {@link TemplateNameFormat#DEFAULT_2_4_0} a {@code ":"} with no {@code "/"} anywhere before it) then * it's an absolute name, otherwise it's a relative path. Relative paths are interpreted relatively to * the {@code baseName}. Absolute names are simply returned as is, ignoring the {@code baseName}, except, * when the {@code baseName} has scheme part while the {@code targetName} doesn't have, then the schema * of the {@code baseName} is prepended to the {@code targetName}. * * @since 2.3.22 */
public String toFullTemplateName(String baseName, String targetName) throws MalformedTemplateNameException { if (isClassicCompatible() /* FM1 only had absolute names */ || baseName == null) { return targetName; } return _CacheAPI.toRootBasedName(configuration.getTemplateNameFormat(), baseName, targetName); }
Converts a root based name (a name that's either relative to the root, or is absolute), which are typically used by the API (such as for Configuration.getTemplate(String)), to an absolute name, which can be safely passed to <#include path> and such, as it won't be misinterpreted to be relative to the directory of the template. For example, "foo/bar.ftl" is converted to "/foo/bar.ftl", while "/foo/bar" or "foo://bar/baz" remains as is, as they are already absolute names (see TemplateNameFormat for more about the format of names).

You only need this if the template name will be passed to <#include name>, <#import name>, .get_optional_template(name) or a similar construct in a template, otherwise using non-absolute root based names is fine.

Since:2.3.28
/** * Converts a root based name (a name that's either relative to the root, or is absolute), which are typically used * by the API (such as for {@link Configuration#getTemplate(String)}), to an absolute name, which can be safely * passed to {@code <#include path>} and such, as it won't be misinterpreted to be relative to the directory of the * template. For example, {@code "foo/bar.ftl"} is converted to {@code "/foo/bar.ftl"}, while {@code "/foo/bar"} or * {@code "foo://bar/baz"} remains as is, as they are already absolute names (see {@link TemplateNameFormat} for * more about the format of names). * * <p> * You only need this if the template name will be passed to {@code <#include name>}, {@code <#import name>}, * {@code .get_optional_template(name)} or a similar construct in a template, otherwise using non-absolute root * based names is fine. * * @since 2.3.28 */
public String rootBasedToAbsoluteTemplateName(String rootBasedName) throws MalformedTemplateNameException { return _CacheAPI.rootBasedNameToAbsoluteName(configuration.getTemplateNameFormat(), rootBasedName); } String renderElementToString(TemplateElement te) throws IOException, TemplateException { Writer prevOut = out; try { StringWriter sw = new StringWriter(); this.out = sw; visit(te); return sw.toString(); } finally { this.out = prevOut; } } void importMacros(Template template) { for (Iterator it = template.getMacros().values().iterator(); it.hasNext();) { visitMacroDef((Macro) it.next()); } }
Returns:the namespace URI registered for this prefix, or null. This is based on the mappings registered in the current namespace.
/** * @return the namespace URI registered for this prefix, or null. This is based on the mappings registered in the * current namespace. */
public String getNamespaceForPrefix(String prefix) { return currentNamespace.getTemplate().getNamespaceForPrefix(prefix); } public String getPrefixForNamespace(String nsURI) { return currentNamespace.getTemplate().getPrefixForNamespace(nsURI); }
Returns:the default node namespace for the current FTL namespace
/** * @return the default node namespace for the current FTL namespace */
public String getDefaultNS() { return currentNamespace.getTemplate().getDefaultNS(); }
A hook that Jython uses.
/** * A hook that Jython uses. */
public Object __getitem__(String key) throws TemplateModelException { return BeansWrapper.getDefaultInstance().unwrap(getVariable(key)); }
A hook that Jython uses.
/** * A hook that Jython uses. */
public void __setitem__(String key, Object o) throws TemplateException { setGlobalVariable(key, getObjectWrapper().wrap(o)); } private IdentityHashMap<Object, Object> customStateVariables;
Returns the value of a custom state variable, or null if it's missing; see setCustomState(Object, Object) for more.
Since:2.3.24
/** * Returns the value of a custom state variable, or {@code null} if it's missing; see * {@link #setCustomState(Object, Object)} for more. * * @since 2.3.24 */
public Object getCustomState(Object identityKey) { if (customStateVariables == null) { return null; } return customStateVariables.get(identityKey); }
Sets the value of a custom state variable. Custom state variables meant to be used by TemplateNumberFormatFactory-es, TemplateDateFormatFactory-es, and similar user-implementable, pluggable objects, which want to maintain an Environment-scoped state (such as a cache).
Params:
  • identityKey – The key that identifies the variable, by its object identity (not by Object.equals(Object)). This should be something like a private static final Object CUSTOM_STATE_KEY = new Object(); in the class that needs this state variable.
  • value – The value of the variable. Can be anything, even null.
Returns:The previous value of the variable, or null if the variable didn't exist.
Since:2.3.24
/** * Sets the value of a custom state variable. Custom state variables meant to be used by * {@link TemplateNumberFormatFactory}-es, {@link TemplateDateFormatFactory}-es, and similar user-implementable, * pluggable objects, which want to maintain an {@link Environment}-scoped state (such as a cache). * * @param identityKey * The key that identifies the variable, by its object identity (not by {@link Object#equals(Object)}). * This should be something like a {@code private static final Object CUSTOM_STATE_KEY = new Object();} * in the class that needs this state variable. * @param value * The value of the variable. Can be anything, even {@code null}. * * @return The previous value of the variable, or {@code null} if the variable didn't exist. * * @since 2.3.24 */
public Object setCustomState(Object identityKey, Object value) { IdentityHashMap<Object, Object> customStateVariables = this.customStateVariables; if (customStateVariables == null) { customStateVariables = new IdentityHashMap<Object, Object>(); this.customStateVariables = customStateVariables; } return customStateVariables.put(identityKey, value); } final class NestedElementTemplateDirectiveBody implements TemplateDirectiveBody { private final TemplateElement[] childBuffer; private NestedElementTemplateDirectiveBody(TemplateElement[] childBuffer) { this.childBuffer = childBuffer; } public void render(Writer newOut) throws TemplateException, IOException { Writer prevOut = out; out = newOut; try { visit(childBuffer); } finally { out = prevOut; } } TemplateElement[] getChildrenBuffer() { return childBuffer; } } public class Namespace extends SimpleHash { private Template template; Namespace() { this.template = Environment.this.getTemplate(); } Namespace(Template template) { this.template = template; }
Returns:the Template object with which this Namespace is associated.
/** * @return the Template object with which this Namespace is associated. */
public Template getTemplate() { return template == null ? Environment.this.getTemplate() : template; } void setTemplate(Template template) { this.template = template; } } private enum InitializationStatus { UNINITIALIZED, INITIALIZING, INITIALIZED, FAILED } class LazilyInitializedNamespace extends Namespace { private final String templateName; private final Locale locale; private final String encoding; private final Object customLookupCondition; private InitializationStatus status = InitializationStatus.UNINITIALIZED;
Params:
  • templateName – Must be root relative
/** * @param templateName * Must be root relative */
private LazilyInitializedNamespace(String templateName) { super(null); this.templateName = templateName; // Make snapshot of all settings that influence template resolution: this.locale = getLocale(); this.encoding = getIncludedTemplateEncoding(); this.customLookupCondition = getIncludedTemplateCustomLookupCondition(); } private void ensureInitializedTME() throws TemplateModelException { if (status != InitializationStatus.INITIALIZED && status != InitializationStatus.INITIALIZING) { if (status == InitializationStatus.FAILED) { throw new TemplateModelException( "Lazy initialization of the imported namespace for " + StringUtil.jQuote(templateName) + " has already failed earlier; won't retry it."); } try { status = InitializationStatus.INITIALIZING; initialize(); status = InitializationStatus.INITIALIZED; } catch (Exception e) { // [FM3] Rethrow TemplateException-s as is throw new TemplateModelException( "Lazy initialization of the imported namespace for " + StringUtil.jQuote(templateName) + " has failed; see cause exception", e); } finally { if (status != InitializationStatus.INITIALIZED) { status = InitializationStatus.FAILED; } } } } private void ensureInitializedRTE() { try { ensureInitializedTME(); } catch (TemplateModelException e) { throw new RuntimeException(e.getMessage(), e.getCause()); } } private void initialize() throws IOException, TemplateException { setTemplate(configuration.getTemplate( templateName, locale, customLookupCondition, encoding, true, false)); Locale lastLocale = getLocale(); try { setLocale(locale); initializeImportLibNamespace(this, getTemplate()); } finally { setLocale(lastLocale); } } @Override protected Map copyMap(Map map) { ensureInitializedRTE(); return super.copyMap(map); } @Override public Template getTemplate() { ensureInitializedRTE(); return super.getTemplate(); } @Override public void put(String key, Object value) { ensureInitializedRTE(); super.put(key, value); } @Override public void put(String key, boolean b) { ensureInitializedRTE(); super.put(key, b); } @Override public TemplateModel get(String key) throws TemplateModelException { ensureInitializedTME(); return super.get(key); } @Override public boolean containsKey(String key) { ensureInitializedRTE(); return super.containsKey(key); } @Override public void remove(String key) { ensureInitializedRTE(); super.remove(key); } @Override public void putAll(Map m) { ensureInitializedRTE(); super.putAll(m); } @Override public Map toMap() throws TemplateModelException { ensureInitializedTME(); return super.toMap(); } @Override public String toString() { ensureInitializedRTE(); return super.toString(); } @Override public int size() { ensureInitializedRTE(); return super.size(); } @Override public boolean isEmpty() { ensureInitializedRTE(); return super.isEmpty(); } @Override public TemplateCollectionModel keys() { ensureInitializedRTE(); return super.keys(); } @Override public TemplateCollectionModel values() { ensureInitializedRTE(); return super.values(); } @Override public KeyValuePairIterator keyValuePairIterator() { ensureInitializedRTE(); return super.keyValuePairIterator(); } } private static final Writer EMPTY_BODY_WRITER = new Writer() { @Override public void write(char[] cbuf, int off, int len) throws IOException { if (len > 0) { throw new IOException( "This transform does not allow nested content."); } } @Override public void flush() { } @Override public void close() { } }; private boolean isBeforeIcI2322() { return configuration.getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_22; } boolean isIcI2324OrLater() { return configuration.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_24; } /** * See {@link #setFastInvalidReferenceExceptions(boolean)}. */ boolean getFastInvalidReferenceExceptions() { return fastInvalidReferenceExceptions; }
Sets if for invalid references InvalidReferenceException.FAST_INSTANCE should be thrown, or a new InvalidReferenceException. The "fast" instance is used if we know that the error will be handled so that its message will not be logged or shown anywhere.
/** * Sets if for invalid references {@link InvalidReferenceException#FAST_INSTANCE} should be thrown, or a new * {@link InvalidReferenceException}. The "fast" instance is used if we know that the error will be handled so that * its message will not be logged or shown anywhere. */
boolean setFastInvalidReferenceExceptions(boolean b) { boolean res = fastInvalidReferenceExceptions; fastInvalidReferenceExceptions = b; return res; } }