http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBeanBase.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBeanBase.java
 
b/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBeanBase.java
deleted file mode 100644
index c01422e..0000000
--- 
a/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBeanBase.java
+++ /dev/null
@@ -1,97 +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.core.model.impl;
-
-public interface Java8DefaultMethodsBeanBase {
-    
-    static final String DEFAULT_METHOD_PROP = "defaultMethodProp";
-    static final String DEFAULT_METHOD_PROP_VALUE = "defaultMethodPropValue";
-    static final String DEFAULT_METHOD_PROP_2 = "defaultMethodProp2";
-    static final String DEFAULT_METHOD_INDEXED_PROP = 
"defaultMethodIndexedProp";
-    static final String DEFAULT_METHOD_INDEXED_PROP_GETTER = 
"getDefaultMethodIndexedProp";
-    static final String DEFAULT_METHOD_INDEXED_PROP_VALUE = 
"defaultMethodIndexedPropValue";
-    static final String DEFAULT_METHOD_INDEXED_PROP_2 = 
"defaultMethodIndexedProp2";
-    static final String DEFAULT_METHOD_INDEXED_PROP_2_VALUE_0 = 
"defaultMethodIndexedProp2(0).value";
-    static final String DEFAULT_METHOD_INDEXED_PROP_3 = 
"defaultMethodIndexedProp3";
-    static final String DEFAULT_METHOD_INDEXED_PROP_3_VALUE_0 = 
"indexedProp3Value[0]";
-    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP = 
"defaultMethodNotAnIndexedProp";
-    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP_VALUE = 
"defaultMethodNotAnIndexedPropValue";
-    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP_2 = 
"defaultMethodNotAnIndexedProp2";
-    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP_2_VALUE = 
"defaultMethodNotAnIndexedProp2Value";
-    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP_3 = 
"defaultMethodNotAnIndexedProp3";
-    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP_3_VALUE_0 = 
"defaultMethodNotAnIndexedProp3Value[0]";
-    static final String DEFAULT_METHOD_ACTION = "defaultMethodAction";
-    static final String DEFAULT_METHOD_ACTION_RETURN_VALUE = 
"defaultMethodActionReturnValue";
-    static final String OVERRIDDEN_DEFAULT_METHOD_ACTION = 
"overriddenDefaultMethodAction";
-
-    default String getDefaultMethodProp() {
-        return DEFAULT_METHOD_PROP_VALUE;
-    }
-
-    default String getDefaultMethodProp2() {
-        return "";
-    }
-
-    /**
-     * Will be kept as there's no non-indexed read methods for this.
-     */
-    default String getDefaultMethodIndexedProp(int i) {
-        return DEFAULT_METHOD_INDEXED_PROP_VALUE;
-    }
-
-    /**
-     * Will be kept as there will be a matching non-indexed read method in the 
subclass.
-     * However, as of FM3, the non-indexed read method is used if it's 
available.
-     */
-    default String getDefaultMethodIndexedProp2(int i) {
-        return DEFAULT_METHOD_INDEXED_PROP_2_VALUE_0;
-    }
-
-    /**
-     * This is not an indexed reader method, but a matching indexed reader 
method will be added in the subclass. 
-     * However, as of FM3, the non-indexed read method is used if it's 
available.
-     */
-    default String[] getDefaultMethodIndexedProp3() {
-        return new String[] {DEFAULT_METHOD_INDEXED_PROP_3_VALUE_0};
-    }
-    
-    /** Will be discarded because of a non-matching non-indexed read method in 
a subclass */
-    default String getDefaultMethodNotAnIndexedProp(int i) {
-        return "";
-    }
-    
-    /** The subclass will try to override this with a non-matching indexed 
reader, but this will be stronger. */
-    default String getDefaultMethodNotAnIndexedProp2() {
-        return DEFAULT_METHOD_NOT_AN_INDEXED_PROP_2_VALUE;
-    }
-
-    /** The subclass will try to override this with a non-matching indexed 
reader, but this will be stronger. */
-    default String[] getDefaultMethodNotAnIndexedProp3() {
-        return new String[] { DEFAULT_METHOD_NOT_AN_INDEXED_PROP_3_VALUE_0 };
-    }
-    
-    default String defaultMethodAction() {
-        return DEFAULT_METHOD_ACTION_RETURN_VALUE;
-    }
-
-    default Object overriddenDefaultMethodAction() {
-        return null;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
 
b/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
deleted file mode 100644
index 495f3f9..0000000
--- 
a/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
+++ /dev/null
@@ -1,65 +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.core.model.impl;
-
-import static org.junit.Assert.*;
-
-import java.util.Collections;
-
-import org.junit.Test;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-public class Java8DefaultObjectWrapperBridgeMethodsTest {
-    
-    @Test
-    public void testWithoutDefaultMethod() throws TemplateModelException {
-        test(BridgeMethodsBean.class);
-    }
-
-    @Test
-    public void testWithDefaultMethod() throws TemplateModelException {
-        test(Java8BridgeMethodsWithDefaultMethodBean.class);
-    }
-
-    @Test
-    public void testWithDefaultMethod2() throws TemplateModelException {
-        test(Java8BridgeMethodsWithDefaultMethodBean2.class);
-    }
-
-    private void test(Class<?> pClass) throws TemplateModelException {
-        DefaultObjectWrapper ow = new 
DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
-        TemplateHashModel wrapped;
-        try {
-            wrapped = (TemplateHashModel) ow.wrap(pClass.newInstance());
-        } catch (Exception e) {
-            throw new IllegalStateException(e);
-        }
-        
-        TemplateMethodModelEx m1 = (TemplateMethodModelEx) wrapped.get("m1");
-        assertEquals(BridgeMethodsBean.M1_RETURN_VALUE, "" + 
m1.exec(Collections.emptyList()));
-        
-        TemplateMethodModelEx m2 = (TemplateMethodModelEx) wrapped.get("m2");
-        assertNull(m2.exec(Collections.emptyList()));
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
 
b/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
deleted file mode 100644
index 905d536..0000000
--- 
a/freemarker-core-java8-test/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
+++ /dev/null
@@ -1,160 +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.core.model.impl;
-
-import static org.junit.Assert.*;
-
-import java.util.Collections;
-
-import org.junit.Test;
-
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateMethodModelEx;
-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.model.TemplateSequenceModel;
-
-public class Java8DefaultObjectWrapperTest {
-
-    @Test
-    public void testDefaultMethodRecognized() throws TemplateModelException {
-        DefaultObjectWrapper.Builder owb = new 
DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
-        DefaultObjectWrapper ow = owb.build();
-        TemplateHashModel wrappedBean = (TemplateHashModel) ow.wrap(new 
Java8DefaultMethodsBean());
-        
-        {
-            TemplateScalarModel prop = (TemplateScalarModel) 
wrappedBean.get(Java8DefaultMethodsBean.NORMAL_PROP);
-            assertNotNull(prop);
-            assertEquals(Java8DefaultMethodsBean.NORMAL_PROP_VALUE, 
prop.getAsString());
-        }
-        {
-            // This is overridden in the subclass, so it's visible even 
without default method support: 
-            TemplateScalarModel prop = (TemplateScalarModel) wrappedBean.get(
-                    Java8DefaultMethodsBean.DEFAULT_METHOD_PROP_2);
-            assertNotNull(prop);
-            assertEquals(Java8DefaultMethodsBean.PROP_2_OVERRIDE_VALUE, 
prop.getAsString());
-        }
-        {
-            TemplateScalarModel prop = (TemplateScalarModel) wrappedBean.get(
-                    Java8DefaultMethodsBeanBase.DEFAULT_METHOD_PROP);
-            assertNotNull(prop);
-            
assertEquals(Java8DefaultMethodsBeanBase.DEFAULT_METHOD_PROP_VALUE, 
prop.getAsString());
-        }
-        {
-            // Has only indexed read method, so it's not exposed as a property
-            
assertNull(wrappedBean.get(Java8DefaultMethodsBeanBase.DEFAULT_METHOD_INDEXED_PROP));
-
-            TemplateMethodModelEx indexedReadMethod = (TemplateMethodModelEx) 
wrappedBean.get(
-                    
Java8DefaultMethodsBeanBase.DEFAULT_METHOD_INDEXED_PROP_GETTER);
-            assertNotNull(indexedReadMethod);
-            
assertEquals(Java8DefaultMethodsBeanBase.DEFAULT_METHOD_INDEXED_PROP_VALUE,
-                    ((TemplateScalarModel) 
indexedReadMethod.exec(Collections.singletonList(new SimpleNumber(0))))
-                            .getAsString
-                            ());
-        }
-        {
-            // We see default method indexed read method, but it's invalidated 
by normal getter in the subclass
-            TemplateNumberModel prop = (TemplateNumberModel) wrappedBean.get(
-                    
Java8DefaultMethodsBean.DEFAULT_METHOD_NOT_AN_INDEXED_PROP);
-            assertNotNull(prop);
-            assertEquals(Java8DefaultMethodsBean.NOT_AN_INDEXED_PROP_VALUE, 
prop.getAsNumber());
-        }
-        {
-            // The default method read method invalidates the indexed read 
method in the subclass
-            TemplateScalarModel prop = (TemplateScalarModel) wrappedBean.get(
-                    
Java8DefaultMethodsBean.DEFAULT_METHOD_NOT_AN_INDEXED_PROP_2);
-            assertNotNull(prop);
-            
assertEquals(Java8DefaultMethodsBean.DEFAULT_METHOD_NOT_AN_INDEXED_PROP_2_VALUE,
 prop.getAsString());
-        }
-        {
-            // The default method read method invalidates the indexed read 
method in the subclass
-            TemplateSequenceModel prop = (TemplateSequenceModel) 
wrappedBean.get(
-                    
Java8DefaultMethodsBean.DEFAULT_METHOD_NOT_AN_INDEXED_PROP_3);
-            assertNotNull(prop);
-            
assertEquals(Java8DefaultMethodsBean.DEFAULT_METHOD_NOT_AN_INDEXED_PROP_3_VALUE_0,
-                    ((TemplateScalarModel) prop.get(0)).getAsString());
-        }
-        {
-            // We see the default method indexed reader, which overrides the 
plain array reader in the subclass.
-            TemplateSequenceModel prop = (TemplateSequenceModel) 
wrappedBean.get(
-                    Java8DefaultMethodsBean.DEFAULT_METHOD_INDEXED_PROP_2);
-            assertNotNull(prop);
-            assertEquals(Java8DefaultMethodsBean.ARRAY_PROP_2_VALUE_0,
-                    ((TemplateScalarModel) prop.get(0)).getAsString());
-        }
-        {
-            // We do see the default method non-indexed reader, but the 
subclass has a matching indexed reader, so that
-            // takes over.
-            TemplateSequenceModel prop = (TemplateSequenceModel) 
wrappedBean.get(
-                    Java8DefaultMethodsBean.DEFAULT_METHOD_INDEXED_PROP_3);
-            assertNotNull(prop);
-            
assertEquals(Java8DefaultMethodsBeanBase.DEFAULT_METHOD_INDEXED_PROP_3_VALUE_0,
-                    ((TemplateScalarModel) prop.get(0)).getAsString());
-        }        
-        {
-            // Only present in the subclass.
-
-            // Has only indexed read method, so it's not exposed as a property
-            
assertNull(wrappedBean.get(Java8DefaultMethodsBean.INDEXED_PROP_4));
-
-            TemplateMethodModelEx indexedReadMethod = (TemplateMethodModelEx) 
wrappedBean.get(
-                    Java8DefaultMethodsBean.INDEXED_PROP_GETTER_4);
-            assertNotNull(indexedReadMethod);
-            assertEquals(Java8DefaultMethodsBean.INDEXED_PROP_4_VALUE,
-                    ((TemplateScalarModel) 
indexedReadMethod.exec(Collections.singletonList(new SimpleNumber(0))))
-                            .getAsString());
-        }        
-        {
-            TemplateMethodModelEx action = (TemplateMethodModelEx) 
wrappedBean.get(
-                    Java8DefaultMethodsBean.NORMAL_ACTION);
-            assertNotNull(action);
-            assertEquals(
-                    Java8DefaultMethodsBean.NORMAL_ACTION_RETURN_VALUE,
-                    ((TemplateScalarModel) 
action.exec(Collections.emptyList())).getAsString());
-        }
-        
-        {
-            TemplateMethodModelEx action = (TemplateMethodModelEx) 
wrappedBean.get(
-                    Java8DefaultMethodsBean.NORMAL_ACTION);
-            assertNotNull(action);
-            assertEquals(
-                    Java8DefaultMethodsBean.NORMAL_ACTION_RETURN_VALUE,
-                    ((TemplateScalarModel) 
action.exec(Collections.emptyList())).getAsString());
-        }
-        {
-            TemplateMethodModelEx action = (TemplateMethodModelEx) 
wrappedBean.get(
-                    Java8DefaultMethodsBean.DEFAULT_METHOD_ACTION);
-            assertNotNull(action);
-            assertEquals(
-                    Java8DefaultMethodsBean.DEFAULT_METHOD_ACTION_RETURN_VALUE,
-                    ((TemplateScalarModel) 
action.exec(Collections.emptyList())).getAsString());
-        }
-        {
-            TemplateMethodModelEx action = (TemplateMethodModelEx) 
wrappedBean.get(
-                    Java8DefaultMethodsBean.OVERRIDDEN_DEFAULT_METHOD_ACTION);
-            assertNotNull(action);
-            assertEquals(
-                    
Java8DefaultMethodsBean.OVERRIDDEN_DEFAULT_METHOD_ACTION_RETURN_VALUE,
-                    ((TemplateScalarModel) 
action.exec(Collections.emptyList())).getAsString());
-        }
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/build.gradle
----------------------------------------------------------------------
diff --git a/freemarker-core-test-java8/build.gradle 
b/freemarker-core-test-java8/build.gradle
new file mode 100644
index 0000000..44b95ae
--- /dev/null
+++ b/freemarker-core-test-java8/build.gradle
@@ -0,0 +1,25 @@
+description = """\
+The unit tests of freemarker-core that need Java 8 features. These were moved 
to a separate project to avoid \
+a project that contains java files that require varous Java versions.
+"""
+
+// Override inherited default Java version:
+sourceCompatibility = "1.8"
+targetCompatibility = "1.8"
+[compileJava, compileTestJava]*.options*.bootClasspath = bootClasspathJava8
+
+dependencies {
+    compile project(":freemarker-core")
+    compile project(":freemarker-test-utils")
+}
+
+// We have nothing to put into the jar, as we have test classes only
+jar.enabled = false
+
+javadoc.enabled = false
+
+// Must not be deployed to a public Maven repository
+uploadArchives.enabled = false
+
+// Doesn't make sense to Maven "install" this, as the artifact won't contain 
test classes
+install.enabled = false

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/main/resources/META-INF/DISCLAIMER
----------------------------------------------------------------------
diff --git a/freemarker-core-test-java8/src/main/resources/META-INF/DISCLAIMER 
b/freemarker-core-test-java8/src/main/resources/META-INF/DISCLAIMER
new file mode 100644
index 0000000..569ba05
--- /dev/null
+++ b/freemarker-core-test-java8/src/main/resources/META-INF/DISCLAIMER
@@ -0,0 +1,8 @@
+Apache FreeMarker is an effort undergoing incubation at The Apache Software
+Foundation (ASF), sponsored by the Apache Incubator. Incubation is required of
+all newly accepted projects until a further review indicates that the
+infrastructure, communications, and decision making process have stabilized in
+a manner consistent with other successful ASF projects. While incubation
+status is not necessarily a reflection of the completeness or stability of the
+code, it does indicate that the project has yet to be fully endorsed by the
+ASF.
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/main/resources/META-INF/LICENSE
----------------------------------------------------------------------
diff --git a/freemarker-core-test-java8/src/main/resources/META-INF/LICENSE 
b/freemarker-core-test-java8/src/main/resources/META-INF/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/freemarker-core-test-java8/src/main/resources/META-INF/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed 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.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/BridgeMethodsBean.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/BridgeMethodsBean.java
 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/BridgeMethodsBean.java
new file mode 100644
index 0000000..2c9d4e9
--- /dev/null
+++ 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/BridgeMethodsBean.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+public class BridgeMethodsBean extends BridgeMethodsBeanBase<String> {
+
+    static final String M1_RETURN_VALUE = "m1ReturnValue"; 
+    
+    @Override
+    public String m1() {
+        return M1_RETURN_VALUE;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/BridgeMethodsBeanBase.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/BridgeMethodsBeanBase.java
 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/BridgeMethodsBeanBase.java
new file mode 100644
index 0000000..4ecec7c
--- /dev/null
+++ 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/BridgeMethodsBeanBase.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+public abstract class BridgeMethodsBeanBase<T> {
+
+    public abstract T m1();
+    
+    public T m2() {
+        return null;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBean.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBean.java
 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBean.java
new file mode 100644
index 0000000..c7d27a6
--- /dev/null
+++ 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBean.java
@@ -0,0 +1,29 @@
+/*
+ * 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;
+
+public class Java8BridgeMethodsWithDefaultMethodBean implements 
Java8BridgeMethodsWithDefaultMethodBeanBase<String> {
+
+    static final String M1_RETURN_VALUE = "m1ReturnValue"; 
+    
+    public String m1() {
+        return M1_RETURN_VALUE;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBean2.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBean2.java
 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBean2.java
new file mode 100644
index 0000000..7dfb39a
--- /dev/null
+++ 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBean2.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+public class Java8BridgeMethodsWithDefaultMethodBean2 implements 
Java8BridgeMethodsWithDefaultMethodBeanBase2 {
+    // All inherited
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBeanBase.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBeanBase.java
 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBeanBase.java
new file mode 100644
index 0000000..fdd8821
--- /dev/null
+++ 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBeanBase.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+public interface Java8BridgeMethodsWithDefaultMethodBeanBase<T> {
+
+    default T m1() {
+        return null;
+    }
+    
+    default T m2() {
+        return null;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBeanBase2.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBeanBase2.java
 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBeanBase2.java
new file mode 100644
index 0000000..6f68dc7
--- /dev/null
+++ 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8BridgeMethodsWithDefaultMethodBeanBase2.java
@@ -0,0 +1,28 @@
+/*
+ * 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;
+
+public interface Java8BridgeMethodsWithDefaultMethodBeanBase2 extends 
Java8BridgeMethodsWithDefaultMethodBeanBase<String> {
+
+    @Override
+    default String m1() {
+        return Java8BridgeMethodsWithDefaultMethodBean.M1_RETURN_VALUE;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBean.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBean.java
 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBean.java
new file mode 100644
index 0000000..eabc3d0
--- /dev/null
+++ 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBean.java
@@ -0,0 +1,84 @@
+/*
+ * 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;
+
+public class Java8DefaultMethodsBean implements Java8DefaultMethodsBeanBase {
+    
+    static final String NORMAL_PROP = "normalProp";
+    static final String NORMAL_PROP_VALUE = "normalPropValue";
+    static final String PROP_2_OVERRIDE_VALUE = "prop2OverrideValue";
+    static final int NOT_AN_INDEXED_PROP_VALUE = 1;
+    static final String ARRAY_PROP_2_VALUE_0 = "arrayProp2[0].value";
+    private static final int NOT_AN_INDEXED_PROP_3_VALUE = 3;
+    private static final String NOT_AN_INDEXED_PROP_2_VALUE = 
"notAnIndecedProp2Value";
+    static final String INDEXED_PROP_4 = "indexedProp4";
+    static final String INDEXED_PROP_GETTER_4 = "getIndexedProp4";
+    static final String INDEXED_PROP_4_VALUE = "indexedProp4Value[0]";
+    static final String NORMAL_ACTION = "normalAction";
+    static final String NORMAL_ACTION_RETURN_VALUE = "normalActionReturnValue";
+    static final String OVERRIDDEN_DEFAULT_METHOD_ACTION_RETURN_VALUE = 
"overriddenValue";
+    
+    public String getNormalProp() {
+        return NORMAL_PROP_VALUE;
+    }
+    
+    @Override
+    public String getDefaultMethodProp2() {
+        return PROP_2_OVERRIDE_VALUE;
+    }
+    
+    public String[] getDefaultMethodIndexedProp2() {
+        return new String[] { ARRAY_PROP_2_VALUE_0 };
+    }
+
+    /**
+     * There's a matching non-indexed reader method in the base class, but as 
this is indexed, it takes over. 
+     */
+    public String getDefaultMethodIndexedProp3(int index) {
+        return "";
+    }
+    
+    public int getDefaultMethodNotAnIndexedProp() {
+        return NOT_AN_INDEXED_PROP_VALUE;
+    }
+
+    /** Actually, this will be indexed if the default method support is off. */
+    public String getDefaultMethodNotAnIndexedProp2(int index) {
+        return NOT_AN_INDEXED_PROP_2_VALUE;
+    }
+    
+    /** Actually, this will be indexed if the default method support is off. */
+    public int getDefaultMethodNotAnIndexedProp3(int index) {
+        return NOT_AN_INDEXED_PROP_3_VALUE;
+    }
+    
+    public String getIndexedProp4(int index) {
+        return INDEXED_PROP_4_VALUE;
+    }
+    
+    public String normalAction() {
+        return NORMAL_ACTION_RETURN_VALUE;
+    }
+    
+    @Override
+    public String overriddenDefaultMethodAction() {
+        return OVERRIDDEN_DEFAULT_METHOD_ACTION_RETURN_VALUE;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBeanBase.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBeanBase.java
 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBeanBase.java
new file mode 100644
index 0000000..c01422e
--- /dev/null
+++ 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultMethodsBeanBase.java
@@ -0,0 +1,97 @@
+/*
+ * 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;
+
+public interface Java8DefaultMethodsBeanBase {
+    
+    static final String DEFAULT_METHOD_PROP = "defaultMethodProp";
+    static final String DEFAULT_METHOD_PROP_VALUE = "defaultMethodPropValue";
+    static final String DEFAULT_METHOD_PROP_2 = "defaultMethodProp2";
+    static final String DEFAULT_METHOD_INDEXED_PROP = 
"defaultMethodIndexedProp";
+    static final String DEFAULT_METHOD_INDEXED_PROP_GETTER = 
"getDefaultMethodIndexedProp";
+    static final String DEFAULT_METHOD_INDEXED_PROP_VALUE = 
"defaultMethodIndexedPropValue";
+    static final String DEFAULT_METHOD_INDEXED_PROP_2 = 
"defaultMethodIndexedProp2";
+    static final String DEFAULT_METHOD_INDEXED_PROP_2_VALUE_0 = 
"defaultMethodIndexedProp2(0).value";
+    static final String DEFAULT_METHOD_INDEXED_PROP_3 = 
"defaultMethodIndexedProp3";
+    static final String DEFAULT_METHOD_INDEXED_PROP_3_VALUE_0 = 
"indexedProp3Value[0]";
+    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP = 
"defaultMethodNotAnIndexedProp";
+    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP_VALUE = 
"defaultMethodNotAnIndexedPropValue";
+    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP_2 = 
"defaultMethodNotAnIndexedProp2";
+    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP_2_VALUE = 
"defaultMethodNotAnIndexedProp2Value";
+    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP_3 = 
"defaultMethodNotAnIndexedProp3";
+    static final String DEFAULT_METHOD_NOT_AN_INDEXED_PROP_3_VALUE_0 = 
"defaultMethodNotAnIndexedProp3Value[0]";
+    static final String DEFAULT_METHOD_ACTION = "defaultMethodAction";
+    static final String DEFAULT_METHOD_ACTION_RETURN_VALUE = 
"defaultMethodActionReturnValue";
+    static final String OVERRIDDEN_DEFAULT_METHOD_ACTION = 
"overriddenDefaultMethodAction";
+
+    default String getDefaultMethodProp() {
+        return DEFAULT_METHOD_PROP_VALUE;
+    }
+
+    default String getDefaultMethodProp2() {
+        return "";
+    }
+
+    /**
+     * Will be kept as there's no non-indexed read methods for this.
+     */
+    default String getDefaultMethodIndexedProp(int i) {
+        return DEFAULT_METHOD_INDEXED_PROP_VALUE;
+    }
+
+    /**
+     * Will be kept as there will be a matching non-indexed read method in the 
subclass.
+     * However, as of FM3, the non-indexed read method is used if it's 
available.
+     */
+    default String getDefaultMethodIndexedProp2(int i) {
+        return DEFAULT_METHOD_INDEXED_PROP_2_VALUE_0;
+    }
+
+    /**
+     * This is not an indexed reader method, but a matching indexed reader 
method will be added in the subclass. 
+     * However, as of FM3, the non-indexed read method is used if it's 
available.
+     */
+    default String[] getDefaultMethodIndexedProp3() {
+        return new String[] {DEFAULT_METHOD_INDEXED_PROP_3_VALUE_0};
+    }
+    
+    /** Will be discarded because of a non-matching non-indexed read method in 
a subclass */
+    default String getDefaultMethodNotAnIndexedProp(int i) {
+        return "";
+    }
+    
+    /** The subclass will try to override this with a non-matching indexed 
reader, but this will be stronger. */
+    default String getDefaultMethodNotAnIndexedProp2() {
+        return DEFAULT_METHOD_NOT_AN_INDEXED_PROP_2_VALUE;
+    }
+
+    /** The subclass will try to override this with a non-matching indexed 
reader, but this will be stronger. */
+    default String[] getDefaultMethodNotAnIndexedProp3() {
+        return new String[] { DEFAULT_METHOD_NOT_AN_INDEXED_PROP_3_VALUE_0 };
+    }
+    
+    default String defaultMethodAction() {
+        return DEFAULT_METHOD_ACTION_RETURN_VALUE;
+    }
+
+    default Object overriddenDefaultMethodAction() {
+        return null;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
new file mode 100644
index 0000000..495f3f9
--- /dev/null
+++ 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperBridgeMethodsTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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 static org.junit.Assert.*;
+
+import java.util.Collections;
+
+import org.junit.Test;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+import org.apache.freemarker.core.model.TemplateModelException;
+
+public class Java8DefaultObjectWrapperBridgeMethodsTest {
+    
+    @Test
+    public void testWithoutDefaultMethod() throws TemplateModelException {
+        test(BridgeMethodsBean.class);
+    }
+
+    @Test
+    public void testWithDefaultMethod() throws TemplateModelException {
+        test(Java8BridgeMethodsWithDefaultMethodBean.class);
+    }
+
+    @Test
+    public void testWithDefaultMethod2() throws TemplateModelException {
+        test(Java8BridgeMethodsWithDefaultMethodBean2.class);
+    }
+
+    private void test(Class<?> pClass) throws TemplateModelException {
+        DefaultObjectWrapper ow = new 
DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+        TemplateHashModel wrapped;
+        try {
+            wrapped = (TemplateHashModel) ow.wrap(pClass.newInstance());
+        } catch (Exception e) {
+            throw new IllegalStateException(e);
+        }
+        
+        TemplateMethodModelEx m1 = (TemplateMethodModelEx) wrapped.get("m1");
+        assertEquals(BridgeMethodsBean.M1_RETURN_VALUE, "" + 
m1.exec(Collections.emptyList()));
+        
+        TemplateMethodModelEx m2 = (TemplateMethodModelEx) wrapped.get("m2");
+        assertNull(m2.exec(Collections.emptyList()));
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
new file mode 100644
index 0000000..905d536
--- /dev/null
+++ 
b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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 static org.junit.Assert.*;
+
+import java.util.Collections;
+
+import org.junit.Test;
+
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateMethodModelEx;
+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.model.TemplateSequenceModel;
+
+public class Java8DefaultObjectWrapperTest {
+
+    @Test
+    public void testDefaultMethodRecognized() throws TemplateModelException {
+        DefaultObjectWrapper.Builder owb = new 
DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
+        DefaultObjectWrapper ow = owb.build();
+        TemplateHashModel wrappedBean = (TemplateHashModel) ow.wrap(new 
Java8DefaultMethodsBean());
+        
+        {
+            TemplateScalarModel prop = (TemplateScalarModel) 
wrappedBean.get(Java8DefaultMethodsBean.NORMAL_PROP);
+            assertNotNull(prop);
+            assertEquals(Java8DefaultMethodsBean.NORMAL_PROP_VALUE, 
prop.getAsString());
+        }
+        {
+            // This is overridden in the subclass, so it's visible even 
without default method support: 
+            TemplateScalarModel prop = (TemplateScalarModel) wrappedBean.get(
+                    Java8DefaultMethodsBean.DEFAULT_METHOD_PROP_2);
+            assertNotNull(prop);
+            assertEquals(Java8DefaultMethodsBean.PROP_2_OVERRIDE_VALUE, 
prop.getAsString());
+        }
+        {
+            TemplateScalarModel prop = (TemplateScalarModel) wrappedBean.get(
+                    Java8DefaultMethodsBeanBase.DEFAULT_METHOD_PROP);
+            assertNotNull(prop);
+            
assertEquals(Java8DefaultMethodsBeanBase.DEFAULT_METHOD_PROP_VALUE, 
prop.getAsString());
+        }
+        {
+            // Has only indexed read method, so it's not exposed as a property
+            
assertNull(wrappedBean.get(Java8DefaultMethodsBeanBase.DEFAULT_METHOD_INDEXED_PROP));
+
+            TemplateMethodModelEx indexedReadMethod = (TemplateMethodModelEx) 
wrappedBean.get(
+                    
Java8DefaultMethodsBeanBase.DEFAULT_METHOD_INDEXED_PROP_GETTER);
+            assertNotNull(indexedReadMethod);
+            
assertEquals(Java8DefaultMethodsBeanBase.DEFAULT_METHOD_INDEXED_PROP_VALUE,
+                    ((TemplateScalarModel) 
indexedReadMethod.exec(Collections.singletonList(new SimpleNumber(0))))
+                            .getAsString
+                            ());
+        }
+        {
+            // We see default method indexed read method, but it's invalidated 
by normal getter in the subclass
+            TemplateNumberModel prop = (TemplateNumberModel) wrappedBean.get(
+                    
Java8DefaultMethodsBean.DEFAULT_METHOD_NOT_AN_INDEXED_PROP);
+            assertNotNull(prop);
+            assertEquals(Java8DefaultMethodsBean.NOT_AN_INDEXED_PROP_VALUE, 
prop.getAsNumber());
+        }
+        {
+            // The default method read method invalidates the indexed read 
method in the subclass
+            TemplateScalarModel prop = (TemplateScalarModel) wrappedBean.get(
+                    
Java8DefaultMethodsBean.DEFAULT_METHOD_NOT_AN_INDEXED_PROP_2);
+            assertNotNull(prop);
+            
assertEquals(Java8DefaultMethodsBean.DEFAULT_METHOD_NOT_AN_INDEXED_PROP_2_VALUE,
 prop.getAsString());
+        }
+        {
+            // The default method read method invalidates the indexed read 
method in the subclass
+            TemplateSequenceModel prop = (TemplateSequenceModel) 
wrappedBean.get(
+                    
Java8DefaultMethodsBean.DEFAULT_METHOD_NOT_AN_INDEXED_PROP_3);
+            assertNotNull(prop);
+            
assertEquals(Java8DefaultMethodsBean.DEFAULT_METHOD_NOT_AN_INDEXED_PROP_3_VALUE_0,
+                    ((TemplateScalarModel) prop.get(0)).getAsString());
+        }
+        {
+            // We see the default method indexed reader, which overrides the 
plain array reader in the subclass.
+            TemplateSequenceModel prop = (TemplateSequenceModel) 
wrappedBean.get(
+                    Java8DefaultMethodsBean.DEFAULT_METHOD_INDEXED_PROP_2);
+            assertNotNull(prop);
+            assertEquals(Java8DefaultMethodsBean.ARRAY_PROP_2_VALUE_0,
+                    ((TemplateScalarModel) prop.get(0)).getAsString());
+        }
+        {
+            // We do see the default method non-indexed reader, but the 
subclass has a matching indexed reader, so that
+            // takes over.
+            TemplateSequenceModel prop = (TemplateSequenceModel) 
wrappedBean.get(
+                    Java8DefaultMethodsBean.DEFAULT_METHOD_INDEXED_PROP_3);
+            assertNotNull(prop);
+            
assertEquals(Java8DefaultMethodsBeanBase.DEFAULT_METHOD_INDEXED_PROP_3_VALUE_0,
+                    ((TemplateScalarModel) prop.get(0)).getAsString());
+        }        
+        {
+            // Only present in the subclass.
+
+            // Has only indexed read method, so it's not exposed as a property
+            
assertNull(wrappedBean.get(Java8DefaultMethodsBean.INDEXED_PROP_4));
+
+            TemplateMethodModelEx indexedReadMethod = (TemplateMethodModelEx) 
wrappedBean.get(
+                    Java8DefaultMethodsBean.INDEXED_PROP_GETTER_4);
+            assertNotNull(indexedReadMethod);
+            assertEquals(Java8DefaultMethodsBean.INDEXED_PROP_4_VALUE,
+                    ((TemplateScalarModel) 
indexedReadMethod.exec(Collections.singletonList(new SimpleNumber(0))))
+                            .getAsString());
+        }        
+        {
+            TemplateMethodModelEx action = (TemplateMethodModelEx) 
wrappedBean.get(
+                    Java8DefaultMethodsBean.NORMAL_ACTION);
+            assertNotNull(action);
+            assertEquals(
+                    Java8DefaultMethodsBean.NORMAL_ACTION_RETURN_VALUE,
+                    ((TemplateScalarModel) 
action.exec(Collections.emptyList())).getAsString());
+        }
+        
+        {
+            TemplateMethodModelEx action = (TemplateMethodModelEx) 
wrappedBean.get(
+                    Java8DefaultMethodsBean.NORMAL_ACTION);
+            assertNotNull(action);
+            assertEquals(
+                    Java8DefaultMethodsBean.NORMAL_ACTION_RETURN_VALUE,
+                    ((TemplateScalarModel) 
action.exec(Collections.emptyList())).getAsString());
+        }
+        {
+            TemplateMethodModelEx action = (TemplateMethodModelEx) 
wrappedBean.get(
+                    Java8DefaultMethodsBean.DEFAULT_METHOD_ACTION);
+            assertNotNull(action);
+            assertEquals(
+                    Java8DefaultMethodsBean.DEFAULT_METHOD_ACTION_RETURN_VALUE,
+                    ((TemplateScalarModel) 
action.exec(Collections.emptyList())).getAsString());
+        }
+        {
+            TemplateMethodModelEx action = (TemplateMethodModelEx) 
wrappedBean.get(
+                    Java8DefaultMethodsBean.OVERRIDDEN_DEFAULT_METHOD_ACTION);
+            assertNotNull(action);
+            assertEquals(
+                    
Java8DefaultMethodsBean.OVERRIDDEN_DEFAULT_METHOD_ACTION_RETURN_VALUE,
+                    ((TemplateScalarModel) 
action.exec(Collections.emptyList())).getAsString());
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/build.gradle
----------------------------------------------------------------------
diff --git a/freemarker-core-test/build.gradle 
b/freemarker-core-test/build.gradle
new file mode 100644
index 0000000..411f6ea
--- /dev/null
+++ b/freemarker-core-test/build.gradle
@@ -0,0 +1,19 @@
+description = """\
+The unit tests of freemarker-core. These tests used to be in freemarker-core, 
but to avoid depenency loop \
+through freemarker-test-utils, they had to be moved into a separate project."""
+
+dependencies {
+    compile project(":freemarker-core")
+    compile project(":freemarker-test-utils")
+}
+
+// We have nothing to put into the jar, as we have test classes only
+jar.enabled = false
+
+javadoc.enabled = false
+
+// Must not be deployed to a public Maven repository
+uploadArchives.enabled = false
+
+// Doesn't make sense to Maven "install" this, as the artifact won't contain 
test classes
+install.enabled = false

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTBasedErrorMessagesTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTBasedErrorMessagesTest.java
 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTBasedErrorMessagesTest.java
new file mode 100644
index 0000000..10d63b3
--- /dev/null
+++ 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTBasedErrorMessagesTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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;
+
+import java.util.Map;
+
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+public class ASTBasedErrorMessagesTest extends TemplateTest {
+    
+    @Test
+    public void testInvalidRefBasic() {
+        assertErrorContains("${foo}", "foo", "specify a default");
+        assertErrorContains("${map[foo]}", "foo", "\\!map[", "specify a 
default");
+    }
+    
+    @Test
+    public void testInvalidRefDollar() {
+        assertErrorContains("${$x}", "$x", "must not start with \"$\"", 
"specify a default");
+        assertErrorContains("${map.$x}", "map.$x", "must not start with 
\"$\"", "specify a default");
+    }
+
+    @Test
+    public void testInvalidRefAfterDot() {
+        assertErrorContains("${map.foo.bar}", "map.foo", "\\!foo.bar", "after 
the last dot", "specify a default");
+    }
+
+    @Test
+    public void testInvalidRefInSquareBrackets() {
+        assertErrorContains("${map['foo']}", "map", "final [] step", "specify 
a default");
+    }
+
+    @Test
+    public void testInvalidRefSize() {
+        assertErrorContains("${map.size()}", "map.size", "?size", "specify a 
default");
+        assertErrorContains("${map.length()}", "map.length", "?length", 
"specify a default");
+    }
+
+    @Override
+    protected Object createDataModel() {
+        Map<String, Object> dataModel = createCommonTestValuesDataModel();
+        dataModel.put("overloads", new Overloads());
+        return dataModel;
+    }
+    
+    public static class Overloads {
+        
+        @SuppressWarnings("unused")
+        public void m(String s) {}
+        
+        @SuppressWarnings("unused")
+        public void m(int i) {}
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTPrinter.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTPrinter.java 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTPrinter.java
new file mode 100644
index 0000000..3518b29
--- /dev/null
+++ 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTPrinter.java
@@ -0,0 +1,438 @@
+/*
+ * 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;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.StandardCharsets;
+import java.util.Enumeration;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.util.FTLUtil;
+import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.test.TestConfigurationBuilder;
+
+/**
+ * Static methods and command-line tool for printing the AST of a template. 
+ */
+public class ASTPrinter {
+
+    private final Configuration cfg;
+    private int successfulCounter;
+    private int failedCounter;
+    
+    static public void main(String[] args) throws IOException {
+        if (args.length == 0) {
+            usage();
+            System.exit(-1);
+        }
+        
+        ASTPrinter astp = new ASTPrinter(); 
+        if (args[0].equalsIgnoreCase("-r")) {
+            astp.mainRecursive(args);
+        } else {
+            astp.mainSingleTemplate(args);
+        }
+    }
+    
+    private ASTPrinter() {
+        cfg = new 
TestConfigurationBuilder(Configuration.VERSION_3_0_0).build();
+    }
+    
+    private void mainSingleTemplate(String[] args) throws IOException, 
FileNotFoundException {
+        final String templateFileName;
+        final String templateContent;
+        if (args[0].startsWith("ftl:")) {
+            templateFileName = null;
+            templateContent = args[0];
+        } else {
+            templateFileName = args[0];
+            templateContent = null;
+        }
+        
+        Template t = new Template(
+                templateFileName,
+                templateFileName == null ? new StringReader(templateContent) : 
new FileReader(templateFileName),
+                cfg);
+        
+        p(getASTAsString(t));
+    }
+
+    private void mainRecursive(String[] args) throws IOException {
+        if (args.length != 4) {
+            p("Number of arguments must be 4, but was: " + args.length);
+            usage();
+            System.exit(-1);
+        }
+        
+        final String srcDirPath = args[1].trim();
+        File srcDir = new File(srcDirPath);
+        if (!srcDir.isDirectory()) {
+            p("This should be an existing directory: " + srcDirPath);
+            System.exit(-1);
+        }
+        
+        Pattern fnPattern;
+        try {
+            fnPattern = Pattern.compile(args[2]);
+        } catch (PatternSyntaxException e) {
+            p(_StringUtil.jQuote(args[2]) + " is not a valid regular 
expression");
+            System.exit(-1);
+            return;
+        }
+        
+        final String dstDirPath = args[3].trim();
+        File dstDir = new File(dstDirPath);
+        if (!dstDir.isDirectory()) {
+            p("This should be an existing directory: " + dstDirPath);
+            System.exit(-1);
+        }
+        
+        long startTime = System.currentTimeMillis();
+        recurse(srcDir, fnPattern, dstDir);
+        long endTime = System.currentTimeMillis();
+        
+        p("Templates successfully processed " + successfulCounter + ", failed 
" + failedCounter
+                + ". Time taken: " + (endTime - startTime) / 1000.0 + " s");
+    }
+    
+    private void recurse(File srcDir, Pattern fnPattern, File dstDir) throws 
IOException {
+        File[] files = srcDir.listFiles();
+        if (files == null) {
+            throw new IOException("Failed to kust directory: " + srcDir);
+        }
+        for (File file : files) {
+            if (file.isDirectory()) {
+                recurse(file, fnPattern, new File(dstDir, file.getName()));
+            } else {
+                if (fnPattern.matcher(file.getName()).matches()) {
+                    File dstFile = new File(dstDir, file.getName());
+                    String res;
+                    try {
+                        Template t = new Template(file.getPath().replace('\\', 
'/'), loadIntoString(file), cfg);
+                        res = getASTAsString(t);
+                        successfulCounter++;
+                    } catch (ParseException e) {
+                        res = "<<<FAILED>>>\n" + e.getMessage();
+                        failedCounter++;
+                        p("");
+                        
p("-------------------------failed-------------------------");
+                        p("Error message was saved into: " + 
dstFile.getAbsolutePath());
+                        p("");
+                        p(e.getMessage());
+                    }
+                    save(res, dstFile);
+                }
+            }
+        }
+    }
+
+    private String loadIntoString(File file) throws IOException {
+        long ln = file.length();
+        if (ln < 0) {
+            throw new IOException("Failed to get the length of " + file);
+        }
+        byte[] buffer = new byte[(int) ln];
+        InputStream in = new FileInputStream(file);
+        try {
+            int offset = 0;
+            int bytesRead;
+            while (offset < buffer.length) {
+                bytesRead = in.read(buffer, offset, buffer.length - offset);
+                if (bytesRead == -1) {
+                    throw new IOException("Unexpected end of file: " + file);
+                }
+                offset += bytesRead;
+            }
+        } finally {
+            in.close();
+        }
+        
+        try {
+            return decode(buffer, StandardCharsets.UTF_8);
+        } catch (CharacterCodingException e) {
+            return decode(buffer, StandardCharsets.ISO_8859_1);
+        }
+    }
+
+    private String decode(byte[] buffer, Charset charset) throws 
CharacterCodingException {
+        return charset.newDecoder()
+                
.onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(CodingErrorAction.REPORT)
+                .decode(ByteBuffer.wrap(buffer)).toString();
+    }
+
+    private void save(String astStr, File file) throws IOException {
+        File parentDir = file.getParentFile();
+        if (!parentDir.isDirectory() && !parentDir.mkdirs()) {
+            throw new IOException("Failed to invoke parent directory: " + 
parentDir);
+        }
+        
+        Writer w = new BufferedWriter(new FileWriter(file));
+        try {
+            w.write(astStr);
+        } finally {
+            w.close();
+        }
+    }
+
+    private static void usage() {
+        p("Prints template Abstract Syntax Tree (AST) as plain text.");
+        p("Usage:");
+        p("    java org.apache.freemarker.core.PrintAST <templateFile>");
+        p("    java org.apache.freemarker.core.PrintAST ftl:<templateSource>");
+        p("    java org.apache.freemarker.core.PrintAST -r <src-directory> 
<regexp> <dst-directory>");
+    }
+
+    private static final String INDENTATION = "    ";
+
+    public static String getASTAsString(String ftl) throws IOException {
+        return getASTAsString(ftl, (Options) null);
+    }
+    
+    public static String getASTAsString(String ftl, Options opts) throws 
IOException {
+        return getASTAsString(null, ftl, opts);
+    }
+
+    public static String getASTAsString(String templateName, String ftl) 
throws IOException {
+        return getASTAsString(templateName, ftl, null);
+    }
+    
+    public static String getASTAsString(String templateName, String ftl, 
Options opts) throws IOException {
+        Template t = new Template(templateName, ftl, new 
TestConfigurationBuilder().build());
+        return getASTAsString(t, opts);
+    }
+
+    public static String getASTAsString(Template t) throws IOException {
+        return getASTAsString(t, null);
+    }
+
+    public static String getASTAsString(Template t, Options opts) throws 
IOException {
+        validateAST(t);
+        
+        StringWriter out = new StringWriter();
+        printNode(t.getRootASTNode(), "", null, opts != null ? opts : 
Options.DEFAULT_INSTANCE, out);
+        return out.toString();
+    }
+    
+    public static void validateAST(Template t) throws InvalidASTException {
+        final ASTElement node = t.getRootASTNode();
+        if (node.getParent() != null) {
+            throw new InvalidASTException("Root node parent must be null."
+                    + "\nRoot node: " + node.dump(false)
+                    + "\nParent"
+                    + ": " + node.getParent().getClass() + ", " + 
node.getParent().dump(false));
+        }
+        validateAST(node);
+    }
+
+    private static void validateAST(ASTElement te) {
+        int childCount = te.getChildCount();
+        for (int i = 0; i < childCount; i++) {
+            ASTElement child = te.getChild(i);
+            ASTElement parentElement = child.getParent();
+            // As ASTImplicitParent.accept does nothing but returns its 
children, it's optimized out in the final
+            // AST tree. While it will be present as a child, the parent 
element also will have children
+            // that contains the children of the ASTImplicitParent directly. 
+            if (parentElement instanceof ASTImplicitParent && 
parentElement.getParent() != null) {
+                parentElement = parentElement.getParent();
+            }
+            if (parentElement != te) {
+                throw new InvalidASTException("Wrong parent node."
+                        + "\nNode: " + child.dump(false)
+                        + "\nExpected parent: " + te.dump(false)
+                        + "\nActual parent: " + parentElement.dump(false));
+            }
+            if (child.getIndex() != i) {
+                throw new InvalidASTException("Wrong node index."
+                        + "\nNode: " + child.dump(false)
+                        + "\nExpected index: " + i
+                        + "\nActual index: " + child.getIndex());
+            }
+        }
+        if (te instanceof ASTImplicitParent && te.getChildCount() < 2) {
+            throw new InvalidASTException("Mixed content with child count less 
than 2 should removed by optimizatoin, "
+                    + "but found one with " + te.getChildCount() + " 
child(ren).");
+        }
+        ASTElement[] children = te.getChildBuffer();
+        if (children != null) {
+            if (childCount == 0) {
+                throw new InvalidASTException(
+                        "Children must be null when childCount is 0."
+                        + "\nNode: " + te.dump(false));
+            }
+            for (int i = 0; i < te.getChildCount(); i++) {
+                if (children[i] == null) {
+                    throw new InvalidASTException(
+                            "Child can't be null at index " + i
+                            + "\nNode: " + te.dump(false));
+                }
+            }
+            for (int i = te.getChildCount(); i < children.length; i++) {
+                if (children[i] != null) {
+                    throw new InvalidASTException(
+                            "Children can't be non-null at index " + i
+                            + "\nNode: " + te.dump(false));
+                }
+            }
+        } else {
+            if (childCount != 0) {
+                throw new InvalidASTException(
+                        "Children mustn't be null when child count isn't 0."
+                        + "\nNode: " + te.dump(false));
+            }
+        }
+    }
+
+    private static void printNode(Object node, String ind, ParameterRole 
paramRole, Options opts, Writer out) throws IOException {
+        if (node instanceof ASTNode) {
+            ASTNode tObj = (ASTNode) node;
+
+            printNodeLineStart(paramRole, ind, out);
+            out.write(tObj.getNodeTypeSymbol());
+            printNodeLineEnd(node, out, opts);
+            
+            if (opts.getShowConstantValue() && node instanceof ASTExpression) {
+                TemplateModel tm = ((ASTExpression) node).constantValue;
+                if (tm != null) {
+                    out.write(INDENTATION);
+                    out.write(ind);
+                    out.write("= const ");
+                    out.write(FTLUtil.getTypeDescription(tm));
+                    out.write(' ');
+                    out.write(tm.toString());
+                    out.write('\n');
+                }
+            }
+            
+            int paramCnt = tObj.getParameterCount();
+            for (int i = 0; i < paramCnt; i++) {
+                ParameterRole role = tObj.getParameterRole(i);
+                if (role == null) throw new NullPointerException("parameter 
role");
+                Object value = tObj.getParameterValue(i);
+                printNode(value, ind + INDENTATION, role, opts, out);
+            }
+            if (tObj instanceof ASTElement) {
+                Enumeration enu = ((ASTElement) tObj).children();
+                while (enu.hasMoreElements()) {
+                    printNode(enu.nextElement(), INDENTATION + ind, null, 
opts, out);
+                }
+            }
+        } else {
+            printNodeLineStart(paramRole, ind, out);
+            out.write(_StringUtil.jQuote(node));
+            printNodeLineEnd(node, out, opts);
+        }
+    }
+
+    protected static void printNodeLineEnd(Object node, Writer out, Options 
opts) throws IOException {
+        boolean commentStared = false;
+        if (opts.getShowJavaClass()) {
+            out.write("  // ");
+            commentStared = true;
+            out.write(_ClassUtil.getShortClassNameOfObject(node, true));
+        }
+        if (opts.getShowLocation() && node instanceof ASTNode) {
+            if (!commentStared) {
+                out.write("  // ");
+                commentStared = true;
+            } else {
+                out.write("; ");
+            }
+            ASTNode tObj = (ASTNode) node;
+            out.write("Location " + tObj.beginLine + ":" + tObj.beginColumn + 
"-" + tObj.endLine + ":" + tObj.endColumn);
+        }
+        out.write('\n');
+    }
+
+    private static void printNodeLineStart(ParameterRole paramRole, String 
ind, Writer out) throws IOException {
+        out.write(ind);
+        if (paramRole != null) {
+            out.write("- ");
+            out.write(paramRole.toString());
+            out.write(": ");
+        }
+    }
+    
+    public static class Options {
+        
+        private final static Options DEFAULT_INSTANCE = new Options(); 
+        
+        private boolean showJavaClass = true;
+        private boolean showConstantValue = false;
+        private boolean showLocation = false;
+        
+        public boolean getShowJavaClass() {
+            return showJavaClass;
+        }
+        
+        public void setShowJavaClass(boolean showJavaClass) {
+            this.showJavaClass = showJavaClass;
+        }
+        
+        public boolean getShowConstantValue() {
+            return showConstantValue;
+        }
+        
+        public void setShowConstantValue(boolean showConstantValue) {
+            this.showConstantValue = showConstantValue;
+        }
+
+        public boolean getShowLocation() {
+            return showLocation;
+        }
+
+        public void setShowLocation(boolean showLocation) {
+            this.showLocation = showLocation;
+        }
+        
+    }
+    
+    private static void p(Object obj) {
+        System.out.println(obj);
+    }
+
+    public static class InvalidASTException extends RuntimeException {
+
+        public InvalidASTException(String message, Throwable cause) {
+            super(message, cause);
+        }
+
+        public InvalidASTException(String message) {
+            super(message);
+        }
+        
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTTest.java 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTTest.java
new file mode 100644
index 0000000..7379e28
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ASTTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import org.apache.freemarker.core.ASTPrinter.Options;
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.test.TestUtil;
+import org.apache.freemarker.test.FileTestCase;
+
+public class ASTTest extends FileTestCase {
+
+    public ASTTest(String name) {
+        super(name);
+    }
+    
+    public void test1() throws Exception {
+        testAST("ast-1");
+    }
+
+    public void testRange() throws Exception {
+        testAST("ast-range");
+    }
+    
+    public void testAssignments() throws Exception {
+        testAST("ast-assignments");
+    }
+    
+    public void testBuiltins() throws Exception {
+        testAST("ast-builtins");
+    }
+    
+    public void testStringLiteralInterpolation() throws Exception {
+        testAST("ast-strlitinterpolation");
+    }
+    
+    public void testWhitespaceStripping() throws Exception {
+        testAST("ast-whitespacestripping");
+    }
+
+    public void testMixedContentSimplifications() throws Exception {
+        testAST("ast-mixedcontentsimplifications");
+    }
+
+    public void testMultipleIgnoredChildren() throws Exception {
+        testAST("ast-multipleignoredchildren");
+    }
+    
+    public void testNestedIgnoredChildren() throws Exception {
+        testAST("ast-nestedignoredchildren");
+    }
+
+    public void testLocations() throws Exception {
+        testASTWithLocations("ast-locations");
+    }
+    
+    private void testAST(String testName) throws FileNotFoundException, 
IOException {
+        testAST(testName, null);
+    }
+
+    private void testASTWithLocations(String testName) throws 
FileNotFoundException, IOException {
+        Options options = new Options();
+        options.setShowLocation(true);
+        testAST(testName, options);
+    }
+
+    private void testAST(String testName, Options ops) throws 
FileNotFoundException, IOException {
+        final String templateName = testName + ".ftl";
+        assertExpectedFileEqualsString(
+                testName + ".ast",
+                ASTPrinter.getASTAsString(templateName,
+                        TestUtil.removeFTLCopyrightComment(
+                                normalizeLineBreaks(
+                                        loadTestTextResource(
+                                                getTestFileURL(
+                                                        
getExpectedContentFileDirectoryResourcePath(), templateName)))
+                        ), ops));
+    }
+    
+    private String normalizeLineBreaks(final String s) throws 
FileNotFoundException, IOException {
+        return _StringUtil.replace(s, "\r\n", "\n").replace('\r', '\n');
+    }
+    
+}


Reply via email to