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/09/14 02:27:45 UTC

[1/4] incubator-freemarker git commit: Added support for calling some Java 6 functions (despite that we only require Java 5)

Repository: incubator-freemarker
Updated Branches:
  refs/heads/2.3-gae 26c1ff1ea -> 1d5198f67


Added support for calling some Java 6 functions (despite that we only require Java 5)


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

Branch: refs/heads/2.3-gae
Commit: 2daf5f3d78d15cfed55b93459ef31af9a1889db9
Parents: 26c1ff1
Author: ddekany <dd...@apache.org>
Authored: Mon Sep 14 02:23:29 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Mon Sep 14 02:23:29 2015 +0200

----------------------------------------------------------------------
 build.properties.sample                         |  1 +
 build.xml                                       | 35 ++++++++-
 src/main/java/freemarker/core/_Java6.java       | 35 +++++++++
 src/main/java/freemarker/core/_Java6Impl.java   | 45 ++++++++++++
 .../java/freemarker/core/_JavaVersions.java     | 77 ++++++++++++++++++++
 5 files changed, 190 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2daf5f3d/build.properties.sample
----------------------------------------------------------------------
diff --git a/build.properties.sample b/build.properties.sample
index 82c818b..1a94745 100644
--- a/build.properties.sample
+++ b/build.properties.sample
@@ -1,4 +1,5 @@
 # Copy this file to "build.properties" before editing!
 # These propeties should point to the rt.jar-s of the respective J2SE versions:
 boot.classpath.j2se1.5=C:/Program Files (x86)/Java/jdk1.5.0_16/jre/lib/rt.jar
+boot.classpath.j2se1.6=C:/Program Files/Java/jdk1.6.0_24/jre/lib/rt.jar
 mvnCommand=C:/Program Files (x86)/maven3/bin/mvn.bat
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2daf5f3d/build.xml
----------------------------------------------------------------------
diff --git a/build.xml b/build.xml
index 03ac2fb..e6b84cc 100644
--- a/build.xml
+++ b/build.xml
@@ -24,9 +24,13 @@
   <condition property="has.explicit.boot.classpath.j2se1.5">
     <isset property="boot.classpath.j2se1.5"/>
   </condition>
+  <condition property="has.explicit.boot.classpath.j2se1.6">
+    <isset property="boot.classpath.j2se1.6"/>
+  </condition>
   <condition property="has.all.explicit.boot.classpaths">
     <and>
       <isset property="has.explicit.boot.classpath.j2se1.5"/>
+      <isset property="has.explicit.boot.classpath.j2se1.6"/>
     </and>
   </condition>
   <available property="atLeastJDK8" classname="java.util.function.Predicate"/>
@@ -34,12 +38,17 @@
   <!-- When boot.classpath.j2se* is missing, these will be the defaults: -->
   <!-- Note: Target "dist" doesn't allow using these. -->
   <property name="boot.classpath.j2se1.5" value="${sun.boot.class.path}" />
+  <property name="boot.classpath.j2se1.6" value="${sun.boot.class.path}" />
   
   <!-- For checking the correctness of the boot.classpath.j2se* -->
   <available classpath="${boot.classpath.j2se1.5}"
-    classname="java.lang.Object" ignoresystemclasses="true" 
+    classname="java.util.concurrent.ConcurrentHashMap" ignoresystemclasses="true" 
     property="boot.classpath.j2se1.5.correct"
   />
+  <available classpath="${boot.classpath.j2se1.6}"
+    classname="java.util.ServiceLoader" ignoresystemclasses="true" 
+    property="boot.classpath.j2se1.6.correct"
+  />
   
   <condition property="example.freemarker.jar.location"
     value="freemarker.jar" else="build/freemarker.jar">
@@ -179,7 +188,17 @@
       -->the &lt;projectDir>/build.properties file, or wherever you <!--
       -->set it.<!--
     --></fail>
-    <echo level="info">Using boot classpaths: 1.5: ${boot.classpath.j2se1.5}</echo>
+    <fail unless="boot.classpath.j2se1.6.correct"><!--
+      -->The "boot.classpath.j2se1.6" property value (${boot.classpath.j2se1.6}) <!--
+      -->seems to be an incorrect boot classpath. Please fix it in <!--
+      -->the &lt;projectDir>/build.properties file, or wherever you <!--
+      -->set it.<!--
+    --></fail>
+    <echo level="info"><!--
+      -->Using boot classpaths:<!--
+      -->Java 5: ${boot.classpath.j2se1.5}; <!--
+      -->Java 6: ${boot.classpath.j2se1.6}<!--
+    --></echo>
 
     <mkdir dir="build/classes" />
     
@@ -191,6 +210,7 @@
       classpathref="ivy.dep"
       bootclasspath="${boot.classpath.j2se1.5}"
       excludes="
