Skip to content

Commit

Permalink
Added more conversions for AtomicLong, Long, and Duration. Prepared f…
Browse files Browse the repository at this point in the history
…or release as 2.4.1.
  • Loading branch information
jdereg committed Feb 26, 2024
1 parent bdb6227 commit 3deb45f
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 26 deletions.
7 changes: 3 additions & 4 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -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.
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.5.0-SNAPSHOT</version>
<version>2.4.1</version>
<description>Java Utilities</description>
<url>https://github.com/jdereg/java-util</url>

Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/cedarsoftware/util/convert/Converter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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},
Expand Down Expand Up @@ -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)) {
Expand All @@ -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);
}
Expand Down

0 comments on commit 3deb45f

Please sign in to comment.