diff --git a/README.md b/README.md
index 0497af25..964e492d 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.6'
+implementation 'com.cedarsoftware:java-util:2.4.7'
```
##### Maven
@@ -23,7 +23,7 @@ implementation 'com.cedarsoftware:java-util:2.4.6'
com.cedarsoftware
java-util
- 2.4.6
+ 2.4.7
```
---
diff --git a/pom.xml b/pom.xml
index 53da8aa3..a7462042 100644
--- a/pom.xml
+++ b/pom.xml
@@ -30,25 +30,25 @@
- 5.10.1
- 5.10.1
- 3.25.1
- 4.19.1
+ 5.10.2
+ 5.10.2
+ 3.25.3
+ 4.19.11
4.11.0
- 1.20.0
+ 1.21.0
1.6.13
3.3.0
- 3.1.0
- 3.12.1
+ 3.2.2
+ 3.13.0
3.6.3
- 3.2.3
+ 3.2.5
3.3.0
- 1.26.4
- 5.1.9
+ 1.26.4
+ 5.1.9
UTF-8
@@ -139,13 +139,13 @@
org.apache.felix
maven-scr-plugin
- ${version.plugin.felix.scr}
+ ${version.plugin.scr}
org.apache.felix
maven-bundle-plugin
- ${version.plugin.felix.bundle}
+ ${version.plugin.bundle}
true
@@ -261,10 +261,10 @@
com.cedarsoftware
json-io
- ${version.json.io}
+ 4.19.11
test
-
+
org.agrona
agrona
diff --git a/src/main/java/com/cedarsoftware/util/DateUtilities.java b/src/main/java/com/cedarsoftware/util/DateUtilities.java
index 847fd299..4c0d7335 100644
--- a/src/main/java/com/cedarsoftware/util/DateUtilities.java
+++ b/src/main/java/com/cedarsoftware/util/DateUtilities.java
@@ -80,7 +80,7 @@ public final class DateUtilities {
private static final Pattern allDigits = Pattern.compile("^\\d+$");
private static final String days = "monday|mon|tuesday|tues|tue|wednesday|wed|thursday|thur|thu|friday|fri|saturday|sat|sunday|sun"; // longer before shorter matters
private static final String mos = "January|Jan|February|Feb|March|Mar|April|Apr|May|June|Jun|July|Jul|August|Aug|September|Sept|Sep|October|Oct|November|Nov|December|Dec";
- private static final String yr = "\\d{4}";
+ private static final String yr = "[+-]?\\d{4,5}\\b";
private static final String d1or2 = "\\d{1,2}";
private static final String d2 = "\\d{2}";
private static final String ord = "st|nd|rd|th";
diff --git a/src/main/java/com/cedarsoftware/util/convert/Converter.java b/src/main/java/com/cedarsoftware/util/convert/Converter.java
index ed8ae5ba..1979914b 100644
--- a/src/main/java/com/cedarsoftware/util/convert/Converter.java
+++ b/src/main/java/com/cedarsoftware/util/convert/Converter.java
@@ -831,6 +831,10 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(String.class, Year.class), StringConversions::toYear);
CONVERSION_DB.put(pair(Map.class, Year.class), MapConversions::toYear);
+ // Throwable conversions supported
+ CONVERSION_DB.put(pair(Void.class, Throwable.class), VoidConversions::toNull);
+ CONVERSION_DB.put(pair(Map.class, Throwable.class), MapConversions::toThrowable);
+
// Map conversions supported
CONVERSION_DB.put(pair(Void.class, Map.class), VoidConversions::toNull);
CONVERSION_DB.put(pair(Byte.class, Map.class), MapConversions::initMap);
@@ -872,6 +876,7 @@ private static void buildFactoryConversions() {
CONVERSION_DB.put(pair(Locale.class, Map.class), LocaleConversions::toMap);
CONVERSION_DB.put(pair(URI.class, Map.class), UriConversions::toMap);
CONVERSION_DB.put(pair(URL.class, Map.class), UrlConversions::toMap);
+ CONVERSION_DB.put(pair(Throwable.class, Map.class), ThrowableConversions::toMap);
}
public Converter(ConverterOptions options) {
diff --git a/src/main/java/com/cedarsoftware/util/convert/MapConversions.java b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java
index 2a1acbd1..061ff1d5 100644
--- a/src/main/java/com/cedarsoftware/util/convert/MapConversions.java
+++ b/src/main/java/com/cedarsoftware/util/convert/MapConversions.java
@@ -1,5 +1,6 @@
package com.cedarsoftware.util.convert;
+import java.lang.reflect.Constructor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
@@ -71,14 +72,12 @@ final class MapConversions {
static final String SECOND = "second";
static final String SECONDS = "seconds";
static final String EPOCH_MILLIS = "epochMillis";
- static final String NANO = "nano";
static final String NANOS = "nanos";
static final String MOST_SIG_BITS = "mostSigBits";
static final String LEAST_SIG_BITS = "leastSigBits";
static final String OFFSET = "offset";
static final String OFFSET_HOUR = "offsetHour";
static final String OFFSET_MINUTE = "offsetMinute";
- static final String DATE_TIME = "dateTime";
static final String ID = "id";
static final String LANGUAGE = "language";
static final String COUNTRY = "country";
@@ -87,6 +86,10 @@ final class MapConversions {
static final String URI_KEY = "URI";
static final String URL_KEY = "URL";
static final String UUID = "UUID";
+ static final String CLASS = "class";
+ static final String MESSAGE = "message";
+ static final String CAUSE = "cause";
+ static final String CAUSE_MESSAGE = "causeMessage";
static final String OPTIONAL = " (optional)";
private MapConversions() {}
@@ -169,7 +172,7 @@ static AtomicBoolean toAtomicBoolean(Object from, Converter converter) {
static java.sql.Date toSqlDate(Object from, Converter converter) {
Map.Entry epochTime = toEpochMillis(from, converter);
if (epochTime == null) {
- return fromMap(from, converter, java.sql.Date.class, new String[]{EPOCH_MILLIS}, new String[]{DATE, TIME, ZONE + OPTIONAL}, new String[]{TIME, ZONE + OPTIONAL});
+ return fromMap(from, converter, java.sql.Date.class, new String[]{EPOCH_MILLIS}, new String[]{TIME, ZONE + OPTIONAL}, new String[]{DATE, TIME, ZONE + OPTIONAL});
}
return new java.sql.Date(epochTime.getKey());
}
@@ -177,45 +180,44 @@ static java.sql.Date toSqlDate(Object from, Converter converter) {
static Date toDate(Object from, Converter converter) {
Map.Entry epochTime = toEpochMillis(from, converter);
if (epochTime == null) {
- return fromMap(from, converter, Date.class, new String[]{EPOCH_MILLIS}, new String[]{DATE, TIME, ZONE + OPTIONAL}, new String[]{TIME, ZONE + OPTIONAL});
+ return fromMap(from, converter, Date.class, new String[]{EPOCH_MILLIS}, new String[]{TIME, ZONE + OPTIONAL}, new String[]{DATE, TIME, ZONE + OPTIONAL});
}
return new Date(epochTime.getKey());
}
+ /**
+ * If the time String contains seconds resolution better than milliseconds, it will be kept. For example,
+ * If the time was "08.37:16.123456789" the sub-millisecond portion here will take precedence over a separate
+ * key/value of "nanos" mapped to a value. However, if "nanos" is specific as a key/value, and the time does
+ * not include nanosecond resolution, then a value > 0 specified in the "nanos" key will be incorporated into
+ * the resolution of the time.
+ */
static Timestamp toTimestamp(Object from, Converter converter) {
Map map = (Map) from;
Object epochMillis = map.get(EPOCH_MILLIS);
+ int ns = converter.convert(map.get(NANOS), int.class); // optional
if (epochMillis != null) {
long time = converter.convert(epochMillis, long.class);
- int ns = converter.convert(map.get(NANOS), int.class); // optional
Timestamp timeStamp = new Timestamp(time);
- timeStamp.setNanos(ns);
+ if (map.containsKey(NANOS) && ns != 0) {
+ timeStamp.setNanos(ns);
+ }
return timeStamp;
}
-
+
+ // Map.Entry return has key of epoch-millis and value of nanos-of-second
Map.Entry epochTime = toEpochMillis(from, converter);
- if (epochTime == null) {
- return fromMap(from, converter, Timestamp.class, new String[]{EPOCH_MILLIS, NANOS + OPTIONAL}, new String[]{DATE, TIME, ZONE + OPTIONAL}, new String[]{TIME, ZONE + OPTIONAL});
+ if (epochTime == null) { // specified as "value" or "_v" are not at all and will give nice exception error message.
+ return fromMap(from, converter, Timestamp.class, new String[]{EPOCH_MILLIS, NANOS + OPTIONAL}, new String[]{TIME, ZONE + OPTIONAL}, new String[]{DATE, TIME, ZONE + OPTIONAL});
}
Timestamp timestamp = new Timestamp(epochTime.getKey());
- int ns = converter.convert(epochTime.getValue(), int.class);
- setNanosPreserveMillis(timestamp, ns);
+ if (timestamp.getTime() % 1000 == 0) { // Add nanoseconds *if* Timestamp time was only to second resolution
+ timestamp.setNanos(epochTime.getValue());
+ }
return timestamp;
}
- static void setNanosPreserveMillis(Timestamp timestamp, int nanoToSet) {
- // Extract the current milliseconds and nanoseconds
- int currentNanos = timestamp.getNanos();
- int milliPart = currentNanos / 1_000_000; // Milliseconds part of the current nanoseconds
-
- // Preserve the millisecond part of the current time and add the new nanoseconds
- int newNanos = milliPart * 1_000_000 + (nanoToSet % 1_000_000);
-
- // Set the new nanoseconds value, preserving the milliseconds
- timestamp.setNanos(newNanos);
- }
-
static TimeZone toTimeZone(Object from, Converter converter) {
return fromMap(from, converter, TimeZone.class, new String[]{ZONE});
}
@@ -229,16 +231,17 @@ static Calendar toCalendar(Object from, Converter converter) {
Object date = map.get(DATE);
Object time = map.get(TIME);
- Object zone = map.get(ZONE);
+ Object zone = map.get(ZONE); // optional
+ ZoneId zoneId;
+ if (zone != null) {
+ zoneId = converter.convert(zone, ZoneId.class);
+ } else {
+ zoneId = converter.getOptions().getZoneId();
+ }
+
if (date != null && time != null) {
LocalDate localDate = converter.convert(date, LocalDate.class);
LocalTime localTime = converter.convert(time, LocalTime.class);
- ZoneId zoneId;
- if (zone != null) {
- zoneId = converter.convert(zone, ZoneId.class);
- } else {
- zoneId = converter.getOptions().getZoneId();
- }
LocalDateTime ldt = LocalDateTime.of(localDate, localTime);
ZonedDateTime zdt = ldt.atZone(zoneId);
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(zoneId));
@@ -254,12 +257,6 @@ static Calendar toCalendar(Object from, Converter converter) {
}
if (time != null && date == null) {
- ZoneId zoneId;
- if (zone != null) {
- zoneId = converter.convert(zone, ZoneId.class);
- } else {
- zoneId = converter.getOptions().getZoneId();
- }
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone(zoneId));
ZonedDateTime zdt = DateUtilities.parseDate((String)time, zoneId, true);
cal.setTimeInMillis(zdt.toInstant().toEpochMilli());
@@ -268,12 +265,14 @@ static Calendar toCalendar(Object from, Converter converter) {
return fromMap(from, converter, Calendar.class, new String[]{EPOCH_MILLIS}, new String[]{TIME, ZONE + OPTIONAL}, new String[]{DATE, TIME, ZONE + OPTIONAL});
}
+ // Map.Entry return has key of epoch-millis and value of nanos-of-second
private static Map.Entry toEpochMillis(Object from, Converter converter) {
Map map = (Map) from;
Object epochMillis = map.get(EPOCH_MILLIS);
+ int ns = converter.convert(map.get(NANOS), int.class); // optional
if (epochMillis != null) {
- return new AbstractMap.SimpleImmutableEntry<>(converter.convert(epochMillis, long.class), 0);
+ return new AbstractMap.SimpleImmutableEntry<>(converter.convert(epochMillis, long.class), ns);
}
Object time = map.get(TIME);
@@ -286,13 +285,13 @@ private static Map.Entry toEpochMillis(Object from, Converter con
LocalTime lt = converter.convert(time, LocalTime.class);
ZoneId zoneId = converter.convert(zone, ZoneId.class);
ZonedDateTime zdt = ZonedDateTime.of(ld, lt, zoneId);
- Instant instant = zdt.toInstant();
- return new AbstractMap.SimpleImmutableEntry<>(instant.toEpochMilli(), instant.getNano());
+ return nanoRule(zdt, ns);
}
// Time only
if (time != null && date == null && zone == null) {
- return new AbstractMap.SimpleImmutableEntry<>(converter.convert(time, Date.class).getTime(), 0);
+ ZonedDateTime zdt = converter.convert(time, ZonedDateTime.class);
+ return nanoRule(zdt, ns);
}
// Time & Zone, no Date
@@ -300,8 +299,7 @@ private static Map.Entry toEpochMillis(Object from, Converter con
LocalDateTime ldt = converter.convert(time, LocalDateTime.class);
ZoneId zoneId = converter.convert(zone, ZoneId.class);
ZonedDateTime zdt = ZonedDateTime.of(ldt, zoneId);
- Instant instant = zdt.toInstant();
- return new AbstractMap.SimpleImmutableEntry<>(instant.toEpochMilli(), instant.getNano());
+ return nanoRule(zdt, ns);
}
// Time & Date, no zone
@@ -309,13 +307,20 @@ private static Map.Entry toEpochMillis(Object from, Converter con
LocalDate ld = converter.convert(date, LocalDate.class);
LocalTime lt = converter.convert(time, LocalTime.class);
ZonedDateTime zdt = ZonedDateTime.of(ld, lt, converter.getOptions().getZoneId());
- Instant instant = zdt.toInstant();
- return new AbstractMap.SimpleImmutableEntry<>(instant.toEpochMilli(), instant.getNano());
+ return nanoRule(zdt, ns);
}
return null;
}
+ private static Map.Entry nanoRule(ZonedDateTime zdt, int nanosFromMap) {
+ int nanos = zdt.getNano();
+ if (nanos != 0) {
+ nanosFromMap = nanos;
+ }
+ return new AbstractMap.SimpleImmutableEntry<>(zdt.toEpochSecond() * 1000, nanosFromMap);
+ }
+
static Locale toLocale(Object from, Converter converter) {
Map map = (Map) from;
@@ -376,7 +381,7 @@ static LocalTime toLocalTime(Object from, Converter converter) {
Object hour = map.get(HOUR);
Object minute = map.get(MINUTE);
Object second = map.get(SECOND);
- Object nano = map.get(NANO);
+ Object nano = map.get(NANOS);
if (hour != null && minute != null) {
int h = converter.convert(hour, int.class);
int m = converter.convert(minute, int.class);
@@ -384,7 +389,7 @@ static LocalTime toLocalTime(Object from, Converter converter) {
int n = converter.convert(nano, int.class);
return LocalTime.of(h, m, s, n);
}
- return fromMap(from, converter, LocalTime.class, new String[]{TIME}, new String[]{HOUR, MINUTE, SECOND + OPTIONAL, NANO + OPTIONAL});
+ return fromMap(from, converter, LocalTime.class, new String[]{TIME}, new String[]{HOUR, MINUTE, SECOND + OPTIONAL, NANOS + OPTIONAL});
}
static OffsetTime toOffsetTime(Object from, Converter converter) {
@@ -392,7 +397,7 @@ static OffsetTime toOffsetTime(Object from, Converter converter) {
Object hour = map.get(HOUR);
Object minute = map.get(MINUTE);
Object second = map.get(SECOND);
- Object nano = map.get(NANO);
+ Object nano = map.get(NANOS);
Object oh = map.get(OFFSET_HOUR);
Object om = map.get(OFFSET_MINUTE);
if (hour != null && minute != null) {
@@ -417,7 +422,7 @@ static OffsetTime toOffsetTime(Object from, Converter converter) {
if (time != null) {
return converter.convert(time, OffsetTime.class);
}
- return fromMap(from, converter, OffsetTime.class, new String[] {TIME}, new String[] {HOUR, MINUTE, SECOND + OPTIONAL, NANO + OPTIONAL, OFFSET_HOUR, OFFSET_MINUTE});
+ return fromMap(from, converter, OffsetTime.class, new String[] {TIME}, new String[] {HOUR, MINUTE, SECOND + OPTIONAL, NANOS + OPTIONAL, OFFSET_HOUR, OFFSET_MINUTE});
}
static OffsetDateTime toOffsetDateTime(Object from, Converter converter) {
@@ -475,7 +480,7 @@ static ZonedDateTime toZonedDateTime(Object from, Converter converter) {
LocalDateTime localDateTime = converter.convert(time, LocalDateTime.class);
return ZonedDateTime.of(localDateTime, zoneId);
}
- return fromMap(from, converter, ZonedDateTime.class, new String[] {EPOCH_MILLIS}, new String[] {DATE_TIME, ZONE}, new String[] {DATE, TIME, ZONE});
+ return fromMap(from, converter, ZonedDateTime.class, new String[] {EPOCH_MILLIS}, new String[] {TIME, ZONE}, new String[] {DATE, TIME, ZONE});
}
static Class> toClass(Object from, Converter converter) {
@@ -580,10 +585,45 @@ static URL toURL(Object from, Converter converter) {
return fromMap(from, converter, URL.class, new String[] {URL_KEY});
}
+ static Throwable toThrowable(Object from, Converter converter) {
+ Map map = (Map) from;
+ try {
+ String className = (String) map.get(CLASS);
+ String message = (String) map.get(MESSAGE);
+ String causeClassName = (String) map.get(CAUSE);
+ String causeMessage = (String) map.get(CAUSE_MESSAGE);
+
+ Class> clazz = Class.forName(className);
+ Throwable cause = null;
+
+ if (causeClassName != null && !causeClassName.isEmpty()) {
+ Class> causeClass = Class.forName(causeClassName);
+ // Assuming the cause class has a constructor that takes a String message.
+ Constructor> causeConstructor = causeClass.getConstructor(String.class);
+ cause = (Throwable) causeConstructor.newInstance(causeMessage);
+ }
+
+ // Check for appropriate constructor based on whether a cause is present.
+ Constructor> constructor;
+ Throwable exception;
+
+ if (cause != null) {
+ constructor = clazz.getConstructor(String.class, Throwable.class);
+ exception = (Throwable) constructor.newInstance(message, cause);
+ } else {
+ constructor = clazz.getConstructor(String.class);
+ exception = (Throwable) constructor.newInstance(message);
+ }
+
+ return exception;
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Unable to reconstruct exception instance from map: " + map);
+ }
+ }
+
static URI toURI(Object from, Converter converter) {
Map map = (Map) from;
- String uri = null;
- uri = (String) map.get(URI_KEY);
+ String uri = (String) map.get(URI_KEY);
if (StringUtilities.hasContent(uri)) {
return converter.convert(map.get(URI_KEY), URI.class);
}
diff --git a/src/main/java/com/cedarsoftware/util/convert/StringConversions.java b/src/main/java/com/cedarsoftware/util/convert/StringConversions.java
index 4a9957d6..3f95bb23 100644
--- a/src/main/java/com/cedarsoftware/util/convert/StringConversions.java
+++ b/src/main/java/com/cedarsoftware/util/convert/StringConversions.java
@@ -434,7 +434,12 @@ static ZoneId toZoneId(Object from, Converter converter) {
try {
return ZoneId.of(str);
} catch (Exception e) {
- throw new IllegalArgumentException("Unknown time-zone ID: '" + str + "'", e);
+ TimeZone tz = TimeZone.getTimeZone(str);
+ if ("GMT".equals(tz.getID())) {
+ throw new IllegalArgumentException("Unknown time-zone ID: '" + str + "'", e);
+ } else {
+ return tz.toZoneId();
+ }
}
}
diff --git a/src/main/java/com/cedarsoftware/util/convert/ThrowableConversions.java b/src/main/java/com/cedarsoftware/util/convert/ThrowableConversions.java
new file mode 100644
index 00000000..7fe68927
--- /dev/null
+++ b/src/main/java/com/cedarsoftware/util/convert/ThrowableConversions.java
@@ -0,0 +1,44 @@
+package com.cedarsoftware.util.convert;
+
+import java.util.Map;
+
+import com.cedarsoftware.util.CompactLinkedMap;
+
+import static com.cedarsoftware.util.convert.MapConversions.CAUSE;
+import static com.cedarsoftware.util.convert.MapConversions.CAUSE_MESSAGE;
+import static com.cedarsoftware.util.convert.MapConversions.CLASS;
+import static com.cedarsoftware.util.convert.MapConversions.MESSAGE;
+
+/**
+ * @author John DeRegnaucourt (jdereg@gmail.com)
+ *
+ * Copyright (c) Cedar Software LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * License
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+final class ThrowableConversions {
+
+ private ThrowableConversions() {}
+
+ static Map toMap(Object from, Converter converter) {
+ Throwable throwable = (Throwable) from;
+ Map target = new CompactLinkedMap<>();
+ target.put(CLASS, throwable.getClass().getName());
+ target.put(MESSAGE, throwable.getMessage());
+ if (throwable.getCause() != null) {
+ target.put(CAUSE, throwable.getCause().getClass().getName());
+ target.put(CAUSE_MESSAGE, throwable.getCause().getMessage());
+ }
+ return target;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/cedarsoftware/util/CollectionUtilitiesTests.java b/src/test/java/com/cedarsoftware/util/CollectionUtilitiesTests.java
index 79fa70b3..bbc1fe6f 100644
--- a/src/test/java/com/cedarsoftware/util/CollectionUtilitiesTests.java
+++ b/src/test/java/com/cedarsoftware/util/CollectionUtilitiesTests.java
@@ -1,13 +1,11 @@
package com.cedarsoftware.util;
-import com.cedarsoftware.util.io.MetaUtils;
-import org.junit.jupiter.api.Test;
-
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.junit.jupiter.api.Test;
+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
diff --git a/src/test/java/com/cedarsoftware/util/TestGraphComparator.java b/src/test/java/com/cedarsoftware/util/TestGraphComparator.java
index c0a89f93..2d9a16c1 100644
--- a/src/test/java/com/cedarsoftware/util/TestGraphComparator.java
+++ b/src/test/java/com/cedarsoftware/util/TestGraphComparator.java
@@ -17,9 +17,11 @@
import java.util.TreeMap;
import java.util.TreeSet;
-import com.cedarsoftware.util.io.JsonIo;
-import com.cedarsoftware.util.io.ReadOptionsBuilder;
-import com.cedarsoftware.util.io.WriteOptions;
+import com.cedarsoftware.io.JsonIo;
+import com.cedarsoftware.io.ReadOptions;
+import com.cedarsoftware.io.ReadOptionsBuilder;
+import com.cedarsoftware.io.WriteOptions;
+import com.cedarsoftware.io.WriteOptionsBuilder;
import org.junit.jupiter.api.Test;
import static com.cedarsoftware.util.GraphComparator.Delta.Command.ARRAY_RESIZE;
@@ -51,7 +53,6 @@ public class TestGraphComparator
private static final int SET_TYPE_HASH = 1;
private static final int SET_TYPE_TREE = 2;
private static final int SET_TYPE_LINKED = 3;
-
public interface HasId
{
Object getId();
@@ -2173,28 +2174,26 @@ private Dude getDude(String name, int age)
return dude;
}
- private Object clone(Object source) throws Exception
- {
- return JsonIo.deepCopy(source, new ReadOptionsBuilder().build(), new WriteOptions());
+ private Object clone(Object source) {
+
+ WriteOptions writeOptions = new WriteOptionsBuilder().showTypeInfoAlways().build();
+ ReadOptions readOptions = new ReadOptionsBuilder().build();
+ return JsonIo.deepCopy(source, readOptions, writeOptions);
}
private GraphComparator.ID getIdFetcher()
{
- return new GraphComparator.ID()
- {
- public Object getId(Object objectToId)
+ return objectToId -> {
+ if (objectToId instanceof HasId)
{
- if (objectToId instanceof HasId)
- {
- HasId obj = (HasId) objectToId;
- return obj.getId();
- }
- else if (objectToId instanceof Collection || objectToId instanceof Map)
- {
- return null;
- }
- throw new RuntimeException("Object does not support getId(): " + (objectToId != null ? objectToId.getClass().getName() : "null"));
+ HasId obj = (HasId) objectToId;
+ return obj.getId();
+ }
+ else if (objectToId instanceof Collection || objectToId instanceof Map)
+ {
+ return null;
}
+ throw new RuntimeException("Object does not support getId(): " + (objectToId != null ? objectToId.getClass().getName() : "null"));
};
}
}
diff --git a/src/test/java/com/cedarsoftware/util/convert/CharArrayConversionsTests.java b/src/test/java/com/cedarsoftware/util/convert/CharArrayConversionsTests.java
index dcefa904..0f471e8d 100644
--- a/src/test/java/com/cedarsoftware/util/convert/CharArrayConversionsTests.java
+++ b/src/test/java/com/cedarsoftware/util/convert/CharArrayConversionsTests.java
@@ -1,15 +1,12 @@
package com.cedarsoftware.util.convert;
-import com.cedarsoftware.util.io.ReadOptionsBuilder;
+import java.util.stream.Stream;
+
import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
-import java.util.Map;
-import java.util.stream.Stream;
-
import static org.assertj.core.api.Assertions.assertThat;
class CharArrayConversionsTests {
diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java
index 09db991c..fe69cc63 100644
--- a/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java
+++ b/src/test/java/com/cedarsoftware/util/convert/ConverterEverythingTest.java
@@ -54,6 +54,9 @@
import static com.cedarsoftware.util.MapUtilities.mapOf;
import static com.cedarsoftware.util.convert.Converter.VALUE;
import static com.cedarsoftware.util.convert.Converter.pair;
+import static com.cedarsoftware.util.convert.MapConversions.CAUSE;
+import static com.cedarsoftware.util.convert.MapConversions.CAUSE_MESSAGE;
+import static com.cedarsoftware.util.convert.MapConversions.CLASS;
import static com.cedarsoftware.util.convert.MapConversions.COUNTRY;
import static com.cedarsoftware.util.convert.MapConversions.DATE;
import static com.cedarsoftware.util.convert.MapConversions.DAY;
@@ -63,11 +66,11 @@
import static com.cedarsoftware.util.convert.MapConversions.ID;
import static com.cedarsoftware.util.convert.MapConversions.LANGUAGE;
import static com.cedarsoftware.util.convert.MapConversions.LEAST_SIG_BITS;
+import static com.cedarsoftware.util.convert.MapConversions.MESSAGE;
import static com.cedarsoftware.util.convert.MapConversions.MINUTE;
import static com.cedarsoftware.util.convert.MapConversions.MINUTES;
import static com.cedarsoftware.util.convert.MapConversions.MONTH;
import static com.cedarsoftware.util.convert.MapConversions.MOST_SIG_BITS;
-import static com.cedarsoftware.util.convert.MapConversions.NANO;
import static com.cedarsoftware.util.convert.MapConversions.NANOS;
import static com.cedarsoftware.util.convert.MapConversions.OFFSET;
import static com.cedarsoftware.util.convert.MapConversions.OFFSET_HOUR;
@@ -105,7 +108,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-// TODO: Throwable conversions need to be added for all the popular exception types
// TODO: EnumSet conversions need to be added?
class ConverterEverythingTest {
private static final String TOKYO = "Asia/Tokyo";
@@ -206,6 +208,7 @@ public ZoneId getZoneId() {
loadUrlTests();
loadUuidTests();
loadEnumTests();
+ loadThrowableTests();
}
/**
@@ -220,6 +223,15 @@ private static void loadEnumTests() {
});
}
+ /**
+ * Throwable
+ */
+ private static void loadThrowableTests() {
+ TEST_DB.put(pair(Void.class, Throwable.class), new Object[][]{
+ {null, null}
+ });
+ }
+
/**
* UUID
*/
@@ -425,11 +437,11 @@ private static void loadOffsetTimeTests() {
{mapOf(TIME, "00:00:00+09:00"), OffsetTime.parse("00:00+09:00")}, // no reverse
{mapOf(TIME, "00:00:00+09:00:00"), OffsetTime.parse("00:00+09:00")}, // no reverse
{mapOf(TIME, "garbage"), new IllegalArgumentException("Unable to parse 'garbage' as an OffsetTime")}, // no reverse
- {mapOf(HOUR, 1, MINUTE,30), new IllegalArgumentException("Map to 'OffsetTime' the map must include: [time], [hour, minute, second (optional), nano (optional), offsetHour, offsetMinute], [value], or [_v] as keys with associated values")},
- {mapOf(HOUR, 1, MINUTE,30, SECOND, 59), new IllegalArgumentException("Map to 'OffsetTime' the map must include: [time], [hour, minute, second (optional), nano (optional), offsetHour, offsetMinute], [value], or [_v] as keys with associated values")},
- {mapOf(HOUR, 1, MINUTE,30, SECOND, 59, NANO, 123456789), new IllegalArgumentException("Map to 'OffsetTime' the map must include: [time], [hour, minute, second (optional), nano (optional), offsetHour, offsetMinute], [value], or [_v] as keys with associated values")},
- {mapOf(HOUR, 1, MINUTE,30, SECOND, 59, NANO, 123456789, OFFSET_HOUR, -5, OFFSET_MINUTE, -30), OffsetTime.parse("01:30:59.123456789-05:30")},
- {mapOf(HOUR, 1, MINUTE,30, SECOND, 59, NANO, 123456789, OFFSET_HOUR, -5, OFFSET_MINUTE, 30), new IllegalArgumentException("Offset 'hour' and 'minute' are not correct")},
+ {mapOf(HOUR, 1, MINUTE,30), new IllegalArgumentException("Map to 'OffsetTime' the map must include: [time], [hour, minute, second (optional), nanos (optional), offsetHour, offsetMinute], [value], or [_v] as keys with associated values")},
+ {mapOf(HOUR, 1, MINUTE,30, SECOND, 59), new IllegalArgumentException("Map to 'OffsetTime' the map must include: [time], [hour, minute, second (optional), nanos (optional), offsetHour, offsetMinute], [value], or [_v] as keys with associated values")},
+ {mapOf(HOUR, 1, MINUTE,30, SECOND, 59, NANOS, 123456789), new IllegalArgumentException("Map to 'OffsetTime' the map must include: [time], [hour, minute, second (optional), nanos (optional), offsetHour, offsetMinute], [value], or [_v] as keys with associated values")},
+ {mapOf(HOUR, 1, MINUTE,30, SECOND, 59, NANOS, 123456789, OFFSET_HOUR, -5, OFFSET_MINUTE, -30), OffsetTime.parse("01:30:59.123456789-05:30")},
+ {mapOf(HOUR, 1, MINUTE,30, SECOND, 59, NANOS, 123456789, OFFSET_HOUR, -5, OFFSET_MINUTE, 30), new IllegalArgumentException("Offset 'hour' and 'minute' are not correct")},
{mapOf(VALUE, "16:20:00-05:00"), OffsetTime.parse("16:20:00-05:00") },
});
TEST_DB.put(pair(OffsetDateTime.class, OffsetTime.class), new Object[][]{
@@ -500,6 +512,10 @@ private static void loadMapTests() {
TEST_DB.put(pair(Map.class, Map.class), new Object[][]{
{ new HashMap<>(), new IllegalArgumentException("Unsupported conversion") }
});
+ TEST_DB.put(pair(Throwable.class, Map.class), new Object[][]{
+ { new Throwable("divide by 0", new IllegalArgumentException("root issue")), mapOf(MESSAGE, "divide by 0", CLASS, Throwable.class.getName(), CAUSE, IllegalArgumentException.class.getName(), CAUSE_MESSAGE, "root issue")},
+ { new IllegalArgumentException("null not allowed"), mapOf(MESSAGE, "null not allowed", CLASS, IllegalArgumentException.class.getName())},
+ });
}
/**
@@ -768,18 +784,6 @@ private static void loadStringTests() {
{ZonedDateTime.parse("1970-01-01T00:00:00Z"), "1970-01-01T00:00:00Z", true},
{ZonedDateTime.parse("1970-01-01T00:00:00.000000001Z"), "1970-01-01T00:00:00.000000001Z", true},
});
- TEST_DB.put(pair(Number.class, String.class), new Object[][]{
- {(byte) 1, "1"},
- {(short) 2, "2"},
- {3, "3"},
- {4L, "4"},
- {5f, "5.0"},
- {6.0, "6.0"},
- {new AtomicInteger(7), "7"},
- {new AtomicLong(8L), "8"},
- {new BigInteger("9"), "9"},
- {new BigDecimal("10"), "10"},
- });
TEST_DB.put(pair(Map.class, String.class), new Object[][]{
{mapOf("_v", "alpha"), "alpha"},
{mapOf("value", "alpha"), "alpha"},
@@ -1093,7 +1097,7 @@ private static void loadLocalTimeTests() {
{mapOf(VALUE, "23:59:59.999999999"), LocalTime.parse("23:59:59.999999999") },
{mapOf(HOUR, 23, MINUTE, 59), LocalTime.parse("23:59") },
{mapOf(HOUR, 23, MINUTE, 59, SECOND, 59), LocalTime.parse("23:59:59") },
- {mapOf(HOUR, 23, MINUTE, 59, SECOND, 59, NANO, 999999999), LocalTime.parse("23:59:59.999999999") },
+ {mapOf(HOUR, 23, MINUTE, 59, SECOND, 59, NANOS, 999999999), LocalTime.parse("23:59:59.999999999") },
});
}
@@ -1280,20 +1284,30 @@ private static void loadTimestampTests() {
});
// 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(Map.class, Timestamp.class), new Object[][] {
- { mapOf(EPOCH_MILLIS, -1L, NANOS, 999999999, DATE, "1970-01-01", TIME, "08:59:59.999999999", ZONE, TOKYO_Z.toString()), timestamp("1969-12-31T23:59:59.999999999Z"), true}, // redundant DATE, TIME, and ZONE fields for reverse test
+ { mapOf(EPOCH_MILLIS, -1L, DATE, "1970-01-01", TIME, "08:59:59.987654321", ZONE, TOKYO_Z.toString()), timestamp("1969-12-31T23:59:59.999Z") }, // Epoch millis take precedence
+ { mapOf(EPOCH_MILLIS, -1L, NANOS, 1, DATE, "1970-01-01", TIME, "08:59:59.987654321", ZONE, TOKYO_Z.toString()), timestamp("1969-12-31T23:59:59.000000001Z") }, // Epoch millis and nanos take precedence
+ { mapOf(EPOCH_MILLIS, -1L, ZONE, TOKYO_Z.toString()), timestamp("1969-12-31T23:59:59.999Z") }, // save as above
+ { mapOf(DATE, "1970-01-01", TIME, "08:59:59.987654321", ZONE, TOKYO_Z.toString()), timestamp("1969-12-31T23:59:59.987654321Z") }, // Epoch millis take precedence
+ { mapOf(NANOS, 123456789, DATE, "1970-01-01", TIME, "08:59:59.987654321", ZONE, TOKYO_Z.toString()), timestamp("1969-12-31T23:59:59.987654321Z") }, // time trumps nanos when it is better than second resolution
+ { mapOf(EPOCH_MILLIS, -1L, NANOS, 123456789, DATE, "1970-01-01", TIME, "08:59:59.999999999", ZONE, TOKYO_Z.toString()), timestamp("1969-12-31T23:59:59.123456789Z") }, // Epoch millis and nanos trump time
+ { mapOf(EPOCH_MILLIS, -1L, NANOS, 999000000, DATE, "1970-01-01", TIME, "08:59:59.999999999", ZONE, TOKYO_Z.toString()), timestamp("1969-12-31T23:59:59.999Z")}, // redundant, conflicting nanos + DATE, TIME, and ZONE fields for reverse test
+ { mapOf(EPOCH_MILLIS, -1L, NANOS, 888888888, DATE, "1970-01-01", TIME, "08:59:59.999999999", ZONE, TOKYO_Z.toString()), timestamp("1969-12-31T23:59:59.888888888Z")}, // redundant, conflicting nanos
{ mapOf(EPOCH_MILLIS, -1L, NANOS, 999000000, DATE, "1970-01-01", TIME, "08:59:59.999", ZONE, TOKYO_Z.toString()), new Timestamp(-1L), true},
{ mapOf(EPOCH_MILLIS, 0L, NANOS, 0, DATE, "1970-01-01", TIME, "09:00", ZONE, TOKYO_Z.toString()), timestamp("1970-01-01T00:00:00Z"), true},
{ mapOf(EPOCH_MILLIS, 0L, NANOS, 0, DATE, "1970-01-01", TIME, "09:00", ZONE, TOKYO_Z.toString()), new Timestamp(0L), true},
{ mapOf(EPOCH_MILLIS, 0L, NANOS, 1, DATE, "1970-01-01", TIME, "09:00:00.000000001", ZONE, TOKYO_Z.toString()), timestamp("1970-01-01T00:00:00.000000001Z"), true},
{ mapOf(EPOCH_MILLIS, 1L, NANOS, 1000000, DATE, "1970-01-01", TIME, "09:00:00.001", ZONE, TOKYO_Z.toString()), new Timestamp(1L), true},
{ mapOf(EPOCH_MILLIS, 1710714535152L, NANOS, 152000000, DATE, "2024-03-18", TIME, "07:28:55.152", ZONE, TOKYO_Z.toString()), new Timestamp(1710714535152L), true},
- { mapOf(TIME, "2024-03-18T07:28:55.152", ZONE, TOKYO_Z.toString()), new Timestamp(1710714535152L)},
+ { mapOf(TIME, "1970-01-01T00:00:00.000000001Z", NANOS, 1234), timestamp("1970-01-01T00:00:00.000000001Z")}, // fractional seconds in time, ignore "nanos" value if it exists
+ { mapOf(TIME, "1970-01-01T00:00:00Z", NANOS, 1234), (Supplier) () -> timestamp("1970-01-01T00:00:00.000001234Z")}, // No fractional seconds in time, use "nanos" value if it exists
+ { mapOf(DATE, "1970-01-01", TIME, "00:00:00.000000001", NANOS, 1234), timestamp("1970-01-01T00:00:00.000000001+09:00")}, // fractional seconds in time, ignore "nanos" value if it exists
+ { mapOf(DATE, "1970-01-01", TIME, "00:00:00", NANOS, 1234), (Supplier) () -> timestamp("1970-01-01T00:00:00.000001234+09:00")}, // No fractional seconds in time, use "nanos" value if it exists
{ mapOf(TIME, "2024-03-18T07:28:55.152000001", ZONE, TOKYO_Z.toString()), (Supplier) () -> {
Timestamp ts = new Timestamp(1710714535152L);
- MapConversions.setNanosPreserveMillis(ts, 1);
+ ts.setNanos(152000001);
return ts;
}},
- { mapOf("bad key", "2024-03-18T07:28:55.152", ZONE, TOKYO_Z.toString()), new IllegalArgumentException("Map to 'Timestamp' the map must include: [epochMillis, nanos (optional)], [date, time, zone (optional)], [time, zone (optional)], [value], or [_v] as keys with associated values")},
+ { mapOf("bad key", "2024-03-18T07:28:55.152", ZONE, TOKYO_Z.toString()), new IllegalArgumentException("Map to 'Timestamp' the map must include: [epochMillis, nanos (optional)], [time, zone (optional)], [date, time, zone (optional)], [value], or [_v] as keys with associated values")},
});
}
@@ -1329,6 +1343,7 @@ private static void loadZoneIdTests() {
{"Z", ZoneId.of("Z"), true},
{"UTC", ZoneId.of("UTC"), true},
{"GMT", ZoneId.of("GMT"), true},
+ {"EST", ZoneId.of("-05:00")},
});
TEST_DB.put(pair(Map.class, ZoneId.class), new Object[][]{
{mapOf("_v", "America/New_York"), NY_Z},
@@ -1748,7 +1763,7 @@ private static void loadSqlDateTests() {
{ mapOf(TIME, "1970-01-01T00:00", ZONE, "Z"), new java.sql.Date(0L)},
{ mapOf(TIME, "X1970-01-01T00:00", ZONE, "Z"), new IllegalArgumentException("Issue parsing date-time, other characters present: X")},
{ mapOf(TIME, "1970-01-01T00:00", ZONE, "bad zone"), new IllegalArgumentException("Unknown time-zone ID: 'bad zone'")},
- { mapOf("foo", "bar"), new IllegalArgumentException("Map to 'java.sql.Date' the map must include: [epochMillis], [date, time, zone (optional)], [time, zone (optional)], [value], or [_v] as keys with associated values")},
+ { mapOf("foo", "bar"), new IllegalArgumentException("Map to 'java.sql.Date' the map must include: [epochMillis], [time, zone (optional)], [date, time, zone (optional)], [value], or [_v] as keys with associated values")},
});
}
@@ -1845,7 +1860,7 @@ private static void loadDateTests() {
{ mapOf(TIME, "1970-01-01T00:00", ZONE, "Z"), new Date(0L)},
{ mapOf(TIME, "X1970-01-01T00:00", ZONE, "Z"), new IllegalArgumentException("Issue parsing date-time, other characters present: X")},
{ mapOf(TIME, "1970-01-01T00:00", ZONE, "bad zone"), new IllegalArgumentException("Unknown time-zone ID: 'bad zone'")},
- { mapOf("foo", "bar"), new IllegalArgumentException("Map to 'Date' the map must include: [epochMillis], [date, time, zone (optional)], [time, zone (optional)], [value], or [_v] as keys with associated values")},
+ { mapOf("foo", "bar"), new IllegalArgumentException("Map to 'Date' the map must include: [epochMillis], [time, zone (optional)], [date, time, zone (optional)], [value], or [_v] as keys with associated values")},
});
}
@@ -2241,9 +2256,6 @@ private static void loadBigIntegerTests() {
{cal(0), BigInteger.ZERO, true},
{cal(1), BigInteger.valueOf(1000000), true},
});
- TEST_DB.put(pair(Number.class, BigInteger.class), new Object[][]{
- {0, BigInteger.ZERO},
- });
TEST_DB.put(pair(Map.class, BigInteger.class), new Object[][]{
{mapOf("_v", 0), BigInteger.ZERO},
{mapOf("_v", BigInteger.valueOf(0)), BigInteger.ZERO, true},
@@ -2358,13 +2370,6 @@ private static void loadCharacterTests() {
{BigDecimal.valueOf(65535), (char) 65535, true},
{BigDecimal.valueOf(65536), new IllegalArgumentException("Value '65536' out of range to be converted to character")},
});
- TEST_DB.put(pair(Number.class, Character.class), new Object[][]{
- {BigDecimal.valueOf(-1), new IllegalArgumentException("Value '-1' out of range to be converted to character")},
- {BigDecimal.ZERO, (char) 0},
- {BigInteger.valueOf(1), (char) 1},
- {BigInteger.valueOf(65535), (char) 65535},
- {BigInteger.valueOf(65536), new IllegalArgumentException("Value '65536' out of range to be converted to character")},
- });
TEST_DB.put(pair(Map.class, Character.class), new Object[][]{
{mapOf("_v", -1), new IllegalArgumentException("Value '-1' out of range to be converted to character")},
{mapOf("value", 0), (char) 0},
@@ -2493,13 +2498,6 @@ private static void loadBooleanTests() {
{BigDecimal.valueOf(1L), true, true},
{BigDecimal.valueOf(2L), true},
});
- TEST_DB.put(pair(Number.class, Boolean.class), new Object[][]{
- {-2, true},
- {-1L, true},
- {0.0, false},
- {1.0f, true},
- {BigInteger.valueOf(2), true},
- });
TEST_DB.put(pair(Map.class, Boolean.class), new Object[][]{
{mapOf("_v", 16), true},
{mapOf("_v", 0), false},
@@ -2640,9 +2638,6 @@ private static void loadDoubleTests() {
{new BigDecimal("-9007199254740991"), -9007199254740991.0, true},
{new BigDecimal("9007199254740991"), 9007199254740991.0, true},
});
- TEST_DB.put(pair(Number.class, Double.class), new Object[][]{
- {2.5f, 2.5}
- });
TEST_DB.put(pair(Map.class, Double.class), new Object[][]{
{mapOf("_v", "-1"), -1.0},
{mapOf("_v", -1.0), -1.0, true},
@@ -2756,9 +2751,6 @@ private static void loadFloatTests() {
{new BigDecimal("-16777216"), -16777216f, true},
{new BigDecimal("16777216"), 16777216f, true},
});
- TEST_DB.put(pair(Number.class, Float.class), new Object[][]{
- {-2.2, -2.2f}
- });
TEST_DB.put(pair(Map.class, Float.class), new Object[][]{
{mapOf("_v", "-1"), -1f},
{mapOf("_v", -1f), -1f, true},
@@ -2884,9 +2876,6 @@ private static void loadLongTests() {
{new BigDecimal("-9223372036854775809"), Long.MAX_VALUE}, // wrap around
{new BigDecimal("9223372036854775808"), Long.MIN_VALUE}, // wrap around
});
- TEST_DB.put(pair(Number.class, Long.class), new Object[][]{
- {-2, -2L},
- });
TEST_DB.put(pair(Map.class, Long.class), new Object[][]{
{mapOf("_v", "-1"), -1L},
{mapOf("_v", -1L), -1L, true},
@@ -3105,9 +3094,6 @@ private static void loadIntegerTests() {
{new BigDecimal("-2147483649"), Integer.MAX_VALUE}, // wrap around test
{new BigDecimal("2147483648"), Integer.MIN_VALUE}, // wrap around test
});
- TEST_DB.put(pair(Number.class, Integer.class), new Object[][]{
- {-2L, -2},
- });
TEST_DB.put(pair(Map.class, Integer.class), new Object[][]{
{mapOf("_v", "-1"), -1},
{mapOf("_v", -1), -1, true},
@@ -3262,9 +3248,6 @@ private static void loadShortTests() {
{new BigDecimal("-32769"), Short.MAX_VALUE},
{new BigDecimal("32768"), Short.MIN_VALUE},
});
- TEST_DB.put(pair(Number.class, Short.class), new Object[][]{
- {-2L, (short) -2},
- });
TEST_DB.put(pair(Map.class, Short.class), new Object[][]{
{mapOf("_v", "-1"), (short) -1},
{mapOf("_v", -1), (short) -1},
@@ -3440,9 +3423,6 @@ private static void loadByteTest() {
{new BigDecimal("-129"), Byte.MAX_VALUE},
{new BigDecimal("128"), Byte.MIN_VALUE},
});
- TEST_DB.put(pair(Number.class, Byte.class), new Object[][]{
- {-2L, (byte) -2},
- });
TEST_DB.put(pair(Map.class, Byte.class), new Object[][]{
{mapOf("_v", "-1"), (byte) -1},
{mapOf("_v", -1), (byte) -1},
diff --git a/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java b/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java
index ecb7c3f7..b98128f7 100644
--- a/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java
+++ b/src/test/java/com/cedarsoftware/util/convert/ConverterTest.java
@@ -1,5 +1,6 @@
package com.cedarsoftware.util.convert;
+import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
@@ -43,12 +44,17 @@
import static com.cedarsoftware.util.ArrayUtilities.EMPTY_BYTE_ARRAY;
import static com.cedarsoftware.util.ArrayUtilities.EMPTY_CHAR_ARRAY;
import static com.cedarsoftware.util.Converter.zonedDateTimeToMillis;
+import static com.cedarsoftware.util.MapUtilities.mapOf;
import static com.cedarsoftware.util.StringUtilities.EMPTY;
import static com.cedarsoftware.util.convert.Converter.VALUE;
import static com.cedarsoftware.util.convert.ConverterTest.fubar.bar;
import static com.cedarsoftware.util.convert.ConverterTest.fubar.foo;
+import static com.cedarsoftware.util.convert.MapConversions.CAUSE;
+import static com.cedarsoftware.util.convert.MapConversions.CAUSE_MESSAGE;
+import static com.cedarsoftware.util.convert.MapConversions.CLASS;
import static com.cedarsoftware.util.convert.MapConversions.DATE;
import static com.cedarsoftware.util.convert.MapConversions.EPOCH_MILLIS;
+import static com.cedarsoftware.util.convert.MapConversions.MESSAGE;
import static com.cedarsoftware.util.convert.MapConversions.TIME;
import static com.cedarsoftware.util.convert.MapConversions.ZONE;
import static org.assertj.core.api.Assertions.assertThat;
@@ -93,12 +99,9 @@ class ConverterTest
private static final LocalDateTime LDT_MILLENNIUM_LA = LocalDateTime.of(1999, 12, 31, 20, 59, 59, 959000000);
private Converter converter;
-
private static final LocalDate LD_MILLENNIUM_NY = LocalDate.of(1999, 12, 31);
private static final LocalDate LD_MILLENNIUM_TOKYO = LocalDate.of(2000, 1, 1);
-
private static final LocalDate LD_MILLENNIUM_CHICAGO = LocalDate.of(1999, 12, 31);
-
private static final LocalDate LD_2023_NY = LocalDate.of(2023, 6, 24);
enum fubar
@@ -106,6 +109,12 @@ enum fubar
foo, bar, baz, quz
}
+ private class GnarlyException extends RuntimeException {
+ public GnarlyException(int x) {
+ super("" + x);
+ }
+ }
+
@BeforeEach
public void before() {
// create converter with default options
@@ -180,12 +189,10 @@ private static Stream paramsForFloatingPointTypes
return arguments.stream();
}
-
private static Stream toByteParams() {
return paramsForIntegerTypes(Byte.MIN_VALUE, Byte.MAX_VALUE);
}
-
@ParameterizedTest
@MethodSource("toByteParams")
void toByte(Object source, Number number)
@@ -2949,7 +2956,7 @@ void testMapToDate() {
map.clear();
assertThatThrownBy(() -> this.converter.convert(map, Date.class))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("Map to 'Date' the map must include: [epochMillis], [date, time, zone (optional)], [time, zone (optional)], [value], or [_v] as keys with associated values");
+ .hasMessageContaining("Map to 'Date' the map must include: [epochMillis], [time, zone (optional)], [date, time, zone (optional)], [value], or [_v] as keys with associated values");
}
@Test
@@ -2972,7 +2979,7 @@ void testMapToSqlDate()
map.clear();
assertThatThrownBy(() -> this.converter.convert(map, java.sql.Date.class))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("Map to 'java.sql.Date' the map must include: [epochMillis], [date, time, zone (optional)], [time, zone (optional)], [value], or [_v] as keys with associated values");
+ .hasMessageContaining("Map to 'java.sql.Date' the map must include: [epochMillis], [time, zone (optional)], [date, time, zone (optional)], [value], or [_v] as keys with associated values");
}
@Test
@@ -2995,7 +3002,7 @@ void testMapToTimestamp()
map.clear();
assertThatThrownBy(() -> this.converter.convert(map, Timestamp.class))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("Map to 'Timestamp' the map must include: [epochMillis, nanos (optional)], [date, time, zone (optional)], [time, zone (optional)], [value], or [_v] as keys with associated values");
+ .hasMessageContaining("Map to 'Timestamp' the map must include: [epochMillis, nanos (optional)], [time, zone (optional)], [date, time, zone (optional)], [value], or [_v] as keys with associated values");
}
@Test
@@ -3060,7 +3067,7 @@ void testMapToZonedDateTime()
map.clear();
assertThatThrownBy(() -> this.converter.convert(map, ZonedDateTime.class))
.isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("Map to 'ZonedDateTime' the map must include: [epochMillis], [dateTime, zone], [date, time, zone], [value], or [_v] as keys with associated values");
+ .hasMessageContaining("Map to 'ZonedDateTime' the map must include: [epochMillis], [time, zone], [date, time, zone], [value], or [_v] as keys with associated values");
}
@@ -4328,6 +4335,41 @@ void testNullTypeInput()
.hasMessageContaining("toType cannot be null");
}
+ @Test
+ void testMapToThrowable()
+ {
+ Map map = mapOf(MESSAGE, "divide by 0", CLASS, Throwable.class.getName(), CAUSE, IllegalArgumentException.class.getName(), CAUSE_MESSAGE, "root issue");
+ Throwable expected = new Throwable("divide by 0", new IllegalArgumentException("root issue"));
+ Throwable actual = converter.convert(map, Throwable.class);
+ assertEquals(expected.getMessage(), actual.getMessage());
+ assertEquals(expected.getClass(), actual.getClass());
+ assertEquals(expected.getCause().getClass(), actual.getCause().getClass());
+ assertEquals(expected.getCause().getMessage(), actual.getCause().getMessage());
+
+ map = mapOf(MESSAGE, "null not allowed", CLASS, IllegalArgumentException.class.getName());
+ expected = new IllegalArgumentException("null not allowed");
+ actual = converter.convert(map, IllegalArgumentException.class);
+ assertEquals(expected.getMessage(), actual.getMessage());
+ assertEquals(expected.getClass(), actual.getClass());
+
+ map = mapOf(MESSAGE, "null not allowed", CLASS, IllegalArgumentException.class.getName(), CAUSE, IOException.class.getName(), CAUSE_MESSAGE, "port not open");
+ expected = new IllegalArgumentException("null not allowed", new IOException("port not open", new IllegalAccessException("foo")));
+ actual = converter.convert(map, IllegalArgumentException.class);
+ assertEquals(expected.getMessage(), actual.getMessage());
+ assertEquals(expected.getClass(), actual.getClass());
+ assertEquals(expected.getCause().getClass(), actual.getCause().getClass());
+ assertEquals(expected.getCause().getMessage(), actual.getCause().getMessage());
+ }
+
+ @Test
+ void testMapToThrowableFail() {
+ Map map = mapOf(MESSAGE, "5", CLASS, GnarlyException.class.getName());
+ Throwable expected = new GnarlyException(5);
+ assertThatThrownBy(() -> converter.convert(map, Throwable.class))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Unable to reconstruct exception instance from map");
+ }
+
private ConverterOptions createCharsetOptions(final Charset charset) {
return new ConverterOptions() {
@Override