+        freemarker/core/_Java6Impl.java,
         freemarker/ext/jsp/**,
         freemarker/ext/servlet/**,
         freemarker/cache/WebappTemplateLoader.java,
@@ -205,6 +225,15 @@
       </src>
     </javac>
 
+    <ivy:cachepath conf="build.base" pathid="ivy.dep" />
+    <javac srcdir="src/main/java" destdir="build/classes" deprecation="off" 
+      debug="on" optimize="off" target="1.5" source="1.5" encoding="utf-8"
+      includeantruntime="false"
+      classpathref="ivy.dep"
+      bootclasspath="${boot.classpath.j2se1.6}"
+      includes="freemarker/core/_Java6Impl.java"
+    />
+    
     <rmic
       base="build/classes" includes="freemarker/debug/impl/Rmi*Impl.class"
       classpathref="ivy.dep"
@@ -331,7 +360,7 @@
       includeantruntime="false"
       classpath="build/classes"
       classpathref="ivy.dep.build.test"
-      bootclasspath="${boot.classpath.j2se1.5}"
+      bootclasspath="${boot.classpath.j2se1.6}"
     />
     <copy toDir="build/test-classes">
       <fileset dir="src/test/resources"

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2daf5f3d/src/main/java/freemarker/core/_Java6.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/_Java6.java b/src/main/java/freemarker/core/_Java6.java
new file mode 100644
index 0000000..8a49389
--- /dev/null
+++ b/src/main/java/freemarker/core/_Java6.java
@@ -0,0 +1,35 @@
+/*
+ * 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 freemarker.core;
+
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+
+/**
+ * Used internally only, might changes without notice!
+ * Used for accessing functionality that's only present in Java 6 or later.
+ */
+public interface _Java6 {
+
+    void setRoundingMode(DecimalFormat df, RoundingMode roundingMode);
+
+    void setExponentSeparator(DecimalFormatSymbols dfs, String exponentSeparator);
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2daf5f3d/src/main/java/freemarker/core/_Java6Impl.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/_Java6Impl.java b/src/main/java/freemarker/core/_Java6Impl.java
new file mode 100644
index 0000000..ba2b21c
--- /dev/null
+++ b/src/main/java/freemarker/core/_Java6Impl.java
@@ -0,0 +1,45 @@
+/*
+ * 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 freemarker.core;
+
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+
+/**
+ * Used internally only, might changes without notice!
+ * Used for accessing functionality that's only present in Java 6 or later.
+ */
+public final class _Java6Impl implements _Java6 {
+    
+    public static final _Java6 INSTANCE = new _Java6Impl();
+
+    private _Java6Impl() {
+        // Not meant to be instantiated
+    }
+
+    public void setRoundingMode(DecimalFormat df, RoundingMode roundingMode) {
+        df.setRoundingMode(roundingMode);
+    }
+
+    public void setExponentSeparator(DecimalFormatSymbols dfs, String exponentSeparator) {
+        dfs.setExponentSeparator(exponentSeparator);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2daf5f3d/src/main/java/freemarker/core/_JavaVersions.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/_JavaVersions.java b/src/main/java/freemarker/core/_JavaVersions.java
new file mode 100644
index 0000000..244862d
--- /dev/null
+++ b/src/main/java/freemarker/core/_JavaVersions.java
@@ -0,0 +1,77 @@
+/*
+ * 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 freemarker.core;
+
+import freemarker.log.Logger;
+import freemarker.template.Version;
+import freemarker.template.utility.SecurityUtilities;
+
+/**
+ * Used internally only, might changes without notice!
+ */
+public final class _JavaVersions {
+    
+    private _JavaVersions() {
+        // Not meant to be instantiated
+    }
+
+    private static final boolean IS_AT_LEAST_6;
+    static {
+        boolean result = false;
+        String vStr = SecurityUtilities.getSystemProperty("java.version", null);
+        if (vStr != null) {
+            try {
+                Version v = new Version(vStr);
+                result = v.getMajor() == 1 && v.getMinor() >= 6 || v.getMajor() > 1;
+            } catch (Exception e) {
+                // Ignore
+            }
+        }
+        if (vStr == null) {
+            try {
+                Class.forName("java.util.ServiceLoader");
+                result = true;
+            } catch (Exception e) {
+                // Ignore
+            }
+        }
+        IS_AT_LEAST_6 = result;
+    }
+    
+    static public final _Java6 JAVA_6;
+    static {
+        _Java6 java6;
+        if (IS_AT_LEAST_6) {
+            try {
+                java6 = (_Java6) Class.forName("freemarker.core._Java6Impl").getField("INSTANCE").get(null);
+            } catch (Exception e) {
+                try {
+                    Logger.getLogger("freemarker.runtime").error("Failed to access Java 6 functionality", e);
+                } catch (Exception e2) {
+                    // Suppressed
+                }
+                java6 = null;
+            }
+        } else {
+            java6 = null;
+        }
+        JAVA_6 = java6;
+    }
+    
+}


[4/4] incubator-freemarker git commit: Initial implementation of extended DecimalFormat format string parsing. It's not used by FTL yet. Also currency-related options are missing.

Posted by dd...@apache.org.
Initial implementation of extended DecimalFormat format string parsing. It's not used by FTL yet. Also currency-related options are missing.


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

Branch: refs/heads/2.3-gae
Commit: 1d5198f67c2e90206742ace0900ea989088a2de0
Parents: aab0286
Author: ddekany <dd...@apache.org>
Authored: Mon Sep 14 02:27:27 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Mon Sep 14 02:27:27 2015 +0200

----------------------------------------------------------------------
 .../core/ExtendedDecimalFormatParser.java       | 460 +++++++++++++++++++
 .../core/ExtendedDecimalFormatTest.java         | 246 ++++++++++
 2 files changed, 706 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/1d5198f6/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java b/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java
new file mode 100644
index 0000000..95acbba
--- /dev/null
+++ b/src/main/java/freemarker/core/ExtendedDecimalFormatParser.java
@@ -0,0 +1,460 @@
+/*
+ * 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 freemarker.core;
+
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Set;
+
+import freemarker.template.utility.StringUtil;
+
+class ExtendedDecimalFormatParser {
+
+    private static final HashMap<String, ? extends ParameterHandler> PARAM_HANDLERS;
+
+    static {
+        HashMap<String, ParameterHandler> m = new HashMap<String, ParameterHandler>();
+        m.put("ro", new ParameterHandler() {
+
+            public void handle(ExtendedDecimalFormatParser parser, String value)
+                    throws InvalidParameterValueException {
+                RoundingMode parsedValue;
+                if (value.equals("u")) {
+                    parsedValue = RoundingMode.UP;
+                } else if (value.equals("d")) {
+                    parsedValue = RoundingMode.DOWN;
+                } else if (value.equals("c")) {
+                    parsedValue = RoundingMode.CEILING;
+                } else if (value.equals("f")) {
+                    parsedValue = RoundingMode.FLOOR;
+                } else if (value.equals("hd")) {
+                    parsedValue = RoundingMode.HALF_DOWN;
+                } else if (value.equals("he")) {
+                    parsedValue = RoundingMode.HALF_EVEN;
+                } else if (value.equals("hu")) {
+                    parsedValue = RoundingMode.HALF_UP;
+                } else if (value.equals("un")) {
+                    parsedValue = RoundingMode.UNNECESSARY;
+                } else {
+                    throw new InvalidParameterValueException("Should be one of: u, d, c, f, hd, he, hu, un");
+                }
+
+                if (_JavaVersions.JAVA_6 == null) {
+                    throw new InvalidParameterValueException("For setting the rounding mode you need Java 6 or later.");
+                }
+
+                parser.roundingMode = parsedValue;
+            }
+        });
+        m.put("mul", new ParameterHandler() {
+
+            public void handle(ExtendedDecimalFormatParser parser, String value)
+                    throws InvalidParameterValueException {
+                try {
+                    parser.multipier = Integer.valueOf(value);
+                } catch (NumberFormatException e) {
+                    throw new InvalidParameterValueException("Malformed integer.");
+                }
+            }
+        });
+        m.put("dec", new ParameterHandler() {
+
+            public void handle(ExtendedDecimalFormatParser parser, String value)
+                    throws InvalidParameterValueException {
+                if (value.length() != 1) {
+                    throw new InvalidParameterValueException("Must contain exactly 1 character.");
+                }
+                parser.symbols.setDecimalSeparator(value.charAt(0));
+            }
+        });
+        m.put("grp", new ParameterHandler() {
+
+            public void handle(ExtendedDecimalFormatParser parser, String value)
+                    throws InvalidParameterValueException {
+                if (value.length() != 1) {
+                    throw new InvalidParameterValueException("Must contain exactly 1 character.");
+                }
+                parser.symbols.setGroupingSeparator(value.charAt(0));
+            }
+        });
+        m.put("exp", new ParameterHandler() {
+
+            public void handle(ExtendedDecimalFormatParser parser, String value)
+                    throws InvalidParameterValueException {
+                if (_JavaVersions.JAVA_6 == null) {
+                    throw new InvalidParameterValueException(
+                            "For setting the exponent separator you need Java 6 or later.");
+                }
+                _JavaVersions.JAVA_6.setExponentSeparator(parser.symbols, value);
+            }
+        });
+        m.put("min", new ParameterHandler() {
+
+            public void handle(ExtendedDecimalFormatParser parser, String value)
+                    throws InvalidParameterValueException {
+                if (value.length() != 1) {
+                    throw new InvalidParameterValueException("Must contain exactly 1 character.");
+                }
+                parser.symbols.setMinusSign(value.charAt(0));
+            }
+        });
+        m.put("inf", new ParameterHandler() {
+
+            public void handle(ExtendedDecimalFormatParser parser, String value)
+                    throws InvalidParameterValueException {
+                parser.symbols.setInfinity(value);
+            }
+        });
+        m.put("nan", new ParameterHandler() {
+
+            public void handle(ExtendedDecimalFormatParser parser, String value)
+                    throws InvalidParameterValueException {
+                parser.symbols.setNaN(value);
+            }
+        });
+        m.put("prc", new ParameterHandler() {
+
+            public void handle(ExtendedDecimalFormatParser parser, String value)
+                    throws InvalidParameterValueException {
+                if (value.length() != 1) {
+                    throw new InvalidParameterValueException("Must contain exactly 1 character.");
+                }
+                parser.symbols.setPercent(value.charAt(0));
+            }
+        });
+        m.put("prm", new ParameterHandler() {
+
+            public void handle(ExtendedDecimalFormatParser parser, String value)
+                    throws InvalidParameterValueException {
+                if (value.length() != 1) {
+                    throw new InvalidParameterValueException("Must contain exactly 1 character.");
+                }
+                parser.symbols.setPerMill(value.charAt(0));
+            }
+        });
+        m.put("zero", new ParameterHandler() {
+
+            public void handle(ExtendedDecimalFormatParser parser, String value)
+                    throws InvalidParameterValueException {
+                if (value.length() != 1) {
+                    throw new InvalidParameterValueException("Must contain exactly 1 character.");
+                }
+                parser.symbols.setZeroDigit(value.charAt(0));
+            }
+        });
+        PARAM_HANDLERS = m;
+    }
+
+    private static final String SNIP_MARK = "[...]";
+    private static final int MAX_QUOTATION_LENGTH = 10; // Must be more than SNIP_MARK.length!
+
+    private final String src;
+    private int pos = 0;
+
+    private final DecimalFormatSymbols symbols;
+    private RoundingMode roundingMode;
+    private Integer multipier;
+
+    static DecimalFormat parse(String formatString, Locale locale) throws ParseException {
+        return new ExtendedDecimalFormatParser(formatString, locale).parse();
+    }
+
+    private DecimalFormat parse() throws ParseException {
+        String stdPattern = fetchStandardPattern();
+        skipWS();
+        parseFormatStringExtension();
+
+        DecimalFormat decimalFormat = new DecimalFormat(stdPattern, symbols);
+
+        if (roundingMode != null) {
+            if (_JavaVersions.JAVA_6 == null) {
+                throw new ParseException("Setting rounding mode needs Java 6 or later", 0);
+            }
+            _JavaVersions.JAVA_6.setRoundingMode(decimalFormat, roundingMode);
+        }
+
+        if (multipier != null) {
+            decimalFormat.setMultiplier(multipier.intValue());
+        }
+
+        return decimalFormat;
+    }
+
+    private void parseFormatStringExtension() throws ParseException {
+        int ln = src.length();
+
+        if (pos == ln) {
+            return;
+        }
+
+        do {
+            int namePos = pos;
+            String name = fetchName();
+            if (name == null) {
+                throw newExpectedSgParseException("name");
+            }
+
+            skipWS();
+
+            if (!fetchChar('=')) {
+                throw newExpectedSgParseException("\"=\"");
+            }
+
+            skipWS();
+
+            int valuePos = pos;
+            String value = fetchValue();
+            if (value == null) {
+                throw newExpectedSgParseException("value");
+            }
+            int paramEndPos = pos;
+
+            applyFormatStringExtensionParameter(name, namePos, value, valuePos);
+
+            skipWS();
+
+            // Optional comma
+            if (fetchChar(',')) {
+                skipWS();
+            } else {
+                if (pos == ln) {
+                    return;
+                }
+                if (pos == paramEndPos) {
+                    throw newExpectedSgParseException("parameter separator whitespace or comma");
+                }
+            }
+        } while (true);
+    }
+
+    private void applyFormatStringExtensionParameter(
+            String name, int namePos, String value, int valuePos) throws ParseException {
+        ParameterHandler handler = PARAM_HANDLERS.get(name);
+        if (handler == null) {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("Unsupported parameter name, ").append(StringUtil.jQuote(name));
+            sb.append(". The supported names are: ");
+            Set<String> legalNames = PARAM_HANDLERS.keySet();
+            String[] legalNameArr = legalNames.toArray(new String[legalNames.size()]);
+            Arrays.sort(legalNameArr);
+            for (int i = 0; i < legalNameArr.length; i++) {
+                if (i != 0) {
+                    sb.append(", ");
+                }
+                sb.append(legalNameArr[i]);
+            }
+            throw new java.text.ParseException(sb.toString(), namePos);
+        }
+
+        try {
+            handler.handle(this, value);
+        } catch (InvalidParameterValueException e) {
+            throw new java.text.ParseException(
+                    StringUtil.jQuote(value) + " is an invalid value for the \"" + name + "\" parameter: " + e.message,
+                    valuePos);
+        }
+    }
+
+    private void skipWS() {
+        int ln = src.length();
+        while (pos < ln && isWS(src.charAt(pos))) {
+            pos++;
+        }
+    }
+
+    private boolean fetchChar(char fetchedChar) {
+        if (pos < src.length() && src.charAt(pos) == fetchedChar) {
+            pos++;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    private boolean isWS(char c) {
+        return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\u00A0';
+    }
+
+    private String fetchName() throws ParseException {
+        int ln = src.length();
+        int startPos = pos;
+        boolean firstChar = true;
+        scanUntilEnd: while (pos < ln) {
+            char c = src.charAt(pos);
+            if (firstChar) {
+                if (!Character.isJavaIdentifierStart(c)) {
+                    break scanUntilEnd;
+                }
+                firstChar = false;
+            } else if (!Character.isJavaIdentifierPart(c)) {
+                break scanUntilEnd;
+            }
+            pos++;
+        }
+        return !firstChar ? src.substring(startPos, pos) : null;
+    }
+
+    private String fetchValue() throws ParseException {
+        int ln = src.length();
+        int startPos = pos;
+        boolean quotedMode = false;
+        boolean needsUnescaping = false;
+        scanUntilEnd: while (pos < ln) {
+            char c = src.charAt(pos);
+            if (c == '\'') {
+                if (!quotedMode) {
+                    if (startPos != pos) {
+                        throw new java.text.ParseException(
+                                "The \"'\" character can only be used for quoting values, "
+                                        + "but it was in the middle of an non-quoted value.",
+                                pos);
+                    }
+                    quotedMode = true;
+                } else {
+                    if (pos + 1 < ln && src.charAt(pos + 1) == '\'') {
+                        pos++; // skip "''" (escaped "'")
+                        needsUnescaping = true;
+                    } else {
+                        String str = src.substring(startPos + 1, pos);
+                        pos++;
+                        return needsUnescaping ? unescape(str) : str;
+                    }
+                }
+            } else {
+                if (!quotedMode && !Character.isJavaIdentifierPart(c)) {
+                    break scanUntilEnd;
+                }
+            }
+            pos++;
+        } // while
+        if (quotedMode) {
+            throw new java.text.ParseException(
+                    "The \"'\" quotation wasn't closed when the end of the source was reached.",
+                    pos);
+        }
+        return startPos == pos ? null : src.substring(startPos, pos);
+    }
+
+    private String unescape(String s) {
+        return StringUtil.replace(s, "\'\'", "\'");
+    }
+
+    private String fetchStandardPattern() {
+        int pos = this.pos;
+        int ln = src.length();
+        int semicolonCnt = 0;
+        boolean quotedMode = false;
+        findStdPartEnd: while (pos < ln) {
+            char c = src.charAt(pos);
+            if (c == ';' && !quotedMode) {
+                semicolonCnt++;
+                if (semicolonCnt == 2) {
+                    break findStdPartEnd;
+                }
+            } else if (c == '\'') {
+                if (quotedMode) {
+                    if (pos + 1 < ln && src.charAt(pos + 1) == '\'') {
+                        // Skips "''" used for escaping "'"
+                        pos++;
+                    } else {
+                        quotedMode = false;
+                    }
+                } else {
+                    quotedMode = true;
+                }
+            }
+            pos++;
+        }
+
+        String stdFormatStr;
+        if (semicolonCnt < 2) { // We have a standard DecimalFormat string
+            // Note that "0.0;" and "0.0" gives the same result with DecimalFormat, so we leave a ';' there
+            stdFormatStr = src;
+        } else { // `pos` points to the 2nd ';'
+            int stdEndPos = pos;
+            if (src.charAt(pos - 1) == ';') { // we have a ";;"
+                // Note that ";;" is illegal in DecimalFormat, so this is backward compatible.
+                stdEndPos--;
+            }
+            stdFormatStr = src.substring(0, stdEndPos);
+        }
+
+        if (pos < ln) {
+            pos++; // Skips closing ';'
+        }
+        this.pos = pos;
+
+        return stdFormatStr;
+    }
+
+    private ExtendedDecimalFormatParser(String formatString, Locale locale) {
+        src = formatString;
+        this.symbols = new DecimalFormatSymbols(locale);
+    }
+
+    private ParseException newExpectedSgParseException(String expectedThing) {
+        String quotation;
+
+        // Ignore trailing WS when calculating the length:
+        int i = src.length() - 1;
+        while (i >= 0 && Character.isWhitespace(src.charAt(i))) {
+            i--;
+        }
+        int ln = i + 1;
+
+        if (pos < ln) {
+            int qEndPos = pos + MAX_QUOTATION_LENGTH;
+            if (qEndPos >= ln) {
+                quotation = src.substring(pos, ln);
+            } else {
+                quotation = src.substring(pos, qEndPos - SNIP_MARK.length()) + SNIP_MARK;
+            }
+        } else {
+            quotation = null;
+        }
+
+        return new ParseException(
+                "Expected a(n) " + expectedThing + " at position " + pos + " (0-based), but "
+                        + (quotation == null ? "reached the end of the input." : "found: " + quotation),
+                pos);
+    }
+
+    private interface ParameterHandler {
+
+        void handle(ExtendedDecimalFormatParser parser, String value)
+                throws InvalidParameterValueException;
+
+    }
+
+    private static class InvalidParameterValueException extends Exception {
+
+        private final String message;
+
+        public InvalidParameterValueException(String message) {
+            this.message = message;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/1d5198f6/src/test/java/freemarker/core/ExtendedDecimalFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/ExtendedDecimalFormatTest.java b/src/test/java/freemarker/core/ExtendedDecimalFormatTest.java
new file mode 100644
index 0000000..d460fd4
--- /dev/null
+++ b/src/test/java/freemarker/core/ExtendedDecimalFormatTest.java
@@ -0,0 +1,246 @@
+/*
+ * 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 freemarker.core;
+
+import static freemarker.test.hamcerst.Matchers.*;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.text.DecimalFormat;
+import java.text.ParseException;
+import java.util.Locale;
+
+import org.junit.Test;
+
+public class ExtendedDecimalFormatTest {
+    
+    private static final Locale LOC = Locale.US;
+    
+    @Test
+    public void testNonExtended() throws ParseException {
+        for (String fStr : new String[] { "0.00", "0.###", "#,#0.###", "#0.####", "0.0;m", "0.0;",
+                "0'x'", "0'x';'m'", "0';'", "0';';m", "0';';'#'m';'", "0';;'", "" }) {
+            assertFormatsEquivalent(new DecimalFormat(fStr), ExtendedDecimalFormatParser.parse(fStr, LOC));
+        }
+        
+        try {
+            new DecimalFormat(";");
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        try {
+            ExtendedDecimalFormatParser.parse(";", LOC);
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    @Test
+    public void testNonExtended2() throws ParseException {
+        assertFormatsEquivalent(new DecimalFormat("0.0"), ExtendedDecimalFormatParser.parse("0.0;", LOC));
+        assertFormatsEquivalent(new DecimalFormat("0.0"), ExtendedDecimalFormatParser.parse("0.0;;", LOC));
+        assertFormatsEquivalent(new DecimalFormat("0.0;m"), ExtendedDecimalFormatParser.parse("0.0;m;", LOC));
+        assertFormatsEquivalent(new DecimalFormat(""), ExtendedDecimalFormatParser.parse(";;", LOC));
+        assertFormatsEquivalent(new DecimalFormat("0'x'"), ExtendedDecimalFormatParser.parse("0'x';;", LOC));
+        assertFormatsEquivalent(new DecimalFormat("0'x';'m'"), ExtendedDecimalFormatParser.parse("0'x';'m';", LOC));
+        assertFormatsEquivalent(new DecimalFormat("0';'"), ExtendedDecimalFormatParser.parse("0';';;", LOC));
+        assertFormatsEquivalent(new DecimalFormat("0';';m"), ExtendedDecimalFormatParser.parse("0';';m;", LOC));
+        assertFormatsEquivalent(new DecimalFormat("0';';'#'m';'"), ExtendedDecimalFormatParser.parse("0';';'#'m';';",
+                LOC));
+        assertFormatsEquivalent(new DecimalFormat("0';;'"), ExtendedDecimalFormatParser.parse("0';;';;", LOC));
+        
+        try {
+            new DecimalFormat(";m");
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        try {
+            ExtendedDecimalFormatParser.parse(";m", LOC);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        try {
+            ExtendedDecimalFormatParser.parse(";m;", LOC);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+    
+    @SuppressWarnings("boxing")
+    @Test
+    public void testExtendedParamsParsing() throws ParseException {
+        for (String fs : new String[] {
+                "00.##;; dec='D'", "00.##;;dec=D", "00.##;;  dec  =  D ", "00.##;; dec = 'D' " }) {
+            assertFormatted(fs, 1.125, "01D12");
+        }
+        for (String fs : new String[] {
+                ",#0.0;; dec=D, grp=_", ",#0.0;;dec=D,grp=_", ",#0.0;; dec = D , grp = _ ", ",#0.0;; dec='D', grp='_'"
+                }) {
+            assertFormatted(fs, 12345, "1_23_45D0");
+        }
+        
+        assertFormatted("0.0;;inf=infinity", Double.POSITIVE_INFINITY, "infinity");
+        assertFormatted("0.0;;inf='infinity'", Double.POSITIVE_INFINITY, "infinity");
+        assertFormatted("0.0;;inf=''", Double.POSITIVE_INFINITY, "");
+        assertFormatted("0.0;;inf='x''y'", Double.POSITIVE_INFINITY, "x'y");
+        assertFormatted("0.0;;dec=''''", 1, "1'0");
+        
+        try {
+            ExtendedDecimalFormatParser.parse(";;dec=D,", LOC);
+            fail();
+        } catch (java.text.ParseException e) {
+            assertThat(e.getMessage(),
+                    allOf(containsStringIgnoringCase("expected a(n) name"), containsString(" end of ")));
+        }
+        try {
+            ExtendedDecimalFormatParser.parse(";;xdec=D,", LOC);
+            fail();
+        } catch (java.text.ParseException e) {
+            assertThat(e.getMessage(),
+                    allOf(containsString("\"xdec\""), containsString("name")));
+        }
+        try {
+            ExtendedDecimalFormatParser.parse(";;dec='D", LOC);
+            fail();
+        } catch (java.text.ParseException e) {
+            assertThat(e.getMessage(),
+                    allOf(containsString("quotation"), containsString("closed")));
+        }
+        try {
+            ExtendedDecimalFormatParser.parse(";;dec='D'grp=G", LOC);
+            fail();
+        } catch (java.text.ParseException e) {
+            assertThat(e.getMessage(), allOf(
+                    containsString("separator"), containsString("whitespace"), containsString("comma")));
+        }
+        try {
+            ExtendedDecimalFormatParser.parse(";;dec=., grp=G", LOC);
+            fail();
+        } catch (java.text.ParseException e) {
+            assertThat(e.getMessage(), allOf(
+                    containsStringIgnoringCase("expected a(n) value"), containsString("., grp")));
+        }
+        try {
+            ExtendedDecimalFormatParser.parse("0.0;;dec=''", LOC);
+            fail();
+        } catch (java.text.ParseException e) {
+            assertThat(e.getMessage(), allOf(
+                    containsStringIgnoringCase("\"dec\""), containsString("exactly 1 char")));
+        }
+        try {
+            ExtendedDecimalFormatParser.parse("0.0;;mul=ten", LOC);
+            fail();
+        } catch (java.text.ParseException e) {
+            assertThat(e.getMessage(), allOf(
+                    containsString("\"mul\""), containsString("\"ten\""), containsString("integer")));
+        }
+    }
+    
+    @SuppressWarnings("boxing")
+    @Test
+    public void testExtendedParamsEffect() throws ParseException {
+        assertFormatted("0",
+                1.5, "2", 2.5, "2", 3.5, "4", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-2", -2.5, "-2", -1.6, "-2");
+        assertFormatted("0;; ro=he",
+                1.5, "2", 2.5, "2", 3.5, "4", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-2", -2.5, "-2", -1.6, "-2");
+        assertFormatted("0;; ro=hu",
+                1.5, "2", 2.5, "3", 3.5, "4", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-2", -2.5, "-3", -1.6, "-2");
+        assertFormatted("0;; ro=hd",
+                1.5, "1", 2.5, "2", 3.5, "3", 1.4, "1", 1.6, "2", -1.4, "-1", -1.5, "-1", -2.5, "-2", -1.6, "-2");
+        assertFormatted("0;; ro=f",
+                1.5, "1", 2.5, "2", 3.5, "3", 1.4, "1", 1.6, "1", -1.4, "-2", -1.5, "-2", -2.5, "-3", -1.6, "-2");
+        assertFormatted("0;; ro=c",
+                1.5, "2", 2.5, "3", 3.5, "4", 1.4, "2", 1.6, "2", -1.4, "-1", -1.5, "-1", -2.5, "-2", -1.6, "-1");
+        assertFormatted("0;; ro=un", 2, "2");
+        try {
+            assertFormatted("0;; ro=un", 2.5, "2");
+            fail();
+        } catch (ArithmeticException e) {
+            // Expected
+        }
+
+        assertFormatted("0.##;; mul=100", 12.345, "1234.5");
+        assertFormatted("0.##;; mul=1000", 12.345, "12345");
+        
+        assertFormatted(",##0.##;; grp=_ dec=D", 12345.1, "12_345D1", 1, "1");
+        
+        assertFormatted("0.##E0;; exp='*10^'", 12345.1, "1.23*10^4");
+        
+        assertFormatted("0.##;; min=m", -1, "m1", 1, "1");
+        
+        assertFormatted("0.##;; inf=foo", Double.POSITIVE_INFINITY, "foo", Double.NEGATIVE_INFINITY, "-foo");
+        
+        assertFormatted("0.##;; nan=foo", Double.NaN, "foo");
+        
+        assertFormatted("0%;; prc='c'", 0.75, "75c");
+        
+        assertFormatted("0\u2030;; prm='m'", 0.75, "750m");
+        
+        assertFormatted("0.00;; zero='@'", 10.5, "A@.E@");
+    }
+    
+    @Test
+    public void testLocale() throws ParseException {
+        assertEquals("1000.0", ExtendedDecimalFormatParser.parse("0.0", Locale.US).format(1000));
+        assertEquals("1000,0", ExtendedDecimalFormatParser.parse("0.0", Locale.FRANCE).format(1000));
+        assertEquals("1_000.0", ExtendedDecimalFormatParser.parse(",000.0;;grp=_", Locale.US).format(1000));
+        assertEquals("1_000,0", ExtendedDecimalFormatParser.parse(",000.0;;grp=_", Locale.FRANCE).format(1000));
+    }
+    
+
+    private void assertFormatted(String formatString, Object... numberAndExpectedOutput) throws ParseException {
+        if (numberAndExpectedOutput.length % 2 != 0) {
+            throw new IllegalArgumentException();
+        }
+        
+        DecimalFormat df = ExtendedDecimalFormatParser.parse(formatString, LOC);
+        Number num = null;
+        for (int i = 0; i < numberAndExpectedOutput.length; i++) {
+            if (i % 2 == 0) {
+                num = (Number) numberAndExpectedOutput[i];
+            } else {
+                assertEquals(numberAndExpectedOutput[i], df.format(num));
+            }
+        }
+    }
+    
+    private void assertFormatsEquivalent(DecimalFormat dfExpected, DecimalFormat dfActual) {
+        for (int signum : new int[] { 1, -1 }) {
+            assertFormatsEquivalent(dfExpected, dfActual, signum * 0);
+            assertFormatsEquivalent(dfExpected, dfActual, signum * 0.5);
+            assertFormatsEquivalent(dfExpected, dfActual, signum * 0.25);
+            assertFormatsEquivalent(dfExpected, dfActual, signum * 0.125);
+            assertFormatsEquivalent(dfExpected, dfActual, signum * 1);
+            assertFormatsEquivalent(dfExpected, dfActual, signum * 10);
+            assertFormatsEquivalent(dfExpected, dfActual, signum * 100);
+            assertFormatsEquivalent(dfExpected, dfActual, signum * 1000);
+            assertFormatsEquivalent(dfExpected, dfActual, signum * 10000);
+            assertFormatsEquivalent(dfExpected, dfActual, signum * 100000);
+        }
+    }
+
+    private void assertFormatsEquivalent(DecimalFormat dfExpected, DecimalFormat dfActual, double n) {
+        assertEquals(dfExpected.format(n), dfActual.format(n));
+    }
+    
+}


[2/4] incubator-freemarker git commit: Eclipse project files update to use Java 6 (the Ant build will still catch non-Java-5 calls where they aren't allowed)

Posted by dd...@apache.org.
Eclipse project files update to use Java 6 (the Ant build will still catch non-Java-5 calls where they aren't allowed)


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

Branch: refs/heads/2.3-gae
Commit: 01552a9c2968c01be09eef9880564a0e1723bf92
Parents: 2daf5f3
Author: ddekany <dd...@apache.org>
Authored: Mon Sep 14 02:24:43 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Mon Sep 14 02:24:43 2015 +0200

----------------------------------------------------------------------
 .classpath                           | 2 +-
 .settings/org.eclipse.jdt.core.prefs | 6 +++---
 2 files changed, 4 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/01552a9c/.classpath
----------------------------------------------------------------------
diff --git a/.classpath b/.classpath
index ad83438..6ca1ecd 100644
--- a/.classpath
+++ b/.classpath
@@ -9,7 +9,7 @@
 			<attribute name="ignore_optional_problems" value="true"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
 	<classpathentry exported="true" kind="lib" path="ide-dependencies/annotations-3.0.0.jar"/>
 	<classpathentry exported="true" kind="lib" path="ide-dependencies/ant-1.6.5.jar"/>
 	<classpathentry exported="true" kind="lib" path="ide-dependencies/avalon-logkit-2.0.jar"/>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/01552a9c/.settings/org.eclipse.jdt.core.prefs
----------------------------------------------------------------------
diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index 7259bb5..e0e730a 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -17,9 +17,9 @@ org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nul
 org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
 org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
-org.eclipse.jdt.core.compiler.compliance=1.5
+org.eclipse.jdt.core.compiler.compliance=1.6
 org.eclipse.jdt.core.compiler.debug.lineNumber=generate
 org.eclipse.jdt.core.compiler.debug.localVariable=generate
 org.eclipse.jdt.core.compiler.debug.sourceFile=generate
@@ -122,7 +122,7 @@ org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
 org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
 org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
 org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
-org.eclipse.jdt.core.compiler.source=1.5
+org.eclipse.jdt.core.compiler.source=1.6
 org.eclipse.jdt.core.formatter.align_type_members_on_columns=false
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
 org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=16


[3/4] incubator-freemarker git commit: (Suppressed a FindBugs false alarm.)

Posted by dd...@apache.org.
(Suppressed a FindBugs false alarm.)


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

Branch: refs/heads/2.3-gae
Commit: aab02867e31f9d576ab9fad00cb65b2cbfb58837
Parents: 01552a9
Author: ddekany <dd...@apache.org>
Authored: Mon Sep 14 02:25:24 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Mon Sep 14 02:25:24 2015 +0200

----------------------------------------------------------------------
 src/test/java/freemarker/template/ConfigurationTest.java | 1 +
 1 file changed, 1 insertion(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/aab02867/src/test/java/freemarker/template/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index 9dd4a1c..3480de3 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -1376,6 +1376,7 @@ public class ConfigurationTest extends TestCase {
         }
     }
     
+    @SuppressFBWarnings(value="NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS", justification="We test failures")
     @Test
     public void testSetCustomDateFormat() throws Exception {
         Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);