From 16ef960289a1c787e28f2a31b80e3d6b48107579 Mon Sep 17 00:00:00 2001 From: John DeRegnaucourt Date: Sun, 10 Mar 2024 12:52:37 -0400 Subject: [PATCH] Added ReflectionUtils.getDeclaredFields(). Fixed DateUtilties test to handle daylight savings old school timezone names Added Instant to Timestamp tests Added Instant to ZonedDateTime tests --- README.md | 4 +- changelog.md | 2 + pom.xml | 2 +- .../cedarsoftware/util/ReflectionUtils.java | 31 ++++++++++++- .../util/convert/InstantConversions.java | 4 +- .../cedarsoftware/util/TestDateUtilities.java | 34 ++++++++++---- .../util/convert/ConverterEverythingTest.java | 44 +++++++++++-------- 7 files changed, 89 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index c5e1cec1..1b9247d5 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ The classes in the`.jar`file are version 52 (`JDK 1.8`). To include in your project: ##### GradleF ``` -implementation 'com.cedarsoftware:java-util:2.4.4' +implementation 'com.cedarsoftware:java-util:2.4.5' ``` ##### Maven @@ -23,7 +23,7 @@ implementation 'com.cedarsoftware:java-util:2.4.4' com.cedarsoftware java-util - 2.4.4 + 2.4.5 ``` --- diff --git a/changelog.md b/changelog.md index 446212f7..de17e7b6 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,6 @@ ### Revision History +* 2.4.5 + * Added `ReflectionUtils.getDeclaredFields()` which gets fields from a `Class`, including an `Enum`, and special handles enum so that system fields are not returned. * 2.4.4 * `Converter` - Enum test added. 683 combinations. * 2.4.3 diff --git a/pom.xml b/pom.xml index 7ee85f9c..cf4f0ef3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.cedarsoftware java-util jar - 2.4.4 + 2.4.5 Java Utilities https://github.com/jdereg/java-util diff --git a/src/main/java/com/cedarsoftware/util/ReflectionUtils.java b/src/main/java/com/cedarsoftware/util/ReflectionUtils.java index 9b1d472a..f1803719 100644 --- a/src/main/java/com/cedarsoftware/util/ReflectionUtils.java +++ b/src/main/java/com/cedarsoftware/util/ReflectionUtils.java @@ -16,6 +16,7 @@ import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -48,6 +49,7 @@ public final class ReflectionUtils private static final ConcurrentMap METHOD_MAP2 = new ConcurrentHashMap<>(); private static final ConcurrentMap METHOD_MAP3 = new ConcurrentHashMap<>(); private static final ConcurrentMap> CONSTRUCTORS = new ConcurrentHashMap<>(); + private static final ConcurrentMap, List> FIELD_META_CACHE = new ConcurrentHashMap<>(); private ReflectionUtils() { @@ -163,6 +165,13 @@ public static Method getMethod(Class c, String methodName, Class...types) } } + /** + * Retrieve the declared fields on a Class. + */ + public static List getDeclaredFields(final Class c) { + return FIELD_META_CACHE.computeIfAbsent(c, ReflectionUtils::buildDeclaredFields); + } + /** * Get all non static, non transient, fields of the passed in class, including * private fields. Note, the special this$ field is also not returned. The result @@ -615,9 +624,29 @@ else if (t == 19 || t == 20) // CONSTANT_Module || CONSTANT_Package return className.replace('/', '.'); } - protected static String getClassLoaderName(Class c) + static String getClassLoaderName(Class c) { ClassLoader loader = c.getClassLoader(); return loader == null ? "bootstrap" : loader.toString(); } + + private static List buildDeclaredFields(final Class c) { + Convention.throwIfNull(c, "class cannot be null"); + + Field[] fields = c.getDeclaredFields(); + List list = new ArrayList<>(fields.length); + + for (Field field : fields) { + if (Modifier.isStatic(field.getModifiers()) || + (field.getDeclaringClass().isEnum() && ("internal".equals(field.getName()) || "ENUM$VALUES".equals(field.getName()))) || + (field.getDeclaringClass().isAssignableFrom(Enum.class) && ("hash".equals(field.getName()) || "ordinal".equals(field.getName())))) { + continue; + } + + list.add(field); + } + + return list; + } + } diff --git a/src/main/java/com/cedarsoftware/util/convert/InstantConversions.java b/src/main/java/com/cedarsoftware/util/convert/InstantConversions.java index d82284fb..73645a91 100644 --- a/src/main/java/com/cedarsoftware/util/convert/InstantConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/InstantConversions.java @@ -38,7 +38,7 @@ private InstantConversions() {} static Map toMap(Object from, Converter converter) { long sec = ((Instant) from).getEpochSecond(); - long nanos = ((Instant) from).getNano(); + int nanos = ((Instant) from).getNano(); Map target = new CompactLinkedMap<>(); target.put("seconds", sec); target.put("nanos", nanos); @@ -67,7 +67,7 @@ static AtomicLong toAtomicLong(Object from, Converter converter) { } static Timestamp toTimestamp(Object from, Converter converter) { - return new Timestamp(toLong(from, converter)); + return Timestamp.from((Instant) from); } static java.sql.Date toSqlDate(Object from, Converter converter) { diff --git a/src/test/java/com/cedarsoftware/util/TestDateUtilities.java b/src/test/java/com/cedarsoftware/util/TestDateUtilities.java index 6b4c9699..35fa92c6 100644 --- a/src/test/java/com/cedarsoftware/util/TestDateUtilities.java +++ b/src/test/java/com/cedarsoftware/util/TestDateUtilities.java @@ -3,11 +3,14 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.text.SimpleDateFormat; +import java.time.DateTimeException; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.TemporalAccessor; +import java.util.Arrays; import java.util.Calendar; import java.util.Date; +import java.util.List; import java.util.TimeZone; import java.util.stream.Stream; @@ -536,14 +539,6 @@ void test2DigitYear() } catch (IllegalArgumentException ignored) {} } - @Test - void testDateToStringFormat() - { - Date x = new Date(); - Date y = DateUtilities.parseDate(x.toString()); - assertEquals(x.toString(), y.toString()); - } - @Test void testDatePrecision() { @@ -552,6 +547,29 @@ void testDatePrecision() assertTrue(x.compareTo(y) < 0); } + @Test + void testDateToStringFormat() + { + List timeZoneOldSchoolNames = Arrays.asList("JST", "IST", "CET", "BST", "EST", "CST", "MST", "PST", "CAT", "EAT", "ART", "ECT", "NST", "AST", "HST"); + Date x = new Date(); + String dateToString = x.toString(); + boolean isOldSchoolTimezone = false; + for (String zoneName : timeZoneOldSchoolNames) { + if (!dateToString.contains(zoneName)) { + isOldSchoolTimezone = true; + } + } + + if (isOldSchoolTimezone) { + assertThatThrownBy(() -> DateUtilities.parseDate(x.toString())) + .isInstanceOf(DateTimeException.class) + .hasMessageContaining("Unknown time-zone ID"); + } else { + Date y = DateUtilities.parseDate(x.toString()); + assertEquals(x.toString(), y.toString()); + } + } + @ParameterizedTest @ValueSource(strings = {"JST", "IST", "CET", "BST", "EST", "CST", "MST", "PST", "CAT", "EAT", "ART", "ECT", "NST", "AST", "HST"}) void testTimeZoneValidShortNames(String timeZoneId) { diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java index 2abbc5de..74ccc2db 100644 --- a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java +++ b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java @@ -236,6 +236,9 @@ private static void loadMapTests() { TEST_DB.put(pair(Duration.class, Map.class), new Object[][] { { Duration.ofMillis(-1), mapOf("seconds", -1L, "nanos", 999000000)}, }); + TEST_DB.put(pair(Instant.class, Map.class), new Object[][] { + { Instant.parse("2024-03-10T11:07:00.123456789Z"), mapOf("seconds", 1710068820L, "nanos", 123456789)}, + }); TEST_DB.put(pair(Character.class, Map.class), new Object[][]{ {(char) 0, mapOf(VALUE, (char)0)}, {(char) 1, mapOf(VALUE, (char)1)}, @@ -678,6 +681,14 @@ private static void loadZoneDateTimeTests() { {BigDecimal.valueOf(86400), ZonedDateTime.parse("1970-01-02T00:00:00Z").withZoneSameInstant(TOKYO_Z), true}, {new BigDecimal("86400.000000001"), ZonedDateTime.parse("1970-01-02T00:00:00.000000001Z").withZoneSameInstant(TOKYO_Z), true}, }); + TEST_DB.put(pair(Instant.class, ZonedDateTime.class), new Object[][]{ + {Instant.ofEpochSecond(-62167219200L), ZonedDateTime.parse("0000-01-01T00:00:00Z").withZoneSameInstant(TOKYO_Z), true}, + {Instant.ofEpochSecond(-62167219200L, 1), ZonedDateTime.parse("0000-01-01T00:00:00.000000001Z").withZoneSameInstant(TOKYO_Z), true}, + {Instant.ofEpochSecond(0, -1), ZonedDateTime.parse("1969-12-31T23:59:59.999999999Z").withZoneSameInstant(TOKYO_Z), true}, + {Instant.ofEpochSecond(0, 0), ZonedDateTime.parse("1970-01-01T00:00:00Z").withZoneSameInstant(TOKYO_Z), true}, + {Instant.ofEpochSecond(0, 1), ZonedDateTime.parse("1970-01-01T00:00:00.000000001Z").withZoneSameInstant(TOKYO_Z), true}, + {Instant.parse("2024-03-10T11:43:00Z"), ZonedDateTime.parse("2024-03-10T11:43:00Z").withZoneSameInstant(TOKYO_Z), true}, + }); } /** @@ -918,6 +929,15 @@ private static void loadTimestampTests() { {Duration.ofNanos(1708255140987654321L), Timestamp.from(Instant.parse("2024-02-18T11:19:00.987654321Z")), true}, {Duration.ofNanos(2682374400000000001L), Timestamp.from(Instant.parse("2055-01-01T00:00:00.000000001Z")), true}, }); + TEST_DB.put(pair(Instant.class, Timestamp.class), new Object[][]{ + {Instant.ofEpochSecond(-62167219200L), Timestamp.from(Instant.parse("0000-01-01T00:00:00Z")), true}, + {Instant.ofEpochSecond(-62167219200L, 1), Timestamp.from(Instant.parse("0000-01-01T00:00:00.000000001Z")), true}, + {Instant.ofEpochSecond(0, -1), Timestamp.from(Instant.parse("1969-12-31T23:59:59.999999999Z")), true}, + {Instant.ofEpochSecond(0, 0), Timestamp.from(Instant.parse("1970-01-01T00:00:00.000000000Z")), true}, + {Instant.ofEpochSecond(0, 1), Timestamp.from(Instant.parse("1970-01-01T00:00:00.000000001Z")), true}, + {Instant.parse("2024-03-10T11:36:00Z"), Timestamp.from(Instant.parse("2024-03-10T11:36:00Z")), true}, + {Instant.parse("2024-03-10T11:36:00.123456789Z"), Timestamp.from(Instant.parse("2024-03-10T11:36:00.123456789Z")), true}, + }); // No symmetry checks - because an OffsetDateTime of "2024-02-18T06:31:55.987654321+00:00" and "2024-02-18T15:31:55.987654321+09:00" are equivalent but not equals. They both describe the same Instant. TEST_DB.put(pair(OffsetDateTime.class, Timestamp.class), new Object[][]{ {OffsetDateTime.parse("1969-12-31T23:59:59.999999999Z"), Timestamp.from(Instant.parse("1969-12-31T23:59:59.999999999Z"))}, @@ -1874,16 +1894,16 @@ private static void loadBooleanTests() { TEST_DB.put(pair(Long.class, Boolean.class), new Object[][]{ {-2L, true}, {-1L, true}, - {0L, false}, - {1L, true}, + {0L, false, true}, + {1L, true, true}, {2L, true}, }); TEST_DB.put(pair(Float.class, Boolean.class), new Object[][]{ {-2f, true}, {-1.5f, true}, {-1f, true}, - {0f, false}, - {1f, true}, + {0f, false, true}, + {1f, true, true}, {1.5f, true}, {2f, true}, }); @@ -1891,8 +1911,8 @@ private static void loadBooleanTests() { {-2.0, true}, {-1.5, true}, {-1.0, true}, - {0.0, false}, - {1.0, true}, + {0.0, false, true}, + {1.0, true, true}, {1.5, true}, {2.0, true}, }); @@ -2023,10 +2043,6 @@ private static void loadDoubleTests() { {Double.MAX_VALUE, Double.MAX_VALUE}, {-Double.MAX_VALUE, -Double.MAX_VALUE}, }); - TEST_DB.put(pair(Boolean.class, Double.class), new Object[][]{ - {true, 1.0}, - {false, 0.0}, - }); TEST_DB.put(pair(Duration.class, Double.class), new Object[][]{ {Duration.ofSeconds(-1, -1), -1.000000001, true}, {Duration.ofSeconds(-1), -1.0, true}, @@ -2214,10 +2230,6 @@ private static void loadFloatTests() { {(double) Float.MAX_VALUE, Float.MAX_VALUE}, {(double) -Float.MAX_VALUE, -Float.MAX_VALUE}, }); - TEST_DB.put(pair(Boolean.class, Float.class), new Object[][]{ - {true, 1f}, - {false, 0f} - }); TEST_DB.put(pair(BigDecimal.class, Float.class), new Object[][]{ {new BigDecimal("-1"), -1f, true}, {new BigDecimal("-1.1"), -1.1f}, // no reverse - IEEE 754 rounding errors @@ -2335,10 +2347,6 @@ private static void loadLongTests() { {-9223372036854775808.0, Long.MIN_VALUE}, {9223372036854775807.0, Long.MAX_VALUE}, }); - TEST_DB.put(pair(Boolean.class, Long.class), new Object[][]{ - {true, 1L}, - {false, 0L}, - }); TEST_DB.put(pair(AtomicBoolean.class, Long.class), new Object[][]{ {new AtomicBoolean(true), 1L}, {new AtomicBoolean(false), 0L},