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 2021/12/29 15:33:04 UTC

[freemarker] branch FREEMARKER-35 updated: [FREEMARKER-35] Made temporal format related tests more reliable across Java versions. Turned off standalone month bug workaround starting from Java 9 (as it was fixed there before the first production release).

This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a commit to branch FREEMARKER-35
in repository https://gitbox.apache.org/repos/asf/freemarker.git


The following commit(s) were added to refs/heads/FREEMARKER-35 by this push:
     new 3d53538  [FREEMARKER-35] Made temporal format related tests more reliable across Java versions. Turned off standalone month bug workaround starting from Java 9 (as it was fixed there before the first production release).
3d53538 is described below

commit 3d53538a817ad9c63619e2b9780022c10cee452f
Author: ddekany <dd...@apache.org>
AuthorDate: Wed Dec 29 16:31:39 2021 +0100

    [FREEMARKER-35] Made temporal format related tests more reliable across Java versions. Turned off standalone month bug workaround starting from Java 9 (as it was fixed there before the first production release).
---
 .../_JavaTimeBugUtils.java}                        | 47 ++++++++++++------
 src/main/java/freemarker/core/_JavaVersion.java    | 58 ++++++++++++++++++++++
 .../java/freemarker/template/utility/DateUtil.java |  8 +--
 .../utility/DateUtilsPatternParsingTest.java       | 28 ++++++++---
 4 files changed, 115 insertions(+), 26 deletions(-)

diff --git a/src/main/java/freemarker/template/utility/JavaTimeBugFlags.java b/src/main/java/freemarker/core/_JavaTimeBugUtils.java
similarity index 64%
rename from src/main/java/freemarker/template/utility/JavaTimeBugFlags.java
rename to src/main/java/freemarker/core/_JavaTimeBugUtils.java
index c5e3a29..0a74663 100644
--- a/src/main/java/freemarker/template/utility/JavaTimeBugFlags.java
+++ b/src/main/java/freemarker/core/_JavaTimeBugUtils.java
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-package freemarker.template.utility;
+package freemarker.core;
 
 import java.time.LocalDate;
 import java.time.format.DateTimeFormatterBuilder;
@@ -29,51 +29,68 @@ import java.util.concurrent.ConcurrentHashMap;
 import freemarker.log.Logger;
 
 /**
- * This is to address https://bugs.openjdk.java.net/browse/JDK-8146356
- * TODO: Detect if the Java version is high enough to always return a fixed value?
+ * Used internally only, might change without notice!
+ * To work around with java.time bugs.
  */
