This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a commit to branch 2.3-gae
in repository https://gitbox.apache.org/repos/asf/freemarker.git


The following commit(s) were added to refs/heads/2.3-gae by this push:
     new ade44352 #switch #on PR post-merge adjustments: - Reorganized how 
#case/#on/#default is parsed in JavaCC, mostly to achieve better error messages 
(also I guess it's also easier to follow). - Reorganized SwitchBlock to branch 
out for the #switch+#case and the #switch+#on logic earlier (less checks on 
runtime, also maybe easier to follow). - Adjusted error message wording - Added 
much more test cases - Added some of this to the Manual, but much more will 
done there
ade44352 is described below

commit ade44352d64f3cd81a2271edb366df90ec126ce5
Author: ddekany <[email protected]>
AuthorDate: Sun Aug 18 18:11:02 2024 +0200

    #switch #on PR post-merge adjustments:
    - Reorganized how #case/#on/#default is parsed in JavaCC, mostly to achieve 
better error messages (also I guess it's also easier to follow).
    - Reorganized SwitchBlock to branch out for the #switch+#case and the 
#switch+#on logic earlier (less checks on runtime, also maybe easier to follow).
    - Adjusted error message wording
    - Added much more test cases
    - Added some of this to the Manual, but much more will done there
---
 .../src/main/java/freemarker/core/SwitchBlock.java |  78 +++++-----
 .../src/main/javacc/freemarker/core/FTL.jj         |  84 +++++-----
 .../core/BreakAndContinuePlacementTest.java        |  18 +--
 .../src/test/java/freemarker/core/SwitchTest.java  | 170 +++++++++++++++++++++
 .../test/templatesuite/templates/switch.ftl        |   2 +-
 freemarker-manual/src/main/docgen/en_US/book.xml   |  68 +++++++--
 6 files changed, 322 insertions(+), 98 deletions(-)

diff --git a/freemarker-core/src/main/java/freemarker/core/SwitchBlock.java 
b/freemarker-core/src/main/java/freemarker/core/SwitchBlock.java
index 5b6131c0..bdcad4d0 100644
--- a/freemarker-core/src/main/java/freemarker/core/SwitchBlock.java
+++ b/freemarker-core/src/main/java/freemarker/core/SwitchBlock.java
@@ -29,6 +29,7 @@ import freemarker.template.TemplateException;
 final class SwitchBlock extends TemplateElement {
 
     private Case defaultCase;
+    private boolean usesOnDirective;
     private final Expression searched;
     private int firstCaseOrOnIndex;
 
@@ -46,50 +47,52 @@ final class SwitchBlock extends TemplateElement {
         firstCaseOrOnIndex = ignoredCnt; // Note that normally 
postParseCleanup will overwrite this
     }
 
-    /**
-     * @param cas a Case element.
-     */
     void addCase(Case cas) {
         if (cas.condition == null) {
             defaultCase = cas;
+        } else if (usesOnDirective) {
+            throw new BugException("#on after #case"); //!!T
         }
         addChild(cas);
     }
 
-    /**
-     * @param on an On element.
-     */
     void addOn(On on) {
         addChild(on);
+        usesOnDirective = true;
+        if (defaultCase != null) {
+            throw new BugException("#on after #default"); //!!T
+        }
     }
 
     @Override
-    TemplateElement[] accept(Environment env)
-        throws TemplateException, IOException {
-        boolean processedCaseOrOn = false;
-        boolean usingOn = false;
+    TemplateElement[] accept(Environment env) throws TemplateException, 
IOException {
         int ln = getChildCount();
-        try {
-            for (int i = firstCaseOrOnIndex; i < ln; i++) {
+        if (usesOnDirective) {
+            processOnDirectives: for (int i = firstCaseOrOnIndex; i < ln; i++) 
{
                 TemplateElement tel = getChild(i);
 
-                if (tel instanceof On) {
-                    usingOn = true;
+                // "default" is always the last; the parser ensures this
+                if (tel == defaultCase) {
+                    env.visit(defaultCase);
+                    break;
+                }
 
-                    for (Expression condition : ((On) tel).conditions) {
-                        boolean processOn = EvalUtil.compare(
-                                searched,
-                                EvalUtil.CMP_OP_EQUALS, "on==", condition, 
condition, env);
-                        if (processOn) {
-                            env.visit(tel);
-                            processedCaseOrOn = true;
-                            break;
-                        }
-                    }
-                    if (processedCaseOrOn) {
-                        break;
+                for (Expression condition : ((On) tel).conditions) {
+                    boolean processOn = EvalUtil.compare(
+                            searched,
+                            EvalUtil.CMP_OP_EQUALS, "on==", condition, 
condition, env);
+                    if (processOn) {
+                        env.visit(tel);
+                        break processOnDirectives;
                     }
-                } else { // Case
+                }
+            }
+        } else { // case-s
+            try {
+                boolean processedCaseOrOn = false;
+                for (int i = firstCaseOrOnIndex; i < ln; i++) {
+                    TemplateElement tel = getChild(i);
+
                     Expression condition = ((Case) tel).condition;
                     boolean processCase = false;
 
@@ -107,19 +110,14 @@ final class SwitchBlock extends TemplateElement {
                         processedCaseOrOn = true;
                     }
                 }
-            }
-
-            // If we didn't process any nestedElements, and we have a default,
-            // process it.
-            if (!processedCaseOrOn && defaultCase != null) {
-                env.visit(defaultCase);
-            }
-        } catch (BreakOrContinueException br) {
-            // This catches both break and continue,
-            // hence continue is incorrectly treated as a break inside a case.
-            // Unless using On, do backwards compatible behavior.
-            if (usingOn) {
-                throw br; // On supports neither break nor continue.
+                // If we didn't process any nestedElements, and we have a 
default,
+                // process it.
+                if (!processedCaseOrOn && defaultCase != null) {
+                    env.visit(defaultCase);
+                }
+            } catch (BreakOrContinueException br) {
+                // This catches both break and continue, hence continue is 
incorrectly treated as a break inside a case.
+                // ("on", does have this bug.)
             }
         }
         return null;
diff --git a/freemarker-core/src/main/javacc/freemarker/core/FTL.jj 
b/freemarker-core/src/main/javacc/freemarker/core/FTL.jj
index 87256917..48bb1d6d 100644
--- a/freemarker-core/src/main/javacc/freemarker/core/FTL.jj
+++ b/freemarker-core/src/main/javacc/freemarker/core/FTL.jj
@@ -3883,11 +3883,13 @@ SwitchBlock Switch() :
 {
     SwitchBlock switchBlock;
     MixedContent ignoredSectionBeforeFirstCase = null;
-    Case caseIns;
-    On onIns;
+    Case caseOrDefault;
+    On on;
+    boolean hadCase = false;
+    boolean hadDefault = false;
+    boolean hadOn = false;
     Expression switchExp;
     Token start, end;
-    boolean defaultFound = false;
 }
 {
     (
@@ -3897,60 +3899,72 @@ SwitchBlock Switch() :
         [ ignoredSectionBeforeFirstCase = WhitespaceAndComments() ]
     )
     {
-        breakableDirectiveNesting++;
+        breakableDirectiveNesting++; // Note that this will be undone when we 
find an "on" directive call.
         switchBlock = new SwitchBlock(switchExp, 
ignoredSectionBeforeFirstCase);
     }
+
     [
         (
-            (
-                caseIns = Case()
-                {
-                    if (caseIns.condition == null) {
-                        if (defaultFound) {
+            caseOrDefault = Case()
+            {
+                if (caseOrDefault.condition == null) {
+                    if (hadDefault) {
+                        throw new ParseException(
+                                "You already had a #default in the #switch 
block",
+                                caseOrDefault);
+                    }
+                    hadDefault = true;
+                } else {
+                    if (hadOn) {
+                        if (caseOrDefault.condition != null) {
                             throw new ParseException(
-                            "You can only have one default case in a switch 
statement", template, start);
+                                    "You can't use both #on, and #case in a 
#switch block, and you already had an #on.",
+                                    caseOrDefault);
                         }
-                        defaultFound = true;
                     }
-                    switchBlock.addCase(caseIns);
+                    hadCase = true;
                 }
-            )+
+
+                switchBlock.addCase(caseOrDefault);
+            }
             |
             (
                 {
-                    // A Switch with Case supports break, but not one with On.
-                    // Do it this way to ensure backwards compatibility.
-                    breakableDirectiveNesting--;
+                    if (!hadOn) {
+                        // A #switch with #case handles #break specially, but 
not #switch with #on.
+                        // Also, this must be done before calling On(), as 
that consumes the nested #break already.
+                        breakableDirectiveNesting--;
+                    }
                 }
 
-                (
-                    onIns = On()
-                    {
-                        switchBlock.addOn(onIns);
+                on = On()
+                {
+                    if (hadCase) {
+                        throw new ParseException(
+                                "You can't use both #on, and #case in a 
#switch block, and you already had a #case.",
+                                on);
                     }
-                )+
-                [
-                    caseIns = Case()
-                    {
-                        // When using on, you can have a default, but not a 
normal case
-                        if (caseIns.condition != null) {
-                            throw new ParseException(
-                            "You cannot mix \"case\" and \"on\" in a switch 
statement", template, start);
-                        }
-                        switchBlock.addCase(caseIns);
+                    if (hadDefault) {
+                        throw new ParseException(
+                                "You can't use #on after #default in a #switch 
block; #default must come last.",
+                                on);
                     }
-                ]
+                    hadOn = true;
 
-                {
-                    breakableDirectiveNesting++;
+                    switchBlock.addOn(on);
                 }
             )
-        )
+        )+
         [<STATIC_TEXT_WS>]
     ]
+
     end = <END_SWITCH>
     {
-        breakableDirectiveNesting--;
+        // If we had #on, then this was already decreased there
+        if (!hadOn) {
+            breakableDirectiveNesting--;
+        }
+
         switchBlock.setLocation(template, start, end);
         return switchBlock;
     }
diff --git 
a/freemarker-core/src/test/java/freemarker/core/BreakAndContinuePlacementTest.java
 
b/freemarker-core/src/test/java/freemarker/core/BreakAndContinuePlacementTest.java
index 0246688f..769400cc 100644
--- 
a/freemarker-core/src/test/java/freemarker/core/BreakAndContinuePlacementTest.java
+++ 
b/freemarker-core/src/test/java/freemarker/core/BreakAndContinuePlacementTest.java
@@ -56,16 +56,16 @@ public class BreakAndContinuePlacementTest extends 
TemplateTest {
 
     @Test
     public void testInvalidPlacements() throws IOException, TemplateException {
-        assertErrorContains("<#break>", BREAK_NESTING_ERROR_MESSAGE_PART);
-        assertErrorContains("<#continue>", 
CONTINUE_NESTING_ERROR_MESSAGE_PART);
-        assertErrorContains("<#switch 1><#case 1>1<#continue></#switch>", 
CONTINUE_NESTING_ERROR_MESSAGE_PART);
-        assertErrorContains("<#switch 1><#on 1>1<#continue></#switch>", 
CONTINUE_NESTING_ERROR_MESSAGE_PART);
-        assertErrorContains("<#switch 1><#on 1>1<#break></#switch>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#break>", ParseException.class, 
BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#continue>", ParseException.class, 
CONTINUE_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#switch 1><#case 1>1<#continue></#switch>", 
ParseException.class, CONTINUE_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#switch 1><#on 1>1<#continue></#switch>", 
ParseException.class, CONTINUE_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#switch 1><#on 1>1<#break></#switch>", 
ParseException.class, BREAK_NESTING_ERROR_MESSAGE_PART);
         assertErrorContains("<#switch 1><#on 1>1<#default><#break></#switch>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
-        assertErrorContains("<#list 1..2 as x>${x}</#list><#break>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
-        assertErrorContains("<#if false><#break></#if>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
-        assertErrorContains("<#list xs><#break></#list>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
-        assertErrorContains("<#list 1..2 as x>${x}<#else><#break></#list>", 
BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#list 1..2 as x>${x}</#list><#break>", 
ParseException.class, BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#if false><#break></#if>", ParseException.class, 
BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#list xs><#break></#list>", 
ParseException.class, BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains("<#list 1..2 as x>${x}<#else><#break></#list>", 
ParseException.class, BREAK_NESTING_ERROR_MESSAGE_PART);
     }
 
     @Test
diff --git a/freemarker-core/src/test/java/freemarker/core/SwitchTest.java 
b/freemarker-core/src/test/java/freemarker/core/SwitchTest.java
new file mode 100644
index 00000000..adf72388
--- /dev/null
+++ b/freemarker-core/src/test/java/freemarker/core/SwitchTest.java
@@ -0,0 +1,170 @@
+/*
+ * 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 org.junit.Test;
+
+import freemarker.template.TemplateException;
+import freemarker.test.TemplateTest;
+
+/**
+ * There are more (older) test in "switch-builtin.ftl", but we prefer creating 
new ones in Java.
+ */
+public class SwitchTest extends TemplateTest {
+
+    @Test
+    public void testCaseBasics() throws TemplateException, IOException {
+        testCaseBasics(true);
+        testCaseBasics(false);
+    }
+
+    private void testCaseBasics(boolean hasDefault) throws TemplateException, 
IOException {
+        for (int i = 1 ; i <= 6; i++) {
+            assertOutput(
+                    "[<#switch " + i + ">\n"
+                            + "<#case 3>Case 3<#break>"
+                            + "<#case 1>Case 1<#break>"
+                            + "<#case 4><#case 5>Case 4 or 5<#break>"
+                            + "<#case 2>Case 2<#break>"
+                            + (hasDefault ? "<#default>D" : "")
+                            + "</#switch>]",
+                    i < 6
+                            ? "[Case " + (i < 4 ? i : "4 or 5") + "]"
+                            : (hasDefault ? "[D]" : "[]"));
+        }
+    }
+
+    /** Tolerated for backward compatibility */
+    @Test
+    public void testCasesWithOddlyPlacedDefault() throws TemplateException, 
IOException {
+        assertOutput("<#list 1..3 as i><#switch i><#case 1>1<#default>D<#case 
3>3</#switch>;</#list>", "1D3;D;3;");
+    }
+
+    @Test
+    public void testDefaultOnly() throws TemplateException, IOException {
+        assertOutput("<#switch 1><#default>D</#switch>", "D");
+        assertOutput("<#list 1..2 as i><#switch 
1><#default>D<#break>unreachable</#switch></#list>", "DD");
+    }
+
+    @Test
+    public void testCaseWhitespace() throws TemplateException, IOException {
+        assertOutput(
+                ""
+                        + "<#list 1..3 as i>\n"
+                        + "[\n"  // <#----> is to avoid unrelated old 
white-space stripping bug
+                        + "  <#switch i>\n"
+                        + "    <#case 1>C1\n"
+                        + "    <#case 2>C2<#break>\n"
+                        + "    <#default>D\n"
+                        + "  </#switch>\n"
+                        + "]\n"
+                        + "</#list>",
+                "[\nC1\n    C2]\n[\nC2]\n[\nD\n]\n");
+    }
+
+    @Test
+    public void testOn() throws TemplateException, IOException {
+        testOnBasics(true);
+        testOnBasics(false);
+    }
+
+    private void testOnBasics(boolean hasDefault) throws TemplateException, 
IOException {
+        for (int i = 1 ; i <= 6; i++) {
+            assertOutput(
+                    "[<#switch " + i + ">\n"
+                            + "<#on 3>On 3"
+                            + "<#on 1>On 1"
+                            + "<#on 4, 5>On 4 or 5"
+                            + "<#on 2>On 2"
+                            + (hasDefault ? "<#default>D" : "")
+                            + "</#switch>]",
+                    i < 6
+                            ? "[On " + (i < 4 ? i : "4 or 5") + "]"
+                            : (hasDefault ? "[D]" : "[]"));
+        }
+    }
+
+    @Test
+    public void testOnParsingErrors() throws TemplateException, IOException {
+        assertErrorContains(
+                "<#switch x><#on 1>On 1<#default>D<#on 2>On 2</#switch>",
+                ParseException.class,
+                "#on after #default");
+        assertErrorContains(
+                "<#switch x><#on 1>On 1<#case 2>On 2</#switch>",
+                ParseException.class,
+                "can't use both #on, and #case", "already had an #on");
+        assertErrorContains(
+                "<#switch x><#case 1>On 1<#on 2>On 2</#switch>",
+                ParseException.class,
+                "can't use both #on, and #case", "already had a #case");
+        assertErrorContains(
+                "<#switch x><#on 1>On 1<#default>D<#case 2>On 2</#switch>",
+                ParseException.class,
+                "can't use both #on, and #case", "already had an #on");
+        assertErrorContains(
+                "<#switch x><#on 1>On 1<#default>D1<#default>D2</#switch>",
+                ParseException.class,
+                "already had a #default");
+        assertErrorContains(
+                "<#switch x><#case 1>On 1<#default>D1<#default>D2</#switch>",
+                ParseException.class,
+                "already had a #default");
+        assertErrorContains(
+                "<#switch x><#on 1>On 1<#default>D<#on 2>On 2</#switch>",
+                ParseException.class,
+                "#on after #default");
+        assertErrorContains(
+                "<#switch x><#default>D<#on 2>On 2</#switch>",
+                ParseException.class,
+                "#on after #default");
+    }
+
+    @Test
+    public void testOnWhitespace() throws TemplateException, IOException {
+        assertOutput(
+                ""
+                        + "<#list 1..3 as i>\n"
+                        + "[\n"  // <#----> is to avoid unrelated old 
white-space stripping bug
+                        + "  <#switch i>\n"
+                        + "    <#on 1>C1\n"
+                        + "    <#on 2>C2\n"
+                        + "    <#default>D\n"
+                        + "  </#switch>\n"
+                        + "]\n"
+                        + "</#list>",
+                "[\nC1\n    ]\n[\nC2\n    ]\n[\nD\n]\n");
+        assertOutput(
+                ""
+                        + "<#list 1..3 as i>\n"
+                        + "[\n"  // <#----> is to avoid unrelated old 
white-space stripping bug
+                        + "  <#switch i>\n"
+                        + "    <#on 1>C1<#t>\n"
+                        + "    <#on 2>C2<#t>\n"
+                        + "    <#default>D<#t>\n"
+                        + "  </#switch>\n"
+                        + "]\n"
+                        + "</#list>",
+                "[\nC1]\n[\nC2]\n[\nD]\n");
+    }
+
+}
diff --git 
a/freemarker-jython25/src/test/resources/freemarker/test/templatesuite/templates/switch.ftl
 
b/freemarker-jython25/src/test/resources/freemarker/test/templatesuite/templates/switch.ftl
index e9bd3775..f51f44a5 100644
--- 
a/freemarker-jython25/src/test/resources/freemarker/test/templatesuite/templates/switch.ftl
+++ 
b/freemarker-jython25/src/test/resources/freemarker/test/templatesuite/templates/switch.ftl
@@ -136,7 +136,7 @@
 </#list>
 
 <#-- two #default-s are parsing error -->
-<@assertFails message="can only have one default"><@"<#switch 1><#case 
1><#default><#default></#switch>"?interpret /></@>
+<@assertFails message="already had a #default"><@"<#switch 1><#case 
1><#default><#default></#switch>"?interpret /></@>
 
 </body>
 </html>
diff --git a/freemarker-manual/src/main/docgen/en_US/book.xml 
b/freemarker-manual/src/main/docgen/en_US/book.xml
index 3c80856c..12a032f0 100644
--- a/freemarker-manual/src/main/docgen/en_US/book.xml
+++ b/freemarker-manual/src/main/docgen/en_US/book.xml
@@ -25210,13 +25210,32 @@ or
         <section>
           <title>Synopsis</title>
 
-          <programlisting role="metaTemplate">
-<literal>&lt;#switch <replaceable>value</replaceable>&gt;
-  &lt;#case <replaceable>refValue1</replaceable>&gt;
+          <para>Recommended form (<literal>on</literal>-based), but this is
+          <emphasis role="bold">only supported since FreeMarker
+          2.3.34</emphasis>:</para>
+
+          <programlisting role="metaTemplate"><literal>&lt;#switch 
<replaceable>value</replaceable>&gt;
+  &lt;#on <replaceable>refValue1</replaceable>&gt;
+    <replaceable>... (Handles both refValue1)</replaceable>
+  &lt;#on <replaceable>refValue2, refValue3</replaceable>&gt;
+    <replaceable>... (Handles both refValue2 and refValue3)</replaceable>)
+  <replaceable>...</replaceable>
+  &lt;#case <replaceable>refValueN</replaceable>&gt;
     <replaceable>...</replaceable>
     &lt;#break&gt;
-  &lt;#case <replaceable>refValue2</replaceable>&gt;
+  &lt;#default&gt;
     <replaceable>...</replaceable>
+&lt;/#switch&gt;</literal></programlisting>
+
+          <para>Deprecated legacy form (<literal>case</literal>-based):</para>
+
+          <programlisting role="metaTemplate"><literal>&lt;#switch 
<replaceable>value</replaceable>&gt;
+  &lt;#case <replaceable>refValue1</replaceable>&gt;
+    <replaceable>... (Handles both refValue1)</replaceable>
+    &lt;#break&gt;
+  &lt;#case <replaceable>refValue2</replaceable>&gt;
+  &lt;#case <replaceable>refValue3</replaceable>&gt;
+    <replaceable>... (Handles both refValue2 and refValue3)</replaceable>
     &lt;#break&gt;
   <replaceable>...</replaceable>
   &lt;#case <replaceable>refValueN</replaceable>&gt;
@@ -25224,9 +25243,7 @@ or
     &lt;#break&gt;
   &lt;#default&gt;
     <replaceable>...</replaceable>
-&lt;/#switch&gt;
-</literal>
-</programlisting>
+&lt;/#switch&gt;</literal></programlisting>
 
           <para>Where:</para>
 
@@ -25238,17 +25255,29 @@ or
             </listitem>
           </itemizedlist>
 
-          <para>The <literal>break</literal>-s and <literal>default</literal>
-          are optional.</para>
+          <para><literal>default</literal> is optional.
+          <literal>break</literal> is used with <literal>case</literal> to
+          prevent fall-through into the next <literal>case</literal>. When
+          <literal>on</literal> is used, <literal>break</literal> is not
+          allowed, except if the <literal>switch</literal> is inside something
+          that supports <literal>break</literal>.</para>
         </section>
 
         <section>
           <title>Description</title>
 
-          <para>The usage of this directive is not recommended, as it's
-          error-prone because of the fall-through behavior. Use <link
-          linkend="ref.directive.elseif"><literal>elseif</literal></link>-s
-          instead unless you want to exploit the fall-through behavior.</para>
+          <note>
+            <para>Using this directive with <literal>case</literal> is not
+            recommended, as it's error-prone because of the fall-through
+            behavior. Starting from FreeMarker 2.3.34, use
+            <literal>on</literal> instead of <literal>case</literal>. In
+            earlier versions use <link
+            linkend="ref.directive.elseif"><literal>elseif</literal></link>-s
+            instead. </para>
+          </note>
+
+          <para>[TODO: Continue updating this for <literal>on</literal>
+          instead of <literal>case</literal>]</para>
 
           <para>Switch is used to choose a fragment of template depending on
           the value of an expression:</para>
@@ -30349,6 +30378,19 @@ TemplateModel x = env.getVariable("x");  // get 
variable x</programlisting>
           <title>Changes on the FTL side</title>
 
           <itemizedlist>
+            <listitem>
+              <para>Added <literal>#on</literal> directive that's now
+              recommended instead of <literal>#case</literal> inside
+              <literal>#switch</literal>. It has no fall-through, so you need
+              not, and must not use <literal>#break</literal> like you did
+              with <literal>#case</literal>. Also, <literal>#on</literal> can
+              have a list of matching values, so you can use <literal>&lt;#on
+              1, 2, 3&gt;Was 1-3</literal> instead of <literal>&lt;#case
+              1&gt;&lt;#case 2&gt;&lt;#case 3&gt;Was
+              1-3&lt;#break&gt;</literal> See more <link
+              linkend="ref.directive.switch">here...</link></para>
+            </listitem>
+
             <listitem>
               <para>Added new built-ins that allow handling empty or blank
               strings like the same way as if they were missing values (Java

Reply via email to