Skip to content

Commit

Permalink
Added ReflectionUtils.getDeclaredFields().
Browse files Browse the repository at this point in the history
Fixed DateUtilties test to handle daylight savings old school timezone names
Added Instant to Timestamp tests
Added Instant to ZonedDateTime tests
  • Loading branch information
jdereg committed Mar 10, 2024
1 parent 5f3aa0a commit 16ef960
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 32 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ 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
```
<dependency>
<groupId>com.cedarsoftware</groupId>
<artifactId>java-util</artifactId>
<version>2.4.4</version>
<version>2.4.5</version>
</dependency>
```
---
Expand Down
2 changes: 2 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<groupId>com.cedarsoftware</groupId>
<artifactId>java-util</artifactId>
<packaging>jar</packaging>
<version>2.4.4</version>
<version>2.4.5</version>
<description>Java Utilities</description>
<url>https://github.com/jdereg/java-util</url>

Expand Down
31 changes: 30 additions & 1 deletion src/main/java/com/cedarsoftware/util/ReflectionUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,6 +49,7 @@ public final class ReflectionUtils
private static final ConcurrentMap<String, Method> METHOD_MAP2 = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, Method> METHOD_MAP3 = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, Constructor<?>> CONSTRUCTORS = new ConcurrentHashMap<>();
private static final ConcurrentMap<Class<?>, List<Field>> FIELD_META_CACHE = new ConcurrentHashMap<>();

private ReflectionUtils()
{
Expand Down Expand Up @@ -163,6 +165,13 @@ public static Method getMethod(Class<?> c, String methodName, Class<?>...types)
}
}

/**
* Retrieve the declared fields on a Class.
*/
public static List<Field> 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
Expand Down Expand Up @@ -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<Field> buildDeclaredFields(final Class<?> c) {
Convention.throwIfNull(c, "class cannot be null");

Field[] fields = c.getDeclaredFields();
List<Field> 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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Object> target = new CompactLinkedMap<>();
target.put("seconds", sec);
target.put("nanos", nanos);
Expand Down Expand Up @@ -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) {
Expand Down
34 changes: 26 additions & 8 deletions src/test/java/com/cedarsoftware/util/TestDateUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()
{
Expand All @@ -552,6 +547,29 @@ void testDatePrecision()
assertTrue(x.compareTo(y) < 0);
}

@Test
void testDateToStringFormat()
{
List<String> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)},
Expand Down Expand Up @@ -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},
});
}

/**
Expand Down Expand Up @@ -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"))},
Expand Down Expand Up @@ -1874,25 +1894,25 @@ 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},
});
TEST_DB.put(pair(Double.class, Boolean.class), new Object[][]{
{-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},
});
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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},
Expand Down

0 comments on commit 16ef960

Please sign in to comment.