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/05/21 21:35:00 UTC

[5/6] incubator-freemarker git commit: Factored out freemarker-dom from freemarker-core. Also added mechanism to "inject" DOM wrapping capability into DefaultObjectWrapper on configuration time. See details below.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/xmlns5.ftl
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/xmlns5.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/xmlns5.ftl
deleted file mode 100644
index edc3b4a..0000000
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/xmlns5.ftl
+++ /dev/null
@@ -1,28 +0,0 @@
-<#ftl ns_prefixes = {"D": "http://y.com", "xx" : "http://x.com"}>
-<#--
-  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.
--->
-<#assign r = doc["N:root"]>
-${r["N:t1"][0]?default('-')} = No NS
-${r["xx:t2"][0]?default('-')} = x NS
-${r["t3"][0]?default('-')} = y NS
-${r["xx:t4"][0]?default('-')} = x NS
-${r["//t1"][0]?default('-')} = No NS
-${r["//t2"][0]?default('-')} = -
-${r["//t3"][0]?default('-')} = -
-${r["//t4"][0]?default('-')} = -

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml
index 866da69..159266b 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/testcases.xml
@@ -73,7 +73,6 @@ Note that for the incompatible_improvements setting you can specify a list of ve
    <testCase name="default-object-wrapper">
       <setting api_builtin_enabled="true" />
    </testCase>
-   <testCase name="default-xmlns" />
    <testCase name="encoding-builtins" />
    <testCase name="escapes" />
    <testCase name="hashliteral" />
@@ -172,13 +171,6 @@ Note that for the incompatible_improvements setting you can specify a list of ve
    <testCase name="variables"/>
    <testCase name="whitespace-trim"/>
    <testCase name="wstrip-in-header"/>
