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: &lt;#global
-     * ...&gt; 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>&lt;#global <i>name</i>=<i>model</i>&gt;</code>. This can be considered a convenient shorthand for:
-     * getGlobalNamespace().put(name, model)
-     */
-    public void setGlobalVariable(String name, TemplateModel model) {
-        globalNamespace.put(name, model);
-    }
-
-    /**
-     * Sets a variable in the current namespace. This is correspondent to FTL
-     * <code>&lt;#assign <i>name</i>=<i>model</i>&gt;</code>. This can be considered a convenient shorthand for:
-     * getCurrentNamespace().put(name, model)
-     */
-    public void setVariable(String name, TemplateModel model) {
-        currentNamespace.put(name, model);
-    }
-
-    /**
-     * Sets a local variable (one effective only during a macro invocation). This is correspondent to FTL
-     * <code>&lt;#local <i>name</i>=<i>model</i>&gt;</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)" />