Merge remote-tracking branch 'origin/2.3-gae' into 2.3
diff --git a/build.xml b/build.xml
index 5e8ccc5..4b68750 100644
--- a/build.xml
+++ b/build.xml
@@ -421,7 +421,7 @@
<target name="test" depends="compileTest" description="Run test cases">
<mkdir dir="build/junit-reports" />
<ivy:cachepath conf="run.test" pathid="ivy.dep.run.test" />
- <junit haltonfailure="off" logfailedtests="true" fork="true" forkmode="once">
+ <junit haltonfailure="on" logfailedtests="true" fork="true" forkmode="once">
<classpath>
<pathelement path="build/test-classes" />
<pathelement path="build/classes" />
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index cd4f783..13bde79 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -2224,9 +2224,9 @@
}
/**
- * Returns the loop or macro local variable corresponding to this variable name. Possibly null. (Note that the
- * misnomer is kept for backward compatibility: loop variables are not local variables according to our
- * terminology.)
+ * Returns the loop or macro local variable corresponding to this variable name.
+ * 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);
@@ -2284,9 +2284,9 @@
}
/**
- * Returns the globally visible variable of the given name (or null). This is corresponds to FTL
- * <code>.globals.<i>name</i></code>. This will first look at variables that were assigned globally via: <#global
- * ...> and then at the data model exposed to the template, and then at the
+ * Returns the globally visible variable of the given name, or {@code null}. This corresponds to FTL
+ * <code>.globals.<i>name</i></code>. This will first look at variables that were assigned globally via:
+ * {@code <#global ...>} and then at the data model exposed to the template, and then at the
* {@linkplain Configuration#setSharedVariables(Map)} shared variables} in the {@link Configuration}.
*/
public TemplateModel getGlobalVariable(String name) throws TemplateModelException {
@@ -2314,39 +2314,56 @@
}
/**
- * Sets a variable that is visible globally. This is correspondent to FTL
- * <code><#global <i>name</i>=<i>model</i>></code>. This can be considered a convenient shorthand for:
- * getGlobalNamespace().put(name, model)
- */
- public void setGlobalVariable(String name, TemplateModel model) {
- globalNamespace.put(name, model);
- }
-
- /**
- * Sets a variable in the current namespace. This is correspondent to FTL
- * <code><#assign <i>name</i>=<i>model</i>></code>. This can be considered a convenient shorthand for:
- * getCurrentNamespace().put(name, model)
- */
- public void setVariable(String name, TemplateModel model) {
- currentNamespace.put(name, model);
- }
-
- /**
- * Sets a local variable (one effective only during a macro invocation). This is correspondent to FTL
- * <code><#local <i>name</i>=<i>model</i>></code>.
- *
+ * 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
+ * if the variable is not in the global namespace.
+ *
* @param name
- * the identifier of the variable
- * @param model
- * the value 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).
+ */
+ 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)}.
+ *
+ * @param name
+ * 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).
+ */
+ public void setVariable(String name, TemplateModel value) {
+ currentNamespace.put(name, value);
+ }
+
+ /**
+ * 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.
+ *
+ * @param name
+ * 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).
* @throws IllegalStateException
* if the environment is not executing a macro body.
*/
- public void setLocalVariable(String name, TemplateModel model) {
+ public void setLocalVariable(String name, TemplateModel value) {
if (currentMacroContext == null) {
throw new IllegalStateException("Not executing macro body");
}
- currentMacroContext.setLocalVar(name, model);
+ currentMacroContext.setLocalVar(name, value);
}
/**
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
index 323275e..7be7ee4 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
@@ -76,6 +76,13 @@
*/
protected BeansWrapperConfiguration(Version incompatibleImprovements, boolean isIncompImprsAlreadyNormalized) {
_TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
+
+ // We can't do this in the BeansWrapper constructor, as by that time the version is normalized.
+ if (!isIncompImprsAlreadyNormalized) {
+ _TemplateAPI.checkCurrentVersionNotRecycled(
+ incompatibleImprovements,
+ "freemarker.beans", "BeansWrapper");
+ }
incompatibleImprovements = isIncompImprsAlreadyNormalized
? incompatibleImprovements
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index db736df..93139ae 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -609,9 +609,10 @@
*
* <p>Bugfixes and improvements that are fully backward compatible, also those that are important security fixes,
* are enabled regardless of the incompatible improvements setting.
- *
+ *
* <p>Do NOT ever use {@link #getVersion()} to set the "incompatible improvements". Always use a fixed value, like
- * {@link #VERSION_2_3_28}. Otherwise your application can break as you upgrade FreeMarker.
+ * {@link #VERSION_2_3_30}. Otherwise your application can break as you upgrade FreeMarker. (As of 2.3.30, doing
+ * this will be logged as an error. As of 2.4.0, it will be probably disallowed, by throwing exception.)
*
* <p>An important consequence of setting this setting is that now your application will check if the stated minimum
* FreeMarker version requirement is met. Like if you set this setting to 2.3.22, but accidentally the application
@@ -944,6 +945,7 @@
checkFreeMarkerVersionClash();
NullArgumentException.check("incompatibleImprovements", incompatibleImprovements);
+ checkCurrentVersionNotRecycled(incompatibleImprovements);
this.incompatibleImprovements = incompatibleImprovements;
createTemplateCache();
@@ -1928,7 +1930,8 @@
* Use {@link #Configuration(Version)} instead if possible; see the meaning of the parameter there.
*
* <p>Do NOT ever use {@link #getVersion()} to set the "incompatible improvements". Always use a fixed value, like
- * {@link #VERSION_2_3_28}. Otherwise your application can break as you upgrade FreeMarker.
+ * {@link #VERSION_2_3_30}. Otherwise your application can break as you upgrade FreeMarker. (As of 2.3.30, doing
+ * this will be logged as an error. As of 2.4.0, it will be probably disallowed, by throwing exception.)
*
* <p>If the default value of a setting depends on the {@code incompatibleImprovements} and the value of that setting
* was never set in this {@link Configuration} object through the public API, its value will be set to the default
@@ -1947,6 +1950,8 @@
_TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
if (!this.incompatibleImprovements.equals(incompatibleImprovements)) {
+ checkCurrentVersionNotRecycled(incompatibleImprovements);
+
this.incompatibleImprovements = incompatibleImprovements;
if (!templateLoaderExplicitlySet) {
@@ -1998,6 +2003,12 @@
}
}
+ private static void checkCurrentVersionNotRecycled(Version incompatibleImprovements) {
+ _TemplateAPI.checkCurrentVersionNotRecycled(
+ incompatibleImprovements,
+ "freemarker.configuration", "Configuration");
+ }
+
/**
* @see #setIncompatibleImprovements(Version)
* @return Never {@code null}.
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java b/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
index 81b4836..ff474fa 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapperConfiguration.java
@@ -38,6 +38,9 @@
protected DefaultObjectWrapperConfiguration(Version incompatibleImprovements) {
super(DefaultObjectWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements), true);
+ _TemplateAPI.checkCurrentVersionNotRecycled(
+ incompatibleImprovements,
+ "freemarker.configuration", "DefaultObjectWrapper");
useAdaptersForContainers = getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_22;
forceLegacyNonListCollections = true; // [2.4]: = IcI < _TemplateAPI.VERSION_INT_2_4_0;
}
diff --git a/src/main/java/freemarker/template/_TemplateAPI.java b/src/main/java/freemarker/template/_TemplateAPI.java
index 8d1683f..18c16e4 100644
--- a/src/main/java/freemarker/template/_TemplateAPI.java
+++ b/src/main/java/freemarker/template/_TemplateAPI.java
@@ -30,6 +30,7 @@
import freemarker.core.Expression;
import freemarker.core.OutputFormat;
import freemarker.core.TemplateObject;
+import freemarker.log.Logger;
import freemarker.template.utility.NullArgumentException;
/**
@@ -94,7 +95,26 @@
throw new IllegalArgumentException("\"incompatibleImprovements\" must be at least 2.3.0.");
}
}
-
+
+ /**
+ * Checks if the object return by {@link Configuration#getVersion()} was used for setting
+ * "incompatibleImprovements", which shouldn't be done.
+ *
+ * @since 2.3.30
+ */
+ public static void checkCurrentVersionNotRecycled(
+ Version incompatibleImprovements,
+ String logCategory, String configuredClassShortName) {
+ if (incompatibleImprovements == Configuration.getVersion()) {
+ Logger.getLogger(logCategory)
+ .error(configuredClassShortName + ".incompatibleImprovements was set to the object returned by " +
+ "Configuration.getVersion(). That defeats the purpose of incompatibleImprovements, " +
+ "and makes upgrading FreeMarker a potentially breaking change. Also, this " +
+ "probably won't be allowed starting from 2.4.0. Instead, set incompatibleImprovements to " +
+ "the highest concrete version that's known to be compatible with your application.");
+ }
+ }
+
public static int getTemplateLanguageVersionAsInt(TemplateObject to) {
return getTemplateLanguageVersionAsInt(to.getTemplate());
}
diff --git a/src/main/resources/freemarker/version.properties b/src/main/resources/freemarker/version.properties
index c85be5a..641b6a6 100644
--- a/src/main/resources/freemarker/version.properties
+++ b/src/main/resources/freemarker/version.properties
@@ -56,11 +56,11 @@
# continue working without modification or recompilation.
# - When the major version number is increased, major backward
# compatibility violations are allowed, but still should be avoided.
-version=2.3.30-nightly_@timestampInVersion@
+version=2.3.30
# This exists as for Maven we use "-SNAPSHOT" for nightly releases,
# and no _nightly_@timestampInVersion@. For final releases it's the
# same as "version".
-mavenVersion=2.3.30-SNAPSHOT
+mavenVersion=2.3.30
# Version string that conforms to OSGi
# ------------------------------------
@@ -73,7 +73,7 @@
# 2.4.0.rc01
# 2.4.0.pre01
# 2.4.0.nightly_@timestampInVersion@
-versionForOSGi=2.3.30.nightly
+versionForOSGi=2.3.30.stable
# Version string that conforms to legacy MF
# -----------------------------------------
@@ -92,7 +92,7 @@
# "97 denotes "nightly", 98 denotes "pre", 99 denotes "rc" build.
# In general, for the nightly/preview/rc Y of version 2.X, the versionForMf is
# 2.X-1.(99|98).Y. Note the X-1.
-versionForMf=2.3.29.97
+versionForMf=2.3.30
# The date of the build.
# This should be automatically filled by the building tool (Ant).
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 5d4d545..89e0fb3 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -9260,9 +9260,10 @@
<para>The same configuring is also doable if you don't have access
to the configuring Java code, but only to a Java
- <literal>*.properties</literal> file or other kind of string-string
+ <literal>*.properties</literal> file, or other kind of string-string
key value pairs (the <literal>\</literal>-s are prescribed by the
- Java Properties file format for multi-line values):</para>
+ Java Properties file format for multi-line values, so omit them
+ elsewhere):</para>
<programlisting role="unspecified">templateConfigurations = \
ConditionalTemplateConfigurationFactory( \
@@ -9291,7 +9292,13 @@
<literal>Configuration</literal> singleton, you set this up like
this:</para>
- <programlisting role="unspecified">cfg.setTemplateConfigurations(
+ <programlisting role="unspecified">TemplateConfiguration tcSubject = new TemplateConfiguration();
+tcSubject.setOutputFormat(PlainTextOutputFormat.INSTANCE);
+
+TemplateConfiguration tcBody = new TemplateConfiguration();
+tcBody.setOutputFormat(HTMLOutputFormat.INSTANCE);
+
+cfg.setTemplateConfigurations(
new ConditionalTemplateConfigurationFactory(
new PathGlobMatcher("mail/**"),
new FirstMatchTemplateConfigurationFactory(
@@ -9309,7 +9316,7 @@
<para>The equivalent configuration using a Java
<literal>*.properties</literal> file or other kind of string-string
key value pairs (the <literal>\</literal>-s are prescribed by the
- Java Properties file format only):</para>
+ Java Properties file format only, so omit them elsewhere):</para>
<programlisting role="unspecified">templateConfigurations = \
ConditionalTemplateConfigurationFactory( \
@@ -9359,9 +9366,9 @@
<para>Here we have 3 independent concerns, and possibly multiple (or
none) of those apply to a template; that's when you need a
- <literal>MergingTemplateConfigurationFactory</literal>. The last
- point describes a rule where you have mutually exclusive choices;
- that's when you need a
+ <literal>MergingTemplateConfigurationFactory</literal>. In file
+ extension related rule above you have mutually exclusive choices, so
+ you need a
<literal>FirstMatchTemplateConfigurationFactory</literal>, but this
time no choice is also allowed. Here's the source code, assuming
<literal>cfg</literal> stores the shared
@@ -11389,6 +11396,13 @@
<td>Logs messages of the FreeMarker JSP support.</td>
</tr>
+
+ <tr>
+ <td><literal>freemarker.configuration</literal></td>
+
+ <td>Logs messages related to configuring FreeMarker, and not
+ fitting any other categories.</td>
+ </tr>
</tbody>
</informaltable>
@@ -29156,7 +29170,7 @@
<section xml:id="versions_2_3_30">
<title>2.3.30</title>
- <para>Release date: [FIXME]</para>
+ <para>Release date: 2020-02-16 + release process</para>
<para>Please note that with this version the minimum required Java
version was increased from Java 5 to Java 7.</para>
@@ -29248,7 +29262,9 @@
property to <literal>BeansWrapper</literal>, and subclasses like
<literal>DefaultObjectWrapper</literal>. This allows users to
implement their own program logic to decide what members of
- classes will be exposed to the templates.</para>
+ classes will be exposed to the templates. See See the <olink
+ targetdoc="api">FreeMarker Java API documentation</olink> for
+ more details.</para>
</listitem>
<listitem>
@@ -29288,6 +29304,28 @@
</listitem>
<listitem>
+ <para>Setting <literal>incompatibleImprovements</literal> to the
+ instance returned by
+ <literal>Configuration.getVersion()</literal> will now be logged
+ as an error, but for backward compatibility it will still work.
+ This applies to said setting of
+ <literal>Configuration</literal>,
+ <literal>DefaultObjectWrapper</literal>, and
+ <literal>BeansWrapper</literal>. The typical bad pattern is
+ this: <literal>new
+ Configuration(Configuration.getVersion())</literal>. Doing that
+ defeats the purpose of
+ <literal>incompatibleImprovements</literal>, and makes upgrading
+ FreeMarker a potentially breaking change. Furthermore, doing
+ this probably won't be allowed starting from 2.4.0, and will
+ throw exception. So if above mistake is present in your
+ application, it should be fixed by setting
+ <literal>incompatibleImprovements</literal> to the highest
+ concrete version that's known to be compatible with the
+ application.</para>
+ </listitem>
+
+ <listitem>
<para>Added
<literal>Environment.getDataModelOrSharedVariable(String)</literal>.</para>
</listitem>
diff --git a/src/test/java/freemarker/manual/ExamplesTest.java b/src/test/java/freemarker/manual/ExamplesTest.java
index 7478d74..5d3a932 100644
--- a/src/test/java/freemarker/manual/ExamplesTest.java
+++ b/src/test/java/freemarker/manual/ExamplesTest.java
@@ -29,6 +29,7 @@
import freemarker.cache.StringTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
+import freemarker.template.Version;
import freemarker.test.TemplateTest;
@Ignore
@@ -44,7 +45,7 @@
@Override
protected final Configuration createConfiguration() {
- Configuration cfg = new Configuration(Configuration.getVersion());
+ Configuration cfg = new Configuration(new Version(Configuration.getVersion().toString()));
setupTemplateLoaders(cfg);
return cfg;
}
diff --git a/src/test/resources/freemarker/core/ast-lambda.ast b/src/test/resources/freemarker/core/ast-lambda.ast
index 2b02f53..4fd33c3 100644
--- a/src/test/resources/freemarker/core/ast-lambda.ast
+++ b/src/test/resources/freemarker/core/ast-lambda.ast
@@ -1,3 +1,21 @@
+/*
+ * 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.
+ */
#mixed_content // f.c.MixedContent
#text // f.c.TextBlock
- content: "1 " // String
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-bwici-2.3.20.ftl b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-bwici-2.3.20.ftl
index 9142bbb..835e64c 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-bwici-2.3.20.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/overloaded-methods-2-bwici-2.3.20.ftl
@@ -97,7 +97,7 @@
<@assertEquals actual=obj.mDecimalLoss(1.5) expected="mDecimalLoss(int a1 = 1)" /><#-- Yes, buggy... -->
<@assertEquals actual=obj.mDecimalLoss(1.5?double) expected="mDecimalLoss(double a1 = 1.5)" />
-<#-- BigDecimal conversions chose the smallest target type before IcI 2.3.31, increasing the risk of overflows: -->
+<#-- BigDecimal conversions chose the smallest target type before IcI 2.3.21, increasing the risk of overflows: -->
<@assertEquals actual=obj.nIntAndLong(1) expected="nIntAndLong(int 1)" />
<@assertEquals actual=obj.nIntAndLong(1?long) expected="nIntAndLong(long 1)" />
<@assertEquals actual=obj.nIntAndShort(1) expected="nIntAndShort(short 1)" />