-class JavaTimeBugFlags {
+public final class _JavaTimeBugUtils {
     private static final Logger LOG = Logger.getLogger("freemarker.runtime");
     // There are around 160 predefined locales, so this should be enough even if the application uses some variants.
-    private static final int LEAK_ALERT_CACHE_SIZE = 1024;
+    private static final int HAS_GOOD_STANDALONE_MONTH_LEAK_ALERT_CACHE_SIZE = 1024;
 
     private static final LocalDate PROBE_LOCAL_DATE = LocalDate.of(2021, 12, 1);
 
-    private static final ConcurrentHashMap<Locale, Boolean> HAS_GOOD_SHORT_STANDALONE_MONTH = new ConcurrentHashMap<>();
-    private static final ConcurrentHashMap<Locale, Boolean> HAS_GOOD_FULL_STANDALONE_MONTH = new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<Locale, Boolean> HAS_GOOD_SHORT_STANDALONE_MONTH
+            = _JavaVersion.FEATURE == 8 ? new ConcurrentHashMap<>() : null;
+    private static final ConcurrentHashMap<Locale, Boolean> HAS_GOOD_FULL_STANDALONE_MONTH
+            = _JavaVersion.FEATURE == 8 ? new ConcurrentHashMap<>() : null;
 
-    static boolean hasGoodShortStandaloneMonth(Locale locale) {
+    private _JavaTimeBugUtils() {
+        throw new AssertionError();
+    }
+
+    /**
+     * To deal with https://bugs.openjdk.java.net/browse/JDK-8146356
+     */
+    public static boolean hasGoodShortStandaloneMonth(Locale locale) {
         return hasGoodStandaloneMonth(
                 locale,
-                TextStyle.SHORT_STANDALONE, JavaTimeBugFlags.HAS_GOOD_SHORT_STANDALONE_MONTH);
+                TextStyle.SHORT_STANDALONE, _JavaTimeBugUtils.HAS_GOOD_SHORT_STANDALONE_MONTH);
     }
 
-    static boolean hasGoodFullStandaloneMonth(Locale locale) {
+    /**
+     * To deal with https://bugs.openjdk.java.net/browse/JDK-8146356
+     */
+    public static boolean hasGoodFullStandaloneMonth(Locale locale) {
         return hasGoodStandaloneMonth(
                 locale,
                 TextStyle.FULL_STANDALONE,
-                JavaTimeBugFlags.HAS_GOOD_FULL_STANDALONE_MONTH);
+                _JavaTimeBugUtils.HAS_GOOD_FULL_STANDALONE_MONTH);
     }
 
     private static boolean hasGoodStandaloneMonth(
             Locale locale, TextStyle textStyle,
             ConcurrentHashMap<Locale, Boolean> cacheMap) {
+        if (cacheMap == null) {
+            // We are using a Java version where the standalone formatting bug was already fixed
+            return true;
+        }
+
         Boolean good = cacheMap.get(locale);
         if (good != null) {
             return good;
         }
 
-        if (cacheMap.size() >= LEAK_ALERT_CACHE_SIZE) {
+        if (cacheMap.size() >= HAS_GOOD_STANDALONE_MONTH_LEAK_ALERT_CACHE_SIZE) {
             boolean triggered = false;
-            synchronized (JavaTimeBugFlags.class) {
-                if (cacheMap.size() >= LEAK_ALERT_CACHE_SIZE) {
+            synchronized (_JavaTimeBugUtils.class) {
+                if (cacheMap.size() >= HAS_GOOD_STANDALONE_MONTH_LEAK_ALERT_CACHE_SIZE) {
                     triggered = true;
                     cacheMap.clear();
                 }
             }
             if (triggered) {
                 LOG.warn("Global HAS_GOOD_STANDALONE_MONTH cache for " + textStyle
-                        + " has exceeded " + LEAK_ALERT_CACHE_SIZE
+                        + " has exceeded " + HAS_GOOD_STANDALONE_MONTH_LEAK_ALERT_CACHE_SIZE
                         + " entries => cache flushed. "
                         + "Typical cause: Something generates high variety of Locale objects.");
             }
diff --git a/src/main/java/freemarker/core/_JavaVersion.java b/src/main/java/freemarker/core/_JavaVersion.java
new file mode 100644
index 0000000..3e61c71
--- /dev/null
+++ b/src/main/java/freemarker/core/_JavaVersion.java
@@ -0,0 +1,58 @@
+/*
+ * 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.lang.reflect.Method;
+
+/**
+ * Used internally only, might change without notice!
+ */
+public final class _JavaVersion {
+    private _JavaVersion() {
+        throw new AssertionError();
+    }
+
+    /**
+     * Like 8 for Java 8, 9 for Java 9, etc. This is  at least 8, since we don't support earlier Java versions anymore.
+     */
+    public static final int FEATURE;
+
+    static {
+        Method versionMethod;
+        try {
+            versionMethod = Runtime.class.getMethod("version");
+        } catch (NoSuchMethodException e) {
+            // Runtime.version() was added in Java 9
+            versionMethod = null;
+        }
+
+        if (versionMethod == null) {
+            FEATURE = 8;
+        } else {
+            try {
+                Object version = versionMethod.invoke(null);
+                // major() was deprecated by feature() added in Java 10, but they do the same.
+                FEATURE = (int) version.getClass().getMethod("major").invoke(version);
+            } catch (Throwable e) {
+                throw new IllegalStateException("Couldn't get Java version", e);
+            }
+        }
+    }
+}
diff --git a/src/main/java/freemarker/template/utility/DateUtil.java b/src/main/java/freemarker/template/utility/DateUtil.java
index 11fe510..4023e21 100644
--- a/src/main/java/freemarker/template/utility/DateUtil.java
+++ b/src/main/java/freemarker/template/utility/DateUtil.java
@@ -38,6 +38,8 @@ import java.util.TimeZone;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import freemarker.core._JavaTimeBugUtils;
+
 /**
  * Date and time related utilities.
  */
@@ -927,7 +929,7 @@ public class DateUtil {
                     }
 
                     if (textStyle == TextStyle.SHORT_STANDALONE
-                            && !JavaTimeBugFlags.hasGoodShortStandaloneMonth(locale)) {
+                            && !_JavaTimeBugUtils.hasGoodShortStandaloneMonth(locale)) {
                         textStyle = TextStyle.SHORT;
                     }
 
@@ -942,7 +944,7 @@ public class DateUtil {
                     }
 
                     if (textStyle == TextStyle.FULL_STANDALONE
-                            && !JavaTimeBugFlags.hasGoodFullStandaloneMonth(locale)) {
+                            && !_JavaTimeBugUtils.hasGoodFullStandaloneMonth(locale)) {
                         textStyle = TextStyle.FULL;
                     }
 
@@ -1119,7 +1121,7 @@ public class DateUtil {
     }
 
     private static String legacyCalendarTypeToJavaTimeApiCompatibleName(String legacyType) {
-        // "gregory" is the Calendar.calendarType in the old API. The closest Chronology.ofLocale recognizes is "ISO".
+        // "gregory" is the Calendar.calendarType in the old API, but Chronology.ofLocale calls it "ISO".
         return "gregory".equals(legacyType) ? "ISO" : legacyType;
     }
 
diff --git a/src/test/java/freemarker/template/utility/DateUtilsPatternParsingTest.java b/src/test/java/freemarker/template/utility/DateUtilsPatternParsingTest.java
index d77b9ab..1f168e1 100644
--- a/src/test/java/freemarker/template/utility/DateUtilsPatternParsingTest.java
+++ b/src/test/java/freemarker/template/utility/DateUtilsPatternParsingTest.java
@@ -39,6 +39,8 @@ import org.apache.commons.lang.StringUtils;
 import org.hamcrest.Matchers;
 import org.junit.Test;
 
+import freemarker.core._JavaVersion;
+
 /**
  * Move pattern parsing related tests from {@link DateUtilTest} to here.
  */
@@ -99,6 +101,11 @@ public class DateUtilsPatternParsingTest {
                 String pattern = StringUtils.repeat(letter, width);
                 for (ZonedDateTime zdt : SAMPLE_ZDTS) {
                     for (Locale locale : SAMPLE_LOCALES) {
+                        if (letter.equals("G") && _JavaVersion.FEATURE > 8 && !locale.equals(Locale.US)) {
+                            // SDF and DTF formats Era differently for many locales after Java 8. US locale remains
+                            // consistent as of Java 13, so let's hope it won't break, and so we can have some coverage.
+                            continue;
+                        }
                         assertSDFAndDTFOutputsEqual(pattern, zdt, locale);
                     }
                 }
@@ -241,13 +248,18 @@ public class DateUtilsPatternParsingTest {
 
     @Test
     public void testParsingLocale() throws ParseException {
-        assertLocalDateParsing(
-                LocalDate.of(2021, 1, 12),
-                "y-MMM-d", "2021-\u044F\u043D\u0432-12", new Locale("ru", "RU"));
-        assertLocalDateParsing(
-                LocalDate.of(2021, 1, 12),
-                "y-MMM-d", "\u0968\u0966\u0968\u0967-\u091C\u0928\u0935\u0930\u0940-\u0967\u0968",
-                new Locale("hi", "IN"));
+        LocalDate localDate = LocalDate.of(2021, 1, 12);
+        String pattern = "y-MMM-d";
+        for (Locale locale : SAMPLE_LOCALES) {
+            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern, locale);
+            simpleDateFormat.setTimeZone(TimeZone.getTimeZone(UTC_ZONE_ID));
+            String sdfFormatted = simpleDateFormat.format(
+                    Date.from(localDate.atTime(0, 0).atZone(UTC_ZONE_ID).toInstant()));
+
+            assertLocalDateParsing(
+                    localDate,
+                    pattern, sdfFormatted, locale);
+        }
     }
 
     private void assertSDFAndDTFOutputsEqual(String pattern, ZonedDateTime zdt, Locale locale) {
@@ -265,7 +277,7 @@ public class DateUtilsPatternParsingTest {
         if (!sdfOutput.equals(dtfOutput)) {
             fail("Output of\n"
                     + "SDF(" + StringUtil.jQuote(pattern) + ", " + date.toInstant().atZone(timeZone.toZoneId()) + "), and\n"
-                    + "DTF(" + dtf + ", " + temporal + ") differs, with locale " + locale + ":\n"
+                    + "DTF(" + dtf + ", " + temporal + ") differs, with locale " + StringUtil.jQuote(locale) + ":\n"
                     + "SDF: " + sdfOutput + "\n"
                     + "DTF: " + dtfOutput);
         }