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);
}