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 7d9f5e57 #switch #on PR post-merge adjustments:
7d9f5e57 is described below

commit 7d9f5e57567d32f755e1e4767d7b922e4ce98169
Author: ddekany <[email protected]>
AuthorDate: Wed Aug 28 19:43:17 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
    - Manual updates for #on, and added version history entry
---
 .../src/main/java/freemarker/core/SwitchBlock.java |  73 ++++---
 .../src/main/javacc/freemarker/core/FTL.jj         |  84 ++++----
 .../core/BreakAndContinuePlacementTest.java        |  22 +--
 .../src/test/java/freemarker/core/SwitchTest.java  | 185 ++++++++++++++++++
 .../test/templatesuite/templates/switch.ftl        |   2 +-
 freemarker-manual/src/main/docgen/en_US/book.xml   | 212 +++++++++++++++++----
 6 files changed, 449 insertions(+), 129 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..6c5641b8 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,9 +47,6 @@ 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;
@@ -56,40 +54,40 @@ final class SwitchBlock extends TemplateElement {
         addChild(cas);
     }
 
-    /**
-     * @param on an On element.
-     */
     void addOn(On on) {
         addChild(on);
+        usesOnDirective = true;
     }
 
     @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 +105,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", doesn't 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..aad6ede4 100644
--- 
a/freemarker-core/src/test/java/freemarker/core/BreakAndContinuePlacementTest.java
+++ 
b/freemarker-core/src/test/java/freemarker/core/BreakAndContinuePlacementTest.java
@@ -50,22 +50,22 @@ public class BreakAndContinuePlacementTest extends 
TemplateTest {
         assertOutput("<#list 1..2 as x><#switch x><#on 
1>one<#continue></#switch>;</#list>", "one;");
         assertOutput("<#forEach x in 1..2>${x}<#break></#forEach>", "1");
         assertOutput("<#forEach x in 1..2>${x}<#continue></#forEach>", "12");
-        assertOutput("<#switch 1><#case 1>1<#break></#switch>", "1");
-        assertOutput("<#switch 1><#default>1<#break></#switch>", "1");
+        assertOutput("<#switch 1><#case 1>1<#break>unreachable</#switch>.", 
"1.");
+        assertOutput("<#switch 1><#default>1<#break>unreachable</#switch>.", 
"1.");
     }
 
     @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..c4a33787
--- /dev/null
+++ b/freemarker-core/src/test/java/freemarker/core/SwitchTest.java
@@ -0,0 +1,185 @@
+/*
+ * 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");
+        assertOutput(
+                ""
+                        + "<#list 1..3 as i>\n"
+                        + "[\n"  // <#----> is to avoid unrelated old 
white-space stripping bug
+                        + "  <#switch i>\n"
+                        + "    <#on 1>\n"
+                        + "      C1\n"
+                        + "    <#on 2>\n"
+                        + "      C2\n"
+                        + "    <#default>\n"
+                        + "      D\n"
+                        + "  </#switch>\n"
+                        + "]\n"
+                        + "</#list>",
+                "[\n      C1\n]\n[\n      C2\n]\n[\n      D\n]\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 46936674..f999f39d 100644
--- a/freemarker-manual/src/main/docgen/en_US/book.xml
+++ b/freemarker-manual/src/main/docgen/en_US/book.xml
@@ -21199,6 +21199,10 @@ with_args_last:
             <para><link linkend="ref.directive.nt">nt</link></para>
           </listitem>
 
+          <listitem>
+            <para><link linkend="ref.directive.on">on</link></para>
+          </listitem>
+
           <listitem>
             <para><link
             linkend="ref_directive_outputformat">outputformat</link></para>
@@ -25181,12 +25185,14 @@ or
       </section>
 
       <section xml:id="ref_directive_switch">
-        <title>switch, case, default, break</title>
+        <title>switch, on, case, default, break</title>
 
         <anchor xml:id="ref.directive.switch"/>
 
         <anchor xml:id="ref.directive.case"/>
 
+        <anchor xml:id="ref.directive.on"/>
+
         <anchor xml:id="ref.directive.default"/>
 
         <anchor xml:id="ref.directive.switch.break"/>
@@ -25199,6 +25205,10 @@ or
           <primary>case directive</primary>
         </indexterm>
 
+        <indexterm>
+          <primary>on directive</primary>
+        </indexterm>
+
         <indexterm>
           <primary>default directive</primary>
         </indexterm>
@@ -25210,13 +25220,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 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 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 +25253,7 @@ or
     &lt;#break&gt;
   &lt;#default&gt;
     <replaceable>...</replaceable>
-&lt;/#switch&gt;
-</literal>
-</programlisting>
+&lt;/#switch&gt;</literal></programlisting>
 
           <para>Where:</para>
 
@@ -25238,54 +25265,140 @@ or
             </listitem>
           </itemizedlist>
 
-          <para>The <literal>break</literal>-s and <literal>default</literal>
-          are optional.</para>
+          <para>Additional rules:</para>
+
+          <itemizedlist>
+            <listitem>
+              <para>A <literal>switch</literal> block either have
+              <literal>on</literal>-s, or <literal>case</literal>-s, but not
+              both.</para>
+            </listitem>
+
+            <listitem>
+              <para><literal>default</literal>: Optional, can occur at most
+              once. Must be after the <literal>on</literal>-s. There's no
+              ordering restriction when used with
+              <literal>case</literal>-s.</para>
+            </listitem>
+
+            <listitem>
+              <para><literal>break</literal>: </para>
+
+              <itemizedlist>
+                <listitem>
+                  <para>If the <literal>switch</literal> uses
+                  <literal>case</literal> then <literal>break</literal>
+                  immediately exits from the closes enclosing
+                  <literal>switch</literal>. This is used to prevent
+                  fall-through into the next <literal>case</literal>, or into
+                  the following (adjacent) <literal>default</literal>. (To be
+                  precise, <literal>break</literal> behaves as described if
+                  the <literal>switch</literal> doesn't contain
+                  <literal>on</literal>, and thus also if all it contains is a
+                  <literal>default</literal>.)</para>
+                </listitem>
+
+                <listitem>
+                  <para>If the <literal>switch</literal> uses
+                  <literal>on</literal> then <literal>break</literal> is not
+                  supported by <literal>switch</literal> directly, but
+                  naturally can be used when the <literal>switch</literal> is
+                  inside something else that supports <literal>break</literal>
+                  (like inside a <link
+                  linkend="ref.directive.list">list</link>).</para>
+                </listitem>
+
+                <listitem>
+                  <para>With <literal>case</literal>,
+                  <literal>continue</literal> does the same as
+                  <literal>break</literal>. This is an old bug that works for
+                  backward compatibility, but don't utilize it. With
+                  <literal>on</literal>, this is fixed.</para>
+                </listitem>
+              </itemizedlist>
+            </listitem>
+          </itemizedlist>
         </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>Switch is used to choose a fragment of template depending on
-          the value of an expression:</para>
+          the value of an expression, and is a shorthand instead using <link
+          
linkend="ref.directive.if"><literal>if</literal>-<literal>elseif</literal>-<literal>else</literal></link>:</para>
+
+          <programlisting role="template">&lt;#switch animal.size&gt;
+  &lt;#on "small"&gt;
+     Processed if animal.size was "small" 
+  &lt;#on "medium"&gt;
+     Processed if animal.size was "medium" 
+  &lt;#on "large", "extra large"&gt;
+     Processed if animal.size was "large" or "extra large"
+  &lt;#default&gt;
+     Processed if animal.size is neither of the above
+&lt;/#switch&gt;</programlisting>
+
+          <para>Before FreeMarker 2.3.24, <literal>on</literal> wasn't
+          supported, only <literal>case</literal> (note the usage of
+          <literal>break</literal>, and the intentional omission of it after
+          <literal>&lt;#case "large"&gt;</literal>):</para>
 
           <programlisting role="template">&lt;#switch animal.size&gt;
   &lt;#case "small"&gt;
-     This will be processed if it is small
+     Processed if animal.size was "small" 
      &lt;#break&gt;
   &lt;#case "medium"&gt;
-     This will be processed if it is medium
+     Processed if animal.size was "medium" 
      &lt;#break&gt;
   &lt;#case "large"&gt;
-     This will be processed if it is large
+  &lt;#case "extra large"&gt;
+     Processed if animal.size was "large" or "extra large"
      &lt;#break&gt;
   &lt;#default&gt;
-     This will be processed if it is neither
+     Processed if animal.size is neither of the above
 &lt;/#switch&gt;</programlisting>
 
-          <para>Inside the <literal>switch</literal> must be one or more
-          <literal>&lt;#case <replaceable>value</replaceable>&gt;</literal>,
-          and after all such <literal>case</literal> tags optionally one
-          <literal>&lt;#default&gt;</literal>. When FM reaches the
-          <literal>switch</literal> directive, it chooses a
-          <literal>case</literal> directive where
+          <para>Above examples are basically equivalent with this:</para>
+
+          <programlisting role="template">&lt;#assign value = animal.size&gt;
+&lt;#if value == "small"&gt;
+   Processed if animal.size was "small" 
+&lt;#elseif value == "medium"&gt;
+   Processed if animal.size was "medium" 
+&lt;#elseif value == "large" || value == "extra large"&gt;
+   Processed if animal.size was "large" or "extra large"
+&lt;#else&gt;
+   Processed if animal.size is neither of the above
+&lt;/#if&gt;</programlisting>
+
+          <para>That is, when the <literal>switch</literal> directive is
+          processed, it chooses an <literal>on</literal> or
+          <literal>case</literal> directive where a
           <literal><replaceable>refValue</replaceable></literal> equals with
-          <literal><replaceable>value</replaceable></literal> and continues
+          <literal><replaceable>value</replaceable></literal>, and continues
           the processing of the template there. If there is no
-          <literal>case</literal> directive with appropriate value then it
-          continues processing at the <literal>default</literal> directive if
-          that exists, otherwise it continues the processing after the end-tag
-          of <literal>switch</literal>. And now comes the confusing thing:
-          when it has chosen a <literal>case</literal> directive, it will
-          continue the processing there, and will go ahead until it reaches a
-          <literal>break</literal> directive. That is, it will not
-          automatically leave the <literal>switch</literal> directive when it
-          reaches another <literal>case</literal> directive or the
-          <literal>&lt;#default&gt;</literal> tag. Example:</para>
+          <literal>on</literal> or <literal>case</literal> directive with
+          appropriate <literal><replaceable>refValue</replaceable></literal>,
+          then it continues processing at the <literal>default</literal>
+          directive, if that exists, otherwise it continues the processing
+          after the end-tag of <literal>switch</literal>.</para>
+
+          <para>Be careful with <literal>case</literal>'s fall-through
+          behavior! It means that after processing have jumped on the matching
+          <literal>case</literal>, it will not leave the
+          <literal>switch</literal> directive when it reaches another
+          <literal>case</literal> or <literal>default</literal>, only when it
+          reaches a <literal>break</literal>. Example:</para>
 
           <programlisting role="template">&lt;#switch x&gt;
   &lt;#case 1&gt;
@@ -25296,12 +25409,14 @@ or
     d
 &lt;/#switch&gt;</programlisting>
 
-          <para>If <literal>x</literal> is 1, then it will print 1 2 d; if
-          <literal>x</literal> is 2 then it will print 2 d; if
-          <literal>x</literal> is 3 then it will print d. This is the
-          mentioned fall-through behavior. The <literal>break</literal> tag
-          instructs FM to immediately skip past the <literal>switch</literal>
-          end-tag.</para>
+          <para>If <literal>x</literal> is <literal>1</literal>, then it will
+          print <literal>1 2 d</literal> (actually with more white-space
+          between); if <literal>x</literal> is <literal>2</literal> then it
+          will print <literal>2 d</literal>; if <literal>x</literal> is
+          <literal>3</literal> then it will print <literal>d</literal>. This
+          is usually unintended, or if it is intended then probably not
+          obvious for the reader, and that's why <literal>on</literal> is
+          recommended over <literal>case</literal>.</para>
         </section>
       </section>
 
@@ -30349,6 +30464,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