You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/09/17 10:56:39 UTC

[1/9] incubator-freemarker git commit: (Some internal code cleanup)

Repository: incubator-freemarker
Updated Branches:
  refs/heads/2.3 b25782910 -> dd60aef18


(Some internal code cleanup)


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/b185b980
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/b185b980
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/b185b980

Branch: refs/heads/2.3
Commit: b185b98001b10281f6c0d1e8df759cdd265714ef
Parents: a4a406a
Author: ddekany <dd...@apache.org>
Authored: Wed Aug 30 21:40:23 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Wed Aug 30 21:40:23 2017 +0200

----------------------------------------------------------------------
 src/main/java/freemarker/core/Macro.java        |  3 +-
 .../java/freemarker/ext/dom/NodeListModel.java  |  5 +-
 .../template/DefaultIterableAdapter.java        |  2 +-
 .../freemarker/template/DefaultListAdapter.java |  2 +-
 .../DefaultNonListCollectionAdapter.java        |  2 +-
 .../DefaultUnassignableIteratorAdapter.java     | 52 --------------------
 .../IteratorToTemplateModelIteratorAdapter.java | 50 +++++++++++++++++++
 7 files changed, 58 insertions(+), 58 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b185b980/src/main/java/freemarker/core/Macro.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Macro.java b/src/main/java/freemarker/core/Macro.java