-   <testCase name="xml-fragment" />
-   <testCase name="xmlns1" />
-   <testCase name="xmlns2" template="xmlns1.ftl"  expected="xmlns1.txt" />
-   <testCase name="xmlns3" />
-   <testCase name="xmlns4" />
-   <testCase name="xmlns5" />
-   <testCase name="xml-ns_prefix-scope" template="xml-ns_prefix-scope-main.ftl" />
    <testCase name="hashconcat"/>
    <testCase name="new-defaultresolver" />
    <testCase name="new-unrestricted" template="new-defaultresolver.ftl" expected="new-defaultresolver.txt">

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core-test/src/test/resources/org/apache/freemarker/dom/DOMSiblingTest.xml
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/dom/DOMSiblingTest.xml b/freemarker-core-test/src/test/resources/org/apache/freemarker/dom/DOMSiblingTest.xml
deleted file mode 100644
index d1fe3dc..0000000
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/dom/DOMSiblingTest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.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.
--->
-<person>
-    <gender>male</gender>
-    <name>pradeep</name>
-    <dob>12th August</dob><address>Chennai, India</address>
-    <!--This is a comment Node -->
-    <?xml-stylesheet type="text/css" href="style.css"?>
-    <profession>Software Engineer</profession>
-    <![CDATA[    ]]>
-    <hobby>gardening</hobby>
-    <![CDATA[this is a valid cdata]]>
-    <phone>12345678</phone>
-</person>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/build.gradle
----------------------------------------------------------------------
diff --git a/freemarker-core/build.gradle b/freemarker-core/build.gradle
index 9de09c4..5adcf87 100644
--- a/freemarker-core/build.gradle
+++ b/freemarker-core/build.gradle
@@ -27,21 +27,9 @@ FreeMarker template engine, core module. This module covers all basic functional
 many applications."""
 
 dependencies {
-    // Note that commond dependencies are added in the root project.
-
-    // ------------------------------------------------------------------------
-    // For the main artifact
+    // Note that common dependencies are added in the root project.
 
     compileOnly "org.zeroturnaround:javarebel-sdk:1.2.2"
-
-    // TODO These will be moved to freemarker-dom module:
-    
-    compileOnly "jaxen:jaxen:1.0-FCS"
-    compileOnly "saxpath:saxpath:1.0-FCS"
-    compileOnly("xalan:xalan:2.7.0") {
-        // xml-apis is part of Java SE since version 1.4:
-        exclude group: "xml-apis", module: "xml-apis"
-    }
 }
 
 compileJavacc {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateNodeModelEx.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateNodeModelEx.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateNodeModelEx.java
index acf43df..0fa7e46 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateNodeModelEx.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateNodeModelEx.java
@@ -19,10 +19,8 @@
 
 package org.apache.freemarker.core.model;
 
-import org.apache.freemarker.dom.NodeModel;
-
 /**
- * A {@link NodeModel} that supports navigating to the previous and next sibling nodes.
+ * A {@link TemplateNodeModel} that supports navigating to the previous and next sibling nodes.
  * 
  * @since 2.3.26
  */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
index 7e04776..09e1e16 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
@@ -28,6 +28,8 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -65,8 +67,7 @@ import org.apache.freemarker.core.model.WrapperTemplateModel;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util.CommonBuilder;
 import org.apache.freemarker.core.util._ClassUtil;
-import org.apache.freemarker.dom.NodeModel;
-import org.w3c.dom.Node;
+import org.apache.freemarker.core.util._NullArgumentException;
 
 /**
  * The default implementation of the {@link ObjectWrapper} interface. Usually, you don't need to invoke instances of
@@ -157,6 +158,11 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
     @Deprecated // Only exists to keep some JUnit tests working... [FM3]
     private final boolean useModelCache;
 
+    /** Extensions applicable at the beginning of wrap(Object); null if it would be 0 length otherwise. */
+    private final DefaultObjectWrapperExtension[] extensionsBeforeWrapSpecialObject;
+    /** Extensions applicable at the end of wrap(Object); null if it would be 0 length otherwise. */
+    private final DefaultObjectWrapperExtension[] extensionsAfterWrapSpecialObject;
+
     private final Version incompatibleImprovements;
 
     /**
@@ -165,7 +171,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
      * @param finalizeConstruction Decides if the construction is finalized now, or the caller will do some more
      *     adjustments on the instance and then call {@link #finalizeConstruction()} itself.
      */
-    protected DefaultObjectWrapper(ExtendableBuilder builder, boolean finalizeConstruction) {
+    protected DefaultObjectWrapper(ExtendableBuilder<?, ?> builder, boolean finalizeConstruction) {
         incompatibleImprovements = builder.getIncompatibleImprovements();  // normalized
 
         defaultDateType = builder.getDefaultDateType();
@@ -189,6 +195,33 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
         enumModels = new EnumModels(this);
         useModelCache = builder.getUseModelCache();
 
+        int extsAfterWSOCnt = 0;
+        int extsBeforeWSOCnt = 0;
+        for (DefaultObjectWrapperExtension ext : builder.getExtensions()) {
+            if (ext.getPhase() == DefaultObjectWrapperExtensionPhase.AFTER_WRAP_SPECIAL_OBJECT) {
+                extsAfterWSOCnt++;
+            } else if (ext.getPhase() == DefaultObjectWrapperExtensionPhase.BEFORE_WRAP_SPECIAL_OBJECT)  {
+                extsBeforeWSOCnt++;
+            } else {
+                throw new BugException();
+            }
+        }
+        extensionsAfterWrapSpecialObject = extsAfterWSOCnt != 0
+                ? new DefaultObjectWrapperExtension[extsAfterWSOCnt] : null;
+        extensionsBeforeWrapSpecialObject = extsBeforeWSOCnt != 0
+                ? new DefaultObjectWrapperExtension[extsBeforeWSOCnt] : null;
+        int extsAfterWSOIdx = 0;
+        int extsBeforeWSOIdx = 0;
+        for (DefaultObjectWrapperExtension ext : builder.getExtensions()) {
+            if (ext.getPhase() == DefaultObjectWrapperExtensionPhase.AFTER_WRAP_SPECIAL_OBJECT) {
+                extensionsAfterWrapSpecialObject[extsAfterWSOIdx++] = ext;
+            } else if (ext.getPhase() == DefaultObjectWrapperExtensionPhase.BEFORE_WRAP_SPECIAL_OBJECT)  {
+                extensionsBeforeWrapSpecialObject[extsBeforeWSOIdx++] = ext;
+            } else {
+                throw new BugException();
+            }
+        }
+
         finalizeConstruction();
     }
 
@@ -330,11 +363,20 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
     }
 
     /**
-     * Wraps the parameter object to {@link TemplateModel} interface(s). Simple types like numbers, strings, booleans
-     * and dates will be wrapped into the corresponding {@code SimpleXxx} classes (like {@link SimpleNumber}).
-     * {@link Map}-s, {@link List}-s, other {@link Collection}-s, arrays and {@link Iterator}-s will be wrapped into the
-     * corresponding {@code DefaultXxxAdapter} classes ({@link DefaultMapAdapter}), depending on). After that, the
-     * wrapping is handled by {@link #handleNonBasicTypes(Object)}, so see more there.
+     * Wraps the parameter object to {@link TemplateModel} interface(s). The wrapping logic uses several phases,
+     * where if a stage manages to wrap the object, this method immediately returns with the result. The stages are
+     * (executed in this order):
+     * <ol>
+     * <li>If the value is {@code null} or {@link TemplateModel} it's returned as is.</li>
+     * <li>If the value is a {@link TemplateModelAdapter}, {@link TemplateModelAdapter#getTemplateModel()} is
+     * returned.</li>
+     * <li>{@link ExtendableBuilder#extensions extensions} which subscribe to the
+     * {@link DefaultObjectWrapperExtensionPhase#BEFORE_WRAP_SPECIAL_OBJECT} phase try to wrap the object</li>
+     * <li>{@link #wrapSpecialObject(Object)} tries to wrap the object</li>
+     * <li>{@link ExtendableBuilder#extensions extensions} which subscribe to the
+     * {@link DefaultObjectWrapperExtensionPhase#AFTER_WRAP_SPECIAL_OBJECT} phase try to wrap the object</li>
+     * <li>{@link #wrapGenericObject(Object)} wraps the object (or if it can't, it must throw exception)</li>
+     * </ol>
      */
     @Override
     public TemplateModel wrap(Object obj) throws TemplateModelException {
@@ -348,6 +390,40 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
             return ((TemplateModelAdapter) obj).getTemplateModel();
         }
 
+        if (extensionsBeforeWrapSpecialObject != null) {
+            for (DefaultObjectWrapperExtension ext : extensionsBeforeWrapSpecialObject) {
+                TemplateModel tm = ext.wrap(obj);
+                if (tm != null) {
+                    return tm;
+                }
+            }
+        }
+
+        {
+            TemplateModel tm = wrapSpecialObject(obj);
+            if (tm != null) {
+                return tm;
+            }
+        }
+
+        if (extensionsAfterWrapSpecialObject != null) {
+            for (DefaultObjectWrapperExtension ext : extensionsAfterWrapSpecialObject) {
+                TemplateModel tm = ext.wrap(obj);
+                if (tm != null) {
+                    return tm;
+                }
+            }
+        }
+
+        return wrapGenericObject(obj);
+    }
+
+    /**
+     * Wraps non-generic objects; see {@link #wrap(Object)} for more.
+     *
+     * @return {@code null} if the object was not of a type that's wrapped "specially".
+     */
+    protected TemplateModel wrapSpecialObject(Object obj) {
         if (obj instanceof String) {
             return new SimpleScalar((String) obj);
         }
@@ -357,7 +433,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
         if (obj instanceof Boolean) {
             return obj.equals(Boolean.TRUE) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
         }
-        if (obj instanceof java.util.Date) {
+        if (obj instanceof Date) {
             if (obj instanceof java.sql.Date) {
                 return new SimpleDate((java.sql.Date) obj);
             }
@@ -367,7 +443,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
             if (obj instanceof java.sql.Timestamp) {
                 return new SimpleDate((java.sql.Timestamp) obj);
             }
-            return new SimpleDate((java.util.Date) obj, getDefaultDateType());
+            return new SimpleDate((Date) obj, getDefaultDateType());
         }
         final Class<?> objClass = obj.getClass();
         if (objClass.isArray()) {
@@ -390,30 +466,21 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
         if (obj instanceof Enumeration) {
             return DefaultEnumerationAdapter.adapt((Enumeration<?>) obj, this);
         }
-
-        return handleNonBasicTypes(obj);
+        if (obj instanceof ResourceBundle) {
+            return new ResourceBundleModel((ResourceBundle) obj, this);
+        }
+        return null;
     }
 
     /**
-     * Called for an object that isn't considered to be of a "basic" Java type, like for all application specific types,
-     * but currently also for {@link Node}-s and {@link ResourceBundle}-s.
-     *
+     * Called for an object that isn't treated specially by this {@link ObjectWrapper}; see {@link #wrap(Object)} for
+     * more. The implementation in {@link DefaultObjectWrapper} wraps the object into a {@link BeanAndStringModel}.
      * <p>
-     * When you override this method, you should first decide if you want to wrap the object in a custom way (and if so
-     * then do it and return with the result), and if not, then you should call the super method (assuming the default
-     * behavior is fine with you).
+     * Note that if you want to wrap some classes in a custom way, you shouldn't override this method. Instead, either
+     * override {@link #wrapSpecialObject(Object)}}, or don't subclass the {@link ObjectWrapper} at all, and
+     * just set the {@link ExtendableBuilder#getExtensions() extensions} configuration setting of it.
      */
-    // [FM3] This is an awkward temporary solution, rework it.
-    protected TemplateModel handleNonBasicTypes(Object obj) throws TemplateModelException {
-        // [FM3] Via plugin mechanism, not by default anymore
-        if (obj instanceof Node) {
-            return NodeModel.wrap((Node) obj);
-        }
-
-        if (obj instanceof ResourceBundle) {
-            return new ResourceBundleModel((ResourceBundle) obj, this);
-        }
-
+    protected TemplateModel wrapGenericObject(Object obj) throws TemplateModelException {
         return new BeanAndStringModel(obj, this);
     }
 
@@ -1339,10 +1406,12 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
         private boolean useModelCacheSet;
         private boolean usePrivateCaches;
         private boolean usePrivateCachesSet;
+        private List<DefaultObjectWrapperExtension> extensions = Collections.emptyList();
+        private boolean extensionsSet;
         // Attention!
         // - As this object is a cache key, non-normalized field values should be avoided.
-        // - Fields with default values must be set until the end of the constructor to ensure that when the lookup happens,
-        //   there will be no unset fields.
+        // - Fields with default values must be set until the end of the constructor to ensure that when the lookup
+        //   happens, there will be no unset fields.
         // - If you add a new field, review all methods in this class
 
         /**
@@ -1415,6 +1484,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
             result = prime * result + (builder.getUseModelCache() ? 1231 : 1237);
             result = prime * result + (builder.getUsePrivateCaches() ? 1231 : 1237);
             result = prime * result + builder.classIntrospectorBuilder.hashCode();
+            result = prime * result + builder.getExtensions().hashCode();
             return result;
         }
 
@@ -1443,6 +1513,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
             if (thisBuilder.isStrict() != thatBuilder.isStrict()) return false;
             if (thisBuilder.getUseModelCache() != thatBuilder.getUseModelCache()) return false;
             if (thisBuilder.getUsePrivateCaches() != thatBuilder.getUsePrivateCaches()) return false;
+            if (!thisBuilder.getExtensions().equals(thatBuilder.getExtensions())) return false;
             return thisBuilder.classIntrospectorBuilder.equals(thatBuilder.classIntrospectorBuilder);
         }
 
@@ -1631,8 +1702,8 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
         }
 
         /**
-         * Tells if the instance cerates should try to caches with other {@link DefaultObjectWrapper} instances (where
-         * possible), or it should always invoke its own caches and not share that with anyone else.
+         * Tells if the instance creates should share caches with other {@link DefaultObjectWrapper} instances
+         * (where possible), or it should always invoke its own caches and not share that with anyone else.
          * */
         public void setUsePrivateCaches(boolean usePrivateCaches) {
             this.usePrivateCaches = usePrivateCaches;
@@ -1654,6 +1725,56 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
             return self();
         }
 
+        /**
+         * Extensions are  used for dynamically decorating the {@link #wrap(Object)} method. In effects this is very
+         * similar to extending {@link DefaultObjectWrapper} and overriding its {@link #wrap(Object)}, and adding
+         * some wrapping logic before and/or after calling {@code super.wrap(obj)} (often referred to as decorating).
+         * But with this approach instead of subclassing {@link DefaultObjectWrapper} and its builder class, you
+         * simply list the desired extensions when you build the {@link DefaultObjectWrapper}. This is usually more
+         * convenient, and more flexible (what extensions you add can be decided on runtime factors) than the
+         * subclassing approach.
+         *
+         * @return An unmodifiable {@link List}.
+         */
+        public List<? extends DefaultObjectWrapperExtension> getExtensions() {
+            return extensions;
+        }
+
+        /**
+         * Setter pair of {@link #getExtensions()}.
+         *
+         * @param extensions The list of extensions; can't be {@code null}.
+         *                   The {@link List} list is copied, so further changes to the
+         *                   {@link List} passed in won't affect the value of this setting.
+         */
+        public void setExtensions(List<? extends DefaultObjectWrapperExtension> extensions) {
+            _NullArgumentException.check("extensions", extensions);
+            this.extensions = Collections.unmodifiableList(new ArrayList(extensions));
+            this.extensionsSet = true;
+        }
+
+        /**
+         * Fluent API equivalent of {@link #setExtensions(List)}.
+         */
+        public SelfT extensions(List<? extends DefaultObjectWrapperExtension> extensions) {
+            setExtensions(extensions);
+            return self();
+        }
+
+        /**
+         * Convenience varargs overload for calling {@link #extensions(List)}.
+         */
+        public SelfT extensions(DefaultObjectWrapperExtension... extensions) {
+            return extensions(Arrays.asList(extensions));
+        }
+
+        /**
+         * Tells if the property was explicitly set, as opposed to just holding its default value.
+         */
+        public boolean isExtensionsSet() {
+            return extensionsSet;
+        }
+
         public int getExposureLevel() {
             return classIntrospectorBuilder.getExposureLevel();
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtension.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtension.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtension.java
new file mode 100644
index 0000000..2d56486
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtension.java
@@ -0,0 +1,48 @@
+/*
+ * 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 org.apache.freemarker.core.model.impl;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelAdapter;
+
+/**
+ * See the {@link DefaultObjectWrapper.ExtendableBuilder#getExtensions() extensions} setting of
+ * {@link DefaultObjectWrapper}.
+ */
+public abstract class DefaultObjectWrapperExtension {
+
+    /**
+     * Specifies when {@link DefaultObjectWrapperExtension#wrap(Object)} is invoked inside
+     * {@link DefaultObjectWrapper#wrap(Object)}.
+     */
+    public DefaultObjectWrapperExtensionPhase getPhase() {
+        return DefaultObjectWrapperExtensionPhase.AFTER_WRAP_SPECIAL_OBJECT;
+    }
+
+    /**
+     * @param obj
+     *         The object to wrap; never {@code null} or a {@link TemplateModel} or a {@link TemplateModelAdapter}.
+     *
+     * @return {@code null} if this {@link DefaultObjectWrapperExtension} doesn't handle this object the wrapped object
+     * otherwise.
+     */
+    public abstract TemplateModel wrap(Object obj);
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtensionPhase.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtensionPhase.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtensionPhase.java
new file mode 100644
index 0000000..4ef1aa4
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperExtensionPhase.java
@@ -0,0 +1,39 @@
+/*
+ * 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 org.apache.freemarker.core.model.impl;
+
+/**
+ * Used for the return value of {@link DefaultObjectWrapperExtension#getPhase()}.
+ */
+public enum DefaultObjectWrapperExtensionPhase {
+
+    /**
+     * Indicates that the {@link DefaultObjectWrapperExtension} should be invoked before
+     * {@link DefaultObjectWrapper#wrapSpecialObject(Object)}.
+     */
+    BEFORE_WRAP_SPECIAL_OBJECT,
+
+    /**
+     * Indicates that the {@link DefaultObjectWrapperExtension} should be invoked after
+     * {@link DefaultObjectWrapper#wrapSpecialObject(Object)}.
+     */
+    AFTER_WRAP_SPECIAL_OBJECT
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java
index e456dc6..43d47c0 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/RestrictedObjectWrapper.java
@@ -45,7 +45,7 @@ public class RestrictedObjectWrapper extends DefaultObjectWrapper {
      * In this implementation, this just throws an exception.
      */
     @Override
-    protected TemplateModel handleNonBasicTypes(Object obj) throws TemplateModelException {
+    protected TemplateModel wrapGenericObject(Object obj) throws TemplateModelException {
         throw new TemplateModelException("RestrictedObjectWrapper deliberately won't wrap this type: "
                 + obj.getClass().getName());
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/AtAtKey.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/AtAtKey.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/AtAtKey.java
deleted file mode 100644
index ca6ac6b..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/AtAtKey.java
+++ /dev/null
@@ -1,58 +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 org.apache.freemarker.dom;
-
-/**
- * The special hash keys that start with "@@".
- */
-enum AtAtKey {
-    
-    MARKUP("@@markup"),
-    NESTED_MARKUP("@@nested_markup"),
-    ATTRIBUTES_MARKUP("@@attributes_markup"),
-    TEXT("@@text"),
-    START_TAG("@@start_tag"),
-    END_TAG("@@end_tag"),
-    QNAME("@@qname"),
-    NAMESPACE("@@namespace"),
-    LOCAL_NAME("@@local_name"),
-    ATTRIBUTES("@@"),
-    PREVIOUS_SIBLING_ELEMENT("@@previous_sibling_element"),
-    NEXT_SIBLING_ELEMENT("@@next_sibling_element");
-
-    private final String key;
-
-    public String getKey() {
-        return key;
-    }
-
-    AtAtKey(String key) {
-        this.key = key;
-    }
-    
-    public static boolean containsKey(String key) {
-        for (AtAtKey item : AtAtKey.values()) {
-            if (item.getKey().equals(key)) {
-                return true;
-            }
-        }
-        return false;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java
deleted file mode 100644
index cc510c4..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/AttributeNodeModel.java
+++ /dev/null
@@ -1,69 +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 org.apache.freemarker.dom;
-
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.w3c.dom.Attr;
-
-class AttributeNodeModel extends NodeModel implements TemplateScalarModel {
-    
-    public AttributeNodeModel(Attr att) {
-        super(att);
-    }
-    
-    @Override
-    public String getAsString() {
-        return ((Attr) node).getValue();
-    }
-    
-    @Override
-    public String getNodeName() {
-        String result = node.getLocalName();
-        if (result == null || result.equals("")) {
-            result = node.getNodeName();
-        }
-        return result;
-    }
-    
-    @Override
-    public boolean isEmpty() {
-        return true;
-    }
-    
-    @Override
-    String getQualifiedName() {
-        String nsURI = node.getNamespaceURI();
-        if (nsURI == null || nsURI.equals(""))
-            return node.getNodeName();
-        Environment env = Environment.getCurrentEnvironment();
-        String defaultNS = env.getDefaultNS();
-        String prefix = null;
-        if (nsURI.equals(defaultNS)) {
-            prefix = "D";
-        } else {
-            prefix = env.getPrefixForNamespace(nsURI);
-        }
-        if (prefix == null) {
-            return null;
-        }
-        return prefix + ":" + node.getLocalName();
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java
deleted file mode 100644
index 264c0db..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/CharacterDataNodeModel.java
+++ /dev/null
@@ -1,46 +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 org.apache.freemarker.dom;
-
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.w3c.dom.CharacterData;
-import org.w3c.dom.Comment;
-
-class CharacterDataNodeModel extends NodeModel implements TemplateScalarModel {
-    
-    public CharacterDataNodeModel(CharacterData text) {
-        super(text);
-    }
-    
-    @Override
-    public String getAsString() {
-        return ((org.w3c.dom.CharacterData) node).getData();
-    }
-    
-    @Override
-    public String getNodeName() {
-        return (node instanceof Comment) ? "@comment" : "@text";
-    }
-    
-    @Override
-    public boolean isEmpty() {
-        return true;
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentModel.java
deleted file mode 100644
index 876b3cf..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentModel.java
+++ /dev/null
@@ -1,76 +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 org.apache.freemarker.dom;
- 
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.w3c.dom.Document;
-import org.w3c.dom.NodeList;
-
-/**
- * A class that wraps the root node of a parsed XML document, using
- * the W3C DOM_WRAPPER API.
- */
-
-class DocumentModel extends NodeModel implements TemplateHashModel {
-    
-    private ElementModel rootElement;
-    
-    DocumentModel(Document doc) {
-        super(doc);
-    }
-    
-    @Override
-    public String getNodeName() {
-        return "@document";
-    }
-    
-    @Override
-    public TemplateModel get(String key) throws TemplateModelException {
-        if (key.equals("*")) {
-            return getRootElement();
-        } else if (key.equals("**")) {
-            NodeList nl = ((Document) node).getElementsByTagName("*");
-            return new NodeListModel(nl, this);
-        } else if (DomStringUtil.isXMLNameLike(key)) {
-            ElementModel em = (ElementModel) NodeModel.wrap(((Document) node).getDocumentElement());
-            if (em.matchesName(key, Environment.getCurrentEnvironment())) {
-                return em;
-            } else {
-                return new NodeListModel(this);
-            }
-        }
-        return super.get(key);
-    }
-    
-    ElementModel getRootElement() {
-        if (rootElement == null) {
-            rootElement = (ElementModel) wrap(((Document) node).getDocumentElement());
-        }
-        return rootElement;
-    }
-    
-    @Override
-    public boolean isEmpty() {
-        return false;
-    }
-} 
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java
deleted file mode 100644
index 3448f77..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/DocumentTypeModel.java
+++ /dev/null
@@ -1,56 +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 org.apache.freemarker.dom;
-
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.w3c.dom.DocumentType;
-import org.w3c.dom.ProcessingInstruction;
-
-class DocumentTypeModel extends NodeModel {
-    
-    public DocumentTypeModel(DocumentType docType) {
-        super(docType);
-    }
-    
-    public String getAsString() {
-        return ((ProcessingInstruction) node).getData();
-    }
-    
-    public TemplateSequenceModel getChildren() throws TemplateModelException {
-        throw new TemplateModelException("entering the child nodes of a DTD node is not currently supported");
-    }
-    
-    @Override
-    public TemplateModel get(String key) throws TemplateModelException {
-        throw new TemplateModelException("accessing properties of a DTD is not currently supported");
-    }
-    
-    @Override
-    public String getNodeName() {
-        return "@document_type$" + node.getNodeName();
-    }
-    
-    @Override
-    public boolean isEmpty() {
-        return true;
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/DomLog.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/DomLog.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/DomLog.java
deleted file mode 100644
index a1f6f0c..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/DomLog.java
+++ /dev/null
@@ -1,32 +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 org.apache.freemarker.dom;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-final class DomLog {
-
-    private DomLog() {
-        //
-    }
-
-    public static final Logger LOG = LoggerFactory.getLogger("org.apache.freemarker.dom");
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/DomStringUtil.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/DomStringUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/DomStringUtil.java
deleted file mode 100644
index f5b58f8..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/DomStringUtil.java
+++ /dev/null
@@ -1,67 +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 org.apache.freemarker.dom;
-
-/**
- * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
- * This class is to work around the lack of module system in Java, i.e., so that other FreeMarker packages can
- * access things inside this package that users shouldn't. 
- */
-final class DomStringUtil {
-
-    private DomStringUtil() {
-        // Not meant to be instantiated
-    }
-
-    static boolean isXMLNameLike(String name) {
-        return isXMLNameLike(name, 0);
-    }
-    
-    /**
-     * Check if the name looks like an XML element name.
-     * 
-     * @param firstCharIdx The index of the character in the string parameter that we treat as the beginning of the
-     *      string to check. This is to spare substringing that has become more expensive in Java 7.  
-     * 
-     * @return whether the name is a valid XML element name. (This routine might only be 99% accurate. REVISIT)
-     */
-    static boolean isXMLNameLike(String name, int firstCharIdx) {
-        int ln = name.length();
-        for (int i = firstCharIdx; i < ln; i++) {
-            char c = name.charAt(i);
-            if (i == firstCharIdx && (c == '-' || c == '.' || Character.isDigit(c))) {
-                return false;
-            }
-            if (!Character.isLetterOrDigit(c) && c != '_' && c != '-' && c != '.') {
-                if (c == ':') {
-                    if (i + 1 < ln && name.charAt(i + 1) == ':') {
-                        // "::" is used in XPath
-                        return false;
-                    }
-                    // We don't return here, as a lonely ":" is allowed.
-                } else {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }    
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/ElementModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/ElementModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/ElementModel.java
deleted file mode 100644
index 220f414..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/ElementModel.java
+++ /dev/null
@@ -1,234 +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 org.apache.freemarker.dom;
-
-import java.util.Collections;
-
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.impl.SimpleScalar;
-import org.apache.freemarker.core.util._StringUtil;
-import org.w3c.dom.Attr;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-class ElementModel extends NodeModel implements TemplateScalarModel {
-
-    public ElementModel(Element element) {
-        super(element);
-    }
-    
-    @Override
-    public boolean isEmpty() {
-        return false;
-    }
-    
-    /**
-     * An Element node supports various hash keys.
-     * Any key that corresponds to the tag name of any child elements
-     * returns a sequence of those elements. The special key "*" returns 
-     * all the element's direct children.
-     * The "**" key return all the element's descendants in the order they
-     * occur in the document.
-     * Any key starting with '@' is taken to be the name of an element attribute.
-     * The special key "@@" returns a hash of all the element's attributes.
-     * The special key "/" returns the root document node associated with this element.
-     */
-    @Override
-    public TemplateModel get(String key) throws TemplateModelException {
-        if (key.equals("*")) {
-            NodeListModel ns = new NodeListModel(this);
-            TemplateSequenceModel children = getChildNodes();
-            for (int i = 0; i < children.size(); i++) {
-                NodeModel child = (NodeModel) children.get(i);
-                if (child.node.getNodeType() == Node.ELEMENT_NODE) {
-                    ns.add(child);
-                }
-            }
-            return ns;
-        } else if (key.equals("**")) {
-            return new NodeListModel(((Element) node).getElementsByTagName("*"), this);    
-        } else if (key.startsWith("@")) {
-            if (key.startsWith("@@")) {
-                if (key.equals(AtAtKey.ATTRIBUTES.getKey())) {
-                    return new NodeListModel(node.getAttributes(), this);
-                } else if (key.equals(AtAtKey.START_TAG.getKey())) {
-                    NodeOutputter nodeOutputter = new NodeOutputter(node);
-                    return new SimpleScalar(nodeOutputter.getOpeningTag((Element) node));
-                } else if (key.equals(AtAtKey.END_TAG.getKey())) {
-                    NodeOutputter nodeOutputter = new NodeOutputter(node);
-                    return new SimpleScalar(nodeOutputter.getClosingTag((Element) node));
-                } else if (key.equals(AtAtKey.ATTRIBUTES_MARKUP.getKey())) {
-                    StringBuilder buf = new StringBuilder();
-                    NodeOutputter nu = new NodeOutputter(node);
-                    nu.outputContent(node.getAttributes(), buf);
-                    return new SimpleScalar(buf.toString().trim());
-                } else if (key.equals(AtAtKey.PREVIOUS_SIBLING_ELEMENT.getKey())) {
-                    Node previousSibling = node.getPreviousSibling();
-                    while (previousSibling != null && !isSignificantNode(previousSibling)) {
-                        previousSibling = previousSibling.getPreviousSibling();
-                    }
-                    return previousSibling != null && previousSibling.getNodeType() == Node.ELEMENT_NODE
-                            ? wrap(previousSibling) : new NodeListModel(Collections.emptyList(), null);  
-                } else if (key.equals(AtAtKey.NEXT_SIBLING_ELEMENT.getKey())) {
-                    Node nextSibling = node.getNextSibling();
-                    while (nextSibling != null && !isSignificantNode(nextSibling)) {
-                        nextSibling = nextSibling.getNextSibling();
-                    }
-                    return nextSibling != null && nextSibling.getNodeType() == Node.ELEMENT_NODE
-                            ? wrap(nextSibling) : new NodeListModel(Collections.emptyList(), null);  
-                } else {
-                    // We don't know anything like this that's element-specific; fall back 
-                    return super.get(key);
-                }
-            } else { // Starts with "@", but not with "@@"
-                if (DomStringUtil.isXMLNameLike(key, 1)) {
-                    Attr att = getAttribute(key.substring(1));
-                    if (att == null) { 
-                        return new NodeListModel(this);
-                    }
-                    return wrap(att);
-                } else if (key.equals("@*")) {
-                    return new NodeListModel(node.getAttributes(), this);
-                } else {
-                    // We don't know anything like this that's element-specific; fall back 
-                    return super.get(key);
-                }
-            }
-        } else if (DomStringUtil.isXMLNameLike(key)) {
-            // We interpret key as an element name
-            NodeListModel result = ((NodeListModel) getChildNodes()).filterByName(key);
-            return result.size() != 1 ? result : result.get(0);
-        } else {
-            // We don't anything like this that's element-specific; fall back 
-            return super.get(key);
-        }
-    }
-
-    @Override
-    public String getAsString() throws TemplateModelException {
-        NodeList nl = node.getChildNodes();
-        String result = "";
-        for (int i = 0; i < nl.getLength(); i++) {
-            Node child = nl.item(i);
-            int nodeType = child.getNodeType();
-            if (nodeType == Node.ELEMENT_NODE) {
-                String msg = "Only elements with no child elements can be processed as text."
-                             + "\nThis element with name \""
-                             + node.getNodeName()
-                             + "\" has a child element named: " + child.getNodeName();
-                throw new TemplateModelException(msg);
-            } else if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) {
-                result += child.getNodeValue();
-            }
-        }
-        return result;
-    }
-    
-    @Override
-    public String getNodeName() {
-        String result = node.getLocalName();
-        if (result == null || result.equals("")) {
-            result = node.getNodeName();
-        }
-        return result;
-    }
-    
-    @Override
-    String getQualifiedName() {
-        String nodeName = getNodeName();
-        String nsURI = getNodeNamespace();
-        if (nsURI == null || nsURI.length() == 0) {
-            return nodeName;
-        }
-        Environment env = Environment.getCurrentEnvironment();
-        String defaultNS = env.getDefaultNS();
-        String prefix;
-        if (defaultNS != null && defaultNS.equals(nsURI)) {
-            prefix = "";
-        } else {
-            prefix = env.getPrefixForNamespace(nsURI);
-            
-        }
-        if (prefix == null) {
-            return null; // We have no qualified name, because there is no prefix mapping
-        }
-        if (prefix.length() > 0) {
-            prefix += ":";
-        }
-        return prefix + nodeName;
-    }
-    
-    private Attr getAttribute(String qname) {
-        Element element = (Element) node;
-        Attr result = element.getAttributeNode(qname);
-        if (result != null)
-            return result;
-        int colonIndex = qname.indexOf(':');
-        if (colonIndex > 0) {
-            String prefix = qname.substring(0, colonIndex);
-            String uri;
-            if (prefix.equals(Template.DEFAULT_NAMESPACE_PREFIX)) {
-                uri = Environment.getCurrentEnvironment().getDefaultNS();
-            } else {
-                uri = Environment.getCurrentEnvironment().getNamespaceForPrefix(prefix);
-            }
-            String localName = qname.substring(1 + colonIndex);
-            if (uri != null) {
-                result = element.getAttributeNodeNS(uri, localName);
-            }
-        }
-        return result;
-    }
-    
-    private boolean isSignificantNode(Node node) throws TemplateModelException {
-        return (node.getNodeType() == Node.TEXT_NODE || node.getNodeType() == Node.CDATA_SECTION_NODE)
-                ? !isBlankXMLText(node.getTextContent())
-                : node.getNodeType() != Node.PROCESSING_INSTRUCTION_NODE && node.getNodeType() != Node.COMMENT_NODE;
-    }
-    
-    private boolean isBlankXMLText(String s) {
-        if (s == null) {
-            return true;
-        }
-        for (int i = 0; i < s.length(); i++) {
-            if (!isXMLWhiteSpace(s.charAt(i))) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * White space according the XML spec. 
-     */
-    private boolean isXMLWhiteSpace(char c) {
-        return c == ' ' || c == '\t' || c == '\n' | c == '\r';
-    }
-
-    boolean matchesName(String name, Environment env) {
-        return _StringUtil.matchesQName(name, getNodeName(), getNodeNamespace(), env);
-    }
-}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java
deleted file mode 100644
index 3e52836..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/JaxenXPathSupport.java
+++ /dev/null
@@ -1,243 +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 org.apache.freemarker.dom;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-import org.apache.freemarker.core.CustomStateKey;
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
-import org.apache.freemarker.core.util._ObjectHolder;
-import org.jaxen.BaseXPath;
-import org.jaxen.Function;
-import org.jaxen.FunctionCallException;
-import org.jaxen.FunctionContext;
-import org.jaxen.JaxenException;
-import org.jaxen.NamespaceContext;
-import org.jaxen.Navigator;
-import org.jaxen.UnresolvableException;
-import org.jaxen.VariableContext;
-import org.jaxen.XPathFunctionContext;
-import org.jaxen.dom.DocumentNavigator;
-import org.w3c.dom.Document;
-import org.xml.sax.EntityResolver;
-import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-
-
-/**
- */
-class JaxenXPathSupport implements XPathSupport {
-
-    private static final CustomStateKey<Map<String, BaseXPath>> XPATH_CACHE_ATTR
-            = new CustomStateKey<Map<String, BaseXPath>>() {
-        @Override
-        protected Map<String, BaseXPath> create() {
-            return new HashMap<String, BaseXPath>();
-        }
-    };
-
-        // [2.4] Can't we just use Collections.emptyList()? 
-    private final static ArrayList EMPTY_ARRAYLIST = new ArrayList();
-
-    @Override
-    public TemplateModel executeQuery(Object context, String xpathQuery) throws TemplateModelException {
-        try {
-            BaseXPath xpath;
-            Map<String, BaseXPath> xpathCache = Environment.getCurrentEnvironmentNotNull().getCurrentTemplateNotNull()
-                    .getCustomState(XPATH_CACHE_ATTR);
-            synchronized (xpathCache) {
-                xpath = xpathCache.get(xpathQuery);
-                if (xpath == null) {
-                    xpath = new BaseXPath(xpathQuery, FM_DOM_NAVIGATOR);
-                    xpath.setNamespaceContext(customNamespaceContext);
-                    xpath.setFunctionContext(FM_FUNCTION_CONTEXT);
-                    xpath.setVariableContext(FM_VARIABLE_CONTEXT);
-                    xpathCache.put(xpathQuery, xpath);
-                }
-            }
-            List result = xpath.selectNodes(context != null ? context : EMPTY_ARRAYLIST);
-            if (result.size() == 1) {
-                return NodeQueryResultItemObjectWrapper.INSTANCE.wrap(result.get(0));
-            }
-            NodeListModel nlm = new NodeListModel(result, null);
-            nlm.xpathSupport = this;
-            return nlm;
-        } catch (UndeclaredThrowableException e) {
-            Throwable t  = e.getUndeclaredThrowable();
-            if (t instanceof TemplateModelException) {
-                throw (TemplateModelException) t;
-            }
-            throw e;
-        } catch (JaxenException je) {
-            throw new TemplateModelException(je);
-        }
-    }
-
-    static private final NamespaceContext customNamespaceContext = new NamespaceContext() {
-        
-        @Override
-        public String translateNamespacePrefixToUri(String prefix) {
-            if (prefix.equals(Template.DEFAULT_NAMESPACE_PREFIX)) {
-                return Environment.getCurrentEnvironment().getDefaultNS();
-            }
-            return Environment.getCurrentEnvironment().getNamespaceForPrefix(prefix);
-        }
-    };
-
-    private static final VariableContext FM_VARIABLE_CONTEXT = new VariableContext() {
-        @Override
-        public Object getVariableValue(String namespaceURI, String prefix, String localName)
-        throws UnresolvableException {
-            try {
-                TemplateModel model = Environment.getCurrentEnvironment().getVariable(localName);
-                if (model == null) {
-                    throw new UnresolvableException("Variable \"" + localName + "\" not found.");
-                }
-                if (model instanceof TemplateScalarModel) {
-                    return ((TemplateScalarModel) model).getAsString();
-                }
-                if (model instanceof TemplateNumberModel) {
-                    return ((TemplateNumberModel) model).getAsNumber();
-                }
-                if (model instanceof TemplateDateModel) {
-                    return ((TemplateDateModel) model).getAsDate();
-                }
-                if (model instanceof TemplateBooleanModel) {
-                    return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean());
-                }
-            } catch (TemplateModelException e) {
-                throw new UndeclaredThrowableException(e);
-            }
-            throw new UnresolvableException(
-                    "Variable \"" + localName + "\" exists, but it's not a string, number, date, or boolean");
-        }
-    };
-     
-    private static final FunctionContext FM_FUNCTION_CONTEXT = new XPathFunctionContext() {
-        @Override
-        public Function getFunction(String namespaceURI, String prefix, String localName)
-        throws UnresolvableException {
-            try {
-                return super.getFunction(namespaceURI, prefix, localName);
-            } catch (UnresolvableException e) {
-                return super.getFunction(null, null, localName);
-            }
-        }
-    };
-    
-    /**
-     * Stores the the template parsed as {@link Document} in the template itself.
-     */
-    private static final CustomStateKey<_ObjectHolder<Document>> FM_DOM_NAVIAGOTOR_CACHED_DOM
-            = new CustomStateKey<_ObjectHolder<Document>>() {
-        @Override
-        protected _ObjectHolder<Document> create() {
-            return new _ObjectHolder<>(null);
-        }
-    };
-     
-    private static final Navigator FM_DOM_NAVIGATOR = new DocumentNavigator() {
-        @Override
-        public Object getDocument(String uri) throws FunctionCallException {
-            try {
-                Template raw = getTemplate(uri);
-                _ObjectHolder<Document> docHolder = Environment.getCurrentEnvironmentNotNull()
-                        .getCurrentTemplateNotNull().getCustomState(FM_DOM_NAVIAGOTOR_CACHED_DOM);
-                synchronized (docHolder) {
-                    Document doc = docHolder.get();
-                    if (doc == null) {
-                        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
-                        factory.setNamespaceAware(true);
-                        DocumentBuilder builder = factory.newDocumentBuilder();
-                        FmEntityResolver er = new FmEntityResolver();
-                        builder.setEntityResolver(er);
-                        doc = builder.parse(createInputSource(null, raw));
-                        // If the entity resolver got called 0 times, the document
-                        // is standalone, so we can safely cache it
-                        if (er.getCallCount() == 0) {
-                            docHolder.set(doc);
-                        }
-                    }
-                    return doc;
-                }
-            } catch (Exception e) {
-                throw new FunctionCallException("Failed to parse document for URI: " + uri, e);
-            }
-        }
-    };
-
-    // [FM3] Look into this "hidden" feature
-    static Template getTemplate(String systemId) throws IOException {
-        Environment env = Environment.getCurrentEnvironment();
-        String templatePath = env.getCurrentTemplate().getLookupName();
-        int lastSlash = templatePath.lastIndexOf('/');
-        templatePath = lastSlash == -1 ? "" : templatePath.substring(0, lastSlash + 1);
-        systemId = env.toFullTemplateName(templatePath, systemId);
-        return env.getConfiguration().getTemplate(systemId, env.getLocale());
-    }
-
-    private static InputSource createInputSource(String publicId, Template raw) throws IOException, SAXException {
-        StringWriter sw = new StringWriter();
-        try {
-            raw.process(Collections.EMPTY_MAP, sw);
-        } catch (TemplateException e) {
-            throw new SAXException(e);
-        }
-        InputSource is = new InputSource();
-        is.setPublicId(publicId);
-        is.setSystemId(raw.getLookupName());
-        is.setCharacterStream(new StringReader(sw.toString()));
-        return is;
-    }
-
-    private static class FmEntityResolver implements EntityResolver {
-        private int callCount = 0;
-        
-        @Override
-        public InputSource resolveEntity(String publicId, String systemId)
-        throws SAXException, IOException {
-            ++callCount;
-            return createInputSource(publicId, getTemplate(systemId));
-        }
-        
-        int getCallCount() {
-            return callCount;
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/be556897/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeListModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeListModel.java b/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeListModel.java
deleted file mode 100644
index 333bb5c..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/dom/NodeListModel.java
+++ /dev/null
@@ -1,219 +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 org.apache.freemarker.dom;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.Environment;
-import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateNodeModel;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.impl.SimpleScalar;
-import org.apache.freemarker.core.model.impl.SimpleSequence;
-import org.w3c.dom.NamedNodeMap;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-/**
- * Used when the result set contains 0 or multiple nodes; shouldn't be used when you have exactly 1 node. For exactly 1
- * node, use {@link NodeModel#wrap(Node)}, because {@link NodeModel} subclasses can have extra features building on that
- * restriction, like single elements with text content can be used as FTL string-s.
- * <p>
- * This class is not guaranteed to be thread safe, so instances of this shouldn't be used as
- * {@linkplain Configuration#getSharedVariables() shared variable}.
- */
-class NodeListModel extends SimpleSequence implements TemplateHashModel, _UnexpectedTypeErrorExplainerTemplateModel {
-    
-    // [2.4] make these private
-    NodeModel contextNode;
-    XPathSupport xpathSupport;
-    
-    NodeListModel(Node contextNode) {
-        this(NodeModel.wrap(contextNode));
-    }
-    
-    NodeListModel(NodeModel contextNode) {
-        super(NodeQueryResultItemObjectWrapper.INSTANCE);
-        this.contextNode = contextNode;
-    }
-    
-    NodeListModel(NodeList nodeList, NodeModel contextNode) {
-        super(NodeQueryResultItemObjectWrapper.INSTANCE);
-        for (int i = 0; i < nodeList.getLength(); i++) {
-            list.add(nodeList.item(i));
-        }
-        this.contextNode = contextNode;
-    }
-    
-    NodeListModel(NamedNodeMap nodeList, NodeModel contextNode) {
-        super(NodeQueryResultItemObjectWrapper.INSTANCE);
-        for (int i = 0; i < nodeList.getLength(); i++) {
-            list.add(nodeList.item(i));
-        }
-        this.contextNode = contextNode;
-    }
-    
-    NodeListModel(List list, NodeModel contextNode) {
-        super(list, NodeQueryResultItemObjectWrapper.INSTANCE);
-        this.contextNode = contextNode;
-    }
-    
-    NodeListModel filterByName(String name) throws TemplateModelException {
-        NodeListModel result = new NodeListModel(contextNode);
-        int size = size();
-        if (size == 0) {
-            return result;
-        }
-        Environment env = Environment.getCurrentEnvironment();
-        for (int i = 0; i < size; i++) {
-            NodeModel nm = (NodeModel) get(i);
-            if (nm instanceof ElementModel) {
-                if (((ElementModel) nm).matchesName(name, env)) {
-                    result.add(nm);
-                }
-            }
-        }
-        return result;
-    }
-    
-    @Override
-    public boolean isEmpty() {
-        return size() == 0;
-    }
-    
-    @Override
-    public TemplateModel get(String key) throws TemplateModelException {
-        if (size() == 1) {
-            NodeModel nm = (NodeModel) get(0);
-            return nm.get(key);
-        }
-        if (key.startsWith("@@")) {
-            if (key.equals(AtAtKey.MARKUP.getKey()) 
-                    || key.equals(AtAtKey.NESTED_MARKUP.getKey()) 
-                    || key.equals(AtAtKey.TEXT.getKey())) {
-                StringBuilder result = new StringBuilder();
-                for (int i = 0; i < size(); i++) {
-                    NodeModel nm = (NodeModel) get(i);
-                    TemplateScalarModel textModel = (TemplateScalarModel) nm.get(key);
-                    result.append(textModel.getAsString());
-                }
-                return new SimpleScalar(result.toString());
-            } else if (key.length() != 2 /* to allow "@@" to fall through */) {
-                // As @@... would cause exception in the XPath engine, we throw a nicer exception now. 
-                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)."
-                                    : "an empty list of XML nodes (no matches)."));
-                } else {
-                    throw new TemplateModelException("Unsupported @@ key: " + key);
-                }
-            }
-        }
-        if (DomStringUtil.isXMLNameLike(key) 
-                || ((key.startsWith("@")
-                        && (DomStringUtil.isXMLNameLike(key, 1)  || key.equals("@@") || key.equals("@*"))))
-                || key.equals("*") || key.equals("**")) {
-            NodeListModel result = new NodeListModel(contextNode);
-            for (int i = 0; i < size(); i++) {
-                NodeModel nm = (NodeModel) get(i);
-                if (nm instanceof ElementModel) {
-                    TemplateSequenceModel tsm = (TemplateSequenceModel) nm.get(key);
-                    if (tsm != null) {
-                        int size = tsm.size();
-                        for (int j = 0; j < size; j++) {
-                            result.add(tsm.get(j));
-                        }
-                    }
-                }
-            }
-            if (result.size() == 1) {
-                return result.get(0);
-            }
-            return result;
-        }
-        XPathSupport xps = getXPathSupport();
-        if (xps != null) {
-            Object context = (size() == 0) ? null : rawNodeList(); 
-            return xps.executeQuery(context, key);
-        } else {
-            throw new TemplateModelException(
-                    "Can't try to resolve the XML query key, because no XPath support is available. "
-                    + "This is either malformed or an XPath expression: " + key);
-        }
-    }
-    
-    private List rawNodeList() throws TemplateModelException {
-        int size = size();
-        ArrayList al = new ArrayList(size);
-        for (int i = 0; i < size; i++) {
-            al.add(((NodeModel) get(i)).node);
-        }
-        return al;
-    }
-    
-    XPathSupport getXPathSupport() throws TemplateModelException {
-        if (xpathSupport == null) {
-            if (contextNode != null) {
-                xpathSupport = contextNode.getXPathSupport();
-            } else if (size() > 0) {
-                xpathSupport = ((NodeModel) get(0)).getXPathSupport();
-            }
-        }
-        return xpathSupport;
-    }
-
-    @Override
-    public Object[] explainTypeError(Class[] expectedClasses) {
-        for (Class expectedClass : expectedClasses) {
-            if (TemplateScalarModel.class.isAssignableFrom(expectedClass)
-                    || TemplateDateModel.class.isAssignableFrom(expectedClass)
-                    || TemplateNumberModel.class.isAssignableFrom(expectedClass)
-                    || TemplateBooleanModel.class.isAssignableFrom(expectedClass)) {
-                return newTypeErrorExplanation("string");
-            } else if (TemplateNodeModel.class.isAssignableFrom(expectedClass)) {
-                return newTypeErrorExplanation("node");
-            }
-        }
-        return null;
-    }
-
-    private Object[] newTypeErrorExplanation(String type) {
-        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. "
-                + "That is, the constructing XML query has found ",
-                isEmpty()
-                    ? "no matches."
-                    : "multiple matches."
-                };
-    }
-
-}
\ No newline at end of file