This is an automated email from the ASF dual-hosted git repository.
ddekany pushed a commit to branch FREEMARKER-35
in repository https://gitbox.apache.org/repos/asf/freemarker.git
The following commit(s) were added to refs/heads/FREEMARKER-35 by this push:
new 895caac8 [FREEMARKER-35] Added Environment-level cache for
TemplateTemporalFormat-s in the case the format string is provided explicitly
(as in t?string(specialFormat)), or the format setting is changed
back-and-forth as the template executes. Some code cleanup related to the
caching of the TemplateTemporalFormat of the default format.
895caac8 is described below
commit 895caac8d80fdfed23e1b2bfa598ec7920937273
Author: ddekany <[email protected]>
AuthorDate: Mon Aug 8 01:44:43 2022 +0200
[FREEMARKER-35] Added Environment-level cache for TemplateTemporalFormat-s
in the case the format string is provided explicitly (as in
t?string(specialFormat)), or the format setting is changed back-and-forth as
the template executes. Some code cleanup related to the caching of the
TemplateTemporalFormat of the default format.
---
src/main/java/freemarker/core/Environment.java | 1033 ++++++++++++--------
.../core/JavaTemplateTemporalFormat.java | 7 +
.../getTemplateTemporalFormatCaching.ftl | 10 +-
...oralFormatAbstractCachingInEnvironmentTest.java | 110 +++
...rmatByFormatStringCachingInEnvironmentTest.java | 232 +++++
...oralFormatCurrentCachingInEnvironmentTest.java} | 123 +--
6 files changed, 1013 insertions(+), 502 deletions(-)
diff --git a/src/main/java/freemarker/core/Environment.java
b/src/main/java/freemarker/core/Environment.java
index c7baa2dd..8f58cef1 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -109,7 +109,7 @@ import
freemarker.template.utility.UndeclaredThrowableException;
* {@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");
@@ -117,10 +117,13 @@ public final class Environment extends Configurable {
// Do not use this object directly; clone it first! DecimalFormat isn't
// thread-safe.
- /** "c" number format as it was before Incompatible Improvements 2.3.21. */
+ /**
+ * "c" number format as it was before Incompatible Improvements 2.3.21.
+ */
private static final DecimalFormat C_NUMBER_FORMAT_ICI_2_3_20 = new
DecimalFormat(
"0.################",
new DecimalFormatSymbols(Locale.US));
+
static {
C_NUMBER_FORMAT_ICI_2_3_20.setGroupingUsed(false);
C_NUMBER_FORMAT_ICI_2_3_20.setDecimalSeparatorAlwaysShown(false);
@@ -128,8 +131,11 @@ public final class Environment extends Configurable {
// Do not use this object directly; clone it first! DecimalFormat isn't
// thread-safe.
- /** "c" number format as it was starting from Incompatible Improvements
2.3.21. */
+ /**
+ * "c" number format as it was starting from Incompatible Improvements
2.3.21.
+ */
private static final DecimalFormat C_NUMBER_FORMAT_ICI_2_3_21 =
(DecimalFormat) C_NUMBER_FORMAT_ICI_2_3_20.clone();
+
static {
DecimalFormatSymbols symbols =
C_NUMBER_FORMAT_ICI_2_3_21.getDecimalFormatSymbols();
symbols.setInfinity("INF");
@@ -153,15 +159,13 @@ public final class Environment extends Configurable {
* Stores the date/time/date-time formatters that are used when no format
is explicitly given where the value is
* converted to text. That is, it's used 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
- *
+ * {@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
@@ -170,11 +174,14 @@ public final class Environment extends Configurable {
private TemplateDateFormat[] cachedTempDateFormatArray;
/**
- * Similar to {@link #cachedTempDateFormatArray}, but for {@link
TemplateTemporalFormat}-s. It's not an array as
- * {@code java.time} classes have no numerical value, unlike legacy
FreeMarker date types.
+ * Similar to {@link #cachedTempDateFormatArray}, but for {@link
TemplateTemporalFormat}-s. At least for now it's
+ * not an array, as {@code java.time} classes have no inherent numerical
value, unlike legacy FreeMarker date types.
+ *
+ * @see #cachedTemporalFormatsByFormatString
*/
- private TemplateTemporalFormatCache cachedTemporalFormatCache;
- private final class TemplateTemporalFormatCache {
+ private CachedTemplateTemporalFormats cachedTemporalFormats;
+
+ private static final class CachedTemplateTemporalFormats {
// Notes:
// - "reusable" fields are set together with related non-reusable
fields
// - non-reusable fields are cleared when any related setting is
changed, but reusableXxx fields are only
@@ -247,13 +254,39 @@ public final class Environment extends Configurable {
}
}
- /** Similar to {@link #cachedTempDateFormatArray}, but used when a
formatting string was specified. */
+ /**
+ * The difference to {@link #cachedTemporalFormats} is that this one
remembers several formatters for
+ * various formatter strings, so it's able to cache for {@code
temporal?string(nonDefaultFormat)}.
+ * But then, it can also help if the default format is changed back and
forth in the template, so it's also used
+ * as a fallback if we have a cache miss in {@link #cachedTemporalFormats}.
+ */
+ private CachedTemplateTemporalFormatsByFormatString
cachedTemporalFormatsByFormatString;
+
+ private static final class CachedTemplateTemporalFormatsByFormatString {
+ private Map<String, TemplateTemporalFormat[]> localDateTimeFormats;
+ private Map<String, TemplateTemporalFormat[]> offsetDateTimeFormats;
+ private Map<String, TemplateTemporalFormat[]> zonedDateTimeFormats;
+ private Map<String, TemplateTemporalFormat[]> localDateFormats;
+ private Map<String, TemplateTemporalFormat[]> localTimeFormats;
+ private Map<String, TemplateTemporalFormat[]> offsetTimeFormats;
+ private Map<String, TemplateTemporalFormat[]> yearMonthFormats;
+ private Map<String, TemplateTemporalFormat[]> yearFormats;
+ private Map<String, TemplateTemporalFormat[]> instantFormats;
+ }
+
+ private static final int
CACHED_TEMPORAL_FORMATS_BY_FORMAT_STRING_VALUE_ARRAY_LENGTH = 4;
+
+ /**
+ * 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()}.
*/
+ /**
+ * Caches the result of {@link #isSQLDateAndTimeTimeZoneSameAsNormal()}.
+ */
private Boolean cachedSQLDateAndTimeTimeZoneSameAsNormal;
@Deprecated
@@ -261,7 +294,7 @@ public final class Environment extends Configurable {
/**
* Used by the "iso_" built-ins to accelerate formatting.
- *
+ *
* @see #getISOBuiltInCalendarFactory()
*/
private DateToISO8601CalendarFactory isoBuiltInCalendarFactory;
@@ -310,7 +343,8 @@ public final class Environment extends Configurable {
public Environment(Template template, final TemplateHashModel
rootDataModel, Writer out) {
super(template);
configuration = template.getConfiguration();
- incompatibleImprovementsGE2328 =
configuration.getIncompatibleImprovements().intValue() >=
_TemplateAPI.VERSION_INT_2_3_28;
+ incompatibleImprovementsGE2328 =
+ configuration.getIncompatibleImprovements().intValue() >=
_TemplateAPI.VERSION_INT_2_3_28;
this.globalNamespace = new Namespace(null);
this.currentNamespace = mainNamespace = new Namespace(template);
this.out = out;
@@ -323,17 +357,19 @@ public final class Environment extends Configurable {
* 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.
+ * {@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();
@@ -343,9 +379,8 @@ public final class Environment extends Configurable {
* 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() {
@@ -355,13 +390,12 @@ public final class Environment extends Configurable {
/**
* 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}.
- *
+ * 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")
@@ -375,7 +409,7 @@ public final class Environment extends Configurable {
* 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")
@@ -453,11 +487,11 @@ public final class Environment extends Configurable {
}
// ATTENTION: This method body above is manually "inlined" into
visit(TemplateElement[]); keep them in sync!
}
-
+
/**
* @param elementBuffer
- * The elements to visit; might contains trailing {@code
null}-s. Can be {@code null}.
- *
+ * 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 {
@@ -468,7 +502,7 @@ public final class Environment extends Configurable {
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);
@@ -493,7 +527,7 @@ public final class Environment extends Configurable {
/**
* 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 {
@@ -505,7 +539,7 @@ public final class Environment extends Configurable {
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;
@@ -520,9 +554,9 @@ public final class Environment extends Configurable {
public void visit(final TemplateElement element,
TemplateDirectiveModel directiveModel, Map args,
final List bodyParameterNames) throws TemplateException,
IOException {
- visit(new TemplateElement[] { element }, directiveModel, args,
bodyParameterNames);
+ visit(new TemplateElement[]{element}, directiveModel, args,
bodyParameterNames);
}
-
+
void visit(final TemplateElement[] childBuffer,
TemplateDirectiveModel directiveModel, Map args,
final List bodyParameterNames) throws TemplateException,
IOException {
@@ -580,18 +614,18 @@ public final class Environment extends Configurable {
/**
* "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
+ * the element to visit through a transform; might contains
trailing {@code null}-s
* @param transform
- * the transform to pass the element output through
+ * the transform to pass the element output through
* @param args
- * optional arguments fed to the transform
+ * optional arguments fed to the transform
*/
void visitAndTransform(TemplateElement[] elementBuffer,
TemplateTransformModel transform,
Map args)
- throws TemplateException, IOException {
+ throws TemplateException, IOException {
try {
Writer tw = transform.getWriter(out, args);
if (tw == null) tw = EMPTY_BODY_WRITER;
@@ -611,8 +645,8 @@ public final class Environment extends Configurable {
try {
if (tc != null
&& !(t instanceof FlowControlException
- &&
getConfiguration().getIncompatibleImprovements().intValue()
- >= _TemplateAPI.VERSION_INT_2_3_27)) {
+ &&
getConfiguration().getIncompatibleImprovements().intValue()
+ >= _TemplateAPI.VERSION_INT_2_3_27)) {
tc.onError(t);
} else {
throw t;
@@ -643,9 +677,9 @@ public final class Environment extends Configurable {
/**
* Visit a block using buffering/recovery
*/
- void visitAttemptRecover(
- AttemptBlock attemptBlock, TemplateElement attemptedSection,
RecoveryBlock recoverySection)
- throws TemplateException, IOException {
+ void visitAttemptRecover(
+ AttemptBlock attemptBlock, TemplateElement attemptedSection,
RecoveryBlock recoverySection)
+ throws TemplateException, IOException {
Writer prevOut = this.out;
StringWriter sw = new StringWriter();
this.out = sw;
@@ -689,7 +723,7 @@ public final class Environment extends Configurable {
* 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() {
@@ -758,8 +792,9 @@ public final class Environment extends Configurable {
/**
* @param loopVarName
- * Then name of the loop variable that's also visible in FTL at
the moment, whose context we are looking
- * for.
+ * Then name of the loop variable that's also visible in FTL at
the moment, whose context we are looking
+ * for.
+ *
* @return The matching context or {@code null} if no such context exists.
*/
IteratorBlock.IterationContext
findEnclosingIterationContextWithVisibleVariable(String loopVarName) {
@@ -780,8 +815,8 @@ public final class Environment extends Configurable {
Object ctx = ctxStack.get(i);
if (ctx instanceof IteratorBlock.IterationContext
&& (visibleLoopVarName == null
- || ((IteratorBlock.IterationContext) ctx)
- .hasVisibleLoopVar(visibleLoopVarName))) {
+ || ((IteratorBlock.IterationContext) ctx)
+ .hasVisibleLoopVar(visibleLoopVarName))) {
return (IteratorBlock.IterationContext) ctx;
}
}
@@ -883,7 +918,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) {
@@ -895,9 +930,9 @@ 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." };
+ ", and there is no fallback handler called @", nodeType, "
either."};
}
void fallback() throws TemplateException, IOException {
@@ -1072,7 +1107,8 @@ public final class Environment extends Configurable {
macroCtx.setLocalVar(argName, argValue);
} else {
if (positionalCatchAllParamValue == null) {
- positionalCatchAllParamValue =
initPositionalCatchAllParameter(macroCtx, catchAllParamName);
+ positionalCatchAllParamValue =
+
initPositionalCatchAllParameter(macroCtx, catchAllParamName);
}
positionalCatchAllParamValue.add(argValue);
}
@@ -1092,7 +1128,8 @@ public final class Environment extends Configurable {
int totalPositionalArgCnt =
(positionalArgs != null ?
positionalArgs.size() : 0) + byPositionWithArgs.size();
if (totalPositionalArgCnt >
macro.getArgumentNamesNoCopy().length) {
- throw newTooManyArgumentsException(macro,
macro.getArgumentNamesNoCopy(), totalPositionalArgCnt);
+ throw newTooManyArgumentsException(macro,
macro.getArgumentNamesNoCopy(),
+ totalPositionalArgCnt);
}
}
}
@@ -1299,7 +1336,7 @@ public final class Environment extends Configurable {
&& templateException.getCause() instanceof TemplateException) {
templateException = (TemplateException)
templateException.getCause();
}
-
+
// Logic to prevent double-handling of the exception in
// nested visit() calls.
if (lastThrowable == templateException) {
@@ -1318,7 +1355,7 @@ public final class Environment extends Configurable {
if (templateException instanceof StopException) {
throw templateException;
}
-
+
// Finally, pass the exception to the handler
getTemplateExceptionHandler().handleTemplateException(templateException, this,
out);
} catch (TemplateException e) {
@@ -1359,8 +1396,8 @@ public final class Environment extends Configurable {
cachedCollator = null;
- if (cachedTemporalFormatCache != null) {
- cachedTemporalFormatCache.evictAfterTimeZoneOrLocaleChange();
+ if (cachedTemporalFormats != null) {
+ cachedTemporalFormats.evictAfterTimeZoneOrLocaleChange();
}
}
}
@@ -1387,8 +1424,8 @@ public final class Environment extends Configurable {
cachedSQLDateAndTimeTimeZoneSameAsNormal = null;
- if (cachedTemporalFormatCache != null) {
- cachedTemporalFormatCache.evictAfterTimeZoneOrLocaleChange();
+ if (cachedTemporalFormats != null) {
+ cachedTemporalFormats.evictAfterTimeZoneOrLocaleChange();
}
}
}
@@ -1478,7 +1515,7 @@ public final class Environment extends Configurable {
/**
* Compares two {@link TemplateModel}-s according the rules of the FTL
"==" operator.
- *
+ *
* @since 2.3.20
*/
public boolean applyEqualsOperator(TemplateModel leftValue, TemplateModel
rightValue)
@@ -1490,7 +1527,7 @@ 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.
- *
+ *
* @since 2.3.20
*/
public boolean applyEqualsOperatorLenient(TemplateModel leftValue,
TemplateModel rightValue)
@@ -1500,7 +1537,7 @@ public final class Environment extends Configurable {
/**
* Compares two {@link TemplateModel}-s according the rules of the FTL
"<" operator.
- *
+ *
* @since 2.3.20
*/
public boolean applyLessThanOperator(TemplateModel leftValue,
TemplateModel rightValue)
@@ -1510,7 +1547,7 @@ public final class Environment extends Configurable {
/**
* Compares two {@link TemplateModel}-s according the rules of the FTL
"<" operator.
- *
+ *
* @since 2.3.20
*/
public boolean applyLessThanOrEqualsOperator(TemplateModel leftValue,
TemplateModel rightValue)
@@ -1520,7 +1557,7 @@ public final class Environment extends Configurable {
/**
* Compares two {@link TemplateModel}-s according the rules of the FTL
">" operator.
- *
+ *
* @since 2.3.20
*/
public boolean applyGreaterThanOperator(TemplateModel leftValue,
TemplateModel rightValue)
@@ -1530,7 +1567,7 @@ public final class Environment extends Configurable {
/**
* Compares two {@link TemplateModel}-s according the rules of the FTL
">=" operator.
- *
+ *
* @since 2.3.20
*/
public boolean applyWithGreaterThanOrEqualsOperator(TemplateModel
leftValue, TemplateModel rightValue)
@@ -1556,7 +1593,7 @@ public final class Environment extends Configurable {
* Format number with the default number format to plain text or markup.
*
* @param exp
- * The blamed expression if an error occurs; it's only needed
for better error messages
+ * The blamed expression if an error occurs; it's only needed for
better error messages
*/
Object formatNumber(TemplateNumberModel number, Expression exp, boolean
useTempModelExc) throws TemplateException {
TemplateNumberFormat format = getTemplateNumberFormat(exp, false);
@@ -1571,9 +1608,9 @@ public final class Environment extends Configurable {
/**
* Format number with the default number format to plain text.
- *
+ *
* @param exp
- * The blamed expression if an error occurs; it's only needed
for better error messages
+ * 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 {
@@ -1582,9 +1619,9 @@ 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
+ * The blamed expression if an error occurs; it's only needed for
better error messages
*/
static String formatNumberToPlainText(
TemplateNumberModel number, TemplateNumberFormat format,
Expression exp,
@@ -1601,9 +1638,9 @@ 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
+ * 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 {
@@ -1618,13 +1655,13 @@ public final class Environment extends Configurable {
/**
* 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 {
@@ -1641,11 +1678,11 @@ public final class Environment extends Configurable {
* (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}.
- *
+ * 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 {
@@ -1655,19 +1692,19 @@ public final class Environment extends Configurable {
/**
* 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.
+ * 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}.
- *
+ * The locale of the number format; not {@code null}.
+ *
* @since 2.3.24
*/
public TemplateNumberFormat getTemplateNumberFormat(String formatString,
Locale locale)
@@ -1690,7 +1727,7 @@ public final class Environment extends Configurable {
_ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
"Failed to get number format object for the current number
format string, ",
new _DelayedJQuote(getNumberFormat()), ": ",
e.getMessage())
- .blame(exp);
+ .blame(exp);
throw useTempModelExc
? new _TemplateModelException(e, this, desc) : new
_MiscTemplateException(e, this, desc);
}
@@ -1699,9 +1736,9 @@ public final class Environment extends Configurable {
/**
* 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
+ * 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 {
@@ -1721,12 +1758,12 @@ public final class Environment extends Configurable {
/**
* Gets the {@link TemplateNumberFormat} <em>for the current locale</em>.
- *
+ *
* @param formatString
- * Not {@code null}
+ * 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.
+ * 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 {
@@ -1752,11 +1789,11 @@ public final class Environment extends Configurable {
/**
* 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}
+ * Not {@code null}
* @param locale
- * Not {@code null}
+ * Not {@code null}
*/
private TemplateNumberFormat getTemplateNumberFormatWithoutCache(String
formatString, Locale locale)
throws TemplateValueFormatException {
@@ -1788,8 +1825,8 @@ public final class Environment extends Configurable {
/**
* Returns the {@link NumberFormat} used for the <tt>c</tt> built-in,
except, if
* {@linkplain Configuration#setIncompatibleImprovements(Version)
Incompatible Improvements} is less than 2.3.31,
- * this will wrongly give the format that the <tt>c</tt> built-in used
before Incompatible Improvements 2.3.21.
- * See more at {@link Configuration#Configuration(Version)}.
+ * this will wrongly give the format that the <tt>c</tt> built-in used
before Incompatible Improvements 2.3.21. See
+ * more at {@link Configuration#Configuration(Version)}.
*/
public NumberFormat getCNumberFormat() {
// Note: DecimalFormat-s aren't thread-safe, so you must clone the
static field value.
@@ -1826,8 +1863,8 @@ public final class Environment extends Configurable {
}
}
- if (cachedTemporalFormatCache != null) {
- cachedTemporalFormatCache.evictAfterTimeFormatChange();
+ if (cachedTemporalFormats != null) {
+ cachedTemporalFormats.evictAfterTimeFormatChange();
}
}
}
@@ -1843,8 +1880,8 @@ public final class Environment extends Configurable {
}
}
- if (cachedTemporalFormatCache != null) {
- cachedTemporalFormatCache.evictAfterDateFormatChange();
+ if (cachedTemporalFormats != null) {
+ cachedTemporalFormats.evictAfterDateFormatChange();
}
}
}
@@ -1860,34 +1897,34 @@ public final class Environment extends Configurable {
}
}
- if (cachedTemporalFormatCache != null) {
- cachedTemporalFormatCache.evictAfterDateTimeFormatChange();
+ if (cachedTemporalFormats != null) {
+ cachedTemporalFormats.evictAfterDateTimeFormatChange();
}
}
}
@Override
public void setYearFormat(String yearFormat) {
- if (cachedTemporalFormatCache == null) {
+ if (cachedTemporalFormats == null) {
super.setYearFormat(yearFormat);
} else {
String prevYearFormat = getYearFormat();
super.setYearFormat(yearFormat);
if (!yearFormat.equals(prevYearFormat)) {
- cachedTemporalFormatCache.evictAfterYearFormatChange();
+ cachedTemporalFormats.evictAfterYearFormatChange();
}
}
}
@Override
public void setYearMonthFormat(String yearMonthFormat) {
- if (cachedTemporalFormatCache == null) {
+ if (cachedTemporalFormats == null) {
super.setYearMonthFormat(yearMonthFormat);
} else {
String prevYearMonthFormat = getYearMonthFormat();
super.setYearMonthFormat(yearMonthFormat);
if (!yearMonthFormat.equals(prevYearMonthFormat)) {
- cachedTemporalFormatCache.evictAfterYearMonthFormatChange();
+ cachedTemporalFormats.evictAfterYearMonthFormatChange();
}
}
}
@@ -1912,7 +1949,7 @@ public final class Environment extends Configurable {
* Format date with the default format to plain text or markup.
*
* @param blamedTdmSourceExpr
- * The blamed expression if an error occurs; only used for
error messages.
+ * The blamed expression if an error occurs; only used for error
messages.
*/
Object formatDate(TemplateDateModel tdm, Expression blamedTdmSourceExpr,
boolean useTempModelExc)
throws TemplateException {
@@ -1930,7 +1967,7 @@ public final class Environment extends Configurable {
* Format date with the default format to plain text.
*
* @param blamedTdmSourceExpr
- * The blamed expression if an error occurs; only used for
error messages.
+ * The blamed expression if an error occurs; only used for error
messages.
*/
String formatDateToPlainText(
TemplateDateModel tdm, Expression blamedTdmSourceExpr,
@@ -1941,9 +1978,9 @@ public final class Environment extends Configurable {
/**
* @param blamedTdmSourceExp
- * The blamed expression if an error occurs; only used for
error messages.
+ * 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.
+ * The blamed expression if an error occurs; only used for error
messages.
*/
String formatDateToPlainText(TemplateDateModel tdm, String formatString,
Expression blamedTdmSourceExp, Expression blamedFormatterExp,
@@ -1971,39 +2008,39 @@ public final class Environment extends Configurable {
* 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)}
+ * 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)}
+ * 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 {@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"}
- *
+ * 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 {
+ throws TemplateValueFormatException {
boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
return getTemplateDateFormat(
formatString, dateType,
@@ -2014,70 +2051,68 @@ public final class Environment extends Configurable {
* 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)}
- *
+ * 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 {
+ throws TemplateValueFormatException {
boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
boolean useSQLDTTZ = shouldUseSQLDTTimeZone(isSQLDateOrTime);
return getTemplateDateFormat(
formatString,
- dateType, locale, useSQLDTTZ ? getSQLDateAndTimeTimeZone() :
getTimeZone(), isSQLDateOrTime);
+ 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}.
+ * 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}.
- *
+ * 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 {
+ throws TemplateValueFormatException {
boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
boolean useSQLDTTZ = shouldUseSQLDTTimeZone(isSQLDateOrTime);
return getTemplateDateFormat(
formatString,
- dateType, locale, useSQLDTTZ ? sqlDateAndTimeTimeZone :
timeZone, isSQLDateOrTime);
+ 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
* 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
@@ -2086,29 +2121,29 @@ public final class Environment extends Configurable {
* 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"}
+ * 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)}
+ * 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)}
+ * 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)}
+ * 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)}
- *
+ * 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 {
+ throws TemplateValueFormatException {
Locale currentLocale = getLocale();
if (locale.equals(currentLocale)) {
int equalCurrentTZ;
@@ -2130,11 +2165,11 @@ public final class Environment extends Configurable {
}
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);
@@ -2147,7 +2182,7 @@ public final class Environment extends Configurable {
*/
TemplateDateFormat getTemplateDateFormat(
int dateType, Class<? extends Date> dateClass, Expression
blamedDateSourceExp, boolean useTempModelExc)
- throws TemplateException {
+ throws TemplateException {
try {
return getTemplateDateFormat(dateType, dateClass);
} catch (UnknownDateTypeFormattingUnsupportedException e) {
@@ -2156,28 +2191,28 @@ public final class Environment extends Configurable {
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 = "???";
+ 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());
+ e.getMessage());
throw useTempModelExc ? new _TemplateModelException(e, desc) : new
_MiscTemplateException(e, desc);
}
}
@@ -2190,7 +2225,7 @@ public final class Environment extends Configurable {
String formatString, int dateType, Class<? extends Date> dateClass,
Expression blamedDateSourceExp, Expression blamedFormatterExp,
boolean useTempModelExc)
- throws TemplateException {
+ throws TemplateException {
try {
return getTemplateDateFormat(formatString, dateType, dateClass);
} catch (UnknownDateTypeFormattingUnsupportedException e) {
@@ -2225,21 +2260,21 @@ public final class Environment extends Configurable {
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));
+ 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;
@@ -2249,18 +2284,20 @@ public final class Environment extends Configurable {
* 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.
+ * 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 {
+ throws TemplateValueFormatException {
HashMap<String, TemplateDateFormat> cachedFormatsByFormatString;
- readFromCache: do {
- HashMap<String, TemplateDateFormat>[]
cachedTempDateFormatsByFmtStrArray = this.cachedTempDateFormatsByFmtStrArray;
+ readFromCache:
+ do {
+ HashMap<String, TemplateDateFormat>[]
cachedTempDateFormatsByFmtStrArray =
+ this.cachedTempDateFormatsByFmtStrArray;
if (cachedTempDateFormatsByFmtStrArray == null) {
if (cacheResult) {
cachedTempDateFormatsByFmtStrArray = new
HashMap[CACHED_TDFS_LENGTH];
@@ -2309,17 +2346,17 @@ public final class Environment extends Configurable {
* 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}
+ * See the similar parameter of {@link
TemplateDateFormatFactory#get}
* @param dateType
- * See the similar parameter of {@link
TemplateDateFormatFactory#get}
+ * See the similar parameter of {@link
TemplateDateFormatFactory#get}
* @param zonelessInput
- * See the similar parameter of {@link
TemplateDateFormatFactory#get}
+ * See the similar parameter of {@link
TemplateDateFormatFactory#get}
*/
private TemplateDateFormat getTemplateDateFormatWithoutCache(
String formatString, int dateType, Locale locale, TimeZone
timeZone, boolean zonelessInput)
- throws TemplateValueFormatException {
+ throws TemplateValueFormatException {
final int formatStringLen = formatString.length();
final String formatParams;
@@ -2382,9 +2419,9 @@ public final class Environment extends Configurable {
// 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))));
+ || (dateClass != Timestamp.class
+ && (java.sql.Date.class.isAssignableFrom(dateClass)
+ || Time.class.isAssignableFrom(dateClass))));
}
private int getTemplateDateFormatCacheArrayIndex(int dateType, boolean
zonelessInput, boolean sqlDTTZ) {
@@ -2397,7 +2434,7 @@ public final class Environment extends Configurable {
* Format temporal with the default format to plain text or markup.
*
* @param blamedTtmSourceExp
- * The blamed expression if an error occurs; only used for
error messages.
+ * The blamed expression if an error occurs; only used for error
messages.
*/
Object formatTemporal(TemplateTemporalModel ttm, Expression
blamedTtmSourceExp, boolean useTempModelExc)
throws TemplateException {
@@ -2415,7 +2452,7 @@ public final class Environment extends Configurable {
* Format temporal with the default format to plain text.
*
* @param blamedTtmSourceExp
- * The blamed expression if an error occurs; only used for
error messages.
+ * The blamed expression if an error occurs; only used for error
messages.
*/
String formatTemporalToPlainText(
TemplateTemporalModel ttm, String formatString,
@@ -2480,7 +2517,7 @@ public final class Environment extends Configurable {
temporalClass,
blamedTemporalSourceExp != null
&&
blamedTemporalSourceExp.getTemplate().getActualNamingConvention()
- ==
Configuration.CAMEL_CASE_NAMING_CONVENTION);
+ == Configuration.CAMEL_CASE_NAMING_CONVENTION);
settingValue = getTemporalFormat(temporalClass);
} catch (IllegalArgumentException e2) {
settingName = "???";
@@ -2504,182 +2541,182 @@ public final class Environment extends Configurable {
public TemplateTemporalFormat getTemplateTemporalFormat(Class<? extends
Temporal> temporalClass)
throws TemplateValueFormatException {
temporalClass =
_TemporalUtils.normalizeSupportedTemporalClass(temporalClass);
- if (cachedTemporalFormatCache == null) {
- cachedTemporalFormatCache = new TemplateTemporalFormatCache();
+ if (cachedTemporalFormats == null) {
+ cachedTemporalFormats = new CachedTemplateTemporalFormats();
}
TemplateTemporalFormat result;
// BEGIN Generated with getTemplateTemporalFormatCaching.ftl
if (temporalClass == LocalDateTime.class) {
- result = cachedTemporalFormatCache.localDateTimeFormat;
+ result = cachedTemporalFormats.localDateTimeFormat;
if (result != null) {
return result;
}
- result = cachedTemporalFormatCache.reusableLocalDateTimeFormat;
+ result = cachedTemporalFormats.reusableLocalDateTimeFormat;
if (result != null
&& result.canBeUsedForTimeZone(getTimeZone()) &&
result.canBeUsedForLocale(getLocale())) {
- cachedTemporalFormatCache.localDateTimeFormat = result;
+ cachedTemporalFormats.localDateTimeFormat = result;
return result;
}
result =
getTemplateTemporalFormat(getTemporalFormat(temporalClass), temporalClass);
- cachedTemporalFormatCache.localDateTimeFormat = result;
+ cachedTemporalFormats.localDateTimeFormat = result;
// We do this ahead of time, to decrease the cost of evictions:
- cachedTemporalFormatCache.reusableLocalDateTimeFormat = result;
+ cachedTemporalFormats.reusableLocalDateTimeFormat = result;
return result;
}
if (temporalClass == Instant.class) {
- result = cachedTemporalFormatCache.instantFormat;
+ result = cachedTemporalFormats.instantFormat;
if (result != null) {
return result;
}
- result = cachedTemporalFormatCache.reusableInstantFormat;
+ result = cachedTemporalFormats.reusableInstantFormat;
if (result != null
&& result.canBeUsedForTimeZone(getTimeZone()) &&
result.canBeUsedForLocale(getLocale())) {
- cachedTemporalFormatCache.instantFormat = result;
+ cachedTemporalFormats.instantFormat = result;
return result;
}
result =
getTemplateTemporalFormat(getTemporalFormat(temporalClass), temporalClass);
- cachedTemporalFormatCache.instantFormat = result;
+ cachedTemporalFormats.instantFormat = result;
// We do this ahead of time, to decrease the cost of evictions:
- cachedTemporalFormatCache.reusableInstantFormat = result;
+ cachedTemporalFormats.reusableInstantFormat = result;
return result;
}
if (temporalClass == LocalDate.class) {
- result = cachedTemporalFormatCache.localDateFormat;
+ result = cachedTemporalFormats.localDateFormat;
if (result != null) {
return result;
}
- result = cachedTemporalFormatCache.reusableLocalDateFormat;
+ result = cachedTemporalFormats.reusableLocalDateFormat;
if (result != null
&& result.canBeUsedForTimeZone(getTimeZone()) &&
result.canBeUsedForLocale(getLocale())) {
- cachedTemporalFormatCache.localDateFormat = result;
+ cachedTemporalFormats.localDateFormat = result;
return result;
}
result =
getTemplateTemporalFormat(getTemporalFormat(temporalClass), temporalClass);
- cachedTemporalFormatCache.localDateFormat = result;
+ cachedTemporalFormats.localDateFormat = result;
// We do this ahead of time, to decrease the cost of evictions:
- cachedTemporalFormatCache.reusableLocalDateFormat = result;
+ cachedTemporalFormats.reusableLocalDateFormat = result;
return result;
}
if (temporalClass == LocalTime.class) {
- result = cachedTemporalFormatCache.localTimeFormat;
+ result = cachedTemporalFormats.localTimeFormat;
if (result != null) {
return result;
}
- result = cachedTemporalFormatCache.reusableLocalTimeFormat;
+ result = cachedTemporalFormats.reusableLocalTimeFormat;
if (result != null
&& result.canBeUsedForTimeZone(getTimeZone()) &&
result.canBeUsedForLocale(getLocale())) {
- cachedTemporalFormatCache.localTimeFormat = result;
+ cachedTemporalFormats.localTimeFormat = result;
return result;
}
result =
getTemplateTemporalFormat(getTemporalFormat(temporalClass), temporalClass);
- cachedTemporalFormatCache.localTimeFormat = result;
+ cachedTemporalFormats.localTimeFormat = result;
// We do this ahead of time, to decrease the cost of evictions:
- cachedTemporalFormatCache.reusableLocalTimeFormat = result;
+ cachedTemporalFormats.reusableLocalTimeFormat = result;
return result;
}
if (temporalClass == ZonedDateTime.class) {
- result = cachedTemporalFormatCache.zonedDateTimeFormat;
+ result = cachedTemporalFormats.zonedDateTimeFormat;
if (result != null) {
return result;
}
- result = cachedTemporalFormatCache.reusableZonedDateTimeFormat;
+ result = cachedTemporalFormats.reusableZonedDateTimeFormat;
if (result != null
&& result.canBeUsedForTimeZone(getTimeZone()) &&
result.canBeUsedForLocale(getLocale())) {
- cachedTemporalFormatCache.zonedDateTimeFormat = result;
+ cachedTemporalFormats.zonedDateTimeFormat = result;
return result;
}
result =
getTemplateTemporalFormat(getTemporalFormat(temporalClass), temporalClass);
- cachedTemporalFormatCache.zonedDateTimeFormat = result;
+ cachedTemporalFormats.zonedDateTimeFormat = result;
// We do this ahead of time, to decrease the cost of evictions:
- cachedTemporalFormatCache.reusableZonedDateTimeFormat = result;
+ cachedTemporalFormats.reusableZonedDateTimeFormat = result;
return result;
}
if (temporalClass == OffsetDateTime.class) {
- result = cachedTemporalFormatCache.offsetDateTimeFormat;
+ result = cachedTemporalFormats.offsetDateTimeFormat;
if (result != null) {
return result;
}
- result = cachedTemporalFormatCache.reusableOffsetDateTimeFormat;
+ result = cachedTemporalFormats.reusableOffsetDateTimeFormat;
if (result != null
&& result.canBeUsedForTimeZone(getTimeZone()) &&
result.canBeUsedForLocale(getLocale())) {
- cachedTemporalFormatCache.offsetDateTimeFormat = result;
+ cachedTemporalFormats.offsetDateTimeFormat = result;
return result;
}
result =
getTemplateTemporalFormat(getTemporalFormat(temporalClass), temporalClass);
- cachedTemporalFormatCache.offsetDateTimeFormat = result;
+ cachedTemporalFormats.offsetDateTimeFormat = result;
// We do this ahead of time, to decrease the cost of evictions:
- cachedTemporalFormatCache.reusableOffsetDateTimeFormat = result;
+ cachedTemporalFormats.reusableOffsetDateTimeFormat = result;
return result;
}
if (temporalClass == OffsetTime.class) {
- result = cachedTemporalFormatCache.offsetTimeFormat;
+ result = cachedTemporalFormats.offsetTimeFormat;
if (result != null) {
return result;
}
- result = cachedTemporalFormatCache.reusableOffsetTimeFormat;
+ result = cachedTemporalFormats.reusableOffsetTimeFormat;
if (result != null
&& result.canBeUsedForTimeZone(getTimeZone()) &&
result.canBeUsedForLocale(getLocale())) {
- cachedTemporalFormatCache.offsetTimeFormat = result;
+ cachedTemporalFormats.offsetTimeFormat = result;
return result;
}
result =
getTemplateTemporalFormat(getTemporalFormat(temporalClass), temporalClass);
- cachedTemporalFormatCache.offsetTimeFormat = result;
+ cachedTemporalFormats.offsetTimeFormat = result;
// We do this ahead of time, to decrease the cost of evictions:
- cachedTemporalFormatCache.reusableOffsetTimeFormat = result;
+ cachedTemporalFormats.reusableOffsetTimeFormat = result;
return result;
}
if (temporalClass == YearMonth.class) {
- result = cachedTemporalFormatCache.yearMonthFormat;
+ result = cachedTemporalFormats.yearMonthFormat;
if (result != null) {
return result;
}
- result = cachedTemporalFormatCache.reusableYearMonthFormat;
+ result = cachedTemporalFormats.reusableYearMonthFormat;
if (result != null
&& result.canBeUsedForTimeZone(getTimeZone()) &&
result.canBeUsedForLocale(getLocale())) {
- cachedTemporalFormatCache.yearMonthFormat = result;
+ cachedTemporalFormats.yearMonthFormat = result;
return result;
}
result =
getTemplateTemporalFormat(getTemporalFormat(temporalClass), temporalClass);
- cachedTemporalFormatCache.yearMonthFormat = result;
+ cachedTemporalFormats.yearMonthFormat = result;
// We do this ahead of time, to decrease the cost of evictions:
- cachedTemporalFormatCache.reusableYearMonthFormat = result;
+ cachedTemporalFormats.reusableYearMonthFormat = result;
return result;
}
if (temporalClass == Year.class) {
- result = cachedTemporalFormatCache.yearFormat;
+ result = cachedTemporalFormats.yearFormat;
if (result != null) {
return result;
}
- result = cachedTemporalFormatCache.reusableYearFormat;
+ result = cachedTemporalFormats.reusableYearFormat;
if (result != null
&& result.canBeUsedForTimeZone(getTimeZone()) &&
result.canBeUsedForLocale(getLocale())) {
- cachedTemporalFormatCache.yearFormat = result;
+ cachedTemporalFormats.yearFormat = result;
return result;
}
result =
getTemplateTemporalFormat(getTemporalFormat(temporalClass), temporalClass);
- cachedTemporalFormatCache.yearFormat = result;
+ cachedTemporalFormats.yearFormat = result;
// We do this ahead of time, to decrease the cost of evictions:
- cachedTemporalFormatCache.reusableYearFormat = result;
+ cachedTemporalFormats.reusableYearFormat = result;
return result;
}
// END Generated with getTemplateTemporalFormatCaching.ftl
@@ -2722,30 +2759,174 @@ public final class Environment extends Configurable {
}
}
- private TemplateTemporalFormat getTemplateTemporalFormat(String
formatString, Class<? extends Temporal> temporalClass)
+ /**
+ * Returns the {@link TemplateTemporalFormat} for the given format string,
assuming the current locale
+ * ({@link #getLocale()}) and time zone ({@link #getTimeZone()}). The
format string is interpreted like
+ * {@link #getDateFormat()}/{@link #getDateTimeFormat()}/{@link
#getDateTimeFormat()}.
+ *
+ * @since 2.3.31
+ */
+ public TemplateTemporalFormat getTemplateTemporalFormat(
+ String formatString, Class<? extends Temporal> temporalClass)
throws TemplateValueFormatException {
return getTemplateTemporalFormat(formatString, temporalClass,
getLocale(), getTimeZone());
}
/**
- * Returns the {@link TemplateTemporalFormat} for the given parameters
without using the {@link Environment}-level
- * cache. The {@link TemplateTemporalFormatFactory} involved might still
uses its own internal cache, which can be
+ * Returns the {@link TemplateTemporalFormat} for the given parameters
without using the
+ * {@link Environment#cachedTemporalFormats} cache, but still using
+ * {@link Environment#cachedTemporalFormatsByFormatString}.
+ * The {@link TemplateTemporalFormatFactory} involved
+ * might still uses its own internal cache, which can be
* global (class-loader-level), or {@link Environment}-level.
*
* @param formatString
- * See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
+ * See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
* @param temporalClass
- * See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
+ * See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
* @param locale
- * See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
+ * See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
* @param timeZone
- * See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
+ * See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
*/
private TemplateTemporalFormat getTemplateTemporalFormat(
String formatString, Class<? extends Temporal> temporalClass,
Locale locale, TimeZone timeZone)
throws TemplateValueFormatException {
- final int formatStringLen = formatString.length();
+ temporalClass =
_TemporalUtils.normalizeSupportedTemporalClass(temporalClass);
+
+ TemplateTemporalFormat[] formatArray =
getCachedTemplateTemporalFormatsByFormatString(
+ formatString, temporalClass);
+
+ int formatIndex = 0;
+ seekMatchOrBlank: while (formatIndex < formatArray.length) {
+ TemplateTemporalFormat format = formatArray[formatIndex];
+ if (format == null) {
+ break seekMatchOrBlank;
+ }
+ if (format.canBeUsedForLocale(locale)
+ && format.canBeUsedForTimeZone(timeZone)) {
+ // formatIndex is now the index of the match;
+ if (formatIndex != 0) {
+ // Bubble hit up by 1
+ formatArray[formatIndex] = formatArray[formatIndex - 1];
+ formatArray[formatIndex - 1] = format;
+ }
+ return format;
+ }
+ formatIndex++;
+ }
+ // Cache miss.
+ // formatIndex is now the index of the next blank element, or if there
was none, the array length.
+
+ TemplateTemporalFormat templateTemporalFormat =
getTemplateTemporalFormatForNormalizedClassNoCache(
+ formatString, temporalClass, locale, timeZone);
+
+ // Insert new format at index 0, shifting others
+ int highestOverwritten = formatIndex == formatArray.length ?
formatArray.length - 1 : formatIndex;
+ for (int overWrittenIndex = highestOverwritten; overWrittenIndex > 0;
overWrittenIndex--) {
+ formatArray[overWrittenIndex] = formatArray[overWrittenIndex - 1];
+ }
+ formatArray[0] = templateTemporalFormat;
+
+ return templateTemporalFormat;
+ }
+
+ /**
+ * @param temporalClass Must be already normalized!
+ */
+ private TemplateTemporalFormat[]
getCachedTemplateTemporalFormatsByFormatString(
+ String formatString, Class<? extends Temporal> temporalClass) {
+ if (cachedTemporalFormatsByFormatString == null) {
+ cachedTemporalFormatsByFormatString = new
CachedTemplateTemporalFormatsByFormatString();
+ }
+
+ Map<String, TemplateTemporalFormat[]> classSpecificMap;
+ if (temporalClass == Instant.class) {
+ classSpecificMap =
cachedTemporalFormatsByFormatString.instantFormats;
+ if (classSpecificMap == null) {
+ classSpecificMap = new HashMap<>();
+ cachedTemporalFormatsByFormatString.instantFormats =
classSpecificMap;
+ }
+ } else if (temporalClass == LocalDate.class) {
+ classSpecificMap =
cachedTemporalFormatsByFormatString.localDateFormats;
+ if (classSpecificMap == null) {
+ classSpecificMap = new HashMap<>();
+ cachedTemporalFormatsByFormatString.localDateFormats =
classSpecificMap;
+ }
+ } else if (temporalClass == LocalDateTime.class) {
+ classSpecificMap =
cachedTemporalFormatsByFormatString.localDateTimeFormats;
+ if (classSpecificMap == null) {
+ classSpecificMap = new HashMap<>();
+ cachedTemporalFormatsByFormatString.localDateTimeFormats =
classSpecificMap;
+ }
+ } else if (temporalClass == LocalTime.class) {
+ classSpecificMap =
cachedTemporalFormatsByFormatString.localTimeFormats;
+ if (classSpecificMap == null) {
+ classSpecificMap = new HashMap<>();
+ cachedTemporalFormatsByFormatString.localTimeFormats =
classSpecificMap;
+ }
+ } else if (temporalClass == OffsetDateTime.class) {
+ classSpecificMap =
cachedTemporalFormatsByFormatString.offsetDateTimeFormats;
+ if (classSpecificMap == null) {
+ classSpecificMap = new HashMap<>();
+ cachedTemporalFormatsByFormatString.offsetDateTimeFormats =
classSpecificMap;
+ }
+ } else if (temporalClass == OffsetTime.class) {
+ classSpecificMap =
cachedTemporalFormatsByFormatString.offsetTimeFormats;
+ if (classSpecificMap == null) {
+ classSpecificMap = new HashMap<>();
+ cachedTemporalFormatsByFormatString.offsetTimeFormats =
classSpecificMap;
+ }
+ } else if (temporalClass == ZonedDateTime.class) {
+ classSpecificMap =
cachedTemporalFormatsByFormatString.zonedDateTimeFormats;
+ if (classSpecificMap == null) {
+ classSpecificMap = new HashMap<>();
+ cachedTemporalFormatsByFormatString.zonedDateTimeFormats =
classSpecificMap;
+ }
+ } else if (temporalClass == YearMonth.class) {
+ classSpecificMap =
cachedTemporalFormatsByFormatString.yearMonthFormats;
+ if (classSpecificMap == null) {
+ classSpecificMap = new HashMap<>();
+ cachedTemporalFormatsByFormatString.yearMonthFormats =
classSpecificMap;
+ }
+ } else if (temporalClass == Year.class) {
+ classSpecificMap = cachedTemporalFormatsByFormatString.yearFormats;
+ if (classSpecificMap == null) {
+ classSpecificMap = new HashMap<>();
+ cachedTemporalFormatsByFormatString.yearFormats =
classSpecificMap;
+ }
+ } else {
+ throw new BugException("Unhandled temporal class: " +
temporalClass.getName());
+ }
+
+ return classSpecificMap.computeIfAbsent(
+ formatString,
+ k -> new
TemplateTemporalFormat[CACHED_TEMPORAL_FORMATS_BY_FORMAT_STRING_VALUE_ARRAY_LENGTH]);
+ }
+
+ void clearCachedTemplateTemporalFormatsByFormatString() {
+ cachedTemporalFormatsByFormatString = null;
+ }
+
+ /**
+ * Returns the {@link TemplateTemporalFormat} for the given parameters
without using any
+ * {@link Environment}-level cacheing. The {@link
TemplateTemporalFormatFactory} involved might still uses its
+ * own internal cache, which can be global (class-loader-level), or {@link
Environment}-level.
+ *
+ * @param formatString
+ * See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
+ * @param temporalClass
+ * Must be already normalized! See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
+ * @param locale
+ * See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
+ * @param timeZone
+ * See the similar parameter of {@link
TemplateTemporalFormatFactory#get}
+ */
+ private TemplateTemporalFormat
getTemplateTemporalFormatForNormalizedClassNoCache(String formatString,
+ Class<? extends Temporal> temporalClass, Locale locale, TimeZone
timeZone)
+ throws TemplateValueFormatException {
final String formatParams;
+ final int formatStringLen = formatString.length();
TemplateTemporalFormatFactory formatFactory;
char firstChar = formatStringLen != 0 ? formatString.charAt(0) : 0;
@@ -2798,9 +2979,9 @@ public final class Environment extends Configurable {
/**
* 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)}
- * .
+ * {@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) {
@@ -2824,9 +3005,9 @@ public final class Environment extends Configurable {
}
/**
- * Returns the loop or macro local variable corresponding to this variable
name.
- * Returns {@code null} if no such variable exists with the given name, or
the variable was set to
- * {@code null}. Doesn't read namespace or global variables.
+ * Returns the loop or macro local variable corresponding to this variable
name. Returns {@code null} if no such
+ * variable exists with the given name, or the variable was set to {@code
null}. Doesn't read namespace or global
+ * variables.
*/
public TemplateModel getLocalVariable(String name) throws
TemplateModelException {
TemplateModel val = getNullableLocalVariable(name);
@@ -2914,31 +3095,32 @@ public final class Environment extends Configurable {
}
/**
- * Sets a variable in the global namespace, like {@code <#global
name=value>}.
- * This can be considered a convenient shorthand for {@code
getGlobalNamespace().put(name, model)}.
+ * Sets a variable in the global namespace, like {@code <#global
name=value>}. This can be considered a convenient
+ * shorthand for {@code getGlobalNamespace().put(name, model)}.
*
- * <p>Note that this is not an exact pair of {@link
#getGlobalVariable(String)}, as that falls back to higher scopes
+ * <p>Note that this is not an exact pair of {@link
#getGlobalVariable(String)}, as that falls back to higher
+ * scopes
* if the variable is not in the global namespace.
*
* @param name
- * The name of the variable.
+ * The name of the variable.
* @param value
- * The new value of the variable. {@code null} in effect
removes the local variable (reading it will fall
- * back to higher scope).
+ * The new value of the variable. {@code null} in effect removes
the local variable (reading it will fall
+ * back to higher scope).
*/
public void setGlobalVariable(String name, TemplateModel value) {
globalNamespace.put(name, value);
}
/**
- * Sets a variable in the current namespace, like {@code <#assign
name=value>}.
- * This can be considered a convenient shorthand for: {@code
getCurrentNamespace().put(name, model)}.
+ * Sets a variable in the current namespace, like {@code <#assign
name=value>}. This can be considered a convenient
+ * shorthand for: {@code getCurrentNamespace().put(name, model)}.
*
* @param name
- * The name of the variable.
+ * The name of the variable.
* @param value
- * The new value of the variable. {@code null} in effect
removes the local variable (reading it will fall
- * back to higher scope).
+ * The new value of the variable. {@code null} in effect removes
the local variable (reading it will fall
+ * back to higher scope).
*/
public void setVariable(String name, TemplateModel value) {
currentNamespace.put(name, value);
@@ -2946,18 +3128,18 @@ public final class Environment extends Configurable {
/**
* Sets a local variable that's on the top-level inside a macro or
function invocation, like
- * {@code <#local name=value>}.
- * Note that just like {@code <#local name=value>}, this will not set loop
variables; it will totally ignore
- * them, and might sets a local variable that a loop variable currently
"shadows". As such, it's not exactly the
- * pair of {@link #getLocalVariable(String)}, which also reads loop
variables.
+ * {@code <#local name=value>}. Note that just like {@code <#local
name=value>}, this will not set loop variables;
+ * it will totally ignore them, and might sets a local variable that a
loop variable currently "shadows". As such,
+ * it's not exactly the pair of {@link #getLocalVariable(String)}, which
also reads loop variables.
*
* @param name
- * The name of the variable.
+ * The name of the variable.
* @param value
- * The new value of the variable. {@code null} in effect
removes the local variable (reading it will fall
- * back to higher scope).
+ * The new value of the variable. {@code null} in effect removes
the local variable (reading it will fall
+ * back to higher scope).
+ *
* @throws IllegalStateException
- * if the environment is not executing a macro body.
+ * if the environment is not executing a macro body.
*/
public void setLocalVariable(String name, TemplateModel value) {
if (currentMacroContext == null) {
@@ -2988,12 +3170,12 @@ public final class Environment extends Configurable {
}
// 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());
}
@@ -3023,9 +3205,10 @@ public final class Environment extends Configurable {
/**
* 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.
+ * If it's a {@link PrintWriter}, {@link PrintWriter#println()}
will be used for line-breaks.
+ *
* @see #getInstructionStackSnapshot()
* @since 2.3.21
*/
@@ -3037,8 +3220,8 @@ public final class Environment extends Configurable {
final int totalFrames = instructionStackSnapshot.length;
int framesToPrint = terseMode
? (totalFrames <=
TERSE_MODE_INSTRUCTION_STACK_TRACE_LIMIT
- ? totalFrames
- : TERSE_MODE_INSTRUCTION_STACK_TRACE_LIMIT - 1)
+ ? totalFrames
+ : TERSE_MODE_INSTRUCTION_STACK_TRACE_LIMIT - 1)
: totalFrames;
boolean hideNestringRelatedFrames = terseMode && framesToPrint
< totalFrames;
int nestingRelatedFramesHidden = 0;
@@ -3105,7 +3288,7 @@ public final class Environment extends Configurable {
/**
* Returns the snapshot of what would be printed as FTL stack trace.
- *
+ *
* @since 2.3.20
*/
TemplateElement[] getInstructionStackSnapshot() {
@@ -3175,11 +3358,11 @@ public final class Environment extends Configurable {
/**
* 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.
+ * 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);
@@ -3217,52 +3400,52 @@ public final class Environment extends Configurable {
}
/**
- * 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}.
+ * 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() {
- @Override
- public boolean isEmpty() throws TemplateModelException {
- return false;
- }
+ @Override
+ public boolean isEmpty() throws TemplateModelException {
+ return false;
+ }
- @Override
- public TemplateModel get(String key) throws
TemplateModelException {
- return getDataModelOrSharedVariable(key);
- }
+ @Override
+ public TemplateModel get(String key) throws TemplateModelException
{
+ return getDataModelOrSharedVariable(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
- @Override
- public TemplateCollectionModel values() throws
TemplateModelException {
- return ((TemplateHashModelEx) rootDataModel).values();
- }
+ // NB: The methods below do not take into account
+ // configuration shared variables even though
+ // the hash will return them, if only for BWC reasons
+ @Override
+ public TemplateCollectionModel values() throws
TemplateModelException {
+ return ((TemplateHashModelEx) rootDataModel).values();
+ }
+ @Override
+ public TemplateCollectionModel keys() throws
TemplateModelException {
+ return ((TemplateHashModelEx) rootDataModel).keys();
+ }
+
+ @Override
+ public int size() throws TemplateModelException {
+ return ((TemplateHashModelEx) rootDataModel).size();
+ }
+ }
+ : new TemplateHashModel() {
@Override
- public TemplateCollectionModel keys() throws
TemplateModelException {
- return ((TemplateHashModelEx) rootDataModel).keys();
+ public boolean isEmpty() {
+ return false;
}
@Override
- public int size() throws TemplateModelException {
- return ((TemplateHashModelEx) rootDataModel).size();
+ public TemplateModel get(String key) throws
TemplateModelException {
+ TemplateModel value = rootDataModel.get(key);
+ return value != null ? value :
configuration.getSharedVariable(key);
}
- }
- : new TemplateHashModel() {
- @Override
- public boolean isEmpty() {
- return false;
- }
-
- @Override
- public TemplateModel get(String key) throws
TemplateModelException {
- TemplateModel value = rootDataModel.get(key);
- return value != null ? value :
configuration.getSharedVariable(key);
- }
- };
+ };
}
/**
@@ -3298,7 +3481,7 @@ public final class Environment extends Configurable {
if (newSize > instructionStack.length) {
final TemplateElement[] newInstructionStack = new
TemplateElement[newSize * 2];
for (int i = 0; i < instructionStack.length; i++) {
- newInstructionStack[i] = instructionStack[i];
+ newInstructionStack[i] = instructionStack[i];
}
instructionStack = newInstructionStack;
this.instructionStack = instructionStack;
@@ -3452,30 +3635,26 @@ public final class Environment extends Configurable {
* 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)}
- *
+ * 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.
- *
+ * 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)}
- *
+ * 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)}
- *
+ * 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)}
- *
+ * 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)
@@ -3506,8 +3685,8 @@ public final class Environment extends Configurable {
* <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)}.
+ * 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 {
@@ -3539,13 +3718,13 @@ public final class Environment extends Configurable {
* 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.
+ * 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.
+ * try to get and process the imported template when you access its
content.
*
* @see #getTemplateForImporting(String templateName)
* @see #importLib(Template includedTemplate, String namespaceVarName)
@@ -3560,14 +3739,14 @@ public final class Environment extends Configurable {
* 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}.
+ * 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.
- *
+ * 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)
*/
@@ -3579,10 +3758,10 @@ public final class Environment extends Configurable {
/**
* 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.
- *
+ * 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)
@@ -3591,17 +3770,17 @@ public final class Environment extends Configurable {
? importLib(templateName, null, targetNsVarName)
: importLib(null, getTemplateForImporting(templateName),
targetNsVarName);
}
-
+
/**
* 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.)
+ * 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);
@@ -3609,10 +3788,10 @@ public final class Environment extends Configurable {
/**
* @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).
+ * 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
+ * {@code null} exactly if we want a lazy import
*/
private Namespace importLib(
String templateName, final Template loadedTemplate, final String
targetNsVarName)
@@ -3632,7 +3811,7 @@ public final class Environment extends Configurable {
TemplateNameFormat tnf =
getConfiguration().getTemplateNameFormat();
templateName = _CacheAPI.normalizeRootBasedName(tnf, templateName);
}
-
+
if (loadedLibs == null) {
loadedLibs = new HashMap();
}
@@ -3651,14 +3830,14 @@ public final class Environment extends Configurable {
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);
}
@@ -3688,25 +3867,25 @@ public final class Environment extends Configurable {
* <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:"}).
+ * 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}.
- *
+ * 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)
@@ -3725,12 +3904,12 @@ public final class Environment extends Configurable {
* 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 {
@@ -3750,14 +3929,14 @@ public final class Environment extends Configurable {
}
void importMacros(Template template) {
- for (Iterator it = template.getMacros().values().iterator();
it.hasNext();) {
+ for (Iterator it = template.getMacros().values().iterator();
it.hasNext(); ) {
visitMacroDef((Macro) it.next());
}
}
/**
* @return the namespace URI registered for this prefix, or null. This is
based on the mappings registered in the
- * current namespace.
+ * current namespace.
*/
public String getNamespaceForPrefix(String prefix) {
return currentNamespace.getTemplate().getNamespaceForPrefix(prefix);
@@ -3793,7 +3972,7 @@ public final class Environment extends Configurable {
/**
* 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) {
@@ -3807,16 +3986,16 @@ public final class Environment extends Configurable {
* 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.
+ * 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}.
- *
+ * 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) {
@@ -3846,7 +4025,7 @@ public final class Environment extends Configurable {
out = prevOut;
}
}
-
+
TemplateElement[] getChildrenBuffer() {
return childBuffer;
}
@@ -3873,33 +4052,33 @@ public final class Environment extends Configurable {
public Template getTemplate() {
return template == null ? Environment.this.getTemplate() :
template;
}
-
+
void setTemplate(Template template) {
- this.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;
-
+
/**
* @param templateName
- * Must be root relative
+ * 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();
@@ -3912,8 +4091,8 @@ public final class Environment extends Configurable {
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.");
+ + StringUtil.jQuote(templateName)
+ + " has already failed earlier; won't
retry it.");
}
try {
status = InitializationStatus.INITIALIZING;
@@ -3923,8 +4102,8 @@ public final class Environment extends Configurable {
// [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);
+ + StringUtil.jQuote(templateName)
+ + " has failed; see cause exception", e);
} finally {
if (status != InitializationStatus.INITIALIZED) {
status = InitializationStatus.FAILED;
@@ -3932,7 +4111,7 @@ public final class Environment extends Configurable {
}
}
}
-
+
private void ensureInitializedRTE() {
try {
ensureInitializedTME();
@@ -4044,7 +4223,7 @@ public final class Environment extends Configurable {
return super.keyValuePairIterator();
}
-
+
}
private static final Writer EMPTY_BODY_WRITER = new Writer() {
diff --git a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
index 47208c2f..5918689b 100644
--- a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
@@ -266,6 +266,13 @@ class JavaTemplateTemporalFormat extends
DateTimeFormatterBasedTemplateTemporalF
return formatString;
}
+ @Override
+ public String toString() {
+ return "JavaTemplateTemporalFormat(" + "dateTimeFormatter=" +
dateTimeFormatter + ", "
+ + "locale=" + locale + ", timeZone=" + timeZone + ",
preFormatValueConversion="
+ + preFormatValueConversion + ")";
+ }
+
private static final ZonedDateTime SHOWS_OFFSET_OR_ZONE_SAMPLE_TEMPORAL_1
= ZonedDateTime.of(
LocalDateTime.of(2011, 1, 1, 1, 1), ZoneOffset.ofHours(0));
private static final ZonedDateTime SHOWS_OFFSET_OR_ZONE_SAMPLE_TEMPORAL_2
= ZonedDateTime.of(
diff --git
a/src/main/misc/templateTemporalFormatCache/getTemplateTemporalFormatCaching.ftl
b/src/main/misc/templateTemporalFormatCache/getTemplateTemporalFormatCaching.ftl
index 5fd50ed2..c0b57a5b 100644
---
a/src/main/misc/templateTemporalFormatCache/getTemplateTemporalFormatCaching.ftl
+++
b/src/main/misc/templateTemporalFormatCache/getTemplateTemporalFormatCaching.ftl
@@ -3,22 +3,22 @@
<#list ['LocalDateTime', 'Instant', 'LocalDate', 'LocalTime', 'ZonedDateTime',
'OffsetDateTime', 'OffsetTime', 'YearMonth', 'Year'] as TemporalClass>
<#assign temporalClass = TemporalClass[0]?lowerCase + TemporalClass[1..]>
if (temporalClass == ${TemporalClass}.class) {
- result = cachedTemporalFormatCache.${temporalClass}Format;
+ result = cachedTemporalFormats.${temporalClass}Format;
if (result != null) {
return result;
}
- result = cachedTemporalFormatCache.reusable${TemporalClass}Format;
+ result = cachedTemporalFormats.reusable${TemporalClass}Format;
if (result != null
&& result.canBeUsedForTimeZone(getTimeZone()) &&
result.canBeUsedForLocale(getLocale())) {
- cachedTemporalFormatCache.${temporalClass}Format = result;
+ cachedTemporalFormats.${temporalClass}Format = result;
return result;
}
result = getTemplateTemporalFormat(getTemporalFormat(temporalClass),
temporalClass);
- cachedTemporalFormatCache.${temporalClass}Format = result;
+ cachedTemporalFormats.${temporalClass}Format = result;
// We do this ahead of time, to decrease the cost of evictions:
- cachedTemporalFormatCache.reusable${TemporalClass}Format = result;
+ cachedTemporalFormats.reusable${TemporalClass}Format = result;
return result;
}
</#list>
diff --git
a/src/test/java/freemarker/core/TemplateTemporalFormatAbstractCachingInEnvironmentTest.java
b/src/test/java/freemarker/core/TemplateTemporalFormatAbstractCachingInEnvironmentTest.java
new file mode 100644
index 00000000..d7f293a8
--- /dev/null
+++
b/src/test/java/freemarker/core/TemplateTemporalFormatAbstractCachingInEnvironmentTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.ZonedDateTime;
+import java.time.temporal.Temporal;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import freemarker.template.utility.DateUtil;
+import freemarker.template.utility.NullWriter;
+
+public abstract class TemplateTemporalFormatAbstractCachingInEnvironmentTest {
+ protected static final TimeZone OTHER_TIME_ZONE =
TimeZone.getTimeZone("GMT+01");
+
+ protected final Environment env;
+
+ public TemplateTemporalFormatAbstractCachingInEnvironmentTest() {
+ try {
+ this.env = createEnvironment();
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected Environment createEnvironment() throws TemplateException,
IOException {
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
+ cfg.setLocale(Locale.US);
+ cfg.setTimeZone(DateUtil.UTC);
+
+ return new Template(null, "", cfg)
+ .createProcessingEnvironment(null, NullWriter.INSTANCE);
+ }
+
+ static final class SettingAssignments {
+ private final Class<? extends Temporal> temporalClass;
+ private final String[] values;
+
+ public SettingAssignments(Class<? extends Temporal> temporalClass,
String[] values) {
+ this.temporalClass = temporalClass;
+ this.values = values;
+ }
+
+ public void execute(Configurable configurable, int valueIndex) {
+ String value = values[valueIndex];
+ if (temporalClass == Instant.class
+ || temporalClass == LocalDateTime.class
+ || temporalClass == ZonedDateTime.class
+ || temporalClass == OffsetDateTime.class) {
+ configurable.setDateTimeFormat(value);
+ } else if (temporalClass == LocalDate.class) {
+ configurable.setDateFormat(value);
+ } else if (temporalClass == LocalTime.class || temporalClass ==
OffsetTime.class) {
+ configurable.setTimeFormat(value);
+ } else if (temporalClass == Year.class) {
+ configurable.setYearFormat(value);
+ } else if (temporalClass == YearMonth.class) {
+ configurable.setYearMonthFormat(value);
+ } else {
+ throw new AssertionError();
+ }
+ }
+
+ public String getValue(int valueIndex) {
+ return values[valueIndex];
+ }
+
+ public int numberOfValues() {
+ return values.length;
+ }
+
+ public Class<? extends Temporal> getTemporalClass() {
+ return temporalClass;
+ }
+ }
+
+ @FunctionalInterface
+ interface SettingSetter {
+ void execute(Configurable configurable, String value);
+ }
+}
diff --git
a/src/test/java/freemarker/core/TemplateTemporalFormatByFormatStringCachingInEnvironmentTest.java
b/src/test/java/freemarker/core/TemplateTemporalFormatByFormatStringCachingInEnvironmentTest.java
new file mode 100644
index 00000000..cda9eb74
--- /dev/null
+++
b/src/test/java/freemarker/core/TemplateTemporalFormatByFormatStringCachingInEnvironmentTest.java
@@ -0,0 +1,232 @@
+/*
+ * 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 static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.Year;
+import java.time.YearMonth;
+import java.time.ZonedDateTime;
+import java.time.temporal.Temporal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.junit.Test;
+
+import freemarker.template.TemplateException;
+
+/**
+ * Tests the caching of the format in the {@link Environment} for a specific
format string that's necessarily the
+ * current value of the related format setting (like for
<code>${temporal?string(someSpecialFormat)}</code>, as opposed
+ * to <code>${temporal}</code>).
+ */
+public class TemplateTemporalFormatByFormatStringCachingInEnvironmentTest
+ extends TemplateTemporalFormatAbstractCachingInEnvironmentTest {
+
+ public void test() throws IOException, TemplateException,
TemplateValueFormatException {
+ Class<? extends Temporal> temporalClass = LocalDateTime.class;
+ TemplateTemporalFormat templateTemporalFormat =
env.getTemplateTemporalFormat(temporalClass);
+ }
+
+ @Test
+ public void testForDateTime() throws Exception {
+ // Locale dependent formatters:
+ String[] formats = {"yyyy-MM-dd HH:mm", "yyyyMMddHHmm", "yyyyMMdd
HHmm"};
+ genericTest(LocalDateTime.class, true, false, formats);
+ genericTest(ZonedDateTime.class, true, true, formats);
+ genericTest(OffsetDateTime.class, true, true, formats);
+ genericTest(Instant.class, true, true, formats);
+
+ // Locale independent formatters:
+ genericTest(LocalDateTime.class, false, false, "iso", "xs");
+ genericTest(ZonedDateTime.class, false, true, "iso", "xs");
+ }
+
+ @Test
+ public void testForDate() throws Exception {
+ // Locale dependent formatters:
+ genericTest(LocalDate.class, true, false, "yyyy-MM-dd", "yyyyMM-dd");
+
+ // Locale independent formatters:
+ genericTest(LocalDate.class, false, false, "iso", "xs");
+ }
+
+ @Test
+ public void testForTime() throws Exception {
+ // Locale dependent formatters:
+ genericTest(LocalTime.class, true, false, "HH:mm", "HHmm");
+ genericTest(OffsetTime.class, true, true, "HH:mm", "HHmm");
+
+ // Locale independent formatters:
+ genericTest(LocalTime.class, false, false, "iso", "xs");
+ genericTest(OffsetTime.class, false, true, "iso", "xs");
+ }
+
+ @Test
+ public void testForYearMonth() throws Exception {
+ // Locale dependent formatters:
+ genericTest(YearMonth.class, true, false, "yyyy-MM", "yyyyMM");
+
+ // Locale independent formatters:
+ genericTest(YearMonth.class, false, false, "iso", "xs");
+ }
+
+ @Test
+ public void testForYear() throws Exception {
+ // Locale dependent formatters:
+ genericTest(Year.class, true, false, "yyyy", "yy");
+
+ // Locale independent formatters:
+ genericTest(Year.class, false, false, "iso", "xs");
+ }
+
+ private void genericTest(
+ Class<? extends Temporal> temporalClass,
+ boolean localeDependent, boolean timeZoneDependent,
+ String... formatStrings
+ ) throws Exception {
+ SettingAssignments settingAssignments = new
SettingAssignments(temporalClass, formatStrings);
+
+ env.clearCachedTemplateTemporalFormatsByFormatString();
+
+ {
+ List<TemplateTemporalFormat> cachedFormats =
getFormatsToBeCachedByFormatString(settingAssignments);
+ testGetByFormatStringReturnsSameAsEarlierCached(temporalClass,
settingAssignments, cachedFormats);
+ testCurrentFormatsSameAsEarlierCached(temporalClass,
settingAssignments, cachedFormats);
+ testGetByFormatStringReturnsSameAsEarlierCached(temporalClass,
settingAssignments, cachedFormats);
+ }
+
+ {
+ List<TemplateTemporalFormat> cachedFormats =
getFormatsToBeCachedByFormatString(settingAssignments);
+ Locale prevLocale = env.getLocale();
+ env.setLocale(Locale.GERMAN);
+ try {
+ if (localeDependent) {
+
testGetByFormatReturnsDifferentThanEarlierCached(temporalClass,
settingAssignments, cachedFormats);
+ } else {
+
testGetByFormatStringReturnsSameAsEarlierCached(temporalClass,
settingAssignments, cachedFormats);
+ }
+ } finally {
+ env.setLocale(prevLocale);
+ }
+ }
+
+ {
+ List<TemplateTemporalFormat> cachedFormats =
getFormatsToBeCachedByFormatString(settingAssignments);
+ TimeZone prevTimeZone = env.getTimeZone();
+ env.setTimeZone(OTHER_TIME_ZONE);
+ try {
+ if (timeZoneDependent) {
+
testGetByFormatReturnsDifferentThanEarlierCached(temporalClass,
settingAssignments, cachedFormats);
+ } else {
+
testGetByFormatStringReturnsSameAsEarlierCached(temporalClass,
settingAssignments, cachedFormats);
+ }
+ } finally {
+ env.setTimeZone(prevTimeZone);
+ }
+ }
+ }
+
+ @Test
+ public void testCacheOverflow() throws TemplateValueFormatException {
+ ArrayList<Locale> locales = new ArrayList<>();
+ for (int n = 0; n < 50; n++) {
+ locales.add(new Locale("en", "US", "v" + n));
+ }
+
+ String formatString = "yyyy-MM-dd";
+
+ List<TemplateTemporalFormat> onceCachedFormats = new ArrayList<>();
+ for (Locale locale : locales) {
+ env.setLocale(locale);
+ onceCachedFormats.add(env.getTemplateTemporalFormat(formatString,
LocalDate.class));
+ }
+
+ for (int i = 1; i <= 3; i++) {
+ env.setLocale(locales.get(locales.size() - i));
+ assertSame(
+ env.getTemplateTemporalFormat(formatString,
LocalDate.class),
+ onceCachedFormats.get(onceCachedFormats.size() - i));
+ }
+
+ env.setLocale(locales.get(0));
+ assertNotSame(
+ env.getTemplateTemporalFormat(formatString, LocalDate.class),
+ onceCachedFormats.get(0));
+ }
+
+ private void testGetByFormatReturnsDifferentThanEarlierCached(Class<?
extends Temporal> temporalClass,
+ SettingAssignments settingAssignments,
+ List<TemplateTemporalFormat> cachedFormats) throws
+ TemplateValueFormatException {
+ for (int valueIndex = 0; valueIndex <
settingAssignments.numberOfValues(); valueIndex++) {
+ String formatString = settingAssignments.getValue(valueIndex);
+ TemplateTemporalFormat earlierFormat =
cachedFormats.get(valueIndex);
+ TemplateTemporalFormat currentFormat =
env.getTemplateTemporalFormat(formatString, temporalClass);
+ if (currentFormat == earlierFormat) {
+ fail("Current format and earlier cache format shouldn't be the
same, bu there were both thus: "
+ + currentFormat);
+ }
+ }
+ }
+
+ private void testCurrentFormatsSameAsEarlierCached(Class<? extends
Temporal> temporalClass,
+ SettingAssignments settingAssignments,
+ List<TemplateTemporalFormat> cachedFormats) throws
+ TemplateValueFormatException {
+ for (int valueIndex = 0; valueIndex <
settingAssignments.numberOfValues(); valueIndex++) {
+ settingAssignments.execute(env, valueIndex);
+ assertSame(env.getTemplateTemporalFormat(temporalClass),
cachedFormats.get(valueIndex));
+ }
+ }
+
+ private void testGetByFormatStringReturnsSameAsEarlierCached(Class<?
extends Temporal> temporalClass,
+ SettingAssignments settingAssignments,
+ List<TemplateTemporalFormat> cachedFormats) throws
+ TemplateValueFormatException {
+ for (int valueIndex = 0; valueIndex <
settingAssignments.numberOfValues(); valueIndex++) {
+ String formatString = settingAssignments.getValue(valueIndex);
+ assertSame(env.getTemplateTemporalFormat(formatString,
temporalClass), cachedFormats.get(valueIndex));
+ }
+ }
+
+ private List<TemplateTemporalFormat>
getFormatsToBeCachedByFormatString(SettingAssignments settingAssignments)
+ throws
+ TemplateValueFormatException {
+ List<TemplateTemporalFormat> cachedFormats = new ArrayList<>();
+ for (int valueIndex = 0; valueIndex <
settingAssignments.numberOfValues(); valueIndex++) {
+ String formatString = settingAssignments.getValue(valueIndex);
+ TemplateTemporalFormat format =
env.getTemplateTemporalFormat(formatString,
+ settingAssignments.getTemporalClass());
+ cachedFormats.add(format);
+ }
+ return cachedFormats;
+ }
+
+}
diff --git
a/src/test/java/freemarker/core/TemplateTemporalFormatCachingInEnvironmentTest.java
b/src/test/java/freemarker/core/TemplateTemporalFormatCurrentCachingInEnvironmentTest.java
similarity index 70%
rename from
src/test/java/freemarker/core/TemplateTemporalFormatCachingInEnvironmentTest.java
rename to
src/test/java/freemarker/core/TemplateTemporalFormatCurrentCachingInEnvironmentTest.java
index 0268f92a..86db4b05 100644
---
a/src/test/java/freemarker/core/TemplateTemporalFormatCachingInEnvironmentTest.java
+++
b/src/test/java/freemarker/core/TemplateTemporalFormatCurrentCachingInEnvironmentTest.java
@@ -32,7 +32,6 @@ import java.time.YearMonth;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.util.Locale;
-import java.util.TimeZone;
import org.junit.Test;
@@ -41,17 +40,17 @@ import freemarker.template.Template;
import freemarker.template.utility.DateUtil;
import freemarker.template.utility.NullWriter;
-public class TemplateTemporalFormatCachingInEnvironmentTest {
+/**
+ * Tests the caching of the format in the {@link Environment} for the current
value of the format setting (like for
+ * <code>${temporal}</code>, as opposed to
<code>${temporal?string(someSpecialFormat)}</code>).
+ *
+ * @see TemplateTemporalFormatByFormatStringCachingInEnvironmentTest
+ */
+public class TemplateTemporalFormatCurrentCachingInEnvironmentTest
+ extends TemplateTemporalFormatAbstractCachingInEnvironmentTest {
@Test
public void testTemporalClassSeparation() throws Exception {
- Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
- cfg.setLocale(Locale.US);
- cfg.setTimeZone(DateUtil.UTC);
-
- Environment env = new Template(null, "", cfg)
- .createProcessingEnvironment(null, NullWriter.INSTANCE);
-
env.setDateTimeFormat("iso");
TemplateTemporalFormat lastLocalDateTimeFormat =
env.getTemplateTemporalFormat(LocalDateTime.class);
TemplateTemporalFormat lastOffsetDateTimeFormat =
env.getTemplateTemporalFormat(OffsetDateTime.class);
@@ -97,95 +96,64 @@ public class TemplateTemporalFormatCachingInEnvironmentTest
{
@Test
public void testForDateTime() throws Exception {
// Locale dependent formatters:
- genericTest(LocalDateTime.class,
- (cfg, first) -> cfg.setDateTimeFormat(first ? "yyyy-MM-dd
HH:mm" : "yyyyMMddHHmm"),
- true, false);
- genericTest(ZonedDateTime.class,
- (cfg, first) -> cfg.setDateTimeFormat(first ? "yyyy-MM-dd
HH:mm" : "yyyyMMddHHmm"),
- true, true);
- genericTest(OffsetDateTime.class,
- (cfg, first) -> cfg.setDateTimeFormat(first ? "yyyy-MM-dd
HH:mm" : "yyyyMMddHHmm"),
- true, true);
- genericTest(Instant.class,
- (cfg, first) -> cfg.setDateTimeFormat(first ? "yyyy-MM-dd
HH:mm" : "yyyyMMddHHmm"),
- true, true);
+ String[] formats = {"yyyy-MM-dd HH:mm", "yyyyMMddHHmm"};
+ genericTest(LocalDateTime.class, true, false, formats);
+ genericTest(ZonedDateTime.class, true, true, formats);
+ genericTest(OffsetDateTime.class, true, true, formats);
+ genericTest(Instant.class, true, true, formats);
// Locale independent formatters:
- genericTest(LocalDateTime.class,
- (cfg, first) -> cfg.setDateTimeFormat(first ? "iso" : "xs"),
- false, false);
- genericTest(ZonedDateTime.class,
- (cfg, first) -> cfg.setDateTimeFormat(first ? "iso" : "xs"),
- false, true);
+ genericTest(LocalDateTime.class, false, false, "iso", "xs");
+ genericTest(ZonedDateTime.class, false, true, "iso", "xs");
}
@Test
public void testForDate() throws Exception {
// Locale dependent formatters:
- genericTest(LocalDate.class,
- (cfg, first) -> cfg.setDateFormat(first ? "yyyy-MM-dd" :
"yyyyMM-dd"),
- true, false);
+ genericTest(LocalDate.class, true, false, "yyyy-MM-dd", "yyyyMM-dd");
// Locale independent formatters:
- genericTest(LocalDate.class,
- (cfg, first) -> cfg.setDateFormat(first ? "iso" : "xs"),
- false, false);
+ genericTest(LocalDate.class, false, false, "iso", "xs");
}
@Test
public void testForTime() throws Exception {
// Locale dependent formatters:
- genericTest(LocalTime.class,
- (cfg, first) -> cfg.setTimeFormat(first ? "HH:mm" : "HHmm"),
- true, false);
- genericTest(OffsetTime.class,
- (cfg, first) -> cfg.setTimeFormat(first ? "HH:mm" : "HHmm"),
- true, true);
+ genericTest(LocalTime.class, true, false, "HH:mm", "HHmm");
+ genericTest(OffsetTime.class, true, true, "HH:mm", "HHmm");
// Locale independent formatters:
- genericTest(LocalTime.class,
- (cfg, first) -> cfg.setTimeFormat(first ? "iso" : "xs"),
- false, false);
- genericTest(OffsetTime.class,
- (cfg, first) -> cfg.setTimeFormat(first ? "iso" : "xs"),
- false, true);
+ genericTest(LocalTime.class, false, false, "iso", "xs");
+ genericTest(OffsetTime.class, false, true, "iso", "xs");
}
@Test
public void testForYearMonth() throws Exception {
// Locale dependent formatters:
- genericTest(YearMonth.class,
- (cfg, first) -> cfg.setYearMonthFormat(first ? "yyyy-MM" :
"yyyyMM"),
- true, false);
+ genericTest(YearMonth.class, true, false, "yyyy-MM", "yyyyMM");
// Locale independent formatters:
- genericTest(YearMonth.class,
- (cfg, first) -> cfg.setYearMonthFormat(first ? "iso" : "xs"),
- false, false);
+ genericTest(YearMonth.class, false, false, "iso", "xs");
}
@Test
public void testForYear() throws Exception {
// Locale dependent formatters:
- genericTest(Year.class,
- (cfg, first) -> cfg.setYearFormat(first ? "yyyy" : "yy"),
- true, false);
+ genericTest(Year.class, true, false, "yyyy", "yy");
// Locale independent formatters:
- genericTest(Year.class,
- (cfg, first) -> cfg.setYearFormat(first ? "iso" : "xs"),
- false, false);
+ genericTest(Year.class, false, false, "iso", "xs");
}
private void genericTest(
Class<? extends Temporal> temporalClass,
- SettingSetter settingSetter,
- boolean localeDependent, boolean timeZoneDependent)
- throws Exception {
+ boolean localeDependent, boolean timeZoneDependent,
+ String... settingValues) throws Exception {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
cfg.setLocale(Locale.GERMANY);
cfg.setTimeZone(DateUtil.UTC);
- settingSetter.setSetting(cfg, true);
+ SettingAssignments settingAssignments = new
SettingAssignments(temporalClass, settingValues);
+ settingAssignments.execute(cfg, 0);
Environment env = new Template(null, "", cfg)
.createProcessingEnvironment(null, NullWriter.INSTANCE);
@@ -193,37 +161,50 @@ public class
TemplateTemporalFormatCachingInEnvironmentTest {
TemplateTemporalFormat lastFormat;
TemplateTemporalFormat newFormat;
+ // Note: We call
env.clearCachedTemplateTemporalFormatsByFormatString() directly before all
+ // env.getTemplateTemporalFormat calls, just to avoid that 2nd level
of cache hiding any bugs in the level
+ // that we want to test here. But in almost all of these tests
scenarios it shouldn't have any effect anyway.
+
+ env.clearCachedTemplateTemporalFormatsByFormatString();
lastFormat = env.getTemplateTemporalFormat(temporalClass);
// Assert that it keeps returning the same instance from cache:
+ env.clearCachedTemplateTemporalFormatsByFormatString();
assertSame(lastFormat, env.getTemplateTemporalFormat(temporalClass));
+ env.clearCachedTemplateTemporalFormatsByFormatString();
assertSame(lastFormat, env.getTemplateTemporalFormat(temporalClass));
- settingSetter.setSetting(env, true);
+ settingAssignments.execute(env, 0);
// Assert that the cache wasn't cleared when the setting was set to
the same value again:
+ env.clearCachedTemplateTemporalFormatsByFormatString();
assertSame(lastFormat, env.getTemplateTemporalFormat(temporalClass));
env.setLocale(Locale.JAPAN); // Possibly clears non-reusable
TemplateTemporalFormatCache field
+ env.clearCachedTemplateTemporalFormatsByFormatString();
newFormat = env.getTemplateTemporalFormat(temporalClass);
if (localeDependent) {
assertNotSame(lastFormat, newFormat);
} else {
+ env.clearCachedTemplateTemporalFormatsByFormatString();
assertSame(lastFormat,
env.getTemplateTemporalFormat(temporalClass));
}
lastFormat = newFormat;
env.setLocale(Locale.JAPAN);
+ env.clearCachedTemplateTemporalFormatsByFormatString();
assertSame(lastFormat, env.getTemplateTemporalFormat(temporalClass));
env.setLocale(Locale.GERMANY); // Possibly clears non-reusable
TemplateTemporalFormatCache field
env.setLocale(Locale.JAPAN);
// Assert that it restores the same instance from
TemplateTemporalFormatCache.reusableXxx field:
+ env.clearCachedTemplateTemporalFormatsByFormatString();
assertSame(lastFormat, env.getTemplateTemporalFormat(temporalClass));
- TimeZone otherTimeZone = TimeZone.getTimeZone("GMT+01");
- env.setTimeZone(otherTimeZone); // Possibly clears non-reusable
TemplateTemporalFormatCache field
+ env.setTimeZone(OTHER_TIME_ZONE); // Possibly clears non-reusable
TemplateTemporalFormatCache field
+ env.clearCachedTemplateTemporalFormatsByFormatString();
newFormat = env.getTemplateTemporalFormat(temporalClass);
if (timeZoneDependent) {
assertNotSame(newFormat, lastFormat);
+ env.clearCachedTemplateTemporalFormatsByFormatString();
assertSame(newFormat,
env.getTemplateTemporalFormat(temporalClass));
} else {
assertSame(newFormat, lastFormat);
@@ -231,18 +212,20 @@ public class
TemplateTemporalFormatCachingInEnvironmentTest {
lastFormat = newFormat;
env.setTimeZone(DateUtil.UTC); // Possibly clears non-reusable
TemplateTemporalFormatCache field
- env.setTimeZone(otherTimeZone);
+ env.setTimeZone(OTHER_TIME_ZONE);
+ env.clearCachedTemplateTemporalFormatsByFormatString();
// Assert that it restores the same instance from
TemplateTemporalFormatCache.reusableXxx field:
assertSame(lastFormat, env.getTemplateTemporalFormat(temporalClass));
- settingSetter.setSetting(env, false); // Clears even
TemplateTemporalFormatCache.reusableXxx
+ settingAssignments.execute(env, 1); // Clears even
TemplateTemporalFormatCache.reusableXxx
+ env.clearCachedTemplateTemporalFormatsByFormatString();
newFormat = env.getTemplateTemporalFormat(temporalClass);
assertNotSame(lastFormat, newFormat);
- }
- @FunctionalInterface
- interface SettingSetter {
- void setSetting(Configurable configurable, boolean firstValue);
+ settingAssignments.execute(env, 0); // Clears even
TemplateTemporalFormatCache.reusableXxx
+ env.clearCachedTemplateTemporalFormatsByFormatString();
+ newFormat = env.getTemplateTemporalFormat(temporalClass);
+ assertNotSame(lastFormat, newFormat);
}
}