index ef657ac..fdce23b 100644
--- a/src/main/java/freemarker/core/Macro.java
+++ b/src/main/java/freemarker/core/Macro.java
@@ -29,6 +29,7 @@ import freemarker.template.TemplateException;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateModelIterator;
+import freemarker.template.TemplateScalarModel;
 
 /**
  * An element representing a macro declaration.
@@ -263,7 +264,7 @@ public final class Macro extends TemplateElement implements TemplateModel {
         public Collection getLocalVariableNames() throws TemplateModelException {
             HashSet result = new HashSet();
             for (TemplateModelIterator it = localVars.keys().iterator(); it.hasNext(); ) {
-                result.add(it.next().toString());
+                result.add(((TemplateScalarModel) it.next()).getAsString());
             }
             return result;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b185b980/src/main/java/freemarker/ext/dom/NodeListModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/dom/NodeListModel.java b/src/main/java/freemarker/ext/dom/NodeListModel.java
index a6a7534..46644d9 100644
--- a/src/main/java/freemarker/ext/dom/NodeListModel.java
+++ b/src/main/java/freemarker/ext/dom/NodeListModel.java
@@ -216,11 +216,12 @@ class NodeListModel extends SimpleSequence implements TemplateHashModel, _Unexpe
     }
 
     private Object[] newTypeErrorExplanation(String type) {
+        int size = size();
         return new Object[] {
                 "This XML query result can't be used as ", type, " because for that it had to contain exactly "
-                + "1 XML node, but it contains ", Integer.valueOf(size()), " nodes. "
+                + "1 XML node, but it contains ", Integer.valueOf(size), " nodes. "
                 + "That is, the constructing XML query has found ",
-                isEmpty()
+                size == 0
                     ? "no matches."
                     : "multiple matches."
                 };

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b185b980/src/main/java/freemarker/template/DefaultIterableAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/DefaultIterableAdapter.java b/src/main/java/freemarker/template/DefaultIterableAdapter.java
index 14aa6d1..824e2d7 100644
--- a/src/main/java/freemarker/template/DefaultIterableAdapter.java
+++ b/src/main/java/freemarker/template/DefaultIterableAdapter.java
@@ -68,7 +68,7 @@ public class DefaultIterableAdapter extends WrappingTemplateModel implements Tem
     }
 
     public TemplateModelIterator iterator() throws TemplateModelException {
-        return new DefaultUnassignableIteratorAdapter(iterable.iterator(), getObjectWrapper());
+        return new IteratorToTemplateModelIteratorAdapter(iterable.iterator(), getObjectWrapper());
     }
 
     public Object getWrappedObject() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b185b980/src/main/java/freemarker/template/DefaultListAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/DefaultListAdapter.java b/src/main/java/freemarker/template/DefaultListAdapter.java
index 62e7f18..3585ed8 100644
--- a/src/main/java/freemarker/template/DefaultListAdapter.java
+++ b/src/main/java/freemarker/template/DefaultListAdapter.java
@@ -96,7 +96,7 @@ public class DefaultListAdapter extends WrappingTemplateModel implements Templat
         }
 
         public TemplateModelIterator iterator() throws TemplateModelException {
-            return new DefaultUnassignableIteratorAdapter(list.iterator(), getObjectWrapper());
+            return new IteratorToTemplateModelIteratorAdapter(list.iterator(), getObjectWrapper());
         }
 
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b185b980/src/main/java/freemarker/template/DefaultNonListCollectionAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/DefaultNonListCollectionAdapter.java b/src/main/java/freemarker/template/DefaultNonListCollectionAdapter.java
index 2ee9ca2..ab87cc6 100644
--- a/src/main/java/freemarker/template/DefaultNonListCollectionAdapter.java
+++ b/src/main/java/freemarker/template/DefaultNonListCollectionAdapter.java
@@ -70,7 +70,7 @@ public class DefaultNonListCollectionAdapter extends WrappingTemplateModel imple
     }
 
     public TemplateModelIterator iterator() throws TemplateModelException {
-        return new DefaultUnassignableIteratorAdapter(collection.iterator(), getObjectWrapper());
+        return new IteratorToTemplateModelIteratorAdapter(collection.iterator(), getObjectWrapper());
     }
 
     public int size() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b185b980/src/main/java/freemarker/template/DefaultUnassignableIteratorAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/DefaultUnassignableIteratorAdapter.java b/src/main/java/freemarker/template/DefaultUnassignableIteratorAdapter.java
deleted file mode 100644
index 80c4bc5..0000000
--- a/src/main/java/freemarker/template/DefaultUnassignableIteratorAdapter.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.template;
-
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-/**
- * As opposed to {@link DefaultIteratorAdapter}, this simpler {@link Iterator} adapter is used in situations where the
- * {@link TemplateModelIterator} won't be assigned to FreeMarker template variables, only used internally by
- * {@code #list} or custom Java code. Because of that, it doesn't have to handle the situation where the user tries to
- * iterate over the same value twice.
- */
-class DefaultUnassignableIteratorAdapter implements TemplateModelIterator {
-
-    private final Iterator<?> it;
-    private final ObjectWrapper wrapper;
-
-    DefaultUnassignableIteratorAdapter(Iterator<?> it, ObjectWrapper wrapper) {
-        this.it = it;
-        this.wrapper = wrapper;
-    }
-
-    public TemplateModel next() throws TemplateModelException {
-        try {
-            return wrapper.wrap(it.next());
-        } catch (NoSuchElementException e) {
-            throw new TemplateModelException("The collection has no more items.", e);
-        }
-    }
-
-    public boolean hasNext() throws TemplateModelException {
-        return it.hasNext();
-    }
-
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b185b980/src/main/java/freemarker/template/IteratorToTemplateModelIteratorAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/IteratorToTemplateModelIteratorAdapter.java b/src/main/java/freemarker/template/IteratorToTemplateModelIteratorAdapter.java
new file mode 100644
index 0000000..6b49237
--- /dev/null
+++ b/src/main/java/freemarker/template/IteratorToTemplateModelIteratorAdapter.java
@@ -0,0 +1,50 @@
+/*
+ * 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.template;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Unlike {@link DefaultIteratorAdapter}, this doesn't adapt to some {@link TemplateModel}, but to {@link
+ * TemplateModelIterator}.
+ */
+class IteratorToTemplateModelIteratorAdapter implements TemplateModelIterator {
+
+    private final Iterator<?> it;
+    private final ObjectWrapper wrapper;
+
+    IteratorToTemplateModelIteratorAdapter(Iterator<?> it, ObjectWrapper wrapper) {
+        this.it = it;
+        this.wrapper = wrapper;
+    }
+
+    public TemplateModel next() throws TemplateModelException {
+        try {
+            return wrapper.wrap(it.next());
+        } catch (NoSuchElementException e) {
+            throw new TemplateModelException("The collection has no more items.", e);
+        }
+    }
+
+    public boolean hasNext() throws TemplateModelException {
+        return it.hasNext();
+    }
+
+}
\ No newline at end of file


[9/9] incubator-freemarker git commit: Merge remote-tracking branch 'origin/2.3-gae' into 2.3

Posted by dd...@apache.org.
Merge remote-tracking branch 'origin/2.3-gae' into 2.3


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/dd60aef1
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/dd60aef1
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/dd60aef1

Branch: refs/heads/2.3
Commit: dd60aef18fbe643298edf64e9eca999d0b7ca945
Parents: b257829 a671110
Author: ddekany <dd...@apache.org>
Authored: Sun Sep 17 12:56:03 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sun Sep 17 12:56:03 2017 +0200

----------------------------------------------------------------------
 README.md                                       |   4 +-
 .../java/freemarker/core/BreakInstruction.java  |   8 +-
 .../core/BreakOrContinueException.java          |  12 ++
 src/main/java/freemarker/core/BuiltIn.java      |   4 +-
 .../freemarker/core/BuiltInsForSequences.java   |  62 ++++----
 .../freemarker/core/BuiltInsForStringsMisc.java |   2 +-
 .../freemarker/core/ContinueInstruction.java    |  64 ++++++++
 .../freemarker/core/DefaultToExpression.java    |  16 +-
 src/main/java/freemarker/core/Environment.java  |  10 +-
 .../java/freemarker/core/IteratorBlock.java     | 102 +++++++------
 src/main/java/freemarker/core/Macro.java        |   3 +-
 src/main/java/freemarker/core/SwitchBlock.java  |   2 +-
 src/main/java/freemarker/core/_CoreAPI.java     |   1 +
 .../java/freemarker/ext/beans/BeanModel.java    |   4 +-
 .../java/freemarker/ext/beans/BeansWrapper.java |  69 +++++----
 .../ext/beans/BeansWrapperConfiguration.java    |  15 ++
 .../java/freemarker/ext/dom/ElementModel.java   |   3 +-
 .../java/freemarker/ext/dom/NodeListModel.java  |  22 +--
 .../template/DefaultIterableAdapter.java        |   2 +-
 .../freemarker/template/DefaultListAdapter.java |   2 +-
 .../DefaultNonListCollectionAdapter.java        |   2 +-
 .../DefaultUnassignableIteratorAdapter.java     |  52 -------
 .../IteratorToTemplateModelIteratorAdapter.java |  50 ++++++
 .../freemarker/template/utility/DeepUnwrap.java |   5 +-
 src/main/javacc/FTL.jj                          |  41 +++++
 src/manual/en_US/book.xml                       | 153 ++++++++++++++++---
 .../core/BreakAndContinuePlacementTest.java     |  75 +++++++++
 .../freemarker/core/BreakPlacementTest.java     |  68 ---------
 .../freemarker/core/ListBreakContinueTest.java  |  93 +++++++++++
 .../freemarker/template/ConfigurationTest.java  |   4 +
 .../template/DefaultObjectWrapperTest.java      |  11 +-
 31 files changed, 665 insertions(+), 296 deletions(-)
----------------------------------------------------------------------



[4/9] incubator-freemarker git commit: FREEMARKER-71: When using exp?eval, if the expression inside evaluated string throws an exception, the cause exception of that exception was lost.

Posted by dd...@apache.org.
FREEMARKER-71: When using exp?eval, if the expression inside evaluated string throws an exception, the cause exception of that exception was lost.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/3aa6c509
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/3aa6c509
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/3aa6c509

Branch: refs/heads/2.3
Commit: 3aa6c5098f9d6a1ac14da45edc6b7e6676700847
Parents: 3cf4223
Author: ddekany <dd...@apache.org>
Authored: Sat Sep 16 15:26:52 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Sep 16 15:26:52 2017 +0200

----------------------------------------------------------------------
 src/main/java/freemarker/core/BuiltInsForStringsMisc.java | 2 +-
 src/manual/en_US/book.xml                                 | 9 +++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3aa6c509/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsMisc.java b/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
index eeeb270..4073235 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsMisc.java
@@ -97,7 +97,7 @@ class BuiltInsForStringsMisc {
             try {
                 return exp.eval(env);
             } catch (TemplateException e) {
-                throw new _MiscTemplateException(this, env,
+                throw new _MiscTemplateException(e, this, env,
                         "Failed to \"?", key, "\" string with this error:\n\n",
                         MessageUtil.EMBEDDED_MESSAGE_BEGIN,
                         new _DelayedGetMessageWithoutStackTop(e),

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3aa6c509/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 809822b..e127a79 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -26943,6 +26943,15 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               the <literal>switch</literal> tag and the first
               <literal>case</literal> tag.</para>
             </listitem>
+
+            <listitem>
+              <para>Bug fixed (<link
+              xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-71">FREEMARKER-71</link>):
+              When using
+              <literal><replaceable>exp</replaceable>?eval</literal>, if the
+              expression inside evaluated string throws an exception, the
+              cause exception of that exception was lost.</para>
+            </listitem>
           </itemizedlist>
         </section>
 


[8/9] incubator-freemarker git commit: (README typo)

Posted by dd...@apache.org.
(README typo)


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/a6711106
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/a6711106
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/a6711106

Branch: refs/heads/2.3
Commit: a671110653a7428a7e041925f8668d5954063dd7
Parents: da570ca
Author: ddekany <dd...@apache.org>
Authored: Sun Sep 17 12:55:05 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sun Sep 17 12:55:05 2017 +0200

----------------------------------------------------------------------
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/a6711106/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 779df07..edd56b7 100644
--- a/README.md
+++ b/README.md
@@ -161,7 +161,7 @@ apply it to your development environment:
   - Java -> Compiler -> Javadoc:
     "Malformed Javadoc comments": Error
     "Only consider members as visible": Private
-    "Validate tag argunebts": true
+    "Validate tag arguments": true
     "Missing tag descriptions": Validate @return tags
     "Missing Javadoc tags": Ignore
     "Missing Javadoc comments": Ignore


[2/9] incubator-freemarker git commit: (Some internal code cleanup)

Posted by dd...@apache.org.
(Some internal code cleanup)


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/78d4a6e2
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/78d4a6e2
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/78d4a6e2

Branch: refs/heads/2.3
Commit: 78d4a6e2690bcd476629f9e0d0045da752f27593
Parents: b185b98
Author: ddekany <dd...@apache.org>
Authored: Fri Sep 1 22:18:29 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Fri Sep 1 22:18:29 2017 +0200

----------------------------------------------------------------------
 .../freemarker/core/BuiltInsForSequences.java   | 62 ++++++++++----------
 .../freemarker/core/DefaultToExpression.java    | 16 +++--
 src/main/java/freemarker/core/Environment.java  | 10 +++-
 .../java/freemarker/ext/beans/BeansWrapper.java |  4 +-
 .../java/freemarker/ext/dom/ElementModel.java   |  3 +-
 .../java/freemarker/ext/dom/NodeListModel.java  | 17 +++---
 .../freemarker/template/utility/DeepUnwrap.java |  5 +-
 7 files changed, 64 insertions(+), 53 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/78d4a6e2/src/main/java/freemarker/core/BuiltInsForSequences.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForSequences.java b/src/main/java/freemarker/core/BuiltInsForSequences.java
index eca5fec..8430133 100644
--- a/src/main/java/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/freemarker/core/BuiltInsForSequences.java
@@ -251,10 +251,11 @@ class BuiltInsForSequences {
         @Override
         TemplateModel calculateResult(TemplateSequenceModel tsm)
         throws TemplateModelException {
-            if (tsm.size() == 0) {
+            int size = tsm.size();
+            if (size == 0) {
                 return null;
             }
-            return tsm.get(tsm.size() - 1);
+            return tsm.get(size - 1);
         }
     }
 
@@ -388,39 +389,36 @@ class BuiltInsForSequences {
                 int argCnt = args.size();
                 checkMethodArgCount(argCnt, 1, 2);
                 
-                TemplateModel target = (TemplateModel) args.get(0);
+                TemplateModel searched = (TemplateModel) args.get(0);
                 int foundAtIdx;
                 if (argCnt > 1) {
                     int startIndex = getNumberMethodArg(args, 1).intValue();
-                    // In 2.3.x only, we prefer TemplateSequenceModel for
-                    // backward compatibility:
+                    // In 2.3, we prefer TemplateSequenceModel for backward compatibility, even if startIndex is 0:
                     foundAtIdx = m_seq != null
-                            ? findInSeq(target, startIndex)
-                            : findInCol(target, startIndex);
+                            ? findInSeq(searched, startIndex)
+                            : findInCol(searched, startIndex);
                 } else {
-                    // In 2.3.x only, we prefer TemplateSequenceModel for
-                    // backward compatibility:
                     foundAtIdx = m_seq != null
-                            ? findInSeq(target)
-                            : findInCol(target);
+                            ? findInSeq(searched)
+                            : findInCol(searched);
                 }
                 return foundAtIdx == -1 ? Constants.MINUS_ONE : new SimpleNumber(foundAtIdx);
             }
             
-            int findInCol(TemplateModel target) throws TemplateModelException {
-                return findInCol(target, 0, Integer.MAX_VALUE);
+            int findInCol(TemplateModel searched) throws TemplateModelException {
+                return findInCol(searched, 0, Integer.MAX_VALUE);
             }
             
-            protected int findInCol(TemplateModel target, int startIndex)
+            protected int findInCol(TemplateModel searched, int startIndex)
                     throws TemplateModelException {
-                if (m_dir == 1) {
-                    return findInCol(target, startIndex, Integer.MAX_VALUE);
+                if (findFirst) {
+                    return findInCol(searched, startIndex, Integer.MAX_VALUE);
                 } else {
-                    return findInCol(target, 0, startIndex);
+                    return findInCol(searched, 0, startIndex);
                 }
             }
         
-            protected int findInCol(TemplateModel target,
+            protected int findInCol(TemplateModel searched,
                     final int allowedRangeStart, final int allowedRangeEnd)
                     throws TemplateModelException {
                 if (allowedRangeEnd < 0) return -1;
@@ -434,10 +432,12 @@ class BuiltInsForSequences {
                     
                     TemplateModel current = it.next();
                     if (idx >= allowedRangeStart) {
-                        if (modelsEqual(idx, current, target, m_env)) {
+                        if (modelsEqual(idx, current, searched, m_env)) {
                             foundAtIdx = idx;
-                            if (m_dir == 1) break searchItem; // "find first"
-                            // Otherwise it's "find last".
+                            // Don't stop if it's "find last".
+                            if (findFirst) {
+                                break searchItem;
+                            }
                         }
                     }
                     idx++;
@@ -445,25 +445,25 @@ class BuiltInsForSequences {
                 return foundAtIdx;
             }
 
-            int findInSeq(TemplateModel target)
+            int findInSeq(TemplateModel searched)
             throws TemplateModelException {
                 final int seqSize = m_seq.size();
                 final int actualStartIndex;
                 
-                if (m_dir == 1) {
+                if (findFirst) {
                     actualStartIndex = 0;
                 } else {
                     actualStartIndex = seqSize - 1;
                 }
             
-                return findInSeq(target, actualStartIndex, seqSize); 
+                return findInSeq(searched, actualStartIndex, seqSize); 
             }
 
-            private int findInSeq(TemplateModel target, int startIndex)
+            private int findInSeq(TemplateModel searched, int startIndex)
                     throws TemplateModelException {
                 int seqSize = m_seq.size();
                 
-                if (m_dir == 1) {
+                if (findFirst) {
                     if (startIndex >= seqSize) {
                         return -1;
                     }
@@ -479,13 +479,13 @@ class BuiltInsForSequences {
                     }
                 }
                 
-                return findInSeq(target, startIndex, seqSize); 
+                return findInSeq(searched, startIndex, seqSize); 
             }
             
             private int findInSeq(
                     TemplateModel target, int scanStartIndex, int seqSize)
                     throws TemplateModelException {
-                if (m_dir == 1) {
+                if (findFirst) {
                     for (int i = scanStartIndex; i < seqSize; i++) {
                         if (modelsEqual(i, m_seq.get(i), target, m_env)) return i;
                     }
@@ -499,10 +499,10 @@ class BuiltInsForSequences {
             
         }
 
-        private int m_dir;
+        private boolean findFirst;
 
-        seq_index_ofBI(int dir) {
-            m_dir = dir;
+        seq_index_ofBI(boolean findFirst) {
+            this.findFirst = findFirst;
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/78d4a6e2/src/main/java/freemarker/core/DefaultToExpression.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/DefaultToExpression.java b/src/main/java/freemarker/core/DefaultToExpression.java
index 1406fa6..245525d 100755
--- a/src/main/java/freemarker/core/DefaultToExpression.java
+++ b/src/main/java/freemarker/core/DefaultToExpression.java
@@ -23,18 +23,20 @@ package freemarker.core;
 import freemarker.template.SimpleCollection;
 import freemarker.template.TemplateCollectionModel;
 import freemarker.template.TemplateException;
-import freemarker.template.TemplateHashModelEx;
+import freemarker.template.TemplateHashModelEx2;
 import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
+import freemarker.template.utility.Constants;
 
 /** {@code exp!defExp}, {@code (exp)!defExp} and the same two with {@code (exp)!}. */
 class DefaultToExpression extends Expression {
 	
     private static final TemplateCollectionModel EMPTY_COLLECTION = new SimpleCollection(new java.util.ArrayList(0));
     
-	static private class EmptyStringAndSequence 
-	  implements TemplateScalarModel, TemplateSequenceModel, TemplateHashModelEx {
+	static private class EmptyStringAndSequenceAndHash implements TemplateScalarModel, TemplateSequenceModel,
+	        TemplateHashModelEx2 {
 		public String getAsString() {
 			return "";
 		}
@@ -56,10 +58,12 @@ class DefaultToExpression extends Expression {
 		public TemplateCollectionModel values() {
 			return EMPTY_COLLECTION;
 		}
-		
+        public KeyValuePairIterator keyValuePairIterator() throws TemplateModelException {
+            return Constants.EMPTY_KEY_VALUE_PAIR_ITERATOR;
+        }
 	}
 	
-	static final TemplateModel EMPTY_STRING_AND_SEQUENCE = new EmptyStringAndSequence();
+	static final TemplateModel EMPTY_STRING_AND_SEQUENCE_AND_HASH = new EmptyStringAndSequenceAndHash();
 	
 	private final Expression lho, rho;
 	
@@ -85,7 +89,7 @@ class DefaultToExpression extends Expression {
 		}
 		
 		if (left != null) return left;
-		else if (rho == null) return EMPTY_STRING_AND_SEQUENCE;
+		else if (rho == null) return EMPTY_STRING_AND_SEQUENCE_AND_HASH;
 		else return rho.eval(env);
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/78d4a6e2/src/main/java/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 565dcd4..8b03afd 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -831,8 +831,11 @@ public final class Environment extends Configurable {
             }
         }
         TemplateSequenceModel children = node.getChildNodes();
-        if (children == null) return;
-        for (int i = 0; i < children.size(); i++) {
+        if (children == null) {
+            return;
+        }
+        int size = children.size();
+        for (int i = 0; i < size; i++) {
             TemplateNodeModel child = (TemplateNodeModel) children.get(i);
             if (child != null) {
                 invokeNodeHandlerFor(child, namespaces);
@@ -2359,7 +2362,8 @@ public final class Environment extends Configurable {
             throws TemplateException {
         TemplateModel result = null;
         int i;
-        for (i = startIndex; i < nodeNamespaces.size(); i++) {
+        int size = nodeNamespaces.size();
+        for (i = startIndex; i < size; i++) {
             Namespace ns = null;
             try {
                 ns = (Namespace) nodeNamespaces.get(i);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/78d4a6e2/src/main/java/freemarker/ext/beans/BeansWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index ea9158d..0195234 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -1301,10 +1301,10 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
             recursionStops = new IdentityHashMap<Object, Object>();
         }
         Class<?> componentType = arrayClass.getComponentType();
-        Object array = Array.newInstance(componentType, seq.size());
+        final int size = seq.size();
+        Object array = Array.newInstance(componentType, size);
         recursionStops.put(seq, array);
         try {
-            final int size = seq.size();
             for (int i = 0; i < size; i++) {
                 final TemplateModel seqItem = seq.get(i);
                 Object val = tryUnwrapTo(seqItem, componentType, 0, recursionStops);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/78d4a6e2/src/main/java/freemarker/ext/dom/ElementModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/dom/ElementModel.java b/src/main/java/freemarker/ext/dom/ElementModel.java
index 20481a8..3f140c1 100644
--- a/src/main/java/freemarker/ext/dom/ElementModel.java
+++ b/src/main/java/freemarker/ext/dom/ElementModel.java
@@ -60,7 +60,8 @@ class ElementModel extends NodeModel implements TemplateScalarModel {
         if (key.equals("*")) {
             NodeListModel ns = new NodeListModel(this);
             TemplateSequenceModel children = getChildNodes();
-            for (int i = 0; i < children.size(); i++) {
+            int size = children.size();
+            for (int i = 0; i < size; i++) {
                 NodeModel child = (NodeModel) children.get(i);
                 if (child.node.getNodeType() == Node.ELEMENT_NODE) {
                     ns.add(child);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/78d4a6e2/src/main/java/freemarker/ext/dom/NodeListModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/dom/NodeListModel.java b/src/main/java/freemarker/ext/dom/NodeListModel.java
index 46644d9..ce83d8c 100644
--- a/src/main/java/freemarker/ext/dom/NodeListModel.java
+++ b/src/main/java/freemarker/ext/dom/NodeListModel.java
@@ -119,7 +119,8 @@ class NodeListModel extends SimpleSequence implements TemplateHashModel, _Unexpe
     }
     
     public TemplateModel get(String key) throws TemplateModelException {
-        if (size() == 1) {
+        int size = size();
+        if (size == 1) {
             NodeModel nm = (NodeModel) get(0);
             return nm.get(key);
         }
@@ -128,7 +129,7 @@ class NodeListModel extends SimpleSequence implements TemplateHashModel, _Unexpe
                     || key.equals(AtAtKey.NESTED_MARKUP.getKey()) 
                     || key.equals(AtAtKey.TEXT.getKey())) {
                 StringBuilder result = new StringBuilder();
-                for (int i = 0; i < size(); i++) {
+                for (int i = 0; i < size; i++) {
                     NodeModel nm = (NodeModel) get(i);
                     TemplateScalarModel textModel = (TemplateScalarModel) nm.get(key);
                     result.append(textModel.getAsString());
@@ -139,8 +140,8 @@ class NodeListModel extends SimpleSequence implements TemplateHashModel, _Unexpe
                 if (AtAtKey.containsKey(key)) {
                     throw new TemplateModelException(
                             "\"" + key + "\" is only applicable to a single XML node, but it was applied on "
-                            + (size() != 0
-                                    ? size() + " XML nodes (multiple matches)."
+                            + (size != 0
+                                    ? size + " XML nodes (multiple matches)."
                                     : "an empty list of XML nodes (no matches)."));
                 } else {
                     throw new TemplateModelException("Unsupported @@ key: " + key);
@@ -152,13 +153,13 @@ class NodeListModel extends SimpleSequence implements TemplateHashModel, _Unexpe
                         && (DomStringUtil.isXMLNameLike(key, 1)  || key.equals("@@") || key.equals("@*"))))
                 || key.equals("*") || key.equals("**")) {
             NodeListModel result = new NodeListModel(contextNode);
-            for (int i = 0; i < size(); i++) {
+            for (int i = 0; i < size; i++) {
                 NodeModel nm = (NodeModel) get(i);
                 if (nm instanceof ElementModel) {
                     TemplateSequenceModel tsm = (TemplateSequenceModel) ((ElementModel) nm).get(key);
                     if (tsm != null) {
-                        int size = tsm.size();
-                        for (int j = 0; j < size; j++) {
+                        int tsmSize = tsm.size();
+                        for (int j = 0; j < tsmSize; j++) {
                             result.add(tsm.get(j));
                         }
                     }
@@ -171,7 +172,7 @@ class NodeListModel extends SimpleSequence implements TemplateHashModel, _Unexpe
         }
         XPathSupport xps = getXPathSupport();
         if (xps != null) {
-            Object context = (size() == 0) ? null : rawNodeList(); 
+            Object context = (size == 0) ? null : rawNodeList(); 
             return xps.executeQuery(context, key);
         } else {
             throw new TemplateModelException(

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/78d4a6e2/src/main/java/freemarker/template/utility/DeepUnwrap.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/utility/DeepUnwrap.java b/src/main/java/freemarker/template/utility/DeepUnwrap.java
index 586787f..85b032c 100644
--- a/src/main/java/freemarker/template/utility/DeepUnwrap.java
+++ b/src/main/java/freemarker/template/utility/DeepUnwrap.java
@@ -129,8 +129,9 @@ public class DeepUnwrap {
         }
         if (model instanceof TemplateSequenceModel) {
             TemplateSequenceModel seq = (TemplateSequenceModel) model;
-            ArrayList list = new ArrayList(seq.size());
-            for (int i = 0; i < seq.size(); ++i) {
+            int size = seq.size();
+            ArrayList list = new ArrayList(size);
+            for (int i = 0; i < size; ++i) {
                 list.add(unwrap(seq.get(i), nullModel, permissive));
             }
             return list;


[3/9] incubator-freemarker git commit: Previously accidentally uncommitted lines.

Posted by dd...@apache.org.
Previously accidentally uncommitted lines.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/3cf42234
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/3cf42234
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/3cf42234

Branch: refs/heads/2.3
Commit: 3cf4223470b9ed9c4219f395b1ad78c99c6d1309
Parents: 78d4a6e
Author: ddekany <dd...@apache.org>
Authored: Fri Sep 1 23:43:05 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Fri Sep 1 23:43:05 2017 +0200

----------------------------------------------------------------------
 src/main/java/freemarker/core/BuiltIn.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cf42234/src/main/java/freemarker/core/BuiltIn.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltIn.java b/src/main/java/freemarker/core/BuiltIn.java
index 70a94bc..b4e3239 100644
--- a/src/main/java/freemarker/core/BuiltIn.java
+++ b/src/main/java/freemarker/core/BuiltIn.java
@@ -260,8 +260,8 @@ abstract class BuiltIn extends Expression implements Cloneable {
         putBI("remove_beginning", "removeBeginning", new BuiltInsForStringsBasic.remove_beginningBI());
         putBI("rtf", new BuiltInsForStringsEncoding.rtfBI());
         putBI("seq_contains", "seqContains", new seq_containsBI());
-        putBI("seq_index_of", "seqIndexOf", new seq_index_ofBI(1));
-        putBI("seq_last_index_of", "seqLastIndexOf", new seq_index_ofBI(-1));
+        putBI("seq_index_of", "seqIndexOf", new seq_index_ofBI(true));
+        putBI("seq_last_index_of", "seqLastIndexOf", new seq_index_ofBI(false));
         putBI("short", new shortBI());
         putBI("size", new BuiltInsForMultipleTypes.sizeBI());
         putBI("sort_by", "sortBy", new sort_byBI());


[7/9] incubator-freemarker git commit: Added the continue directive, which can be used inside a list to skip to the next iteration (similarly as in Java).

Posted by dd...@apache.org.
Added the continue directive, which can be used inside a list to skip to the next iteration (similarly as in Java).


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/da570caa
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/da570caa
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/da570caa

Branch: refs/heads/2.3
Commit: da570caa85c9b17ece822fe72fcce22502f57b54
Parents: 3dfa8ce
Author: ddekany <dd...@apache.org>
Authored: Sun Sep 17 12:46:02 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sun Sep 17 12:46:02 2017 +0200

----------------------------------------------------------------------
 .../java/freemarker/core/BreakInstruction.java  |   8 +-
 .../core/BreakOrContinueException.java          |  12 ++
 .../freemarker/core/ContinueInstruction.java    |  64 ++++++++++
 .../java/freemarker/core/IteratorBlock.java     | 102 ++++++++--------
 src/main/java/freemarker/core/SwitchBlock.java  |   2 +-
 src/main/java/freemarker/core/_CoreAPI.java     |   1 +
 src/main/javacc/FTL.jj                          |  41 +++++++
 src/manual/en_US/book.xml                       | 116 ++++++++++++++++---
 .../core/BreakAndContinuePlacementTest.java     |  75 ++++++++++++
 .../freemarker/core/BreakPlacementTest.java     |  68 -----------
 .../freemarker/core/ListBreakContinueTest.java  |  93 +++++++++++++++
 11 files changed, 443 insertions(+), 139 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/da570caa/src/main/java/freemarker/core/BreakInstruction.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BreakInstruction.java b/src/main/java/freemarker/core/BreakInstruction.java
index b41f05b..e18f6d0 100644
--- a/src/main/java/freemarker/core/BreakInstruction.java
+++ b/src/main/java/freemarker/core/BreakInstruction.java
@@ -26,7 +26,7 @@ final class BreakInstruction extends TemplateElement {
 
     @Override
     TemplateElement[] accept(Environment env) {
-        throw Break.INSTANCE;
+        throw BreakOrContinueException.BREAK_INSTANCE;
     }
 
     @Override
@@ -54,12 +54,6 @@ final class BreakInstruction extends TemplateElement {
         throw new IndexOutOfBoundsException();
     }
     
-    static class Break extends RuntimeException {
-        static final Break INSTANCE = new Break();
-        private Break() {
-        }
-    }
-
     @Override
     boolean isNestedBlockRepeater() {
         return false;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/da570caa/src/main/java/freemarker/core/BreakOrContinueException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BreakOrContinueException.java b/src/main/java/freemarker/core/BreakOrContinueException.java
new file mode 100644
index 0000000..86bfbd8
--- /dev/null
+++ b/src/main/java/freemarker/core/BreakOrContinueException.java
@@ -0,0 +1,12 @@
+package freemarker.core;
+
+/**
+ * Used for implementing #break and #continue. 
+ */
+class BreakOrContinueException extends RuntimeException {
+    
+    static final BreakOrContinueException BREAK_INSTANCE = new BreakOrContinueException();
+    static final BreakOrContinueException CONTINUE_INSTANCE = new BreakOrContinueException();
+    
+    private BreakOrContinueException() { }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/da570caa/src/main/java/freemarker/core/ContinueInstruction.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ContinueInstruction.java b/src/main/java/freemarker/core/ContinueInstruction.java
new file mode 100644
index 0000000..21ff84f
--- /dev/null
+++ b/src/main/java/freemarker/core/ContinueInstruction.java
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+/**
+ * Represents a &lt;break&gt; instruction to break out of a loop.
+ */
+final class ContinueInstruction extends TemplateElement {
+
+    @Override
+    TemplateElement[] accept(Environment env) {
+        throw BreakOrContinueException.CONTINUE_INSTANCE;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        return canonical ? "<" + getNodeTypeSymbol() + "/>" : getNodeTypeSymbol();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#continue";
+    }
+
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+    
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}
+
+

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/da570caa/src/main/java/freemarker/core/IteratorBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/IteratorBlock.java b/src/main/java/freemarker/core/IteratorBlock.java
index 0d9fcec..83c0b22 100644
--- a/src/main/java/freemarker/core/IteratorBlock.java
+++ b/src/main/java/freemarker/core/IteratorBlock.java
@@ -284,16 +284,18 @@ final class IteratorBlock extends TemplateElement {
                 listNotEmpty = iterModel.hasNext();
                 if (listNotEmpty) {
                     if (loopVarName != null) {
-                        try {
-                            do {
+                            listLoop: do {
                                 loopVar = iterModel.next();
                                 hasNext = iterModel.hasNext();
-                                env.visit(childBuffer);
+                                try {
+                                    env.visit(childBuffer);
+                                } catch (BreakOrContinueException br) {
+                                    if (br == BreakOrContinueException.BREAK_INSTANCE) {
+                                        break listLoop;
+                                    }
+                                }
                                 index++;
                             } while (hasNext);
-                        } catch (BreakInstruction.Break br) {
-                            // Silently exit loop
-                        }
                         openedIterator = null;
                     } else {
                         // We must reuse this later, because TemplateCollectionModel-s that wrap an Iterator only
@@ -308,15 +310,17 @@ final class IteratorBlock extends TemplateElement {
                 listNotEmpty = size != 0;
                 if (listNotEmpty) {
                     if (loopVarName != null) {
-                        try {
-                            for (index = 0; index < size; index++) {
+                            listLoop: for (index = 0; index < size; index++) {
                                 loopVar = seqModel.get(index);
                                 hasNext = (size > index + 1);
-                                env.visit(childBuffer);
+                                try {
+                                    env.visit(childBuffer);
+                                } catch (BreakOrContinueException br) {
+                                    if (br == BreakOrContinueException.BREAK_INSTANCE) {
+                                        break listLoop;
+                                    }
+                                }
                             }
-                        } catch (BreakInstruction.Break br) {
-                            // Silently exit loop
-                        }
                     } else {
                         env.visit(childBuffer);
                     }
@@ -329,7 +333,7 @@ final class IteratorBlock extends TemplateElement {
                 }
                 try {
                     env.visit(childBuffer);
-                } catch (BreakInstruction.Break br) {
+                } catch (BreakOrContinueException br) {
                     // Silently exit "loop"
                 }
             } else if (listedValue instanceof TemplateHashModelEx
@@ -359,18 +363,20 @@ final class IteratorBlock extends TemplateElement {
                     hashNotEmpty = kvpIter.hasNext();
                     if (hashNotEmpty) {
                         if (loopVarName != null) {
-                            try {
-                                do {
-                                    KeyValuePair kvp = kvpIter.next();
-                                    loopVar = kvp.getKey();
-                                    loopVar2 = kvp.getValue();
-                                    hasNext = kvpIter.hasNext();
+                            listLoop: do {
+                                KeyValuePair kvp = kvpIter.next();
+                                loopVar = kvp.getKey();
+                                loopVar2 = kvp.getValue();
+                                hasNext = kvpIter.hasNext();
+                                try {
                                     env.visit(childBuffer);
-                                    index++;
-                                } while (hasNext);
-                            } catch (BreakInstruction.Break br) {
-                                // Silently exit loop
-                            }
+                                } catch (BreakOrContinueException br) {
+                                    if (br == BreakOrContinueException.BREAK_INSTANCE) {
+                                        break listLoop;
+                                    }
+                                }
+                                index++;
+                            } while (hasNext);
                             openedIterator = null;
                         } else {
                             // We will reuse this at the #iterms
@@ -383,30 +389,32 @@ final class IteratorBlock extends TemplateElement {
                     hashNotEmpty = keysIter.hasNext();
                     if (hashNotEmpty) {
                         if (loopVarName != null) {
-                            try {
-                                do {
-                                    loopVar = keysIter.next();
-                                    if (!(loopVar instanceof TemplateScalarModel)) {
-                                        throw new NonStringException(env,
-                                                new _ErrorDescriptionBuilder(
-                                                        "When listing key-value pairs of traditional hash "
-                                                        + "implementations, all keys must be strings, but one of them "
-                                                        + "was ",
-                                                        new _DelayedAOrAn(new _DelayedFTLTypeDescription(loopVar)), "."
-                                                        ).tip("The listed value's TemplateModel class was ",
-                                                                new _DelayedShortClassName(listedValue.getClass()),
-                                                                ", which doesn't implement ",
-                                                                new _DelayedShortClassName(TemplateHashModelEx2.class),
-                                                                ", which leads to this restriction."));
-                                    }
-                                    loopVar2 = listedHash.get(((TemplateScalarModel) loopVar).getAsString());
-                                    hasNext = keysIter.hasNext();
+                            listLoop: do {
+                                loopVar = keysIter.next();
+                                if (!(loopVar instanceof TemplateScalarModel)) {
+                                    throw new NonStringException(env,
+                                            new _ErrorDescriptionBuilder(
+                                                    "When listing key-value pairs of traditional hash "
+                                                    + "implementations, all keys must be strings, but one of them "
+                                                    + "was ",
+                                                    new _DelayedAOrAn(new _DelayedFTLTypeDescription(loopVar)), "."
+                                                    ).tip("The listed value's TemplateModel class was ",
+                                                            new _DelayedShortClassName(listedValue.getClass()),
+                                                            ", which doesn't implement ",
+                                                            new _DelayedShortClassName(TemplateHashModelEx2.class),
+                                                            ", which leads to this restriction."));
+                                }
+                                loopVar2 = listedHash.get(((TemplateScalarModel) loopVar).getAsString());
+                                hasNext = keysIter.hasNext();
+                                try {
                                     env.visit(childBuffer);
-                                    index++;
-                                } while (hasNext);
-                            } catch (BreakInstruction.Break br) {
-                                // Silently exit loop
-                            }
+                                } catch (BreakOrContinueException br) {
+                                    if (br == BreakOrContinueException.BREAK_INSTANCE) {
+                                        break listLoop;
+                                    }
+                                }
+                                index++;
+                            } while (hasNext);
                         } else {
                             env.visit(childBuffer);
                         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/da570caa/src/main/java/freemarker/core/SwitchBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/SwitchBlock.java b/src/main/java/freemarker/core/SwitchBlock.java
index 3f8a320..19c6a3e 100644
--- a/src/main/java/freemarker/core/SwitchBlock.java
+++ b/src/main/java/freemarker/core/SwitchBlock.java
@@ -86,7 +86,7 @@ final class SwitchBlock extends TemplateElement {
             if (!processedCase && defaultCase != null) {
                 env.visit(defaultCase);
             }
-        } catch (BreakInstruction.Break br) {}
+        } catch (BreakOrContinueException br) {}
         return null;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/da570caa/src/main/java/freemarker/core/_CoreAPI.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/_CoreAPI.java b/src/main/java/freemarker/core/_CoreAPI.java
index 1f10f81..3491568 100644
--- a/src/main/java/freemarker/core/_CoreAPI.java
+++ b/src/main/java/freemarker/core/_CoreAPI.java
@@ -78,6 +78,7 @@ public class _CoreAPI {
         addName(allNames, lcNames, ccNames, "case");
         addName(allNames, lcNames, ccNames, "comment");
         addName(allNames, lcNames, ccNames, "compress");
+        addName(allNames, lcNames, ccNames, "continue");
         addName(allNames, lcNames, ccNames, "default");
         addName(allNames, lcNames, ccNames, "else");
         addName(allNames, lcNames, ccNames, "elseif", "elseIf");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/da570caa/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index b7bdd48..ddbf78f 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -85,6 +85,11 @@ public class FMParser {
      */
     private int breakableDirectiveNesting;
     
+    /**
+     * Keeps track of the nesting depth of directives that support #continue.
+     */
+    private int continuableDirectiveNesting;
+    
     private boolean inMacro, inFunction;
     private LinkedList escapes = new LinkedList();
     private int mixedContentNesting; // for stripText
@@ -1019,6 +1024,8 @@ TOKEN:
     |
     <BREAK : <START_TAG> "break" <CLOSE_TAG2>> { strictSyntaxCheck(matchedToken, DEFAULT); }
     |
+    <CONTINUE : <START_TAG> "continue" <CLOSE_TAG2>> { strictSyntaxCheck(matchedToken, DEFAULT); }
+    |
     <SIMPLE_RETURN : <START_TAG> "return" <CLOSE_TAG2>> { strictSyntaxCheck(matchedToken, DEFAULT); }
     |
     <HALT : <START_TAG> "stop" <CLOSE_TAG2>> { strictSyntaxCheck(matchedToken, DEFAULT); }
@@ -2580,6 +2587,7 @@ TemplateElement List() :
         if (loopVar != null) {
             iterCtx.loopVarName = loopVar.image;
             breakableDirectiveNesting++;
+            continuableDirectiveNesting++;
             if (loopVar2 != null) {
                 iterCtx.loopVar2Name = loopVar2.image;
                 iterCtx.hashListing = true;
@@ -2596,6 +2604,7 @@ TemplateElement List() :
     {
         if (loopVar != null) {
             breakableDirectiveNesting--;
+            continuableDirectiveNesting--;
         } else if (iterCtx.kind != ITERATOR_BLOCK_KIND_ITEMS) {
             throw new ParseException(
                     "#list must have either \"as loopVar\" parameter or nested #items that belongs to it.",
@@ -2660,6 +2669,7 @@ IteratorBlock ForEach() :
         iterCtx.loopVarName = loopVar.image;
         iterCtx.kind = ITERATOR_BLOCK_KIND_FOREACH;
         breakableDirectiveNesting++;
+        continuableDirectiveNesting++;
     }
     
     children = MixedContentElements()
@@ -2667,6 +2677,7 @@ IteratorBlock ForEach() :
     end = <END_FOREACH>
     {
         breakableDirectiveNesting--;
+        continuableDirectiveNesting--;
         popIteratorBlockContext();
                 
         IteratorBlock result = new IteratorBlock(exp, loopVar.image, null, children, false, true);
@@ -2718,6 +2729,7 @@ Items Items() :
         }
     
         breakableDirectiveNesting++;
+        continuableDirectiveNesting++;
     }
     
     children = MixedContentElements()
@@ -2725,6 +2737,7 @@ Items Items() :
     end = <END_ITEMS>
     {
         breakableDirectiveNesting--;
+        continuableDirectiveNesting--;
         iterCtx.loopVarName = null;
         iterCtx.loopVar2Name = null;
         
@@ -2851,6 +2864,27 @@ BreakInstruction Break() :
 }
 
 /**
+ * Production used to skip an iteration in a loop.
+ */
+ContinueInstruction Continue() :
+{
+    Token start;
+}
+{
+    start = <CONTINUE>
+    {
+        if (continuableDirectiveNesting < 1) {
+            throw new ParseException(start.image + " must be nested inside a directive that supports it: " 
+                    + " #list with \"as\", #items (or the deprecated " + forEachDirectiveSymbol() + ")",
+                    template, start);
+        }
+        ContinueInstruction result = new ContinueInstruction();
+        result.setLocation(template, start, start);
+        return result;
+    }
+}
+
+/**
  * Production used to jump out of a macro.
  * The stop instruction terminates the rendering of the template.
  */
@@ -3221,6 +3255,7 @@ Macro Macro() :
     Expression defValue = null;
     List lastIteratorBlockContexts;
     int lastBreakableDirectiveNesting;
+    int lastContiunableDirectiveNesting;
     TemplateElements children;
     boolean isFunction = false, hasDefaults = false;
     boolean isCatchAll = false;
@@ -3292,9 +3327,12 @@ Macro Macro() :
         iteratorBlockContexts = null;
         if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_23) {
 	        lastBreakableDirectiveNesting = breakableDirectiveNesting;
+	        lastContiunableDirectiveNesting = continuableDirectiveNesting;
 	        breakableDirectiveNesting = 0; 
+	        continuableDirectiveNesting = 0;
         } else {
             lastBreakableDirectiveNesting = 0; // Just to prevent uninitialized local variable error later
+            lastContiunableDirectiveNesting = 0;
         }
     }
     children = MixedContentElements()
@@ -3313,6 +3351,7 @@ Macro Macro() :
         iteratorBlockContexts = lastIteratorBlockContexts;
         if (incompatibleImprovements >= _TemplateAPI.VERSION_INT_2_3_23) {
             breakableDirectiveNesting = lastBreakableDirectiveNesting;
+            continuableDirectiveNesting = lastContiunableDirectiveNesting;
         }
         
         inMacro = inFunction = false;
@@ -3937,6 +3976,8 @@ TemplateElement FreemarkerDirective() :
         |
         tp = Break()
         |
+        tp = Continue()
+        |
         tp = Return()
         |
         tp = Stop()

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/da570caa/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index f1dc57a..21c3479 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -20428,7 +20428,7 @@ All rights reserved.</emphasis></programlisting>
       </section>
 
       <section xml:id="ref_directive_list">
-        <title>list, else, items, sep, break</title>
+        <title>list, else, items, sep, break, continue</title>
 
         <anchor xml:id="ref.directive.list"/>
 
@@ -20522,7 +20522,7 @@ All rights reserved.</emphasis></programlisting>
         <section>
           <title>Description</title>
 
-          <section>
+          <section xml:id="ref_list_simple">
             <title>Simplest form</title>
 
             <para>Assuming <literal>users</literal> contains the
@@ -20569,7 +20569,7 @@ All rights reserved.</emphasis></programlisting>
             <literal>Map</literal> objects can be listed.</para>
           </section>
 
-          <section>
+          <section xml:id="ref_list_else">
             <title>else directive</title>
 
             <anchor xml:id="ref.directive.list.else"/>
@@ -20609,7 +20609,7 @@ All rights reserved.</emphasis></programlisting>
             included template.</para>
           </section>
 
-          <section>
+          <section xml:id="ref_list_items">
             <title>items directive</title>
 
             <anchor xml:id="ref.directive.items"/>
@@ -20712,7 +20712,7 @@ All rights reserved.</emphasis></programlisting>
             </itemizedlist>
           </section>
 
-          <section>
+          <section xml:id="ref_list_sep">
             <title>sep directive</title>
 
             <anchor xml:id="ref.directive.sep"/>
@@ -20765,7 +20765,7 @@ All rights reserved.</emphasis></programlisting>
             will be called from).</para>
           </section>
 
-          <section>
+          <section xml:id="ref_list_break">
             <title>break directive</title>
 
             <anchor xml:id="ref.directive.list.break"/>
@@ -20792,24 +20792,100 @@ All rights reserved.</emphasis></programlisting>
             anywhere inside <literal>list</literal> as far as it has
             <literal>as <replaceable>item</replaceable></literal> parameter,
             otherwise it can be placed anywhere inside the
-            <literal>items</literal> directive. If the
-            <literal>break</literal> is inside <literal>items</literal>, it
-            will only exit from <literal>items</literal>, not from
-            <literal>list</literal>. In general, <literal>break</literal> will
-            only exit from the directive whose body is called for each item,
-            and can only be placed inside such directive. So for example can't
-            use <literal>break</literal> inside <literal>list</literal>'s
+            <literal>items</literal> directive. However, it's strongly
+            recommended to place it either before or after all the other
+            things that you do inside the iteration. Otherwise it's easy to
+            end up with unclosed elements in the output, or otherwise make the
+            template harder to understand. Especially, avoid breaking out from
+            the nested content of custom directives (like <literal>&lt;#list
+            ...&gt;...&lt;@foo&gt;...&lt;#break&gt;...&lt;/@foo&gt;...&lt;/#list&gt;</literal>),
+            as the author of the directive may not expect that the closing tag
+            (<literal>&lt;/@foo&gt;</literal>) is never executed.</para>
+
+            <para>If the <literal>break</literal> is inside
+            <literal>items</literal>, it will only exit from
+            <literal>items</literal>, not from <literal>list</literal>. In
+            general, <literal>break</literal> will only exit from the
+            directive whose body is called for each item, and can only be
+            placed inside such directive. So for example can't use
+            <literal>break</literal> inside <literal>list</literal>'s
             <literal>else</literal> section, unless there's the
             <literal>list</literal> is nested into another
             <literal>break</literal>-able directive.</para>
 
+            <para>Using <literal>break</literal> together with
+            <literal>sep</literal> is generally a bad idea, as
+            <literal>sep</literal> can't know if you will skip the rest of
+            items with <literal>break</literal>, and then you end up with a
+            separator after the item printed last.</para>
+
             <para>Just like <literal>else</literal> and
             <literal>items</literal>, <literal>break</literal> must be
             literally inside body of the directive to break out from, and
             can't be moved out into a macro or included template.</para>
           </section>
 
-          <section>
+          <section xml:id="ref_list_continue">
+            <title>continue directive</title>
+
+            <anchor xml:id="ref.directive.list.continue"/>
+
+            <indexterm>
+              <primary>continue directive</primary>
+            </indexterm>
+
+            <note>
+              <para>The <literal>continue</literal> directive exists since
+              FreeMarker 2.3.27</para>
+            </note>
+
+            <para>You can skip the rest of the iteration body (the section
+            until the <literal>&lt;/#list&gt;</literal> or
+            <literal>&lt;/#items&gt;</literal> tag) with the
+            <literal>continue</literal> directive, then FreeMarker will
+            continue with the next item. For example:</para>
+
+            <programlisting role="template">&lt;#list 1..5 as x&gt;
+  &lt;#if x == 3&gt;
+    &lt;#continue&gt;
+  &lt;/#if&gt;
+  ${x}
+&lt;/#list&gt;</programlisting>
+
+            <programlisting role="output">  1
+  2
+  4
+  5</programlisting>
+
+            <para>The <literal>continue</literal> directives can be placed
+            anywhere inside <literal>list</literal> as far as it has
+            <literal>as <replaceable>item</replaceable></literal> parameter,
+            otherwise it can be placed anywhere inside the
+            <literal>items</literal> directive. However, it's strongly
+            recommended to place it before all the other things you do inside
+            the iteration. Otherwise it's easy to end up with unclosed
+            elements in the output, or otherwise make the template harder to
+            understand. Especially, avoid breaking out from the nested content
+            of custom directives (like <literal>&lt;#list
+            ...&gt;...&lt;@foo&gt;...&lt;#continue&gt;...&lt;/@foo&gt;...&lt;/#list&gt;</literal>),
+            as the author of the directive may not expect that the closing tag
+            (<literal>&lt;/@foo&gt;</literal>) is never executed.</para>
+
+            <para>When you call <literal>continue</literal>, the
+            <literal>sep</literal> directive will not be executed for that
+            iteration. Using <literal>continue</literal> together with
+            <literal>sep</literal> is generally a bad idea, as
+            <literal>sep</literal> can't know if you will skip the rest of the
+            items, and then you end up with a separator after the item printed
+            last.</para>
+
+            <para>Just like <literal>break</literal>,
+            <literal>continue</literal> must be literally inside body of the
+            directive whose iteration need to be <quote>continued</quote>, and
+            can't be moved out into a macro or included template.</para>
+          </section>
+
+          <section xml:id="ref_list_accessing_state">
             <title>Accessing iteration state</title>
 
             <indexterm>
@@ -20876,7 +20952,7 @@ All rights reserved.</emphasis></programlisting>
             1}</literal>.</para>
           </section>
 
-          <section>
+          <section xml:id="ref_list_nesting">
             <title>Nesting loops into each other</title>
 
             <para>Naturally, <literal>list</literal> or
@@ -20919,7 +20995,7 @@ All rights reserved.</emphasis></programlisting>
   Outer again: 2</programlisting>
           </section>
 
-          <section>
+          <section xml:id="ref_list_java_notes">
             <title>Notes for Java programmers</title>
 
             <para><phrase role="forProgrammers">If classic compatible mode
@@ -26913,6 +26989,14 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
 
           <itemizedlist>
             <listitem>
+              <para>Added the <link
+              linkend="ref.directive.list.continue"><literal>continue</literal>
+              directive</link>, which can be used inside a
+              <literal>list</literal> to skip to the next iteration (similarly
+              as in Java).</para>
+            </listitem>
+
+            <listitem>
               <para>Added alternative syntaxes for the
               <literal>&amp;&amp;</literal> (logical <quote>and</quote>)
               operator: <literal>\and</literal> and

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/da570caa/src/test/java/freemarker/core/BreakAndContinuePlacementTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/BreakAndContinuePlacementTest.java b/src/test/java/freemarker/core/BreakAndContinuePlacementTest.java
new file mode 100644
index 0000000..e4ef01c
--- /dev/null
+++ b/src/test/java/freemarker/core/BreakAndContinuePlacementTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.Configuration;
+import freemarker.template.TemplateException;
+import freemarker.test.TemplateTest;
+
+public class BreakAndContinuePlacementTest extends TemplateTest {
+    
+    private static final String BREAK_NESTING_ERROR_MESSAGE_PART = "<#break> must be nested";
+    private static final String CONTINUE_NESTING_ERROR_MESSAGE_PART = "<#continue> must be nested";
+
+    @Test
+    public void testValidPlacements() throws IOException, TemplateException {
+        assertOutput("<#assign x = 1><#switch x><#case 1>one<#break><#case 2>two</#switch>", "one");
+        assertOutput("<#list 1..2 as x>${x}<#break></#list>", "1");
+        assertOutput("<#list 1..2 as x>${x}<#continue></#list>", "12");
+        assertOutput("<#list 1..2>[<#items as x>${x}<#break></#items>]</#list>", "[1]");
+        assertOutput("<#list 1..2 as x>${x}<#list 1..3>B<#break>E<#items as y></#items></#list>E</#list>.", "1B.");
+        assertOutput("<#list 1..2 as x>${x}<#list 3..4 as x>${x}<#break></#list>;</#list>", "13;23;");
+        assertOutput("<#list [1..2, 3..4, [], 5..6] as xs>[<#list xs as x>${x}<#else><#break></#list>]</#list>.",
+                "[12][34][.");
+        assertOutput("<#list [1..2, 3..4, [], 5..6] as xs>"
+                + "<#list xs>[<#items as x>${x}</#items>]<#else><#break></#list>"
+                + "</#list>.",
+                "[12][34].");
+        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");
+    }
+
+    @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("<#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);
+    }
+
+    @Test
+    public void testInvalidPlacementMacroLoophole() throws IOException, TemplateException {
+        final String ftl = "<#list 1..2 as x>${x}<#macro m><#break></#macro></#list>";
+        getConfiguration().setIncompatibleImprovements(Configuration.VERSION_2_3_22);
+        assertOutput(ftl, "12");
+        getConfiguration().setIncompatibleImprovements(Configuration.VERSION_2_3_23);
+        assertErrorContains(ftl, BREAK_NESTING_ERROR_MESSAGE_PART);
+        assertErrorContains(ftl.replaceAll("#break", "#continue"), CONTINUE_NESTING_ERROR_MESSAGE_PART);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/da570caa/src/test/java/freemarker/core/BreakPlacementTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/BreakPlacementTest.java b/src/test/java/freemarker/core/BreakPlacementTest.java
deleted file mode 100644
index 8844956..0000000
--- a/src/test/java/freemarker/core/BreakPlacementTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.Configuration;
-import freemarker.template.TemplateException;
-import freemarker.test.TemplateTest;
-
-public class BreakPlacementTest extends TemplateTest {
-    
-    private static final String BREAK_NESTING_ERROR_MESSAGE_PART = "<#break> must be nested";
-
-    @Test
-    public void testValidPlacements() throws IOException, TemplateException {
-        assertOutput("<#assign x = 1><#switch x><#case 1>one<#break><#case 2>two</#switch>", "one");
-        assertOutput("<#list 1..2 as x>${x}<#break></#list>", "1");
-        assertOutput("<#list 1..2>[<#items as x>${x}<#break></#items>]</#list>", "[1]");
-        assertOutput("<#list 1..2 as x>${x}<#list 1..3>B<#break>E<#items as y></#items></#list>E</#list>.", "1B.");
-        assertOutput("<#list 1..2 as x>${x}<#list 3..4 as x>${x}<#break></#list>;</#list>", "13;23;");
-        assertOutput("<#list [1..2, 3..4, [], 5..6] as xs>[<#list xs as x>${x}<#else><#break></#list>]</#list>.",
-                "[12][34][.");
-        assertOutput("<#list [1..2, 3..4, [], 5..6] as xs>"
-                + "<#list xs>[<#items as x>${x}</#items>]<#else><#break></#list>"
-                + "</#list>.",
-                "[12][34].");
-        assertOutput("<#forEach x in 1..2>${x}<#break></#forEach>", "1");
-    }
-
-    @Test
-    public void testInvalidPlacements() throws IOException, TemplateException {
-        assertErrorContains("<#break>", 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);
-    }
-
-    @Test
-    public void testInvalidPlacementMacroLoophole() throws IOException, TemplateException {
-        final String ftl = "<#list 1..2 as x>${x}<#macro m><#break></#macro></#list>";
-        getConfiguration().setIncompatibleImprovements(Configuration.VERSION_2_3_22);
-        assertOutput(ftl, "12");
-        getConfiguration().setIncompatibleImprovements(Configuration.VERSION_2_3_23);
-        assertErrorContains(ftl, BREAK_NESTING_ERROR_MESSAGE_PART);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/da570caa/src/test/java/freemarker/core/ListBreakContinueTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/ListBreakContinueTest.java b/src/test/java/freemarker/core/ListBreakContinueTest.java
new file mode 100644
index 0000000..8ee4af8
--- /dev/null
+++ b/src/test/java/freemarker/core/ListBreakContinueTest.java
@@ -0,0 +1,93 @@
+package freemarker.core;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapperBuilder;
+import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateHashModelEx;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.test.TemplateTest;
+
+public class ListBreakContinueTest extends TemplateTest {
+    
+    @Test
+    public void testNonHash() throws IOException, TemplateException {
+        testNonHash(ImmutableList.of(1, 2, 3, 4, 5)); // Listing a TemplateSequenceModel
+        testNonHash(ImmutableSet.of(1, 2, 3, 4, 5)); // Listing a TemplateCollectionModel
+    }
+    
+    private void testNonHash(Object listed) throws IOException, TemplateException {
+        addToDataModel("listed", listed);
+        assertOutput(
+                "<#list listed as i>B(${i}) <#if i == 3>Break!<#break></#if>A(${i})<#sep>, </#list>",
+                "B(1) A(1), B(2) A(2), B(3) Break!");
+        assertOutput(
+                "<#list listed as i>B(${i}) <#if i == 3>Continue! <#continue></#if>A(${i})<#sep>, </#list>",
+                "B(1) A(1), B(2) A(2), B(3) Continue! B(4) A(4), B(5) A(5)");
+    }
+
+    @Test
+    public void testHash() throws IOException, TemplateException {
+        testHash(ImmutableMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5)); // Listing a TemplateHashModelEx2
+        testHash(new NonEx2Hash((TemplateHashModelEx) getConfiguration().getObjectWrapper().wrap(
+                ImmutableMap.of("a", 1, "b", 2, "c", 3, "d", 4, "e", 5)))); // Listing a TemplateHashModelEx (non-Ex2)
+    }
+
+    private void testHash(Object listed) throws IOException, TemplateException {
+        addToDataModel("listed", listed);
+        assertOutput(
+                "<#list listed as k, v>B(${k}=${v}) <#if k == 'c'>Break!<#break></#if>A(${k}=${v})<#sep>, </#list>",
+                "B(a=1) A(a=1), B(b=2) A(b=2), B(c=3) Break!");
+        assertOutput(
+                "<#list listed as k, v>B(${k}=${v}) <#if k == 'c'>Continue! <#continue></#if>A(${k}=${v})<#sep>, </#list>",
+                "B(a=1) A(a=1), B(b=2) A(b=2), B(c=3) Continue! B(d=4) A(d=4), B(e=5) A(e=5)");
+    }
+    
+    @Override
+    protected Configuration createConfiguration() throws Exception {
+        Configuration conf = super.createConfiguration();
+        DefaultObjectWrapperBuilder owb = new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_27);
+        owb.setForceLegacyNonListCollections(false);
+        conf.setObjectWrapper(owb.build());
+        return conf;
+    }
+    
+    /** Hides the Ex2 features of another hash */
+    static class NonEx2Hash implements TemplateHashModelEx {
+        private final TemplateHashModelEx delegate;
+
+        public NonEx2Hash(TemplateHashModelEx delegate) {
+            this.delegate = delegate;
+        }
+
+        public TemplateModel get(String key) throws TemplateModelException {
+            return delegate.get(key);
+        }
+
+        public int size() throws TemplateModelException {
+            return delegate.size();
+        }
+
+        public TemplateCollectionModel keys() throws TemplateModelException {
+            return delegate.keys();
+        }
+
+        public boolean isEmpty() throws TemplateModelException {
+            return delegate.isEmpty();
+        }
+
+        public TemplateCollectionModel values() throws TemplateModelException {
+            return delegate.values();
+        }
+    }
+    
+}



[5/9] incubator-freemarker git commit: (README typo)

Posted by dd...@apache.org.
(README typo)


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/19501e41
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/19501e41
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/19501e41

Branch: refs/heads/2.3
Commit: 19501e419ee02d2552a20b6a80182a940a41ab59
Parents: 3aa6c50
Author: ddekany <dd...@apache.org>
Authored: Sat Sep 16 15:27:31 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Sep 16 15:27:31 2017 +0200

----------------------------------------------------------------------
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/19501e41/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 9ed8a0d..779df07 100644
--- a/README.md
+++ b/README.md
@@ -139,7 +139,7 @@ apply it to your development environment:
 - Window -> Preferences
   - General -> Workspace, set the text file encoding
     to "UTF-8". (Or, you can set the same later on project level instead.)
-  - General -> Editors, set:
+  - General -> Editors -> Text Editors, set:
     - Insert space for tabs
     - Show print margin, 120 columns
   - Java -> Code Style -> Formatter -> Import...


[6/9] incubator-freemarker git commit: Added a new BeansWrapper setting, preferIndexedReadMethod. With this one can address the Java 8 compatibility problem with indexed property read methods without changing the incompatibleImprovements of the object wr

Posted by dd...@apache.org.
Added a new BeansWrapper setting, preferIndexedReadMethod. With this one can address the Java 8 compatibility problem with indexed property read methods without changing the incompatibleImprovements of the object wrapper.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/3dfa8ce8
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/3dfa8ce8
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/3dfa8ce8

Branch: refs/heads/2.3
Commit: 3dfa8ce8a74a09de9e0830a364d5371802472f4f
Parents: 19501e4
Author: ddekany <dd...@apache.org>
Authored: Sat Sep 16 16:52:37 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Sep 16 16:52:37 2017 +0200

----------------------------------------------------------------------
 .../java/freemarker/ext/beans/BeanModel.java    |  4 +-
 .../java/freemarker/ext/beans/BeansWrapper.java | 65 +++++++++++---------
 .../ext/beans/BeansWrapperConfiguration.java    | 15 +++++
 src/manual/en_US/book.xml                       | 28 ++++++---
 .../freemarker/template/ConfigurationTest.java  |  4 ++
 .../template/DefaultObjectWrapperTest.java      | 11 +++-
 6 files changed, 86 insertions(+), 41 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3dfa8ce8/src/main/java/freemarker/ext/beans/BeanModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/beans/BeanModel.java b/src/main/java/freemarker/ext/beans/BeanModel.java
index 312ec98..4139b09 100644
--- a/src/main/java/freemarker/ext/beans/BeanModel.java
+++ b/src/main/java/freemarker/ext/beans/BeanModel.java
@@ -50,7 +50,6 @@ import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateModelIterator;
 import freemarker.template.TemplateModelWithAPISupport;
 import freemarker.template.TemplateScalarModel;
-import freemarker.template._TemplateAPI;
 import freemarker.template.utility.StringUtil;
 
 /**
@@ -221,8 +220,7 @@ implements
         TemplateModel resultModel = UNKNOWN;
         if (desc instanceof IndexedPropertyDescriptor) {
             IndexedPropertyDescriptor pd = (IndexedPropertyDescriptor) desc;
-            if (wrapper.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_27
-                    && pd.getReadMethod() != null) {
+            if (!wrapper.getPreferIndexedReadMethod() && pd.getReadMethod() != null) {
                 resultModel = wrapper.invokeMethod(object, pd.getReadMethod(), null);
                 // cachedModel remains null, as we don't cache these
             } else {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3dfa8ce8/src/main/java/freemarker/ext/beans/BeansWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index 0195234..3dbb3e1 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -179,11 +179,12 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
     private volatile boolean writeProtected;
     
     private TemplateModel nullModel = null;
-    private int defaultDateType; // initialized by PropertyAssignments.apply
+    private int defaultDateType; // initialized from the BeansWrapperConfiguration
     private ObjectWrapper outerIdentity = this;
     private boolean methodsShadowItems = true;
-    private boolean simpleMapWrapper;  // initialized by PropertyAssignments.apply
-    private boolean strict;  // initialized by PropertyAssignments.apply
+    private boolean simpleMapWrapper;  // initialized from the BeansWrapperConfiguration
+    private boolean strict;  // initialized from the BeansWrapperConfiguration
+    private boolean preferIndexedReadMethod; // initialized from the BeansWrapperConfiguration
     
     private final Version incompatibleImprovements;
     
@@ -250,13 +251,8 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
      *     </li>  
      *     <li>
      *       <p>2.3.27 (or higher):
-     *       If the same JavaBean property has both an indexed property reader (like {@code String getFoo(int)}) and
-     *       a non-indexed property reader (like {@code String[] getFoo()}), and {@link Introspector} exposes both
-     *       (which apparently only happens since Java 8), we will use the non-indexed property reader method, while
-     *       before this improvement we have used the indexed property method. When using the indexed property reader,
-     *       FreeMarker doesn't know the size of the array, so the value becomes unlistable. Before Java 8 this problem
-     *       haven't surfaced, as {@link Introspector} has only exposed the non-indexed property reader method when both
-     *       kind of read method was present. So this can be seen as a Java 8 compatibility fix.  
+     *       The default of the {@link #setPreferIndexedReadMethod(boolean) preferIndexedReadMethod} setting changes
+     *       from {@code true} to {@code false}.
      *     </li>  
      *   </ul>
      *   
@@ -344,6 +340,7 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
         this.incompatibleImprovements = bwConf.getIncompatibleImprovements();  // normalized
         
         simpleMapWrapper = bwConf.isSimpleMapWrapper();
+        preferIndexedReadMethod =  bwConf.getPreferIndexedReadMethod();
         defaultDateType = bwConf.getDefaultDateType();
         outerIdentity = bwConf.getOuterIdentity() != null ? bwConf.getOuterIdentity() : this;
         strict = bwConf.isStrict();
@@ -523,31 +520,40 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
         return simpleMapWrapper;
     }
 
-    // I have commented this out, as it won't be in 2.3.20 yet.
-    /*
     /**
-     * Tells which non-backward-compatible overloaded method selection fixes to apply;
-     * see {@link #setOverloadedMethodSelection(Version)}.
-     * /
-    public Version getOverloadedMethodSelection() {
-        return overloadedMethodSelection;
+     * Getter pair of {@link #setPreferIndexedReadMethod(boolean)} 
+     * 
+     * @since 2.3.27
+     */
+    public boolean getPreferIndexedReadMethod() {
+        return preferIndexedReadMethod;
     }
 
     /**
-     * Sets which non-backward-compatible overloaded method selection fixes to apply.
-     * This has similar logic as {@link Configuration#setIncompatibleImprovements(Version)},
-     * but only applies to this aspect.
+     * Sets if when a JavaBean property has both a normal read method (like {@code String[] getFoos()}) and an indexed
+     * read method (like {@code String getFoos(int index)}), and the Java {@link Introspector} exposes both (which only
+     * happens since Java 8, apparently), which read method will be used when the property is accessed with the
+     * shorthand syntax (like {@code myObj.foos}). Before {@link #getIncompatibleImprovements() incompatibleImprovements}
+     * 2.3.27 it defaults to {@code true} for backward compatibility (although it's actually less backward compatible if
+     * you are just switching to Java 8; see later), but the recommended value and the default starting with
+     * {@link #getIncompatibleImprovements() incompatibleImprovements} 2.3.27 is {@code false}. This setting has no
+     * effect on properties that only has normal read method, or only has indexed read method. In case a property has
+     * both, using the indexed reader method is disadvantageous, as then FreeMarker can't tell what the highest allowed
+     * index is, and so the property will be unlistable ({@code <#list foo as myObj.foos>} will fail).
      * 
-     * Currently significant values:
-     * <ul>
-     *   <li>2.3.21: Completetlly rewritten overloaded method selection, fixes several issues with the old one.</li>
-     * </ul>
-     * /
-    public void setOverloadedMethodSelection(Version version) {
-        overloadedMethodSelection = version;
+     * <p>
+     * Apparently, this setting only matters since Java 8, as before that {@link Introspector} did not expose the
+     * indexed reader method if there was also a normal reader method. As with Java 8 the behavior of
+     * {@link Introspector} has changed, some old templates started to break, as the property has suddenly become
+     * unlistable (see earlier why). So setting this to {@code false} can be seen as a Java 8 compatibility fix.
+     * 
+     * @since 2.3.27
+     */
+    public void setPreferIndexedReadMethod(boolean preferIndexedReadMethod) {
+        checkModifiable();
+        this.preferIndexedReadMethod = preferIndexedReadMethod;
     }
-    */
-    
+
     /**
      * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>.
      * @param exposureLevel can be any of the <code>EXPOSE_xxx</code>
@@ -1746,6 +1752,7 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
         return "simpleMapWrapper=" + simpleMapWrapper + ", "
                + "exposureLevel=" + classIntrospector.getExposureLevel() + ", "
                + "exposeFields=" + classIntrospector.getExposeFields() + ", "
+               + "preferIndexedReadMethod=" + preferIndexedReadMethod + ", "
                + "treatDefaultMethodsAsBeanMembers="
                + classIntrospector.getTreatDefaultMethodsAsBeanMembers() + ", "
                + "sharedClassIntrospCache="

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3dfa8ce8/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
index ba19633..905bde9 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
@@ -46,6 +46,7 @@ public abstract class BeansWrapperConfiguration implements Cloneable {
     
     // Properties and their *defaults*:
     private boolean simpleMapWrapper = false;
+    private boolean preferIndexedReadMethod;
     private int defaultDateType = TemplateDateModel.UNKNOWN;
     private ObjectWrapper outerIdentity = null;
     private boolean strict = false;
@@ -81,6 +82,8 @@ public abstract class BeansWrapperConfiguration implements Cloneable {
                 : BeansWrapper.normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
         this.incompatibleImprovements = incompatibleImprovements;
         
+        preferIndexedReadMethod = incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_27;
+        
         classIntrospectorBuilder = new ClassIntrospectorBuilder(incompatibleImprovements);
     }
     
@@ -97,6 +100,7 @@ public abstract class BeansWrapperConfiguration implements Cloneable {
         int result = 1;
         result = prime * result + incompatibleImprovements.hashCode();
         result = prime * result + (simpleMapWrapper ? 1231 : 1237);
+        result = prime * result + (preferIndexedReadMethod ? 1231 : 1237);
         result = prime * result + defaultDateType;
         result = prime * result + (outerIdentity != null ? outerIdentity.hashCode() : 0);
         result = prime * result + (strict ? 1231 : 1237);
@@ -118,6 +122,7 @@ public abstract class BeansWrapperConfiguration implements Cloneable {
         
         if (!incompatibleImprovements.equals(other.incompatibleImprovements)) return false;
         if (simpleMapWrapper != other.simpleMapWrapper) return false;
+        if (preferIndexedReadMethod != other.preferIndexedReadMethod) return false;
         if (defaultDateType != other.defaultDateType) return false;
         if (outerIdentity != other.outerIdentity) return false;
         if (strict != other.strict) return false;
@@ -148,6 +153,16 @@ public abstract class BeansWrapperConfiguration implements Cloneable {
     public void setSimpleMapWrapper(boolean simpleMapWrapper) {
         this.simpleMapWrapper = simpleMapWrapper;
     }
+    
+    /** @since 2.3.27 */
+    public boolean getPreferIndexedReadMethod() {
+        return preferIndexedReadMethod;
+    }
+
+    /** See {@link BeansWrapper#setPreferIndexedReadMethod(boolean)}. @since 2.3.27 */
+    public void setPreferIndexedReadMethod(boolean preferIndexedReadMethod) {
+        this.preferIndexedReadMethod = preferIndexedReadMethod;
+    }
 
     public int getDefaultDateType() {
         return defaultDateType;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3dfa8ce8/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index e127a79..f1dc57a 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -26974,18 +26974,26 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>Added new <literal>BeansWrapper</literal> setting,
+              <literal>preferIndexedReadMethod</literal>. This was added to
+              address a Java 8 compatibility problem; see the bug fix entry
+              below for more information.</para>
+            </listitem>
+
+            <listitem>
               <para>Bug fixed: <literal>BeansWrapper</literal> and
               <literal>DefaultObjectWrapper</literal>, starting from Java 8,
               when the same JavaBeans property has both non-indexed read
-              method (like <literal>String[] getFoo()</literal>) and indexed
-              read method (like <literal>String getFoo(int index)</literal>),
+              method (like <literal>String[] getFoos()</literal>) and indexed
+              read method (like <literal>String getFoos(int index)</literal>),
               has mistakenly used the indexed read method to access the
               property. This is a problem because then the array size was
               unknown, and thus the property has suddenly become unlistable on
-              Java 8. To enable the fix (where it will use the non-indexed
-              read method), you have to increase the value of the
-              <literal>incompatibleImprovements</literal> constructor argument
-              of the used <literal>DefaultObjectWrapper</literal> or
+              Java 8 (that is, <literal>&lt;#list myObject.foos as
+              foo&gt;</literal> fails). To enable the fix (where it will use
+              the non-indexed read method), you should to increase the value
+              of the <literal>incompatibleImprovements</literal> constructor
+              argument of the used <literal>DefaultObjectWrapper</literal> or
               <literal>BeansWrapper</literal> to 2.3.27. Note that if you
               leave the <literal>object_wrapper</literal> setting of the
               <literal>Configuration</literal> on its default, it's enough to
@@ -26993,8 +27001,12 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               linkend="pgui_config_incompatible_improvements_how_to_set"><literal>incompatibleImprovements</literal>
               setting</link> of the <literal>Configuration</literal> to
               2.3.27, as that's inherited by the default
-              <literal>object_wrapper</literal>. Note that this bug haven't
-              surfaced before Java 8, as then
+              <literal>object_wrapper</literal>. In case increasing the
+              <literal>incompatibleImprovements</literal> is not an option
+              (because of the other changes it brings), you can instead set
+              the <literal>preferIndexedReadMethod</literal> property of the
+              object wrapper to <literal>false</literal>. Note that this bug
+              haven't surfaced before Java 8, as then
               <literal>java.beans.Inrospector</literal> has only exposed the
               non-indexed method when both kind of read method was
               present.</para>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3dfa8ce8/src/test/java/freemarker/template/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index 2beeff0..226306f 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -167,6 +167,10 @@ public class ConfigurationTest extends TestCase {
         assertFalse(((DefaultObjectWrapper) cfg.getObjectWrapper()).getTreatDefaultMethodsAsBeanMembers());
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_26);
         assertTrue(((DefaultObjectWrapper) cfg.getObjectWrapper()).getTreatDefaultMethodsAsBeanMembers());
+        assertTrue(((DefaultObjectWrapper) cfg.getObjectWrapper()).getPreferIndexedReadMethod());
+        cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_27);
+        assertTrue(((DefaultObjectWrapper) cfg.getObjectWrapper()).getTreatDefaultMethodsAsBeanMembers());
+        assertFalse(((DefaultObjectWrapper) cfg.getObjectWrapper()).getPreferIndexedReadMethod());
     }
 
     private void assertUses2322ObjectWrapper(Configuration cfg) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3dfa8ce8/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
index 26bcf9a..833f509 100644
--- a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
+++ b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
@@ -1001,7 +1001,7 @@ public class DefaultObjectWrapperTest {
     }
     
     @Test
-    public void assertCanWrapDOM() throws SAXException, IOException, ParserConfigurationException,
+    public void testCanWrapDOM() throws SAXException, IOException, ParserConfigurationException,
             TemplateModelException {
         DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder();
         InputSource is = new InputSource();
@@ -1009,6 +1009,15 @@ public class DefaultObjectWrapperTest {
         Document doc = db.parse(is);        
         assertTrue(OW22.wrap(doc) instanceof TemplateNodeModel);
     }
+
+    @Test
+    public void testPreferIndexedReadMethodAndIcI() {
+        assertTrue(new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_26).build().getPreferIndexedReadMethod());
+        assertTrue(new DefaultObjectWrapper(Configuration.VERSION_2_3_26).getPreferIndexedReadMethod());
+        
+        assertFalse(new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_27).build().getPreferIndexedReadMethod());
+        assertFalse(new DefaultObjectWrapper(Configuration.VERSION_2_3_27).getPreferIndexedReadMethod());
+    }
     
     private void assertSizeThroughAPIModel(int expectedSize, TemplateModel normalModel) throws TemplateModelException {
         if (!(normalModel instanceof TemplateModelWithAPISupport)) {