DefaultObjectWrapper, only with its incompatible_improvements set to 2.3.26, 
wraps java.util.Enumeration-s into  
freemarker.template.DefaultEnumerationAdapter (a new class) instead of into 
freemarker.ext.beans.EnumerationModel (as far as useAdaptersForContainers is 
true, which is the default). This adapter is cleaner than EnumerationModel as 
it only implements the minimally required FTL type, which avoids some ambiguous 
situations. (Note that Java API methods aren't exposed anymore as subvariables; 
if you really need them, you can use ?api).


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

Branch: refs/heads/2.3
Commit: 544e9105c54c0afbdcea6d2eac00cf19aa0f4daa
Parents: 593d35d
Author: ddekany <[email protected]>
Authored: Sun Mar 12 15:42:24 2017 +0100
Committer: ddekany <[email protected]>
Committed: Sun Mar 12 15:42:24 2017 +0100

----------------------------------------------------------------------
 .../template/DefaultEnumerationAdapter.java     | 96 ++++++++++++++++++++
 .../template/DefaultObjectWrapper.java          | 16 ++++
 src/manual/en_US/book.xml                       | 22 ++++-
 .../template/DefaultObjectWrapperTest.java      | 40 ++++++++
 4 files changed, 172 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/544e9105/src/main/java/freemarker/template/DefaultEnumerationAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/DefaultEnumerationAdapter.java 
