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 2015/12/30 19:11:26 UTC

[06/35] incubator-freemarker git commit: In the mini-language used for configuring FreeMarker from java.util.Properties or other string-only sources (but not used inside templates): public static fields can be referred like com.example.MyClass.MY_CONSTAN

In the mini-language used for configuring FreeMarker from java.util.Properties or other string-only sources (but not used inside templates): public static fields can be referred like com.example.MyClass.MY_CONSTANT or Configuration.AUTO_DETECT_TAG_SYNTAX.


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

Branch: refs/heads/2.3
Commit: fe799f5d382fad7e35be634f660e73f62d6ec9e8
Parents: 731be2c
Author: ddekany <dd...@apache.org>
Authored: Mon Dec 21 02:05:03 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Mon Dec 21 02:05:16 2015 +0100

----------------------------------------------------------------------
 src/main/java/freemarker/core/Configurable.java |   6 +-
 .../core/_ObjectBuilderSettingEvaluator.java    | 123 ++++++++++++++++---
 src/manual/en_US/book.xml                       |  18 ++-
 .../core/ObjectBuilderSettingsTest.java         |  43 +++++++
 4 files changed, 167 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fe799f5d/src/main/java/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index ba5d8c8..9c82428 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -1887,6 +1887,8 @@ public class Configurable {
      *            The keys and values can be any kind of expression, like even object builder expressions.
      *            The resulting Java object will be a {@link Map} that keeps the item order ({@link LinkedHashMap} as
      *            of this writing).
+     *        <li>A reference to a public static filed, like {@code Configuration.AUTO_DETECT_TAG_SYNTAX} or
+     *            {@code com.example.MyClass.MY_CONSTANT}.
      *        <li>An object builder expression. That is, object builder expressions can be nested into each other. 
      *      </ul>
      *   </li>
@@ -1901,13 +1903,13 @@ public class Configurable {
      *     can't be omitted for nested expressions.
      *   </li>
      *   <li>
-     *     <p>The following classes can be referred to with short class name instead of full qualified name:
+     *     <p>The following classes can be referred to with simple (unqualified) name instead of fully qualified name:
      *     {@link DefaultObjectWrapper}, {@link BeansWrapper}, {@link SimpleObjectWrapper}, {@link Locale},
      *     {@link TemplateConfiguration}, {@link PathGlobMatcher}, {@link FileNameGlobMatcher}, {@link PathRegexMatcher},
      *     {@link AndMatcher}, {@link OrMatcher}, {@link NotMatcher}, {@link ConditionalTemplateConfigurationFactory},
      *     {@link MergingTemplateConfigurationFactory}, {@link FirstMatchTemplateConfigurationFactory},
      *     {@link HTMLOutputFormat}, {@link XMLOutputFormat}, {@link RTFOutputFormat}, {@link PlainTextOutputFormat},
-     *     {@link UndefinedOutputFormat}.
+     *     {@link UndefinedOutputFormat}, {@link Configuration}.
      *   </li>
      *   <li>
      *     <p>{@link TimeZone} objects can be created like {@code TimeZone("UTC")}, despite that there's no a such

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fe799f5d/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
index a1e1c22..afbb436 100644
--- a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
+++ b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
@@ -46,6 +46,7 @@ import freemarker.cache.OrMatcher;
 import freemarker.cache.PathGlobMatcher;
 import freemarker.cache.PathRegexMatcher;
 import freemarker.ext.beans.BeansWrapper;
+import freemarker.template.Configuration;
 import freemarker.template.DefaultObjectWrapper;
 import freemarker.template.SimpleObjectWrapper;
 import freemarker.template.TemplateHashModel;
@@ -75,7 +76,7 @@ public class _ObjectBuilderSettingEvaluator {
 
     private static final String BUILDER_CLASS_POSTFIX = "Builder";
 
-    private static Map/*<String,String>*/ SHORTHANDS;
+    private static Map<String,String> SHORTHANDS;
     
     private static final Object VOID = new Object();
 
@@ -165,9 +166,12 @@ public class _ObjectBuilderSettingEvaluator {
         int startPos = pos;
         
         BuilderCallExpression exp = new BuilderCallExpression();
+        // We need the canBeStaticField/mustBeStaticFiled complication to deal with legacy syntax where parentheses
+        // weren't required for constructor calls.
+        exp.canBeStaticField = true;
         
+        final String fetchedClassName = fetchClassName(optional);
         {
-            final String fetchedClassName = fetchClassName(optional);
             if (fetchedClassName == null) {
                 if (!optional) {
                     throw new _ObjectBuilderSettingEvaluationException("class name", src, pos);
@@ -178,6 +182,7 @@ public class _ObjectBuilderSettingEvaluator {
             if (!fetchedClassName.equals(exp.className)) {
                 // Before 2.3.21 only full-qualified class names were allowed
                 modernMode = true;
+                exp.canBeStaticField = false;
             }
         }
         
@@ -186,15 +191,17 @@ public class _ObjectBuilderSettingEvaluator {
         char openParen = fetchOptionalChar("(");
         // Only the top-level expression can omit the "(...)"
         if (openParen == 0 && !topLevel) {
-            if (!optional) {
-                throw new _ObjectBuilderSettingEvaluationException("(", src, pos);
+            if (fetchedClassName.indexOf('.') != -1) {
+                exp.mustBeStaticField = true;
+            } else {
+                pos = startPos;
+                return VOID;
             }
-            pos = startPos;
-            return VOID;
         }
     
         if (openParen != 0) {
             fetchParameterListInto(exp);
+            exp.canBeStaticField = false;
         }
         
         return exp;
@@ -683,8 +690,11 @@ public class _ObjectBuilderSettingEvaluator {
             
             addWithSimpleName(SHORTHANDS, Locale.class);
             SHORTHANDS.put("TimeZone", "freemarker.core._TimeZone");
+
+            // For accessing static fields:
+            addWithSimpleName(SHORTHANDS, Configuration.class);
         }
-        String fullClassName = (String) SHORTHANDS.get(className);
+        String fullClassName = SHORTHANDS.get(className);
         return fullClassName == null ? className : fullClassName;
     }
     
@@ -842,20 +852,41 @@ public class _ObjectBuilderSettingEvaluator {
     
     private class BuilderCallExpression extends ExpressionWithParameters {
         private String className;
+        private boolean canBeStaticField;
+        private boolean mustBeStaticField;
         
         @Override
         Object eval() throws _ObjectBuilderSettingEvaluationException {
+            if (mustBeStaticField) {
+                if (!canBeStaticField) {
+                    throw new BugException();
+                }
+                return getStaticFieldValue(className);
+            }
+            
             Class cl;
             
             if (!modernMode) {
                 try {
-                    return ClassUtil.forName(className).newInstance();
-                } catch (InstantiationException e) {
-                    throw new LegacyExceptionWrapperSettingEvaluationExpression(e);
-                } catch (IllegalAccessException e) {
-                    throw new LegacyExceptionWrapperSettingEvaluationExpression(e);
-                } catch (ClassNotFoundException e) {
-                    throw new LegacyExceptionWrapperSettingEvaluationExpression(e);
+                    try {
+                        return ClassUtil.forName(className).newInstance();
+                    } catch (InstantiationException e) {
+                        throw new LegacyExceptionWrapperSettingEvaluationExpression(e);
+                    } catch (IllegalAccessException e) {
+                        throw new LegacyExceptionWrapperSettingEvaluationExpression(e);
+                    } catch (ClassNotFoundException e) {
+                        throw new LegacyExceptionWrapperSettingEvaluationExpression(e);
+                    }
+                } catch (LegacyExceptionWrapperSettingEvaluationExpression e) {
+                    if (!canBeStaticField) {
+                        throw e;
+                    }
+                    // Silently try to interpret className as static filed, throw the original exception if that fails. 
+                    try {
+                        return getStaticFieldValue(className);
+                    } catch (_ObjectBuilderSettingEvaluationException e2) {
+                        throw e;
+                    }
                 }
             }
 
@@ -868,8 +899,23 @@ public class _ObjectBuilderSettingEvaluator {
                 try {
                     cl = ClassUtil.forName(className);
                 } catch (Exception e2) {
+                    boolean failedToGetAsStaticField;
+                    if (canBeStaticField) {
+                        // Try to interpret className as static filed: 
+                        try {
+                            return getStaticFieldValue(className);
+                        } catch (_ObjectBuilderSettingEvaluationException e3) {
+                            // Suppress it
+                            failedToGetAsStaticField = true;
+                        }
+                    } else {
+                        failedToGetAsStaticField = false;
+                    }
                     throw new _ObjectBuilderSettingEvaluationException(
-                            "Failed to get class " + StringUtil.jQuote(className) + ".", e2);
+                            "Failed to get class " + StringUtil.jQuote(className)
+                            + (failedToGetAsStaticField ? " (also failed to resolve name as static field)" : "")
+                            + ".",
+                            e2);
                 }
             }
             
@@ -908,6 +954,53 @@ public class _ObjectBuilderSettingEvaluator {
             return result;
         }
         
+        private Object getStaticFieldValue(String dottedName) throws _ObjectBuilderSettingEvaluationException {
+            int lastDotIdx = dottedName.lastIndexOf('.');
+            if (lastDotIdx == -1) {
+                throw new IllegalArgumentException();
+            }
+            String className = shorthandToFullQualified(dottedName.substring(0, lastDotIdx));
+            String fieldName = dottedName.substring(lastDotIdx + 1);
+
+            Class<?> cl;
+            try {
+                cl = ClassUtil.forName(className);
+            } catch (Exception e) {
+                throw new _ObjectBuilderSettingEvaluationException(
+                        "Failed to get field's parent class, " + StringUtil.jQuote(className) + ".",
+                        e);
+            }
+            
+            Field field;
+            try {
+                field = cl.getField(fieldName);
+            } catch (Exception e) {
+                throw new _ObjectBuilderSettingEvaluationException(
+                        "Failed to get field " + StringUtil.jQuote(fieldName) + " from class "
+                        + StringUtil.jQuote(className) + ".",
+                        e);
+            }
+            
+            if ((field.getModifiers() & Modifier.STATIC) == 0) {
+                throw new _ObjectBuilderSettingEvaluationException("Referred field isn't static: " + field);
+            }
+            if ((field.getModifiers() & Modifier.PUBLIC) == 0) {
+                throw new _ObjectBuilderSettingEvaluationException("Referred field isn't public: " + field);
+            }
+
+            if (field.getName().equals(INSTANCE_FIELD_NAME)) {
+                throw new _ObjectBuilderSettingEvaluationException(
+                        "The " + INSTANCE_FIELD_NAME + " field is only accessible through pseudo-constructor call: "
+                        + className + "()");
+            }
+            
+            try {
+                return field.get(null);
+            } catch (Exception e) {
+                throw new _ObjectBuilderSettingEvaluationException("Failed to get field value: " + field, e);
+            }
+        }
+
         private Object callConstructor(Class cl)
                 throws _ObjectBuilderSettingEvaluationException {
             if (hasNoParameters()) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fe799f5d/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index c561322..ce8137e 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -26131,12 +26131,12 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
 
             <listitem>
               <para>Fixes and improvements in the <quote>object
-              builder</quote> syntax used for configuring FreeMarker from
-              <literal>java.util.Properties</literal> (or other string-only
-              sources). This is not to be confused with the template language
-              syntax, which has nothing to do with the <quote>object
-              builder</quote> syntax we are writing about here. The
-              improvements are:</para>
+              builder</quote> mini-language used for configuring FreeMarker
+              from <literal>java.util.Properties</literal> or other
+              string-only sources (not used in templates). This is not to be
+              confused with the template language syntax, which has nothing to
+              do with the <quote>object builder</quote> syntax we are writing
+              about here. The improvements are:</para>
 
               <itemizedlist>
                 <listitem>
@@ -26196,6 +26196,12 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                   <literal>BigDecimal</literal> and <literal>bi</literal> for
                   <literal>BigInteger</literal>.</para>
                 </listitem>
+
+                <listitem>
+                  <para>Public static fields can be referred, like
+                  <literal>com.example.MyClass.MY_CONSTANT</literal> or
+                  <literal>Configuration.AUTO_DETECT_TAG_SYNTAX</literal>.</para>
+                </listitem>
               </itemizedlist>
             </listitem>
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fe799f5d/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java b/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
index b708314..4276c7a 100644
--- a/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
+++ b/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
@@ -913,6 +913,46 @@ public class ObjectBuilderSettingsTest {
                 "{ -1: -11, -0.5: -0.55, -0.1bd: -0.11bd }");
     }
     
+    @Test
+    public void testStaticFields() throws Exception {
+        {
+            TestBean1 res = (TestBean1) _ObjectBuilderSettingEvaluator.eval(
+                    "freemarker.core.ObjectBuilderSettingsTest$TestBean1("
+                    + "freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST, true)",
+                    Object.class, false, _SettingEvaluationEnvironment.getCurrent());
+            assertEquals(TestStaticFields.CONST, (int) res.i);
+        }
+        {
+            TestBean1 res = (TestBean1) _ObjectBuilderSettingEvaluator.eval(
+                    "freemarker.core.ObjectBuilderSettingsTest$TestBean1("
+                    + "p2 = freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST)",
+                    Object.class, false, _SettingEvaluationEnvironment.getCurrent());
+            assertEquals(TestStaticFields.CONST, res.getP2());
+        }
+        assertEqualsEvaled(123, "freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST");
+        
+        // With shorthand class name:
+        assertEqualsEvaled(Configuration.AUTO_DETECT_TAG_SYNTAX, "Configuration.AUTO_DETECT_TAG_SYNTAX");
+        
+        try {
+            _ObjectBuilderSettingEvaluator.eval(
+                    "freemarker.core.ObjectBuilderSettingsTest$TestBean1("
+                    + "p2 = freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST())",
+                    Object.class, false, _SettingEvaluationEnvironment.getCurrent());
+            fail();
+        } catch (_ObjectBuilderSettingEvaluationException e) {
+            assertThat(e.getMessage(),
+                    containsString("freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST"));
+        }
+        try {
+            assertEqualsEvaled(123, "freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST()");
+            fail();
+        } catch (_ObjectBuilderSettingEvaluationException e) {
+            assertThat(e.getMessage(),
+                    containsString("freemarker.core.ObjectBuilderSettingsTest$TestStaticFields.CONST"));
+        }
+    }
+    
     private void assertEqualsEvaled(Object expectedValue, String s)
             throws _ObjectBuilderSettingEvaluationException, ClassNotFoundException, InstantiationException,
             IllegalAccessException {
@@ -1352,6 +1392,9 @@ public class ObjectBuilderSettingsTest {
         
     }
     
+    public static class TestStaticFields {
+        public static final int CONST = 123;
+    }
     
     public static class DummyArithmeticEngine extends ArithmeticEngine {