You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2015/09/08 00:29:34 UTC
[3/6] incubator-freemarker git commit: Added public Environment
methods for getting TemplateDateFormat objects. Other formatting code clean
up and JavaDoc comments.
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52dcb977/src/main/java/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 5079acf..b616b5e 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -23,8 +23,9 @@ 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.DateFormat;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
@@ -75,19 +76,19 @@ import freemarker.template.utility.StringUtil;
import freemarker.template.utility.UndeclaredThrowableException;
/**
- * Object that represents the runtime environment during template processing.
- * For every invocation of a <tt>Template.process()</tt> method, a new instance
- * of this object is created, and then discarded when <tt>process()</tt> returns.
- * This object stores the set of temporary variables created by the template,
- * the value of settings set by the template, the reference to the data model root,
- * etc. Everything that is needed to fulfill the template processing job.
+ * 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>
+ * 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)}
+ * <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 {
@@ -98,10 +99,10 @@ public final class Environment extends Configurable {
// 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));
+ 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);
@@ -109,7 +110,7 @@ public final class Environment extends Configurable {
private final Configuration configuration;
private final TemplateHashModel rootDataModel;
- private final ArrayList/*<TemplateElement>*/ instructionStack = new ArrayList();
+ private final ArrayList/* <TemplateElement> */ instructionStack = new ArrayList();
private final ArrayList recoveredErrorStack = new ArrayList();
private TemplateNumberFormat cachedTemplateNumberFormat;
@@ -117,19 +118,21 @@ public final class Environment extends Configurable {
/**
* 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}.
+ * 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>
+ * <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.
+ * <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 {@link #cachedTempDateFormatArray}, but used when a formatting string was specified. */
@@ -137,23 +140,24 @@ public final class Environment extends Configurable {
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 #getISOBuiltInCalendarFactory()
+ *
+ * @see #getISOBuiltInCalendarFactory()
*/
private DateToISO8601CalendarFactory isoBuiltInCalendarFactory;
-
+
private Collator cachedCollator;
private Writer out;
private Macro.Context currentMacroContext;
- private ArrayList localContextStack;
+ private ArrayList localContextStack;
private final Namespace mainNamespace;
private Namespace currentNamespace, globalNamespace;
private HashMap loadedLibs;
@@ -161,21 +165,21 @@ public final class Environment extends Configurable {
private boolean inAttemptBlock;
private Throwable lastThrowable;
-
+
private TemplateModel lastReturnValue;
private HashMap macroToNamespaceLookup = new HashMap();
- private TemplateNodeModel currentVisitorNode;
+ 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 {@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
@@ -185,7 +189,7 @@ public final class Environment extends Configurable {
public static Environment getCurrentEnvironment() {
return (Environment) threadEnv.get();
}
-
+
static void setCurrentEnvironment(Environment env) {
threadEnv.set(env);
}
@@ -214,11 +218,11 @@ public final class Environment extends Configurable {
public Template getTemplate() {
return (Template) getParent();
}
-
+
/** Returns the same value as pre-IcI 2.3.22 getTemplate() did. */
Template getTemplate230() {
Template legacyParent = (Template) this.legacyParent;
- return legacyParent != null ? legacyParent : getTemplate();
+ return legacyParent != null ? legacyParent : getTemplate();
}
/**
@@ -232,7 +236,7 @@ public final class Environment extends Configurable {
public Template getMainTemplate() {
return mainNamespace.getTemplate();
}
-
+
/**
* 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
@@ -267,23 +271,22 @@ public final class Environment extends Configurable {
}
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.
*/
@@ -308,12 +311,12 @@ public final class Environment extends Configurable {
threadEnv.set(savedEnv);
}
}
-
+
/**
* "Visit" the template element.
*/
void visit(TemplateElement element)
- throws TemplateException, IOException {
+ throws TemplateException, IOException {
pushElement(element);
try {
element.accept(this);
@@ -323,16 +326,16 @@ public final class Environment extends Configurable {
popElement();
}
}
-
+
/**
* Instead of pushing into the element stack, we replace the top element for the time the parameter element is
* visited, and then we restore the top element. The main purpose of this is to get rid of elements in the error
* stack trace that from user perspective shouldn't have a stack frame. The typical example is
* {@code [#if foo]...[@failsHere/]...[/#if]}, where the #if call shouldn't be in the stack trace. (Simply marking
- * #if as hidden in stack traces would be wrong, because we still want to show #if when its test expression fails.)
+ * #if as hidden in stack traces would be wrong, because we still want to show #if when its test expression fails.)
*/
void visitByHiddingParent(TemplateElement element)
- throws TemplateException, IOException {
+ throws TemplateException, IOException {
TemplateElement parent = replaceTopElement(element);
try {
element.accept(this);
@@ -348,9 +351,9 @@ public final class Environment extends Configurable {
}
private static final TemplateModel[] NO_OUT_ARGS = new TemplateModel[0];
-
+
public void visit(final TemplateElement element,
- TemplateDirectiveModel directiveModel, Map args,
+ TemplateDirectiveModel directiveModel, Map args,
final List bodyParameterNames) throws TemplateException, IOException {
TemplateDirectiveBody nested;
if (element == null) {
@@ -366,6 +369,7 @@ public final class Environment extends Configurable {
}
if (outArgs.length > 0) {
pushLocalContext(new LocalContext() {
+
public TemplateModel getLocalVariable(String name) {
int index = bodyParameterNames.indexOf(name);
return index != -1 ? outArgs[index] : null;
@@ -384,26 +388,27 @@ public final class Environment extends Configurable {
}
}
}
-
+
/**
- * "Visit" the template element, passing the output
- * through a TemplateTransformModel
- * @param element the element to visit through a transform
- * @param transform the transform to pass the element output
- * through
- * @param args optional arguments fed to the transform
+ * "Visit" the template element, passing the output through a TemplateTransformModel
+ *
+ * @param element
+ * the element to visit through a transform
+ * @param transform
+ * the transform to pass the element output through
+ * @param args
+ * optional arguments fed to the transform
*/
void visitAndTransform(TemplateElement element,
- TemplateTransformModel transform,
- Map args)
- throws TemplateException, IOException {
+ 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;
+ TransformControl tc = tw instanceof TransformControl
+ ? (TransformControl) tw
+ : null;
Writer prevOut = out;
out = tw;
@@ -441,62 +446,61 @@ public final class Environment extends Configurable {
handleTemplateException(te);
}
}
-
+
/**
* Visit a block using buffering/recovery
*/
- void visitAttemptRecover(TemplateElement attemptBlock, RecoveryBlock recoveryBlock)
- 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;
- visitByHiddingParent(attemptBlock);
- } 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(recoveryBlock);
- } 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 <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;
- }
+ void visitAttemptRecover(TemplateElement attemptBlock, RecoveryBlock recoveryBlock)
+ 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;
+ visitByHiddingParent(attemptBlock);
+ } 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(recoveryBlock);
+ } 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 <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 {@code #nested}.
@@ -508,7 +512,7 @@ public final class Environment extends Configurable {
if (nestedContent != null) {
this.currentMacroContext = invokingMacroContext.prevMacroContext;
currentNamespace = invokingMacroContext.nestedContentNamespace;
-
+
final Configurable prevParent;
final boolean parentReplacementOn = isBeforeIcI2322();
prevParent = getParent();
@@ -517,7 +521,7 @@ public final class Environment extends Configurable {
} else {
legacyParent = currentNamespace.getTemplate();
}
-
+
this.localContextStack = invokingMacroContext.prevLocalContextStack;
if (invokingMacroContext.nestedContentParameterNames != null) {
pushLocalContext(bodyCtx);
@@ -544,7 +548,7 @@ public final class Environment extends Configurable {
* "visit" an IteratorBlock
*/
boolean visitIteratorBlock(IteratorBlock.IterationContext ictxt)
- throws TemplateException, IOException {
+ throws TemplateException, IOException {
pushLocalContext(ictxt);
try {
return ictxt.accept(this);
@@ -555,12 +559,12 @@ public final class Environment extends Configurable {
popLocalContext();
}
}
-
+
/**
* Used for {@code #visit} and {@code #recurse}.
*/
- void invokeNodeHandlerFor(TemplateNodeModel node, TemplateSequenceModel namespaces)
- throws TemplateException, IOException {
+ void invokeNodeHandlerFor(TemplateNodeModel node, TemplateSequenceModel namespaces)
+ throws TemplateException, IOException {
if (nodeNamespaces == null) {
SimpleSequence ss = new SimpleSequence(1);
ss.add(currentNamespace);
@@ -580,21 +584,21 @@ public final class Environment extends Configurable {
if (macroOrTransform instanceof Macro) {
invoke((Macro) macroOrTransform, null, null, null, null);
} else if (macroOrTransform instanceof TemplateTransformModel) {
- visitAndTransform(null, (TemplateTransformModel) macroOrTransform, null);
+ 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());
+ 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")) {
+ else if (!nodeType.equals("pi")
+ && !nodeType.equals("comment")
+ && !nodeType.equals("document_type")) {
throw new _MiscTemplateException(
this, noNodeHandlerDefinedDescription(node, node.getNodeNamespace(), nodeType));
}
@@ -614,7 +618,7 @@ public final class Environment extends Configurable {
private Object[] noNodeHandlerDefinedDescription(
TemplateNodeModel node, String ns, String nodeType)
- throws TemplateModelException {
+ throws TemplateModelException {
String nsPrefix;
if (ns != null) {
if (ns.length() > 0) {
@@ -626,44 +630,44 @@ public final class Environment extends Configurable {
nsPrefix = "";
ns = "";
}
- return new Object[] { "No macro or directive is defined for node named ",
+ 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);
+ visitAndTransform(null, (TemplateTransformModel) macroOrTransform, null);
}
}
-
+
/**
* Calls the macro or function with the given arguments and nested block.
*/
- void invoke(Macro macro,
- Map namedArgs, List positionalArgs,
- List bodyParameterNames, TemplateElement nestedBlock) throws TemplateException, IOException {
+ void invoke(Macro macro,
+ Map namedArgs, List positionalArgs,
+ List bodyParameterNames, TemplateElement nestedBlock) throws TemplateException, IOException {
if (macro == Macro.DO_NOTHING_MACRO) {
return;
}
-
+
pushElement(macro);
try {
final Macro.Context macroCtx = macro.new Context(this, nestedBlock, bodyParameterNames);
setMacroContextLocalsFromArguments(macroCtx, macro, namedArgs, positionalArgs);
-
+
final Macro.Context prevMacroCtx = currentMacroContext;
currentMacroContext = macroCtx;
-
+
final ArrayList prevLocalContextStack = localContextStack;
localContextStack = null;
-
+
final Namespace prevNamespace = currentNamespace;
currentNamespace = (Namespace) macroToNamespaceLookup.get(macro);
-
+
try {
macroCtx.runMacro(this);
} catch (ReturnInstruction.Return re) {
@@ -696,8 +700,8 @@ public final class Environment extends Configurable {
} else {
catchAllParamValue = null;
}
-
- for (Iterator it = namedArgs.entrySet().iterator(); it.hasNext(); ) {
+
+ 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);
@@ -723,11 +727,11 @@ public final class Environment extends Configurable {
} else {
catchAllParamValue = null;
}
-
+
String[] argNames = macro.getArgumentNamesInternal();
final int argsCnt = positionalArgs.size();
if (argNames.length < argsCnt && catchAllParamName == null) {
- throw new _MiscTemplateException(this,
+ throw new _MiscTemplateException(this,
(macro.isFunction() ? "Function " : "Macro "), new _DelayedJQuote(macro.getName()),
" only accepts ", new _DelayedToString(argNames.length), " parameters, but got ",
new _DelayedToString(argsCnt), ".");
@@ -748,7 +752,7 @@ public final class Environment extends Configurable {
}
}
}
-
+
/**
* Defines the given macro in the current namespace (doesn't call it).
*/
@@ -756,13 +760,13 @@ public final class Environment extends Configurable {
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 {
+ throws TemplateException, IOException {
if (node == null) {
node = this.getCurrentVisitorNode();
if (node == null) {
@@ -783,9 +787,9 @@ public final class Environment extends Configurable {
Macro.Context getCurrentMacroContext() {
return currentMacroContext;
}
-
+
private void handleTemplateException(TemplateException templateException)
- throws TemplateException {
+ throws TemplateException {
// Logic to prevent double-handling of the exception in
// nested visit() calls.
if (lastThrowable == templateException) {
@@ -814,7 +818,7 @@ public final class Environment extends Configurable {
super.setTemplateExceptionHandler(templateExceptionHandler);
lastThrowable = null;
}
-
+
@Override
public void setLocale(Locale locale) {
Locale prevLocale = getLocale();
@@ -824,7 +828,7 @@ public final class Environment extends Configurable {
if (cachedTemplateNumberFormat != null && cachedTemplateNumberFormat.isLocaleBound()) {
cachedTemplateNumberFormat = null;
}
-
+
if (cachedTempDateFormatArray != null) {
for (int i = 0; i < CACHED_TDFS_LENGTH; i++) {
final TemplateDateFormat f = cachedTempDateFormatArray[i];
@@ -833,9 +837,9 @@ public final class Environment extends Configurable {
}
}
}
-
+
cachedTempDateFormatsByFmtStrArray = null;
-
+
cachedCollator = null;
}
}
@@ -844,7 +848,7 @@ public final class Environment extends Configurable {
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++) {
@@ -859,16 +863,16 @@ public final class Environment extends Configurable {
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++) {
@@ -880,14 +884,14 @@ public final class Environment extends Configurable {
}
if (cachedTempDateFormatsByFmtStrArray != null) {
for (int i = CACHED_TDFS_SQL_D_T_TZ_OFFS; i < CACHED_TDFS_LENGTH; i++) {
- cachedTempDateFormatsByFmtStrArray[i] = null;
+ 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;
@@ -902,35 +906,32 @@ public final class Environment extends Configurable {
boolean isSQLDateAndTimeTimeZoneSameAsNormal() {
if (cachedSQLDateAndTimeTimeZoneSameAsNormal == null) {
cachedSQLDateAndTimeTimeZoneSameAsNormal = Boolean.valueOf(
- getSQLDateAndTimeTimeZone() == null
- || getSQLDateAndTimeTimeZone().equals(getTimeZone()));
+ 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.
+ * 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 <code>null</code> if the information is not available.
- * The function caches the return value, so it's quick to call it
- * repeately.
+ * 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 repeately.
*/
String getEffectiveURLEscapingCharset() {
if (!cachedURLEscapingCharsetSet) {
@@ -949,7 +950,7 @@ public final class Environment extends Configurable {
}
return cachedCollator;
}
-
+
/**
* Compares two {@link TemplateModel}-s according the rules of the FTL "==" operator.
*
@@ -962,8 +963,8 @@ public final class Environment extends Configurable {
/**
* 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.
+ * 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
*/
@@ -971,7 +972,7 @@ public final class Environment extends Configurable {
throws TemplateException {
return EvalUtil.compareLenient(leftValue, EvalUtil.CMP_OP_EQUALS, rightValue, this);
}
-
+
/**
* Compares two {@link TemplateModel}-s according the rules of the FTL "<" operator.
*
@@ -991,7 +992,7 @@ public final class Environment extends Configurable {
throws TemplateException {
return EvalUtil.compare(leftValue, EvalUtil.CMP_OP_LESS_THAN_EQUALS, rightValue, this);
}
-
+
/**
* Compares two {@link TemplateModel}-s according the rules of the FTL ">" operator.
*
@@ -1028,18 +1029,30 @@ public final class Environment extends Configurable {
/**
* 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 formatNumber(TemplateNumberModel number, Expression exp) throws TemplateException {
return formatNumber(number, getTemplateNumberFormat(exp), exp);
}
/**
- * Format number with the number format specified as the parameter.
+ * 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 formatNumber(TemplateNumberModel number, String formatString, Expression exp) throws TemplateException {
return formatNumber(number, getTemplateNumberFormat(formatString, exp), exp);
}
+ /**
+ * 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 formatNumber(TemplateNumberModel number, TemplateNumberFormat format, Expression exp)
throws TemplateModelException, _MiscTemplateException {
try {
@@ -1051,6 +1064,12 @@ public final class Environment extends Configurable {
}
}
+ /**
+ * 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 formatNumber(Number number, BackwardCompatibleTemplateNumberFormat format, Expression exp)
throws TemplateModelException, _MiscTemplateException {
try {
@@ -1061,7 +1080,7 @@ public final class Environment extends Configurable {
e.getMessage());
}
}
-
+
/**
* Returns the current number format ({@link #getNumberFormat()}) as {@link TemplateNumberFormat}.
*
@@ -1083,43 +1102,82 @@ public final class Environment extends Configurable {
}
/**
- * Returns the number format as {@link TemplateNumberFormat}, for the given format string and locale.
+ * 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 InvalidFormatStringException {
+ return getTemplateNumberFormat(formatString, true);
+ }
+
+ /**
+ * 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. If {@code null} then the current locale ({@link #getLocale()}) will
- * be used. Note on performance (which was true at least for 2.3.24): If the locale is {@code null} or
- * equals to the current locale, then the {@link Environment}-level format cache will be used, otherwise
- * 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 in the last case the number 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.
+ * The locale of the number format; not {@code null}.
*
* @since 2.3.24
*/
public TemplateNumberFormat getTemplateNumberFormat(String formatString, Locale locale)
throws InvalidFormatStringException {
- if (locale == null || locale.equals(getLocale())) {
+ if (locale.equals(getLocale())) {
getTemplateNumberFormat(formatString);
}
-
+
return getTemplateNumberFormatWithoutCache(formatString, locale);
}
/**
- * 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()}.)
- *
- * @param formatString
- * A string that you could also use as the value of the {@code numberFormat} configuration setting.
+ * Convenience wrapper around {@link #getTemplateNumberFormat()} to be called during expression evaluation.
+ */
+ TemplateNumberFormat getTemplateNumberFormat(Expression exp) throws _MiscTemplateException {
+ TemplateNumberFormat format;
+ try {
+ format = getTemplateNumberFormat();
+ } catch (InvalidFormatStringException e) {
+ throw new _MiscTemplateException(exp, e, this,
+ "Failed to get number format object for the current number format string, ",
+ new _DelayedJQuote(getNumberFormat()), ": " + e.getMessage());
+ }
+ return format;
+ }
+
+ /**
+ * Convenience wrapper around {@link #getTemplateNumberFormat(String)} to be called during expression evaluation.
*
- * @since 2.3.24
+ * @param exp
+ * The blamed expression if an error occurs; it's only needed for better error messages
*/
- public TemplateNumberFormat getTemplateNumberFormat(String formatString) throws InvalidFormatStringException {
- return getTemplateNumberFormat(formatString, true);
+ TemplateNumberFormat getTemplateNumberFormat(String formatString, Expression exp)
+ throws _MiscTemplateException {
+ TemplateNumberFormat format;
+ try {
+ format = getTemplateNumberFormat(formatString);
+ } catch (InvalidFormatStringException e) {
+ throw new _MiscTemplateException(exp, e, this,
+ "Failed to get number format object for the ", new _DelayedJQuote(formatString),
+ " number format string: " + e.getMessage());
+ }
+ return format;
}
-
+
/**
* Gets the {@link TemplateNumberFormat} <em>for the current locale</em>.
*
@@ -1143,7 +1201,7 @@ public final class Environment extends Configurable {
}
TemplateNumberFormat format = getTemplateNumberFormatWithoutCache(formatString, getLocale());
-
+
if (cacheResult) {
cachedTemplateNumberFormats.put(formatString, format);
}
@@ -1179,13 +1237,13 @@ public final class Environment extends Configurable {
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 {
if (formatStringLen > 1
@@ -1200,9 +1258,8 @@ public final class Environment extends Configurable {
}
/**
- * 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.
+ * 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
@@ -1213,37 +1270,6 @@ public final class Environment extends Configurable {
return cNumberFormat;
}
- /**
- * Convenience wrapper around {@link #getTemplateNumberFormat()} to be called during expression evaluation.
- */
- TemplateNumberFormat getTemplateNumberFormat(Expression exp) throws _MiscTemplateException {
- TemplateNumberFormat format;
- try {
- format = getTemplateNumberFormat();
- } catch (InvalidFormatStringException e) {
- throw new _MiscTemplateException(exp, e, this,
- "Failed to get number format object for the current number format string, ",
- new _DelayedJQuote(getNumberFormat()), ": " + e.getMessage());
- }
- return format;
- }
-
- /**
- * Convenience wrapper around {@link #getTemplateNumberFormat(String)} to be called during expression evaluation.
- */
- TemplateNumberFormat getTemplateNumberFormat(String formatString, Expression exp)
- throws _MiscTemplateException {
- TemplateNumberFormat format;
- try {
- format = getTemplateNumberFormat(formatString);
- } catch (InvalidFormatStringException e) {
- throw new _MiscTemplateException(exp, e, this,
- "Failed to get number format object for the ", new _DelayedJQuote(formatString),
- " number format string: " + e.getMessage());
- }
- return format;
- }
-
@Override
public void setTimeFormat(String timeFormat) {
String prevTimeFormat = getTimeFormat();
@@ -1286,85 +1312,239 @@ public final class Environment extends Configurable {
public Configuration getConfiguration() {
return configuration;
}
-
+
TemplateModel getLastReturnValue() {
return lastReturnValue;
}
-
+
void setLastReturnValue(TemplateModel lastReturnValue) {
this.lastReturnValue = lastReturnValue;
}
-
+
void clearLastReturnValue() {
this.lastReturnValue = null;
}
- String formatDate(TemplateDateModel tdm, Expression tdmSourceExpr) throws TemplateModelException {
+ /**
+ * @param tdmSourceExpr
+ * The blamed expression if an error occurs; only used for error messages.
+ */
+ String formatDate(TemplateDateModel tdm, Expression tdmSourceExpr) throws TemplateException {
Date date = EvalUtil.modelToDate(tdm, tdmSourceExpr);
+
+ TemplateDateFormat format = getTemplateDateFormat(
+ tdm.getDateType(), date.getClass(), tdmSourceExpr);
+
try {
- boolean isSQLDateOrTime = isSQLDateOrTimeClass(date.getClass());
- return getTemplateDateFormat(
- tdm.getDateType(), isSQLDateOrTime, shouldUseSQLDTTimeZone(isSQLDateOrTime), tdmSourceExpr)
- .format(tdm);
- } catch (UnknownDateTypeFormattingUnsupportedException e) {
- throw MessageUtil.newCantFormatUnknownTypeDateException(tdmSourceExpr, e);
+ return format.format(tdm);
} catch (UnformattableDateException e) {
throw MessageUtil.newCantFormatDateException(tdmSourceExpr, e);
}
}
- String formatDate(TemplateDateModel tdm, String formatString, Expression tdmSourceExpr)
- throws TemplateModelException {
- Date date = EvalUtil.modelToDate(tdm, tdmSourceExpr);
- boolean isSQLDateOrTime = isSQLDateOrTimeClass(date.getClass());
+ /**
+ * @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 formatDate(TemplateDateModel tdm, String formatString,
+ Expression blamedDateSourceExp, Expression blamedFormatterExp) throws TemplateException {
+ Date date = EvalUtil.modelToDate(tdm, blamedDateSourceExp);
+
+ TemplateDateFormat format = getTemplateDateFormat(
+ formatString, tdm.getDateType(), date.getClass(),
+ blamedDateSourceExp, blamedFormatterExp);
+
try {
- return getTemplateDateFormat(
- tdm.getDateType(), isSQLDateOrTime, shouldUseSQLDTTimeZone(isSQLDateOrTime), formatString, true,
- null)
- .format(tdm);
- } catch (UnknownDateTypeFormattingUnsupportedException e) {
- throw MessageUtil.newCantFormatUnknownTypeDateException(tdmSourceExpr, e);
+ return format.format(tdm);
} catch (UnformattableDateException e) {
- throw MessageUtil.newCantFormatDateException(tdmSourceExpr, e);
+ throw MessageUtil.newCantFormatDateException(blamedDateSourceExp, e);
}
}
-
+
/**
- * @param dateType The FTL date type, one of {@link TemplateDateModel#DATETIME}, {@link TemplateDateModel#TIME}
- * and {@link TemplateDateModel#DATE}.
- * @param dateClass The exact Java class of the formatted or created (via parsing) object. This matters because
- * the time zone is part of the returned {@link DateFormat}, and if
- * {@link #getSQLDateAndTimeTimeZone()} differs from {@link #getTimeZone()} then the exact class influences
- * the time zone.
- * @param dateSourceExpr Used for better error messages only; may be {@code null}
+ * 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)}
*/
- TemplateDateFormat getTemplateDateFormat(int dateType, Class/*<? extends Date>*/ dateClass, Expression dateSourceExpr)
- throws TemplateModelException {
- try {
- boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
- return getTemplateDateFormat(dateType, isSQLDateOrTime, shouldUseSQLDTTimeZone(isSQLDateOrTime), dateSourceExpr);
- } catch (UnknownDateTypeFormattingUnsupportedException e) {
- throw MessageUtil.newCantFormatUnknownTypeDateException(dateSourceExpr, e);
- }
+ public TemplateDateFormat getTemplateDateFormat(
+ int dateType, Class<? extends Date> dateClass)
+ throws UnknownDateTypeFormattingUnsupportedException, UndefinedCustomFormatException,
+ InvalidFormatParametersException {
+ boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
+ return getTemplateDateFormat(dateType, shouldUseSQLDTTimeZone(isSQLDateOrTime), isSQLDateOrTime);
}
- private TemplateDateFormat getTemplateDateFormat(
- int dateType, boolean isSQLDateOrTime,
- boolean useSQLDTTZ, Expression dateSourceExpr)
- throws TemplateModelException, UnknownDateTypeFormattingUnsupportedException {
- if (dateType == TemplateDateModel.UNKNOWN) {
- throw MessageUtil.newCantFormatUnknownTypeDateException(dateSourceExpr, null);
- }
- int cacheIdx = getTemplateDateFormatCacheArrayIndex(dateType, isSQLDateOrTime, useSQLDTTZ);
- TemplateDateFormat[] cachedTemplateDateFormats = this.cachedTempDateFormatArray;
- if (cachedTemplateDateFormats == null) {
- cachedTemplateDateFormats = new TemplateDateFormat[CACHED_TDFS_LENGTH];
- this.cachedTempDateFormatArray = cachedTemplateDateFormats;
+ /**
+ * 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 UnknownDateTypeFormattingUnsupportedException, UndefinedCustomFormatException,
+ InvalidFormatParametersException {
+ boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
+ return getTemplateDateFormat(
+ formatString, dateType,
+ shouldUseSQLDTTimeZone(isSQLDateOrTime), isSQLDateOrTime, true);
+ }
+
+ /**
+ * 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 UndefinedCustomFormatException, InvalidFormatParametersException,
+ UnknownDateTypeFormattingUnsupportedException {
+ boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
+ boolean useSQLDTTZ = shouldUseSQLDTTimeZone(isSQLDateOrTime);
+ return getTemplateDateFormat(
+ formatString,
+ dateType, locale, useSQLDTTZ ? getSQLDateAndTimeTimeZone() : getTimeZone(), isSQLDateOrTime);
+ }
+
+ /**
+ * 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 UndefinedCustomFormatException, InvalidFormatParametersException,
+ UnknownDateTypeFormattingUnsupportedException {
+ boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
+ boolean useSQLDTTZ = shouldUseSQLDTTimeZone(isSQLDateOrTime);
+ return getTemplateDateFormat(
+ formatString,
+ dateType, locale, useSQLDTTZ ? sqlDateAndTimeTimeZone : timeZone, isSQLDateOrTime);
+ }
+
+ /**
+ * 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's
+ * 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 UndefinedCustomFormatException, InvalidFormatParametersException,
+ UnknownDateTypeFormattingUnsupportedException {
+ 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
}
- TemplateDateFormat format = cachedTemplateDateFormats[cacheIdx];
- if (format == null) {
- final String settingName;
- final String settingValue;
+ return getTemplateDateFormatWithoutCache(formatString, dateType, locale, timeZone, zonelessInput);
+ }
+
+ /**
+ * Same as {@link #getTemplateDateFormat(int, Class)}, but translates the exceptions to {@link TemplateException}-s.
+ */
+ TemplateDateFormat getTemplateDateFormat(
+ int dateType, Class<? extends Date> dateClass, Expression blamedDateSourceExp) throws TemplateException {
+ try {
+ return getTemplateDateFormat(dateType, dateClass);
+ } catch (UnknownDateTypeFormattingUnsupportedException e) {
+ throw MessageUtil.newCantFormatUnknownTypeDateException(blamedDateSourceExp, e);
+ } catch (InvalidFormatStringException e) {
+ String settingName;
+ String settingValue;
switch (dateType) {
case TemplateDateModel.TIME:
settingName = Configurable.TIME_FORMAT_KEY;
@@ -1379,59 +1559,97 @@ public final class Environment extends Configurable {
settingValue = getDateTimeFormat();
break;
default:
- throw new _TemplateModelException("Invalid date type enum: ", Integer.valueOf(dateType));
- } // switch
-
- format = getTemplateDateFormat(
- dateType, isSQLDateOrTime,
- useSQLDTTZ, settingValue, false,
- settingName);
+ settingName = "???";
+ settingValue = "???";
+ }
- cachedTemplateDateFormats[cacheIdx] = format;
+ throw new _MiscTemplateException(e,
+ "The value of the \"", settingName,
+ "\" FreeMarker configuration setting is a malformed date/time/datetime format string: ",
+ new _DelayedJQuote(settingValue), ". Reason given: ",
+ e.getMessage());
}
- return format;
}
-
+
/**
- * @param dateType {@link TemplateDateModel#UNKNOWN} is accepted or not depending on the {@code formatString}
- * value. When it isn't, a {@link TemplateModelException} will be thrown.
- * @param dateClass The exact class of the date object, such as {@link java.sql.Timestamp}.
- * @param formatString Like "iso m" or "dd.MM.yyyy HH:mm"
+ * Same as {@link #getTemplateDateFormat(String, int, Class)}, but translates the exceptions to
+ * {@link TemplateException}-s.
*/
TemplateDateFormat getTemplateDateFormat(
- int dateType, Class/*<? extends Date>*/ dateClass, String formatString, Expression dateSourceExpr)
- throws TemplateModelException {
+ String formatString, int dateType, Class<? extends Date> dateClass,
+ Expression blamedDateSourceExp, Expression blamedFormatterExp) throws TemplateException {
try {
- boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
- return getTemplateDateFormat(
- dateType, isSQLDateOrTime,
- shouldUseSQLDTTimeZone(isSQLDateOrTime), formatString, true,
- null);
+ return getTemplateDateFormat(formatString, dateType, dateClass);
} catch (UnknownDateTypeFormattingUnsupportedException e) {
- throw MessageUtil.newCantFormatUnknownTypeDateException(dateSourceExpr, e);
+ throw MessageUtil.newCantFormatUnknownTypeDateException(blamedDateSourceExp, e);
+ } catch (InvalidFormatStringException e) {
+ throw new _MiscTemplateException(e,
+ new _ErrorDescriptionBuilder(
+ "Malformed date/time/datetime format string: ",
+ new _DelayedJQuote(formatString), ". Reason given: ",
+ e.getMessage())
+ .blame(blamedFormatterExp));
}
}
-
+
/**
- * @param dateType
- * See the similar parameter of {@link TemplateDateFormatFactory#get}
- * @param zonelessInput
- * See the similar parameter of {@link TemplateDateFormatFactory#get}
- * @param formatString
- * The string that describes the date format. See the similar parameter of
- * {@link TemplateDateFormatFactory#get}
- * @param formatStringCfgSettingName
- * The name of the configuration setting where the {@code formatZtring} comes from, or {@code null}
- * if the format string was specified directly for this formatting call.
+ * 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 if the parameters.
+ */
+ private TemplateDateFormat getTemplateDateFormat(int dateType, boolean useSQLDTTZ, boolean zonelessInput)
+ throws UnknownDateTypeFormattingUnsupportedException, UndefinedCustomFormatException,
+ InvalidFormatParametersException {
+ 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 {@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(
- int dateType, boolean zonelessInput, boolean useSQLDTTZ, String formatString, boolean cacheResult,
- String formatStringCfgSettingName)
- throws TemplateModelException, UnknownDateTypeFormattingUnsupportedException {
+ String formatString, int dateType, boolean useSQLDTTimeZone, boolean zonelessInput,
+ boolean cacheResult)
+ throws UnknownDateTypeFormattingUnsupportedException, UndefinedCustomFormatException,
+ InvalidFormatParametersException {
HashMap<String, TemplateDateFormat> cachedFormatsByFormatString;
readFromCache: do {
- HashMap<String, TemplateDateFormat>[] cachedTempDateFormatsByFmtStrArray
- = this.cachedTempDateFormatsByFmtStrArray;
+ HashMap<String, TemplateDateFormat>[] cachedTempDateFormatsByFmtStrArray = this.cachedTempDateFormatsByFmtStrArray;
if (cachedTempDateFormatsByFmtStrArray == null) {
if (cacheResult) {
cachedTempDateFormatsByFmtStrArray = new HashMap[CACHED_TDFS_LENGTH];
@@ -1441,10 +1659,10 @@ public final class Environment extends Configurable {
break readFromCache;
}
}
-
+
TemplateDateFormat format;
{
- int cacheArrIdx = getTemplateDateFormatCacheArrayIndex(dateType, zonelessInput, useSQLDTTZ);
+ int cacheArrIdx = getTemplateDateFormatCacheArrayIndex(dateType, zonelessInput, useSQLDTTimeZone);
cachedFormatsByFormatString = cachedTempDateFormatsByFmtStrArray[cacheArrIdx];
if (cachedFormatsByFormatString == null) {
if (cacheResult) {
@@ -1458,126 +1676,136 @@ public final class Environment extends Configurable {
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 {@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 UndefinedCustomFormatException, InvalidFormatParametersException,
+ UnknownDateTypeFormattingUnsupportedException {
final int formatStringLen = formatString.length();
final String formatParams;
-
- // As of Java 8, 'x' and 'i' (in lower case) are illegal date format letters, so this is backward-compatible.
+
TemplateDateFormatFactory formatFactory;
- char firstChar = formatStringLen != 0 ? formatString.charAt(0) : 0;
- try {
- 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
- && formatString.charAt(1) != '@'
- && isIcI2324OrLater()) {
- final String name;
- {
- int endIdx;
- findParamsStart: for (endIdx = 1; endIdx < formatStringLen; endIdx++) {
- char c = formatString.charAt(endIdx);
- if (c == ' ' || c == '_') {
- break findParamsStart;
- }
+ 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
+ && formatString.charAt(1) != '@'
+ && isIcI2324OrLater()) {
+ 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 {
- if (firstChar == '@'
- && formatStringLen > 1
- && formatString.charAt(1) == '@'
- && isIcI2324OrLater()) {
- // Unescape @ escaped as @@
- formatString = formatString.substring(1);
- }
-
- formatFactory = JavaTemplateDateFormatFactory.INSTANCE;
- formatParams = formatString;
+ name = formatString.substring(1, endIdx);
+ formatParams = endIdx < formatStringLen ? formatString.substring(endIdx + 1) : "";
}
- TemplateDateFormat format = formatFactory.get(dateType, zonelessInput, formatParams, getLocale(),
- useSQLDTTZ ? getSQLDateAndTimeTimeZone() : getTimeZone(), this);
- if (cacheResult) {
- // We know here that cachedFormatsByFormatString != null
- cachedFormatsByFormatString.put(formatString, format);
+ formatFactory = getCustomDateFormat(name);
+ if (formatFactory == null) {
+ throw new UndefinedCustomFormatException(
+ "No custom date format was defined with name " + StringUtil.jQuote(name));
}
- return format;
- } catch (InvalidFormatStringException e) {
- throw new _TemplateModelException(e,
- (formatStringCfgSettingName == null
- ? (Object) "Malformed date/time format string: "
- : new Object[] {
- "The value of the \"", formatStringCfgSettingName,
- "\" FreeMarker configuration setting is a malformed date/time format string: "
- }),
- new _DelayedJQuote(formatString), ". Reason given: ",
- e.getMessage());
+ } else {
+ String unescapedFormatString;
+ if (firstChar == '@'
+ && formatStringLen > 1
+ && formatString.charAt(1) == '@'
+ && isIcI2324OrLater()) {
+ // Unescape @ escaped as @@
+ unescapedFormatString = formatString.substring(1);
+ } else {
+ unescapedFormatString = formatString;
+ }
+
+ formatFactory = JavaTemplateDateFormatFactory.INSTANCE;
+ formatParams = unescapedFormatString;
}
+
+ 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
+ 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();
+ return sqlDateOrTime && !isSQLDateAndTimeTimeZoneSameAsNormal();
}
-
+
/**
* 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 == java.sql.Time.class
- || (dateClass != java.sql.Timestamp.class
- && (
- java.sql.Date.class.isAssignableFrom(dateClass)
- || java.sql.Time.class.isAssignableFrom(dateClass))));
+ && (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)}.
+ * 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) {
@@ -1585,7 +1813,7 @@ public final class Environment extends Configurable {
}
return isoBuiltInCalendarFactory;
}
-
+
TemplateTransformModel getTransform(Expression exp) throws TemplateException {
TemplateTransformModel ttm = null;
TemplateModel tm = exp.eval(this);
@@ -1601,10 +1829,9 @@ public final class Environment extends Configurable {
}
/**
- * 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) {
@@ -1620,21 +1847,20 @@ public final class Environment extends Configurable {
}
/**
- * 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:
+ * 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 <#assign ...>)
- * <li>A variable defined globally (say, via <#global ....>)
- * <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>
+ * <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 <#assign ...>)
+ * <li>A variable defined globally (say, via <#global ....>)
+ * <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 {
@@ -1649,10 +1875,9 @@ public final class Environment extends Configurable {
}
/**
- * 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:
- * <#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: <#global
+ * ...> and then at the data model exposed to the template.
*/
public TemplateModel getGlobalVariable(String name) throws TemplateModelException {
TemplateModel result = globalNamespace.get(name);
@@ -1666,9 +1891,8 @@ public final class Environment extends Configurable {
}
/**
- * Sets a variable that is visible globally.
- * This is correspondent to FTL <code><#global <i>name</i>=<i>model</i>></code>.
- * This can be considered a convenient shorthand for:
+ * Sets a variable that is visible globally. This is correspondent to FTL
+ * <code><#global <i>name</i>=<i>model</i>></code>. This can be considered a convenient shorthand for:
* getGlobalNamespace().put(name, model)
*/
public void setGlobalVariable(String name, TemplateModel model) {
@@ -1676,9 +1900,8 @@ public final class Environment extends Configurable {
}
/**
- * Sets a variable in the current namespace.
- * This is correspondent to FTL <code><#assign <i>name</i>=<i>model</i>></code>.
- * This can be considered a convenient shorthand for:
+ * Sets a variable in the current namespace. This is correspondent to FTL
+ * <code><#assign <i>name</i>=<i>model</i>></code>. This can be considered a convenient shorthand for:
* getCurrentNamespace().put(name, model)
*/
public void setVariable(String name, TemplateModel model) {
@@ -1686,12 +1909,15 @@ public final class Environment extends Configurable {
}
/**
- * Sets a local variable (one effective only during a macro invocation).
- * This is correspondent to FTL <code><#local <i>name</i>=<i>model</i>></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.
+ * Sets a local variable (one effective only during a macro invocation). This is correspondent to FTL
+ * <code><#local <i>name</i>=<i>model</i>></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) {
@@ -1701,40 +1927,36 @@ public final class Environment extends Configurable {
}
/**
- * 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.
+ * 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();
+ TemplateModelIterator rootNames = ((TemplateHashModelEx) rootDataModel).keys().iterator();
while (rootNames.hasNext()) {
set.add(((TemplateScalarModel) rootNames.next()).getAsString());
}
}
-
+
// globals
- for (TemplateModelIterator tmi = globalNamespace.keys().iterator(); tmi.hasNext(); ) {
+ 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(); ) {
+ 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());
@@ -1749,19 +1971,21 @@ public final class Environment extends Configurable {
}
/**
- * Prints the current FTL stack trace. Useful for debugging.
- * {@link TemplateException}s 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);
+ 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.
- * @param w If it's a {
<TRUNCATED>