From 3deb45f9111f5494b54ab1e01d2f9ceefbd761c2 Mon Sep 17 00:00:00 2001 From: John DeRegnaucourt Date: Mon, 26 Feb 2024 01:28:31 -0500 Subject: [PATCH] Added more conversions for AtomicLong, Long, and Duration. Prepared for release as 2.4.1. --- changelog.md | 7 +- pom.xml | 2 +- .../cedarsoftware/util/convert/Converter.java | 5 + .../util/convert/DurationConversions.java | 10 ++ .../util/convert/NumberConversions.java | 7 +- .../util/convert/ConverterEverythingTest.java | 115 +++++++++++++++--- 6 files changed, 120 insertions(+), 26 deletions(-) diff --git a/changelog.md b/changelog.md index ebd0a053..f867f868 100644 --- a/changelog.md +++ b/changelog.md @@ -1,11 +1,10 @@ ### Revision History -* 2.4.2 - * Fixed `SafeSimpleDateFormat` to properly format dates having years with fewer than four digits. * 2.4.1 - * `Converter` has had significant expansion in the types that it can convert between, greater than 500 combinations. In addition, you can add your own conversions to it as well. Call the `Converter.getSupportedConversions()` to see all the combinations supported. Also, you can use `Converter` instance-based now, allowing it to have different conversion tables if needed. - * `DateUtilities` has had performance improvements (> 35%), and adds a new `.parseDate()` API that allows it to return a `ZonedDateTime.` See the updated Javadoc on the class for a complete description of all of the formats it supports. Normally, you do not need to use this class directly, as you can use `Converter` to convert between `Dates`, `Calendars`, and the new Temporal classes like `ZonedDateTime,` `Duration,` `Instance,` as well as `long,` `BigInteger,` etc. + * `Converter` has had significant expansion in the types that it can convert between, about 670 combinations. In addition, you can add your own conversions to it as well. Call the `Converter.getSupportedConversions()` to see all the combinations supported. Also, you can use `Converter` instance-based now, allowing it to have different conversion tables if needed. + * `DateUtilities` has had performance improvements (> 35%), and adds a new `.parseDate()` API that allows it to return a `ZonedDateTime.` See the updated Javadoc on the class for a complete description of all the formats it supports. Normally, you do not need to use this class directly, as you can use `Converter` to convert between `Dates`, `Calendars`, and the new Temporal classes like `ZonedDateTime,` `Duration,` `Instance,` as well as Strings. * `FastByteArrayOutputStream` updated to match `ByteArrayOutputStream` API. This means that `.getBuffer()` is `.toByteArray()` and `.clear()` is now `.reset().` * `FastByteArrayInputStream` added. Matches `ByteArrayInputStream` API. + * Bug fix: `SafeSimpleDateFormat` to properly format dates having years with fewer than four digits. * Bug fix: SafeSimpleDateFormat .toString(), .hashCode(), and .equals() now delegate to the contain SimpleDataFormat instance. We recommend using the newer DateTimeFormatter, however, this class works well for Java 1.8+ if needed. * 2.4.0 * Added ClassUtilities. This class has a method to get the distance between a source and destination class. It includes support for Classes, multiple inheritance of interfaces, primitives, and class-to-interface, interface-interface, and class to class. diff --git a/pom.xml b/pom.xml index 25f6ee50..241dc2b0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.cedarsoftware java-util jar - 2.5.0-SNAPSHOT + 2.4.1 Java Utilities https://github.com/jdereg/java-util diff --git a/src/main/java/com/cedarsoftware/util/convert/Converter.java b/src/main/java/com/cedarsoftware/util/convert/Converter.java index a91af93a..bb23773b 100644 --- a/src/main/java/com/cedarsoftware/util/convert/Converter.java +++ b/src/main/java/com/cedarsoftware/util/convert/Converter.java @@ -182,6 +182,7 @@ private static void buildFactoryConversions() { CONVERSION_DB.put(pair(java.sql.Date.class, Long.class), DateConversions::toLong); CONVERSION_DB.put(pair(Timestamp.class, Long.class), DateConversions::toLong); CONVERSION_DB.put(pair(Instant.class, Long.class), InstantConversions::toLong); + CONVERSION_DB.put(pair(Duration.class, Long.class), DurationConversions::toLong); CONVERSION_DB.put(pair(LocalDate.class, Long.class), LocalDateConversions::toLong); CONVERSION_DB.put(pair(LocalDateTime.class, Long.class), LocalDateTimeConversions::toLong); CONVERSION_DB.put(pair(OffsetDateTime.class, Long.class), OffsetDateTimeConversions::toLong); @@ -406,6 +407,7 @@ private static void buildFactoryConversions() { CONVERSION_DB.put(pair(java.sql.Date.class, AtomicLong.class), DateConversions::toAtomicLong); CONVERSION_DB.put(pair(Timestamp.class, AtomicLong.class), DateConversions::toAtomicLong); CONVERSION_DB.put(pair(Instant.class, AtomicLong.class), InstantConversions::toAtomicLong); + CONVERSION_DB.put(pair(Duration.class, AtomicLong.class), DurationConversions::toAtomicLong); CONVERSION_DB.put(pair(LocalDate.class, AtomicLong.class), LocalDateConversions::toAtomicLong); CONVERSION_DB.put(pair(LocalDateTime.class, AtomicLong.class), LocalDateTimeConversions::toAtomicLong); CONVERSION_DB.put(pair(ZonedDateTime.class, AtomicLong.class), ZonedDateTimeConversions::toAtomicLong); @@ -616,6 +618,7 @@ private static void buildFactoryConversions() { CONVERSION_DB.put(pair(Map.class, OffsetDateTime.class), MapConversions::toOffsetDateTime); CONVERSION_DB.put(pair(String.class, OffsetDateTime.class), StringConversions::toOffsetDateTime); CONVERSION_DB.put(pair(Long.class, OffsetDateTime.class), NumberConversions::toOffsetDateTime); + CONVERSION_DB.put(pair(AtomicLong.class, OffsetDateTime.class), NumberConversions::toOffsetDateTime); CONVERSION_DB.put(pair(Double.class, OffsetDateTime.class), DoubleConversions::toOffsetDateTime); CONVERSION_DB.put(pair(BigInteger.class, OffsetDateTime.class), BigIntegerConversions::toOffsetDateTime); CONVERSION_DB.put(pair(BigDecimal.class, OffsetDateTime.class), BigDecimalConversions::toOffsetDateTime); @@ -729,7 +732,9 @@ private static void buildFactoryConversions() { // Duration conversions supported CONVERSION_DB.put(pair(Void.class, Duration.class), VoidConversions::toNull); CONVERSION_DB.put(pair(Duration.class, Duration.class), Converter::identity); + CONVERSION_DB.put(pair(Long.class, Duration.class), NumberConversions::toDuration); CONVERSION_DB.put(pair(Double.class, Duration.class), DoubleConversions::toDuration); + CONVERSION_DB.put(pair(AtomicLong.class, Duration.class), NumberConversions::toDuration); CONVERSION_DB.put(pair(BigInteger.class, Duration.class), BigIntegerConversions::toDuration); CONVERSION_DB.put(pair(BigDecimal.class, Duration.class), BigDecimalConversions::toDuration); CONVERSION_DB.put(pair(Timestamp.class, Duration.class), TimestampConversions::toDuration); diff --git a/src/main/java/com/cedarsoftware/util/convert/DurationConversions.java b/src/main/java/com/cedarsoftware/util/convert/DurationConversions.java index 5fd1f140..b316fd6a 100644 --- a/src/main/java/com/cedarsoftware/util/convert/DurationConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/DurationConversions.java @@ -6,6 +6,7 @@ import java.time.Duration; import java.time.Instant; import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; import com.cedarsoftware.util.CompactLinkedMap; @@ -39,6 +40,15 @@ static Map toMap(Object from, Converter converter) { return target; } + static long toLong(Object from, Converter converter) { + return ((Duration) from).toMillis(); + } + + static AtomicLong toAtomicLong(Object from, Converter converter) { + Duration duration = (Duration) from; + return new AtomicLong(duration.toMillis()); + } + static BigInteger toBigInteger(Object from, Converter converter) { Duration duration = (Duration) from; BigInteger epochSeconds = BigInteger.valueOf(duration.getSeconds()); diff --git a/src/main/java/com/cedarsoftware/util/convert/NumberConversions.java b/src/main/java/com/cedarsoftware/util/convert/NumberConversions.java index 0d96f2ac..6ab697a7 100644 --- a/src/main/java/com/cedarsoftware/util/convert/NumberConversions.java +++ b/src/main/java/com/cedarsoftware/util/convert/NumberConversions.java @@ -3,6 +3,7 @@ import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Timestamp; +import java.time.Duration; import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; @@ -174,7 +175,11 @@ static char toCharacter(Object from, Converter converter) { static Date toDate(Object from, Converter converter) { return new Date(toLong(from, converter)); } - + + static Duration toDuration(Object from, Converter converter) { + return Duration.ofMillis(toLong(from, converter)); + } + static Instant toInstant(Object from, Converter converter) { return Instant.ofEpochMilli(toLong(from, converter)); } diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java index 5069abe2..8e873933 100644 --- a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java +++ b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java @@ -113,8 +113,36 @@ public ZoneId getZoneId() { loadZoneDateTimeTests(); loadZoneOffsetTests(); loadStringTests(); + loadAtomicLongTests(); } + /** + * AtomicLong + */ + private static void loadAtomicLongTests() { + TEST_DB.put(pair(Void.class, AtomicLong.class), new Object[][]{ + {null, null} + }); + TEST_DB.put(pair(Instant.class, AtomicLong.class), new Object[][]{ + { Instant.parse("0000-01-01T00:00:00Z"), new AtomicLong(-62167219200000L), true}, + { Instant.parse("0000-01-01T00:00:00.001Z"), new AtomicLong(-62167219199999L), true}, + { Instant.parse("1969-12-31T23:59:59Z"), new AtomicLong(-1000L), true}, + { Instant.parse("1969-12-31T23:59:59.999Z"), new AtomicLong(-1L), true}, + { Instant.parse("1970-01-01T00:00:00Z"), new AtomicLong(0L), true}, + { Instant.parse("1970-01-01T00:00:00.001Z"), new AtomicLong(1L), true}, + { Instant.parse("1970-01-01T00:00:00.999Z"), new AtomicLong(999L), true}, + }); + TEST_DB.put(pair(Duration.class, AtomicLong.class), new Object[][]{ + { Duration.ofMillis(Long.MIN_VALUE / 2), new AtomicLong(Long.MIN_VALUE / 2), true }, + { Duration.ofMillis(Integer.MIN_VALUE), new AtomicLong(Integer.MIN_VALUE), true }, + { Duration.ofMillis(-1), new AtomicLong(-1), true }, + { Duration.ofMillis(0), new AtomicLong(0), true }, + { Duration.ofMillis(1), new AtomicLong(1), true }, + { Duration.ofMillis(Integer.MAX_VALUE), new AtomicLong(Integer.MAX_VALUE), true }, + { Duration.ofMillis(Long.MAX_VALUE / 2), new AtomicLong(Long.MAX_VALUE / 2), true }, + }); + } + /** * String */ @@ -811,6 +839,24 @@ private static void loadDurationTests() { {"PT16M40S", Duration.ofSeconds(1000), true}, {"PT2H46M40S", Duration.ofSeconds(10000), true}, }); + TEST_DB.put(pair(Long.class, Duration.class), new Object[][]{ + { Long.MIN_VALUE / 2, Duration.ofMillis(Long.MIN_VALUE / 2), true }, + { (long)Integer.MIN_VALUE, Duration.ofMillis(Integer.MIN_VALUE), true }, + { -1L, Duration.ofMillis(-1), true }, + { 0L, Duration.ofMillis(0), true }, + { 1L, Duration.ofMillis(1), true }, + { (long)Integer.MAX_VALUE, Duration.ofMillis(Integer.MAX_VALUE), true }, + { Long.MAX_VALUE / 2, Duration.ofMillis(Long.MAX_VALUE / 2), true }, + }); + TEST_DB.put(pair(AtomicLong.class, Duration.class), new Object[][]{ + { new AtomicLong(Long.MIN_VALUE / 2), Duration.ofMillis(Long.MIN_VALUE / 2), true }, + { new AtomicLong(Integer.MIN_VALUE), Duration.ofMillis(Integer.MIN_VALUE), true }, + { new AtomicLong(-1), Duration.ofMillis(-1), true }, + { new AtomicLong(0), Duration.ofMillis(0), true }, + { new AtomicLong(1), Duration.ofMillis(1), true }, + { new AtomicLong(Integer.MAX_VALUE), Duration.ofMillis(Integer.MAX_VALUE), true }, + { new AtomicLong(Long.MAX_VALUE / 2), Duration.ofMillis(Long.MAX_VALUE / 2), true }, + }); TEST_DB.put(pair(Double.class, Duration.class), new Object[][]{ {-0.000000001, Duration.ofNanos(-1) }, // IEEE 754 prevents reverse {0d, Duration.ofNanos(0), true}, @@ -936,6 +982,24 @@ private static void loadInstantTests() { { "1980-01-01T00:00:00Z", Instant.parse("1980-01-01T00:00:00Z"), true}, { "2024-12-31T23:59:59.999999999Z", Instant.parse("2024-12-31T23:59:59.999999999Z")}, }); + TEST_DB.put(pair(Long.class, Instant.class), new Object[][]{ + {-62167219200000L, Instant.parse("0000-01-01T00:00:00Z"), true}, + {-62167219199999L, Instant.parse("0000-01-01T00:00:00.001Z"), true}, + {-1000L, Instant.parse("1969-12-31T23:59:59Z"), true}, + {-1L, Instant.parse("1969-12-31T23:59:59.999Z"), true}, + {0L, Instant.parse("1970-01-01T00:00:00Z"), true}, + {1L, Instant.parse("1970-01-01T00:00:00.001Z"), true}, + {999L, Instant.parse("1970-01-01T00:00:00.999Z"), true}, + }); + TEST_DB.put(pair(AtomicLong.class, Instant.class), new Object[][]{ + {new AtomicLong(-62167219200000L), Instant.parse("0000-01-01T00:00:00Z"), true}, + {new AtomicLong(-62167219199999L), Instant.parse("0000-01-01T00:00:00.001Z"), true}, + {new AtomicLong(-1000L), Instant.parse("1969-12-31T23:59:59Z"), true}, + {new AtomicLong(-1L), Instant.parse("1969-12-31T23:59:59.999Z"), true}, + {new AtomicLong(0L), Instant.parse("1970-01-01T00:00:00Z"), true}, + {new AtomicLong(1L), Instant.parse("1970-01-01T00:00:00.001Z"), true}, + {new AtomicLong(999L), Instant.parse("1970-01-01T00:00:00.999Z"), true}, + }); TEST_DB.put(pair(Double.class, Instant.class), new Object[][]{ { -62167219200d, Instant.parse("0000-01-01T00:00:00Z"), true}, { -0.000000001, Instant.parse("1969-12-31T23:59:59.999999999Z")}, // IEEE-754 precision not good enough for reverse @@ -2118,28 +2182,37 @@ private static void loadLongTests(long now) { {Year.of(9999), 9999L}, }); TEST_DB.put(pair(Date.class, Long.class), new Object[][]{ - {new Date(Long.MIN_VALUE), Long.MIN_VALUE}, - {new Date(now), now}, - {new Date(Integer.MIN_VALUE), (long) Integer.MIN_VALUE}, - {new Date(0), 0L}, - {new Date(Integer.MAX_VALUE), (long) Integer.MAX_VALUE}, - {new Date(Long.MAX_VALUE), Long.MAX_VALUE}, + {new Date(Long.MIN_VALUE), Long.MIN_VALUE, true}, + {new Date(now), now, true}, + {new Date(Integer.MIN_VALUE), (long) Integer.MIN_VALUE, true}, + {new Date(0), 0L, true}, + {new Date(Integer.MAX_VALUE), (long) Integer.MAX_VALUE, true}, + {new Date(Long.MAX_VALUE), Long.MAX_VALUE, true}, }); TEST_DB.put(pair(java.sql.Date.class, Long.class), new Object[][]{ - {new java.sql.Date(Long.MIN_VALUE), Long.MIN_VALUE}, - {new java.sql.Date(Integer.MIN_VALUE), (long) Integer.MIN_VALUE}, - {new java.sql.Date(now), now}, - {new java.sql.Date(0), 0L}, - {new java.sql.Date(Integer.MAX_VALUE), (long) Integer.MAX_VALUE}, - {new java.sql.Date(Long.MAX_VALUE), Long.MAX_VALUE}, + {new java.sql.Date(Long.MIN_VALUE), Long.MIN_VALUE, true}, + {new java.sql.Date(Integer.MIN_VALUE), (long) Integer.MIN_VALUE, true}, + {new java.sql.Date(now), now, true}, + {new java.sql.Date(0), 0L, true}, + {new java.sql.Date(Integer.MAX_VALUE), (long) Integer.MAX_VALUE, true}, + {new java.sql.Date(Long.MAX_VALUE), Long.MAX_VALUE, true}, }); TEST_DB.put(pair(Timestamp.class, Long.class), new Object[][]{ - {new Timestamp(Long.MIN_VALUE), Long.MIN_VALUE}, - {new Timestamp(Integer.MIN_VALUE), (long) Integer.MIN_VALUE}, - {new Timestamp(now), now}, - {new Timestamp(0), 0L}, - {new Timestamp(Integer.MAX_VALUE), (long) Integer.MAX_VALUE}, - {new Timestamp(Long.MAX_VALUE), Long.MAX_VALUE}, + {new Timestamp(Long.MIN_VALUE), Long.MIN_VALUE, true}, + {new Timestamp(Integer.MIN_VALUE), (long) Integer.MIN_VALUE, true}, + {new Timestamp(now), now, true}, + {new Timestamp(0), 0L, true}, + {new Timestamp(Integer.MAX_VALUE), (long) Integer.MAX_VALUE, true}, + {new Timestamp(Long.MAX_VALUE), Long.MAX_VALUE, true}, + }); + TEST_DB.put(pair(Duration.class, Long.class), new Object[][]{ + { Duration.ofMillis(Long.MIN_VALUE / 2), Long.MIN_VALUE / 2, true }, + { Duration.ofMillis(Integer.MIN_VALUE), (long)Integer.MIN_VALUE, true }, + { Duration.ofMillis(-1), -1L, true }, + { Duration.ofMillis(0), 0L, true }, + { Duration.ofMillis(1), 1L, true }, + { Duration.ofMillis(Integer.MAX_VALUE), (long)Integer.MAX_VALUE, true }, + { Duration.ofMillis(Long.MAX_VALUE / 2), Long.MAX_VALUE / 2, true }, }); TEST_DB.put(pair(Instant.class, Long.class), new Object[][]{ {Instant.parse("0000-01-01T00:00:00Z"), -62167219200000L, true}, @@ -2864,7 +2937,7 @@ void testConvert(String shortNameSource, String shortNameTarget, Object source, } else { assert ClassUtilities.toPrimitiveWrapperClass(sourceClass).isInstance(source) : "source type mismatch ==> Expected: " + shortNameSource + ", Actual: " + Converter.getShortName(source.getClass()); } - assert target == null || target instanceof Throwable || ClassUtilities.toPrimitiveWrapperClass(targetClass).isInstance(target) : "target type mismatch ==> " + shortNameTarget + ", actual: " + Converter.getShortName(target.getClass()); + assert target == null || target instanceof Throwable || ClassUtilities.toPrimitiveWrapperClass(targetClass).isInstance(target) : "target type mismatch ==> Expected: " + shortNameTarget + ", Actual: " + Converter.getShortName(target.getClass()); // if the source/target are the same Class, then ensure identity lambda is used. if (sourceClass.equals(targetClass)) { @@ -2880,7 +2953,9 @@ void testConvert(String shortNameSource, String shortNameTarget, Object source, // Assert values are equals Object actual = converter.convert(source, targetClass); try { - if (target instanceof BigDecimal) { + if (target instanceof AtomicLong) { + assertEquals(((AtomicLong) target).get(), ((AtomicLong) actual).get()); + } else if (target instanceof BigDecimal) { if (((BigDecimal) target).compareTo((BigDecimal) actual) != 0) { assertEquals(target, actual); }