b/src/main/java/freemarker/template/DefaultEnumerationAdapter.java
new file mode 100644
index 0000000..570e0e8
--- /dev/null
+++ b/src/main/java/freemarker/template/DefaultEnumerationAdapter.java
@@ -0,0 +1,96 @@
+package freemarker.template;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+import java.util.Iterator;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import freemarker.ext.util.WrapperTemplateModel;
+
+/**
+ * Adapts an {@link Enumeration} to the corresponding {@link TemplateModel} 
interface(s), most importantly to
+ * {@link TemplateCollectionModel}. Putting aside that it wraps an {@link 
Enumeration} instead of an {@link Iterator},
+ * this is identical to {@link DefaultIteratorAdapter}, so see further details 
there.
+ * 
+ * @since 2.3.26
+ */
+@SuppressWarnings("serial")
+public class DefaultEnumerationAdapter extends WrappingTemplateModel 
implements TemplateCollectionModel,
+        AdapterTemplateModel, WrapperTemplateModel, Serializable {
+
+    @SuppressFBWarnings(value="SE_BAD_FIELD", justification="We hope it's 
Seralizable")
+    private final Enumeration<?> enumeration;
+    private boolean enumerationOwnedBySomeone;
+
+    /**
+     * Factory method for creating new adapter instances.
+     *
+     * @param enumeration
+     *            The enumeration to adapt; can't be {@code null}.
+     */
+    public static DefaultEnumerationAdapter adapt(Enumeration<?> enumeration, 
ObjectWrapper wrapper) {
+        return new DefaultEnumerationAdapter(enumeration, wrapper);
+    }
+
+    private DefaultEnumerationAdapter(Enumeration<?> enumeration, 
ObjectWrapper wrapper) {
+        super(wrapper);
+        this.enumeration = enumeration;
+    }
+
+    @Override
+    public Object getWrappedObject() {
+        return enumeration;
+    }
+
+    @Override
+    public Object getAdaptedObject(Class<?> hint) {
+        return getWrappedObject();
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateModelException {
+        return new SimpleTemplateModelIterator();
+    }
+
+    /**
+     * Not thread-safe.
+     */
+    private class SimpleTemplateModelIterator implements TemplateModelIterator 
{
+
+        private boolean enumerationOwnedByMe;
+
+        @Override
+        public TemplateModel next() throws TemplateModelException {
+            if (!enumerationOwnedByMe) {
+                checkNotOwner();
+                enumerationOwnedBySomeone = true;
+                enumerationOwnedByMe = true;
+            }
+
+            if (!enumeration.hasMoreElements()) {
+                throw new TemplateModelException("The collection has no more 
items.");
+            }
+
+            Object value = enumeration.nextElement();
+            return value instanceof TemplateModel ? (TemplateModel) value : 
wrap(value);
+        }
+
+        @Override
+        public boolean hasNext() throws TemplateModelException {
+            // Calling hasNext may looks safe, but I have met sync. problems.
+            if (!enumerationOwnedByMe) {
+                checkNotOwner();
+            }
+
+            return enumeration.hasMoreElements();
+        }
+
+        private void checkNotOwner() throws TemplateModelException {
+            if (enumerationOwnedBySomeone) {
+                throw new TemplateModelException(
+                        "This collection value wraps a java.util.Enumeration, 
thus it can be listed only once.");
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/544e9105/src/main/java/freemarker/template/DefaultObjectWrapper.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapper.java 
b/src/main/java/freemarker/template/DefaultObjectWrapper.java
index 1398be4..4c5a39d 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapper.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapper.java
@@ -22,6 +22,7 @@ package freemarker.template;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Enumeration;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -31,6 +32,7 @@ import org.w3c.dom.Node;
 
 import freemarker.ext.beans.BeansWrapper;
 import freemarker.ext.beans.BeansWrapperConfiguration;
+import freemarker.ext.beans.EnumerationModel;
 import freemarker.ext.dom.NodeModel;
 import freemarker.log.Logger;
 
@@ -68,6 +70,7 @@ public class DefaultObjectWrapper extends 
freemarker.ext.beans.BeansWrapper {
     private boolean useAdaptersForContainers;
     private boolean forceLegacyNonListCollections;
     private boolean iterableSupport;
+    private final boolean useAdapterForEnumerations;
     
     /**
      * Creates a new instance with the incompatible-improvements-version 
specified in
@@ -97,6 +100,13 @@ public class DefaultObjectWrapper extends 
freemarker.ext.beans.BeansWrapper {
      *                  won't cause the a later iteration (or further 
emptiness check) to fail anymore. Earlier, in
      *                  certain situations, the second operation has failed 
saying that the iterator "can be listed only
      *                  once".  
+     *              <li>2.3.26 (or higher): {@link Enumeration}-s are wrapped 
into {@link DefaultEnumerationAdapter}
+     *                  instead of into {@link EnumerationModel} (as far as
+     *                  {@link #setUseAdaptersForContainers(boolean) 
useAdaptersForContainers} is {@code true}, which is
+     *                  the default). This adapter is cleaner than {@link 
EnumerationModel} as it only implements the
+     *                  minimally required FTL type, which avoids some 
ambiguous situations. (Note that Java API methods
+     *                  aren't exposed anymore as subvariables; if you really 
need them, you can use {@code ?api}). 
+     *                  </li>
      *            </ul>
      * 
      * @since 2.3.21
@@ -117,6 +127,8 @@ public class DefaultObjectWrapper extends 
freemarker.ext.beans.BeansWrapper {
                 ? (DefaultObjectWrapperConfiguration) bwCfg
                 : new 
DefaultObjectWrapperConfiguration(bwCfg.getIncompatibleImprovements()) { }; 
         useAdaptersForContainers = dowDowCfg.getUseAdaptersForContainers();
+        useAdapterForEnumerations = useAdaptersForContainers
+                && getIncompatibleImprovements().intValue() >= 
_TemplateAPI.VERSION_INT_2_3_26;
         forceLegacyNonListCollections = 
dowDowCfg.getForceLegacyNonListCollections();
         iterableSupport = dowDowCfg.getIterableSupport();
         finalizeConstruction(writeProtected);
@@ -226,9 +238,13 @@ public class DefaultObjectWrapper extends 
freemarker.ext.beans.BeansWrapper {
                     ? (TemplateModel) 
DefaultIteratorAdapter.adapt((Iterator<?>) obj, this)
                     : (TemplateModel) new SimpleCollection((Iterator<?>) obj, 
this);
         }
+        if (useAdapterForEnumerations && obj instanceof Enumeration) {
+            return DefaultEnumerationAdapter.adapt((Enumeration<?>) obj, this);
+        }        
         if (iterableSupport && obj instanceof Iterable) {
             return DefaultIterableAdapter.adapt((Iterable<?>) obj, this);
         }
+        
         return handleUnknownType(obj);
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/544e9105/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 3e0814f..beee581 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -7,9 +7,9 @@
   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
@@ -26887,6 +26887,24 @@ TemplateModel x = env.getVariable("x");  // get 
variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para><literal>DefaultObjectWrapper</literal>, only with its
+              <literal>incompatible_improvements</literal> set to 2.3.26
+              (<link linkend="topic.defaultObjectWrapperIcI">see how
+              here...</link>), wraps
+              <literal>java.util.Enumeration</literal>-s into
+              <literal>freemarker.template.DefaultEnumerationAdapter</literal>
+              (a new class) instead of into
+              <literal>freemarker.ext.beans.EnumerationModel</literal> (as far
+              as <literal>useAdaptersForContainers</literal> is
+              <literal>true</literal>, which is the default). This adapter is
+              cleaner than <literal>EnumerationModel</literal> as it only
+              implements the minimally required FTL type, which avoids some
+              ambiguous situations. (Note that Java API methods aren't exposed
+              anymore as subvariables; if you really need them, you can use
+              <literal>?api</literal>).</para>
+            </listitem>
+
+            <listitem>
               <para>Better error messages when someone tries to get an invalid
               <literal>@@<replaceable>...</replaceable></literal> subvariable
               of an XML DOM node. (Now it's not issued by the XPath

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/544e9105/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 5d3ad2b..4be352a 100644
--- a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
+++ b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
@@ -39,6 +39,7 @@ import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.Vector;
 
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
@@ -53,6 +54,7 @@ import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
 import freemarker.ext.beans.BeansWrapper;
+import freemarker.ext.beans.EnumerationModel;
 import freemarker.ext.beans.HashAdapter;
 import freemarker.ext.util.WrapperTemplateModel;
 
@@ -943,6 +945,44 @@ public class DefaultObjectWrapperTest {
             assertTemplateOutput(OW22IS, iterable, listingFTL, "a, b, c");
         }
     }
+
+    @Test
+    public void testNoEnumerationAdapter() throws TemplateModelException {
+         DefaultObjectWrapper ow = new 
DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_25).build();
+         Vector<String> vector = new Vector<String>();
+         vector.add("a");
+         vector.add("b");
+         
+         TemplateModel wrappedEnumeration = ow.wrap(vector.elements());
+         assertThat(wrappedEnumeration, instanceOf(EnumerationModel.class));
+         EnumerationModel enumModel = (EnumerationModel) wrappedEnumeration;
+         assertNotNull(enumModel.get("nextElement"));
+    }
+    
+    @Test
+    public void testEnumerationAdapter() throws TemplateModelException {
+         DefaultObjectWrapper ow = new 
DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_26).build();
+         Vector<String> vector = new Vector<String>();
+         vector.add("a");
+         vector.add("b");
+         
+         TemplateModel wrappedEnumeration = ow.wrap(vector.elements());
+         assertThat(wrappedEnumeration, 
instanceOf(DefaultEnumerationAdapter.class));
+         DefaultEnumerationAdapter enumAdapter = (DefaultEnumerationAdapter) 
wrappedEnumeration;
+         TemplateModelIterator iterator = enumAdapter.iterator();
+         assertTrue(iterator.hasNext());
+         assertEquals("a", ((TemplateScalarModel) 
iterator.next()).getAsString());
+         assertTrue(iterator.hasNext());
+         assertEquals("b", ((TemplateScalarModel) 
iterator.next()).getAsString());
+         assertFalse(iterator.hasNext());
+         
+         iterator = enumAdapter.iterator();
+         try {
+             iterator.hasNext();
+         } catch (TemplateException e) {
+             assertThat(e.getMessage(), containsStringIgnoringCase("only 
once"));
+         }
+    }
     
     @Test
     public void assertCanWrapDOM() throws SAXException, IOException, 
ParserConfigurationException,

Reply via email to