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},