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/11/29 12:59:17 UTC

[13/25] incubator-freemarker git commit: Fixes and improvements in the object builder syntax used for configuring FreeMarker from java.util.Properties (or other string-only sources). This is not to be confused with the template language syntax, which has

Fixes and improvements in the object builder syntax used for configuring FreeMarker from java.util.Properties (or other string-only sources). This is not to be confused with the template language syntax, which has nothing to do with the object builder syntax:

(a) Number literals can have Java type specified postfixes (f, d, l), plus bd for BigDecimal and bi for BigInteger.

(b) Number literals with decimal point inside List literals and Map literals create Double values instead of BigDecimal-s. (For method and constructor call parameters and bean properties we still use BigDecimal, which is then converted to the target type anyway.)


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

Branch: refs/heads/2.3
Commit: a761e994c11d5f84718ac5256134b9199176e95d
Parents: 4ba6736
Author: ddekany <dd...@apache.org>
Authored: Sun Oct 25 19:44:27 2015 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Oct 25 19:44:27 2015 +0100

----------------------------------------------------------------------
 src/main/java/freemarker/core/Configurable.java |  9 +-
 .../core/_ObjectBuilderSettingEvaluator.java    | 96 ++++++++++++++------
 src/manual/book.xml                             | 28 ++++++
 .../core/ObjectBuilderSettingsTest.java         | 66 +++++++++++++-
 4 files changed, 169 insertions(+), 30 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/a761e994/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 e7b9a99..c42fdcb 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -1869,8 +1869,13 @@ public class Configurable {
      *   <li>
      *      <p>Currently, the values of arguments and properties can only be one of these:
      *      <ul>
-     *        <li>A numerical literal, like {@code 123} or {@code -1.5}. Like in FTL, there are no numerical types,
-     *            the value will be automatically converted to the type of the target.</li>
+     *        <li>A numerical literal, like {@code 123} or {@code -1.5}. The value will be automatically converted to
+     *        the type of the target (just like in FTL). However, a target type is only available if the number will
+     *        be a parameter to a method or constructor, not when it's a value (or key) in a {@code List} or
+     *        {@code Map} literal. Thus in the last case the type of number will be like in Java language, like
+     *        {@code 1} is an {@code int}, and {@code 1.0} is a {@code double}, and {@code 1.0f} is a {@code float},
+     *        etc. In all cases, the standard Java type postfixes can be used ("f", "d", "l"), plus "bd" for
+     *        {@code BigDecimal} and "bi" for {@code BigInteger}.</li>
      *        <li>A boolean literal: {@code true} or {@code false}
      *        <li>The null literal: {@code null}
      *        <li>A string literal with FTL syntax, except that  it can't contain <tt>${...}</tt>-s and

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/a761e994/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 30abfdb..efcd3cc 100644
--- a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
+++ b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
@@ -125,7 +125,7 @@ public class _ObjectBuilderSettingEvaluator {
         
         skipWS();
         try {
-            value = ensureEvaled(fetchValue(false, true, true));
+            value = ensureEvaled(fetchValue(false, true, false, true));
         } catch (LegacyExceptionWrapperSettingEvaluationExpression e) {
             e.rethrowLegacy();
             value = null; // newer reached
@@ -209,7 +209,7 @@ public class _ObjectBuilderSettingEvaluator {
             do {
                 skipWS();
                 
-                Object paramNameOrValue = fetchValue(false, false, false);
+                Object paramNameOrValue = fetchValue(false, false, true, false);
                 if (paramNameOrValue != VOID) {
                     skipWS();
                     if (paramNameOrValue instanceof Name) {
@@ -219,7 +219,7 @@ public class _ObjectBuilderSettingEvaluator {
                         fetchRequiredChar("=");
                         skipWS();
                         
-                        Object paramValue = fetchValue(false, false, true);
+                        Object paramValue = fetchValue(false, false, true, true);
                         exp.namedParamValues.add(ensureEvaled(paramValue));
                     } else {
                         if (!exp.namedParamNames.isEmpty()) {
@@ -239,10 +239,10 @@ public class _ObjectBuilderSettingEvaluator {
         }
     }
 
-    private Object fetchValue(boolean optional, boolean topLevel, boolean resolveVariables)
+    private Object fetchValue(boolean optional, boolean topLevel, boolean resultCoerced, boolean resolveVariables)
             throws _ObjectBuilderSettingEvaluationException {
         if (pos < src.length()) {
-            Object val = fetchNumberLike(true);
+            Object val = fetchNumberLike(true, resultCoerced);
             if (val != VOID) {
                 return val;
             }
@@ -361,7 +361,8 @@ public class _ObjectBuilderSettingEvaluator {
         return className;
     }
 
-    private Object fetchNumberLike(boolean optional) throws _ObjectBuilderSettingEvaluationException {
+    private Object fetchNumberLike(boolean optional, boolean resultCoerced)
+            throws _ObjectBuilderSettingEvaluationException {
         int startPos = pos;
         boolean isVersion = false;
         boolean hasDot = false;
@@ -391,38 +392,81 @@ public class _ObjectBuilderSettingEvaluator {
             }
         }
         
-        String tk = src.substring(startPos, pos);
+        String numStr = src.substring(startPos, pos);
         if (isVersion) {
             try {
-                return new Version(tk);
+                return new Version(numStr);
             } catch (IllegalArgumentException e) {
-                throw new _ObjectBuilderSettingEvaluationException("Malformed version number: " + tk, e);
+                throw new _ObjectBuilderSettingEvaluationException("Malformed version number: " + numStr, e);
             }
         } else {
+            // For example, in 1.0f, numStr is "1.0", and typePostfix is "f".
+            String typePostfix = null;
+            seekTypePostfixEnd: while (true) {
+                if (pos == src.length()) {
+                    break seekTypePostfixEnd;
+                }
+                char c = src.charAt(pos);
+                if (Character.isLetter(c)) {
+                    if (typePostfix == null) {
+                        typePostfix = String.valueOf(c);
+                    } else {
+                        typePostfix += c; 
+                    }
+                } else {
+                    break seekTypePostfixEnd;
+                }
+                pos++;
+            }
+            
             try {
-                if (tk.endsWith(".")) {
+                if (numStr.endsWith(".")) {
                     throw new NumberFormatException("A number can't end with a dot");
                 }
-                if (tk.startsWith(".") || tk.startsWith("-.")  || tk.startsWith("+.")) {
+                if (numStr.startsWith(".") || numStr.startsWith("-.")  || numStr.startsWith("+.")) {
                     throw new NumberFormatException("A number can't start with a dot");
                 }
-                
-                if (tk.indexOf('.') == -1) {
-                    BigInteger biNum = new BigInteger(tk);
-                    final int bitLength = biNum.bitLength();  // Doesn't include sign bit
-                    if (bitLength <= 31) {
-                        return Integer.valueOf(biNum.intValue());
-                    } else if (bitLength <= 63) {
-                        return Long.valueOf(biNum.longValue());
+
+                if (typePostfix == null) {
+                    // Auto-detect type
+                    if (numStr.indexOf('.') == -1) {
+                        BigInteger biNum = new BigInteger(numStr);
+                        final int bitLength = biNum.bitLength();  // Doesn't include sign bit
+                        if (bitLength <= 31) {
+                            return Integer.valueOf(biNum.intValue());
+                        } else if (bitLength <= 63) {
+                            return Long.valueOf(biNum.longValue());
+                        } else {
+                            return biNum;
+                        }
+                    } else {
+                        if (resultCoerced) {
+                            // The FTL way (BigDecimal is loseless, and it will be coerced to the target type later):
+                            return new BigDecimal(numStr);
+                        } else {
+                            // The Java way (lossy but familiar):
+                            return Double.valueOf(numStr);
+                        }
+                    }
+                } else { // Has explicitly specified type
+                    if (typePostfix.equalsIgnoreCase("l")) {
+                        return Long.valueOf(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("bi")) {
+                        return new BigInteger(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("bd")) {
+                        return new BigDecimal(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("d")) {
+                        return Double.valueOf(numStr);
+                    } else if (typePostfix.equalsIgnoreCase("f")) {
+                        return Float.valueOf(numStr);
                     } else {
-                        return biNum;
+                        throw new _ObjectBuilderSettingEvaluationException(
+                                "Unrecognized number type postfix: " + typePostfix);
                     }
-                } else {
-                    return new BigDecimal(tk);
                 }
                 
             } catch (NumberFormatException e) {
-                throw new _ObjectBuilderSettingEvaluationException("Malformed number: " + tk, e);
+                throw new _ObjectBuilderSettingEvaluationException("Malformed number: " + numStr, e);
             }
         }
     }
@@ -516,7 +560,7 @@ public class _ObjectBuilderSettingEvaluator {
                 skipWS();
             }
             
-            listExp.addItem(fetchValue(false, false, true));
+            listExp.addItem(fetchValue(false, false, false, true));
             
             skipWS();
         }
@@ -544,11 +588,11 @@ public class _ObjectBuilderSettingEvaluator {
                 skipWS();
             }
             
-            Object key = fetchValue(false, false, true);
+            Object key = fetchValue(false, false, false, true);
             skipWS();
             fetchRequiredChar(":");
             skipWS();
-            Object value = fetchValue(false, false, true);
+            Object value = fetchValue(false, false, false, true);
             mapExp.addItem(new KeyValuePair(key, value));
             
             skipWS();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/a761e994/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 82f89ac..e461e94 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26128,6 +26128,14 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                   importance as number types are converted to the constructor
                   parameter type anyway.</para>
                 </listitem>
+
+                <listitem>
+                  <para>Number literals can have Java type specified postfixes
+                  (<literal>f</literal>, <literal>d</literal>,
+                  <literal>l</literal>), plus <literal>bd</literal> for
+                  <literal>BigDecimal</literal> and <literal>bi</literal> for
+                  <literal>BigInteger</literal>.</para>
+                </listitem>
               </itemizedlist>
             </listitem>
 
@@ -26229,6 +26237,26 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               <literal>String</literal>-s and
               <literal>Interpolation</literal>-s.</para>
             </listitem>
+
+            <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>
+
+              <itemizedlist>
+                <listitem>
+                  <para>Number literals can have Java type specified postfixes
+                  (<literal>f</literal>, <literal>d</literal>,
+                  <literal>l</literal>), plus <literal>bd</literal> for
+                  <literal>BigDecimal</literal> and <literal>bi</literal> for
+                  <literal>BigInteger</literal>.</para>
+                </listitem>
+              </itemizedlist>
+            </listitem>
           </itemizedlist>
         </section>
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/a761e994/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 0a1d53c..b708314 100644
--- a/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
+++ b/src/test/java/freemarker/core/ObjectBuilderSettingsTest.java
@@ -632,7 +632,7 @@ public class ObjectBuilderSettingsTest {
         assertEquals(Boolean.TRUE, _ObjectBuilderSettingEvaluator.eval(
                 "  true  ",
                 Object.class, true, _SettingEvaluationEnvironment.getCurrent()));
-        assertEquals(new BigDecimal("1.23"), _ObjectBuilderSettingEvaluator.eval(
+        assertEquals(Double.valueOf("1.23"), _ObjectBuilderSettingEvaluator.eval(
                 "1.23 ",
                 Number.class, true, _SettingEvaluationEnvironment.getCurrent()));
         assertEquals(new Version(1, 2, 3), _ObjectBuilderSettingEvaluator.eval(
@@ -642,7 +642,7 @@ public class ObjectBuilderSettingsTest {
 
     @Test
     public void testNumberLiteralJavaTypes() throws Exception {
-        assertEquals(new BigDecimal("1.0"), _ObjectBuilderSettingEvaluator.eval(
+        assertEquals(Double.valueOf("1.0"), _ObjectBuilderSettingEvaluator.eval(
                 "1.0",
                 Number.class, true, _SettingEvaluationEnvironment.getCurrent()));
 
@@ -858,6 +858,68 @@ public class ObjectBuilderSettingsTest {
             assertThat(e.getMessage(), containsString("null as key"));
         }
     }
+
+    @Test
+    public void testMethodParameterNumberTypes() throws Exception {
+        {
+            TestBean8 result = (TestBean8) _ObjectBuilderSettingEvaluator.eval(
+                    "freemarker.core.ObjectBuilderSettingsTest$TestBean8(anyObject=1)",
+                    TestBean8.class, false, new _SettingEvaluationEnvironment());
+            assertEquals(result.getAnyObject(), 1);
+        }
+        {
+            TestBean8 result = (TestBean8) _ObjectBuilderSettingEvaluator.eval(
+                    "freemarker.core.ObjectBuilderSettingsTest$TestBean8(anyObject=2147483649)",
+                    TestBean8.class, false, new _SettingEvaluationEnvironment());
+            assertEquals(result.getAnyObject(), 2147483649L);
+        }
+        {
+            TestBean8 result = (TestBean8) _ObjectBuilderSettingEvaluator.eval(
+                    "freemarker.core.ObjectBuilderSettingsTest$TestBean8(anyObject=1.0)",
+                    TestBean8.class, false, new _SettingEvaluationEnvironment());
+            // Like in FTL, non-integer numbers are BigDecimal-s, that are later coerced to the actual parameter type.
+            // However, here the type is Object, so it remains BigDecimal.
+            assertEquals(new BigDecimal("1.0"), result.getAnyObject());
+        }
+    }
+    
+    @Test
+    public void testNonMethodParameterNumberTypes() throws Exception {
+        assertEqualsEvaled(Integer.valueOf(1), "1");
+        assertEqualsEvaled(Double.valueOf(1), "1.0");
+        assertEqualsEvaled(Long.valueOf(2147483649l), "2147483649");
+
+        assertEqualsEvaled(Double.valueOf(1), "1d");
+        assertEqualsEvaled(Double.valueOf(1), "1D");
+        assertEqualsEvaled(Float.valueOf(1), "1f");
+        assertEqualsEvaled(Float.valueOf(1), "1F");
+        assertEqualsEvaled(Long.valueOf(1), "1l");
+        assertEqualsEvaled(Long.valueOf(1), "1L");
+        assertEqualsEvaled(BigDecimal.valueOf(1), "1bd");
+        assertEqualsEvaled(BigDecimal.valueOf(1), "1Bd");
+        assertEqualsEvaled(BigDecimal.valueOf(1), "1BD");
+        assertEqualsEvaled(BigInteger.valueOf(1), "1bi");
+        assertEqualsEvaled(BigInteger.valueOf(1), "1bI");
+        
+        assertEqualsEvaled(Float.valueOf(1.5f), "1.5f");
+        assertEqualsEvaled(Double.valueOf(1.5), "1.5d");
+        assertEqualsEvaled(BigDecimal.valueOf(1.5), "1.5bd");
+        
+        assertEqualsEvaled(
+                ImmutableList.of(-1, -0.5, new BigDecimal("-0.1")),
+                "[ -1, -0.5, -0.1bd ]");
+        assertEqualsEvaled(
+                ImmutableMap.of(-1, -11, -0.5, -0.55, new BigDecimal("-0.1"), new BigDecimal("-0.11")),
+                "{ -1: -11, -0.5: -0.55, -0.1bd: -0.11bd }");
+    }
+    
+    private void assertEqualsEvaled(Object expectedValue, String s)
+            throws _ObjectBuilderSettingEvaluationException, ClassNotFoundException, InstantiationException,
+            IllegalAccessException {
+        Object actualValue = _ObjectBuilderSettingEvaluator.eval(
+                s, Object.class, true, _SettingEvaluationEnvironment.getCurrent());
+        assertEquals(expectedValue, actualValue);
+    }
     
     @Test
     public void visibilityTest() throws Exception {