[JOHNZON-317] lazy lookup of implicit converters
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
index 26f380b..5bbfa2c 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
@@ -18,72 +18,6 @@
*/
package org.apache.johnzon.jsonb;
-import static java.time.format.DateTimeFormatter.ofPattern;
-import static java.time.temporal.ChronoField.DAY_OF_MONTH;
-import static java.time.temporal.ChronoField.HOUR_OF_DAY;
-import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
-import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
-import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
-import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
-import static java.time.temporal.ChronoField.YEAR;
-import static java.util.Collections.emptyMap;
-import static java.util.Optional.ofNullable;
-import static javax.json.bind.config.PropertyNamingStrategy.IDENTITY;
-import static javax.json.bind.config.PropertyOrderStrategy.ANY;
-
-import java.io.Closeable;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.nio.charset.StandardCharsets;
-import java.time.Duration;
-import java.time.Instant;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.OffsetDateTime;
-import java.time.OffsetTime;
-import java.time.Period;
-import java.time.ZoneId;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeParseException;
-import java.time.temporal.TemporalAccessor;
-import java.time.temporal.TemporalQueries;
-import java.util.Base64;
-import java.util.Calendar;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.SimpleTimeZone;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Stream;
-
-import javax.json.JsonBuilderFactory;
-import javax.json.bind.Jsonb;
-import javax.json.bind.JsonbBuilder;
-import javax.json.bind.JsonbConfig;
-import javax.json.bind.JsonbException;
-import javax.json.bind.adapter.JsonbAdapter;
-import javax.json.bind.config.BinaryDataStrategy;
-import javax.json.bind.config.PropertyNamingStrategy;
-import javax.json.bind.config.PropertyVisibilityStrategy;
-import javax.json.bind.serializer.JsonbDeserializer;
-import javax.json.bind.serializer.JsonbSerializer;
-import javax.json.spi.JsonProvider;
-import javax.json.stream.JsonGenerator;
-import javax.json.stream.JsonParserFactory;
-
import org.apache.johnzon.core.AbstractJsonFactory;
import org.apache.johnzon.core.JsonGeneratorFactoryImpl;
import org.apache.johnzon.core.JsonParserFactoryImpl;
@@ -95,7 +29,6 @@
import org.apache.johnzon.jsonb.serializer.JohnzonDeserializationContext;
import org.apache.johnzon.jsonb.serializer.JohnzonSerializationContext;
import org.apache.johnzon.jsonb.spi.JohnzonAdapterFactory;
-import org.apache.johnzon.mapper.Adapter;
import org.apache.johnzon.mapper.Converter;
import org.apache.johnzon.mapper.Mapper;
import org.apache.johnzon.mapper.MapperBuilder;
@@ -103,8 +36,42 @@
import org.apache.johnzon.mapper.SerializeValueFilter;
import org.apache.johnzon.mapper.access.AccessMode;
import org.apache.johnzon.mapper.access.FieldAndMethodAccessMode;
+import org.apache.johnzon.mapper.converter.LocaleConverter;
import org.apache.johnzon.mapper.internal.AdapterKey;
-import org.apache.johnzon.mapper.internal.ConverterAdapter;
+
+import javax.json.JsonBuilderFactory;
+import javax.json.bind.Jsonb;
+import javax.json.bind.JsonbBuilder;
+import javax.json.bind.JsonbConfig;
+import javax.json.bind.adapter.JsonbAdapter;
+import javax.json.bind.config.BinaryDataStrategy;
+import javax.json.bind.config.PropertyNamingStrategy;
+import javax.json.bind.config.PropertyVisibilityStrategy;
+import javax.json.bind.serializer.JsonbDeserializer;
+import javax.json.bind.serializer.JsonbSerializer;
+import javax.json.spi.JsonProvider;
+import javax.json.stream.JsonGenerator;
+import javax.json.stream.JsonParserFactory;
+import java.io.Closeable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import static java.time.format.DateTimeFormatter.ofPattern;
+import static java.util.Collections.emptyMap;
+import static java.util.Optional.ofNullable;
+import static javax.json.bind.config.PropertyNamingStrategy.IDENTITY;
+import static javax.json.bind.config.PropertyOrderStrategy.ANY;
public class JohnzonBuilder implements JsonbBuilder {
private static final Object NO_BM = new Object();
@@ -212,8 +179,13 @@
builder.setUseJsRange(toBool( // https://github.com/eclipse-ee4j/jsonb-api/issues/180
System.getProperty("johnzon.use-js-range", config.getProperty("johnzon.use-js-range")
.map(String::valueOf).orElse("false"))));
-
- final Map<AdapterKey, Adapter<?, ?>> defaultConverters = createJava8Converters(builder);
+ builder.setUseShortISO8601Format(false);
+ config.getProperty(JsonbConfig.DATE_FORMAT)
+ .map(String.class::cast)
+ .ifPresent(dateFormat -> builder.setAdaptersDateTimeFormatter(config.getProperty(JsonbConfig.LOCALE)
+ .map(it -> String.class.isInstance(it) ? new LocaleConverter().to(it.toString()) : Locale.class.cast(it))
+ .map(value -> ofPattern(dateFormat, value))
+ .orElseGet(() -> ofPattern(dateFormat))));
final JohnzonAdapterFactory factory = config.getProperty("johnzon.factory").map(val -> {
if (JohnzonAdapterFactory.class.isInstance(val)) {
@@ -241,7 +213,7 @@
.orElseGet(() -> new JsonbAccessMode(
propertyNamingStrategy, orderValue, visibilityStrategy,
!namingStrategyValue.orElse("").equals(PropertyNamingStrategy.CASE_INSENSITIVE),
- defaultConverters,
+ builder.getAdapters(),
factory, jsonp, builderFactorySupplier, parserFactoryProvider,
config.getProperty("johnzon.accessModeDelegate")
.map(this::toAccessMode)
@@ -266,7 +238,7 @@
final Type[] args = pt.getActualTypeArguments();
final JohnzonJsonbAdapter johnzonJsonbAdapter = new JohnzonJsonbAdapter(adapter, args[0], args[1]);
builder.addAdapter(args[0], args[1], johnzonJsonbAdapter);
- defaultConverters.put(new AdapterKey(args[0], args[1]), johnzonJsonbAdapter);
+ builder.getAdapters().put(new AdapterKey(args[0], args[1]), johnzonJsonbAdapter);
}));
ofNullable(config.getProperty("johnzon.fail-on-unknown-properties")
@@ -448,405 +420,6 @@
return ofNullable(Thread.currentThread().getContextClassLoader()).orElseGet(ClassLoader::getSystemClassLoader);
}
- // TODO: move these converters in converter package
- private Map<AdapterKey, Adapter<?, ?>> createJava8Converters(final MapperBuilder builder) {
- final Map<AdapterKey, Adapter<?, ?>> converters = new HashMap<>();
-
- final ZoneId zoneIDUTC = ZoneId.of("UTC");
-
- // built-in converters not in mapper
- converters.put(new AdapterKey(Period.class, String.class), new ConverterAdapter<>(new Converter<Period>() {
- @Override
- public String toString(final Period instance) {
- return instance.toString();
- }
-
- @Override
- public Period fromString(final String text) {
- return Period.parse(text);
- }
- }));
- converters.put(new AdapterKey(Duration.class, String.class), new ConverterAdapter<>(new Converter<Duration>() {
- @Override
- public String toString(final Duration instance) {
- return instance.toString();
- }
-
- @Override
- public Duration fromString(final String text) {
- return Duration.parse(text);
- }
- }));
- converters.put(new AdapterKey(Date.class, String.class), new ConverterAdapter<>(new Converter<Date>() {
- @Override
- public String toString(final Date instance) {
- return ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC)
- .format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
- }
-
- @Override
- public Date fromString(final String text) {
- try {
- return Date.from(ZonedDateTime.parse(text).toInstant());
- } catch (final DateTimeParseException dte) {
- return Date.from(LocalDateTime.parse(text).toInstant(ZoneOffset.UTC));
- }
- }
- }));
- converters.put(new AdapterKey(Calendar.class, String.class), new ConverterAdapter<>(new Converter<Calendar>() {
- @Override
- public String toString(final Calendar instance) {
- return toStringCalendar(instance);
- }
-
- @Override
- public Calendar fromString(final String text) {
- return fromCalendar(text, zdt -> {
- final Calendar instance = Calendar.getInstance();
- instance.clear();
- instance.setTimeZone(TimeZone.getTimeZone(zdt.getZone()));
- instance.setTimeInMillis(zdt.toInstant().toEpochMilli());
- return instance;
- });
- }
- }));
- converters.put(new AdapterKey(GregorianCalendar.class, String.class), new ConverterAdapter<>(new Converter<GregorianCalendar>() {
- @Override
- public String toString(final GregorianCalendar instance) {
- return toStringCalendar(instance);
- }
-
- @Override
- public GregorianCalendar fromString(final String text) {
- return fromCalendar(text, GregorianCalendar::from);
- }
- }));
- converters.put(new AdapterKey(TimeZone.class, String.class), new ConverterAdapter<>(new Converter<TimeZone>() {
- @Override
- public String toString(final TimeZone instance) {
- return instance.getID();
- }
-
- @Override
- public TimeZone fromString(final String text) {
- checkForDeprecatedTimeZone(text);
- return TimeZone.getTimeZone(text);
- }
- }));
- converters.put(new AdapterKey(ZoneId.class, String.class), new ConverterAdapter<>(new Converter<ZoneId>() {
- @Override
- public String toString(final ZoneId instance) {
- return instance.getId();
- }
-
- @Override
- public ZoneId fromString(final String text) {
- return ZoneId.of(text);
- }
- }));
- converters.put(new AdapterKey(ZoneOffset.class, String.class), new ConverterAdapter<>(new Converter<ZoneOffset>() {
- @Override
- public String toString(final ZoneOffset instance) {
- return instance.getId();
- }
-
- @Override
- public ZoneOffset fromString(final String text) {
- return ZoneOffset.of(text);
- }
- }));
- converters.put(new AdapterKey(SimpleTimeZone.class, String.class), new ConverterAdapter<>(new Converter<SimpleTimeZone>() {
- @Override
- public String toString(final SimpleTimeZone instance) {
- return instance.getID();
- }
-
- @Override
- public SimpleTimeZone fromString(final String text) {
- checkForDeprecatedTimeZone(text);
- final TimeZone timeZone = TimeZone.getTimeZone(text);
- return new SimpleTimeZone(timeZone.getRawOffset(), timeZone.getID());
- }
- }));
- converters.put(new AdapterKey(Instant.class, String.class), new ConverterAdapter<>(new Converter<Instant>() {
- @Override
- public String toString(final Instant instance) {
- return instance.toString();
- }
-
- @Override
- public Instant fromString(final String text) {
- return Instant.parse(text);
- }
- }));
- converters.put(new AdapterKey(LocalDate.class, String.class), new ConverterAdapter<>(new Converter<LocalDate>() {
- @Override
- public String toString(final LocalDate instance) {
- return instance.toString();
- }
-
- @Override
- public LocalDate fromString(final String text) {
- return LocalDate.parse(text);
- }
- }));
- converters.put(new AdapterKey(LocalTime.class, String.class), new ConverterAdapter<>(new Converter<LocalTime>() {
- @Override
- public String toString(final LocalTime instance) {
- return instance.toString();
- }
-
- @Override
- public LocalTime fromString(final String text) {
- return LocalTime.parse(text);
- }
- }));
- converters.put(new AdapterKey(LocalDateTime.class, String.class), new ConverterAdapter<>(new Converter<LocalDateTime>() {
- @Override
- public String toString(final LocalDateTime instance) {
- return instance.toString();
- }
-
- @Override
- public LocalDateTime fromString(final String text) {
- return LocalDateTime.parse(text);
- }
- }));
- converters.put(new AdapterKey(ZonedDateTime.class, String.class), new ConverterAdapter<>(new Converter<ZonedDateTime>() {
- @Override
- public String toString(final ZonedDateTime instance) {
- return instance.toString();
- }
-
- @Override
- public ZonedDateTime fromString(final String text) {
- return ZonedDateTime.parse(text);
- }
- }));
- converters.put(new AdapterKey(OffsetDateTime.class, String.class), new ConverterAdapter<>(new Converter<OffsetDateTime>() {
- @Override
- public String toString(final OffsetDateTime instance) {
- return instance.toString();
- }
-
- @Override
- public OffsetDateTime fromString(final String text) {
- return OffsetDateTime.parse(text);
- }
- }));
- converters.put(new AdapterKey(OffsetTime.class, String.class), new ConverterAdapter<>(new Converter<OffsetTime>() {
- @Override
- public String toString(final OffsetTime instance) {
- return instance.toString();
- }
-
- @Override
- public OffsetTime fromString(final String text) {
- return OffsetTime.parse(text);
- }
- }));
- addDateFormatConfigConverters(converters, zoneIDUTC);
-
- converters.forEach((k, v) -> builder.addAdapter(k.getFrom(), k.getTo(), v));
- return converters;
- }
-
- private String toStringCalendar(final Calendar instance) {
- if (!hasTime(instance)) { // spec
- final LocalDate localDate = LocalDate.of(
- instance.get(Calendar.YEAR),
- instance.get(Calendar.MONTH) + 1,
- instance.get(Calendar.DAY_OF_MONTH));
- return localDate.toString() +
- (instance.getTimeZone() != null ?
- instance.getTimeZone().toZoneId().getRules()
- .getOffset(Instant.ofEpochMilli(TimeUnit.DAYS.toMillis(localDate.toEpochDay()))) : "");
- }
- return ZonedDateTime.ofInstant(instance.toInstant(), instance.getTimeZone().toZoneId())
- .format(DateTimeFormatter.ISO_DATE_TIME);
- }
-
- private boolean hasTime(final Calendar instance) {
- if (!instance.isSet(Calendar.HOUR_OF_DAY)) {
- return false;
- }
- return instance.get(Calendar.HOUR_OF_DAY) != 0 ||
- (instance.isSet(Calendar.MINUTE)&& instance.get(Calendar.MINUTE) != 0) ||
- (instance.isSet(Calendar.SECOND) && instance.get(Calendar.SECOND) != 0);
- }
-
- private <T extends Calendar> T fromCalendar(final String text, final Function<ZonedDateTime, T> calendarSupplier) {
- switch (text.length()) {
- case 10: {
- final ZonedDateTime date = LocalDate.parse(text)
- .atTime(0, 0, 0)
- .atZone(ZoneId.of("UTC"));
- return calendarSupplier.apply(date);
- }
- default:
- final ZonedDateTime zonedDateTime = ZonedDateTime.parse(text);
- return calendarSupplier.apply(zonedDateTime);
- }
- }
-
-
- private void addDateFormatConfigConverters(final Map<AdapterKey, Adapter<?, ?>> converters, final ZoneId zoneIDUTC) {
- // config, override defaults
- config.getProperty(JsonbConfig.DATE_FORMAT).map(String.class::cast).ifPresent(dateFormat -> {
- final Optional<Locale> locale = config.getProperty(JsonbConfig.LOCALE).map(Locale.class::cast);
- final DateTimeFormatter formatter = locale.isPresent() ? ofPattern(dateFormat, locale.get()) : ofPattern(dateFormat);
-
- converters.put(new AdapterKey(Date.class, String.class), new ConverterAdapter<>(new Converter<Date>() {
-
- @Override
- public String toString(final Date instance) {
- return formatter.format(ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC));
- }
-
- @Override
- public Date fromString(final String text) {
- try {
- return Date.from(parseZonedDateTime(text, formatter, zoneIDUTC).toInstant());
- } catch (final DateTimeParseException dpe) {
- return Date.from(LocalDateTime.parse(text).toInstant(ZoneOffset.UTC));
- }
- }
- }));
- converters.put(new AdapterKey(LocalDateTime.class, String.class), new ConverterAdapter<>(new Converter<LocalDateTime>() {
-
- @Override
- public String toString(final LocalDateTime instance) {
- return formatter.format(ZonedDateTime.ofInstant(instance.toInstant(ZoneOffset.UTC), zoneIDUTC));
- }
-
- @Override
- public LocalDateTime fromString(final String text) {
- try {
- return parseZonedDateTime(text, formatter, zoneIDUTC).toLocalDateTime();
- } catch (final DateTimeParseException dpe) {
- return LocalDateTime.parse(text);
- }
- }
- }));
- converters.put(new AdapterKey(LocalDate.class, String.class), new ConverterAdapter<>(new Converter<LocalDate>() {
-
- @Override
- public String toString(final LocalDate instance) {
- return formatter.format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(TimeUnit.DAYS.toMillis(instance.toEpochDay())), zoneIDUTC));
- }
-
- @Override
- public LocalDate fromString(final String text) {
- try {
- return parseZonedDateTime(text, formatter, zoneIDUTC).toLocalDate();
- } catch (final DateTimeParseException dpe) {
- return LocalDate.parse(text);
- }
- }
- }));
- converters.put(new AdapterKey(OffsetDateTime.class, String.class), new ConverterAdapter<>(new Converter<OffsetDateTime>() {
-
- @Override
- public String toString(final OffsetDateTime instance) {
- return formatter.format(ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC));
- }
-
- @Override
- public OffsetDateTime fromString(final String text) {
- try {
- return parseZonedDateTime(text, formatter, zoneIDUTC).toOffsetDateTime();
- } catch (final DateTimeParseException dpe) {
- return OffsetDateTime.parse(text);
- }
- }
- }));
- converters.put(new AdapterKey(ZonedDateTime.class, String.class), new ConverterAdapter<>(new Converter<ZonedDateTime>() {
-
- @Override
- public String toString(final ZonedDateTime instance) {
- return formatter.format(ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC));
- }
-
- @Override
- public ZonedDateTime fromString(final String text) {
- try {
- return parseZonedDateTime(text, formatter, zoneIDUTC);
- } catch (final DateTimeParseException dpe) {
- return ZonedDateTime.parse(text);
- }
- }
- }));
- converters.put(new AdapterKey(Calendar.class, String.class), new ConverterAdapter<>(new Converter<Calendar>() {
-
- @Override
- public String toString(final Calendar instance) {
- return formatter.format(ZonedDateTime.ofInstant(instance.toInstant(), instance.getTimeZone().toZoneId()));
- }
-
- @Override
- public Calendar fromString(final String text) {
- final ZonedDateTime zonedDateTime = parseZonedDateTime(text, formatter, zoneIDUTC);
- final Calendar instance = Calendar.getInstance();
- instance.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone()));
- instance.setTime(Date.from(zonedDateTime.toInstant()));
- return instance;
- }
- }));
- converters.put(new AdapterKey(GregorianCalendar.class, String.class), new ConverterAdapter<>(new Converter<GregorianCalendar>() {
-
- @Override
- public String toString(final GregorianCalendar instance) {
- return formatter.format(ZonedDateTime.ofInstant(instance.toInstant(), instance.getTimeZone().toZoneId()));
- }
-
- @Override
- public GregorianCalendar fromString(final String text) {
- final ZonedDateTime zonedDateTime = parseZonedDateTime(text, formatter, zoneIDUTC);
- final Calendar instance = GregorianCalendar.getInstance();
- instance.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone()));
- instance.setTime(Date.from(zonedDateTime.toInstant()));
- return GregorianCalendar.class.cast(instance);
- }
- }));
- converters.put(new AdapterKey(Instant.class, String.class), new ConverterAdapter<>(new Converter<Instant>() {
-
- @Override
- public String toString(final Instant instance) {
- return formatter.format(ZonedDateTime.ofInstant(instance, zoneIDUTC));
- }
-
- @Override
- public Instant fromString(final String text) {
- return parseZonedDateTime(text, formatter, zoneIDUTC).toInstant();
- }
- }));
- });
- }
-
- private static ZonedDateTime parseZonedDateTime(final String text, final DateTimeFormatter formatter, final ZoneId defaultZone){
- TemporalAccessor parse = formatter.parse(text);
- ZoneId zone = parse.query(TemporalQueries.zone());
- if (Objects.isNull(zone)) {
- zone = defaultZone;
- }
- int year = parse.isSupported(YEAR) ? parse.get(YEAR) : 0;
- int month = parse.isSupported(MONTH_OF_YEAR) ? parse.get(MONTH_OF_YEAR) : 0;
- int day = parse.isSupported(DAY_OF_MONTH) ? parse.get(DAY_OF_MONTH) : 0;
- int hour = parse.isSupported(HOUR_OF_DAY) ? parse.get(HOUR_OF_DAY) : 0;
- int minute = parse.isSupported(MINUTE_OF_HOUR) ? parse.get(MINUTE_OF_HOUR) : 0;
- int second = parse.isSupported(SECOND_OF_MINUTE) ? parse.get(SECOND_OF_MINUTE) : 0;
- int millisecond = parse.isSupported(MILLI_OF_SECOND) ? parse.get(MILLI_OF_SECOND) : 0;
- return ZonedDateTime.of(year, month, day, hour, minute, second, millisecond, zone);
- }
-
- private static void checkForDeprecatedTimeZone(final String text) {
- switch (text) {
- case "CST": // really for TCK, this sucks for end users so we don't fail for all deprecated zones
- throw new JsonbException("Deprecated timezone: '" + text + '"');
- default:
- }
- }
-
private Map<String, ?> generatorConfig() {
final Map<String, Object> map = new HashMap<>();
if (config == null) {
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
index 1130f67..363f9b6 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
@@ -441,20 +441,20 @@
converter = new JohnzonJsonbAdapter(instance.getValue(), actualTypeArguments[0], actualTypeArguments[1]);
} else if (dateFormat != null) { // TODO: support lists, LocalDate?
if (Date.class == type) {
- converter = new ConverterAdapter<>(new JsonbDateConverter(dateFormat));
+ converter = new ConverterAdapter<>(new JsonbDateConverter(dateFormat), Date.class);
} else if (LocalDateTime.class == type) {
- converter = new ConverterAdapter<>(new JsonbLocalDateTimeConverter(dateFormat));
+ converter = new ConverterAdapter<>(new JsonbLocalDateTimeConverter(dateFormat), LocalDateTime.class);
} else if (LocalDate.class == type) {
- converter = new ConverterAdapter<>(new JsonbLocalDateConverter(dateFormat));
+ converter = new ConverterAdapter<>(new JsonbLocalDateConverter(dateFormat), LocalDate.class);
} else if (ZonedDateTime.class == type) {
- converter = new ConverterAdapter<>(new JsonbZonedDateTimeConverter(dateFormat));
+ converter = new ConverterAdapter<>(new JsonbZonedDateTimeConverter(dateFormat), ZonedDateTime.class);
} else { // can happen if set on the class, todo: refine the checks
converter = null; // todo: should we fallback on numberformat?
}
} else if (numberFormat != null) { // TODO: support lists?
- converter = new ConverterAdapter<>(new JsonbNumberConverter(numberFormat));
+ converter = new ConverterAdapter<>(new JsonbNumberConverter(numberFormat), Number.class);
} else {
- converter = new ConverterAdapter<>(new JsonbValueConverter());
+ converter = new ConverterAdapter<>(new JsonbValueConverter(), Object.class);
}
return converter;
}
@@ -985,7 +985,7 @@
try {
MapperConverter mapperConverter = johnzonConverter.value().newInstance();
if (mapperConverter instanceof Converter) {
- converter = new ConverterAdapter<>((Converter) mapperConverter);
+ converter = new ConverterAdapter<>((Converter) mapperConverter, annotationHolder.getType());
} else if (mapperConverter instanceof ObjectConverter.Reader) {
reader = (ObjectConverter.Reader) mapperConverter;
}
@@ -1045,7 +1045,7 @@
try {
MapperConverter mapperConverter = johnzonConverter.value().newInstance();
if (mapperConverter instanceof Converter) {
- converter = new ConverterAdapter<>((Converter) mapperConverter) ;
+ converter = new ConverterAdapter<>((Converter) mapperConverter, reader.getType()) ;
} else if (mapperConverter instanceof ObjectConverter.Writer) {
writer = (ObjectConverter.Writer) mapperConverter;
}
diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JohnzonJsonbAdapter.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JohnzonJsonbAdapter.java
index 7fc505f..079c782 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JohnzonJsonbAdapter.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JohnzonJsonbAdapter.java
@@ -19,6 +19,7 @@
package org.apache.johnzon.jsonb.converter;
import org.apache.johnzon.mapper.TypeAwareAdapter;
+import org.apache.johnzon.mapper.internal.AdapterKey;
import javax.json.bind.JsonbException;
import javax.json.bind.adapter.JsonbAdapter;
@@ -28,11 +29,13 @@
private final JsonbAdapter<OriginalType, JsonType> delegate;
private final Type from;
private final Type to;
+ private final AdapterKey key;
public JohnzonJsonbAdapter(final JsonbAdapter<OriginalType, JsonType> delegate, final Type from, final Type to) {
this.delegate = delegate;
this.from = from;
this.to = to;
+ this.key = new AdapterKey(from, to);
}
@Override
@@ -60,6 +63,11 @@
}
@Override
+ public AdapterKey getKey() {
+ return key;
+ }
+
+ @Override
public Type getTo() {
return to;
}
diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonJsonbTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonJsonbTest.java
index debf27f..757359b 100644
--- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonJsonbTest.java
+++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/JohnzonJsonbTest.java
@@ -24,9 +24,14 @@
import javax.json.bind.Jsonb;
import javax.json.bind.JsonbBuilder;
+import org.apache.johnzon.jsonb.test.JsonbRule;
+import org.junit.Rule;
import org.junit.Test;
public class JohnzonJsonbTest {
+ @Rule
+ public final JsonbRule rule = new JsonbRule();
+
@Test
public void jsonArray() throws Exception {
try (final Jsonb jsonb = JsonbBuilder.create()) {
@@ -35,4 +40,27 @@
assertEquals(json, array.toString());
}
}
+
+ @Test
+ public void longBounds() {
+ final String max = rule.toJson(new LongWrapper(Long.MAX_VALUE));
+ assertEquals("{\"value\":9223372036854775807}", max);
+ assertEquals(Long.MAX_VALUE, rule.fromJson(max, LongWrapper.class).value, 0);
+
+ final String min = rule.toJson(new LongWrapper(Long.MIN_VALUE));
+ assertEquals("{\"value\":-9223372036854775808}", min);
+ assertEquals(Long.MIN_VALUE, rule.fromJson(min, LongWrapper.class).value, 0);
+ }
+
+ public static class LongWrapper {
+ public Long value;
+
+ public LongWrapper() {
+ // no-op
+ }
+
+ public LongWrapper(final Long value) {
+ this.value = value;
+ }
+ }
}
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
index 81cc049..ade0e26 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
@@ -28,25 +28,18 @@
import org.apache.johnzon.mapper.access.FieldAccessMode;
import org.apache.johnzon.mapper.access.FieldAndMethodAccessMode;
import org.apache.johnzon.mapper.access.MethodAccessMode;
-import org.apache.johnzon.mapper.converter.BigDecimalConverter;
-import org.apache.johnzon.mapper.converter.BigIntegerConverter;
import org.apache.johnzon.mapper.converter.BooleanConverter;
import org.apache.johnzon.mapper.converter.ByteConverter;
import org.apache.johnzon.mapper.converter.CachedDelegateConverter;
import org.apache.johnzon.mapper.converter.CharacterConverter;
-import org.apache.johnzon.mapper.converter.ClassConverter;
-import org.apache.johnzon.mapper.converter.DateConverter;
import org.apache.johnzon.mapper.converter.DoubleConverter;
import org.apache.johnzon.mapper.converter.FloatConverter;
import org.apache.johnzon.mapper.converter.IntegerConverter;
-import org.apache.johnzon.mapper.converter.LocaleConverter;
import org.apache.johnzon.mapper.converter.LongConverter;
import org.apache.johnzon.mapper.converter.ShortConverter;
-import org.apache.johnzon.mapper.converter.StringConverter;
-import org.apache.johnzon.mapper.converter.URIConverter;
-import org.apache.johnzon.mapper.converter.URLConverter;
import org.apache.johnzon.mapper.internal.AdapterKey;
import org.apache.johnzon.mapper.internal.ConverterAdapter;
+import org.apache.johnzon.mapper.map.LazyConverterMap;
import javax.json.JsonBuilderFactory;
import javax.json.JsonReaderFactory;
@@ -56,58 +49,20 @@
import java.io.Closeable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.net.URI;
-import java.net.URL;
import java.nio.charset.Charset;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
-import java.util.Date;
import java.util.HashMap;
-import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;
import java.util.function.Predicate;
// this class is responsible to hold any needed config
// to build the runtime
public class MapperBuilder {
- private static final Map<AdapterKey, Adapter<?, ?>> DEFAULT_CONVERTERS = new HashMap<AdapterKey, Adapter<?, ?>>(24);
-
- static {
- //DEFAULT_CONVERTERS.put(Date.class, new DateConverter("yyyy-MM-dd'T'HH:mm:ssZ")); // ISO8601 long RFC822 zone
- DEFAULT_CONVERTERS.put(new AdapterKey(Date.class, String.class), new ConverterAdapter<Date>(new DateConverter("yyyyMMddHHmmssZ"))); // ISO8601 short
- DEFAULT_CONVERTERS.put(new AdapterKey(URL.class, String.class), new ConverterAdapter<URL>(new URLConverter()));
- DEFAULT_CONVERTERS.put(new AdapterKey(URI.class, String.class), new ConverterAdapter<URI>(new URIConverter()));
- DEFAULT_CONVERTERS.put(new AdapterKey(Class.class, String.class), new ConverterAdapter<Class<?>>(new ClassConverter()));
- DEFAULT_CONVERTERS.put(new AdapterKey(String.class, String.class), new ConverterAdapter<String>(new StringConverter()));
- DEFAULT_CONVERTERS.put(new AdapterKey(BigDecimal.class, String.class), new ConverterAdapter<BigDecimal>(new BigDecimalConverter()));
- DEFAULT_CONVERTERS.put(new AdapterKey(BigInteger.class, String.class), new ConverterAdapter<BigInteger>(new BigIntegerConverter()));
- /* primitives should be hanlded low level and adapters will wrap them in string which is unlikely
- DEFAULT_CONVERTERS.put(new AdapterKey(Byte.class, String.class), new ConverterAdapter<Byte>(new CachedDelegateConverter<Byte>(new ByteConverter())));
- DEFAULT_CONVERTERS.put(new AdapterKey(Character.class, String.class), new ConverterAdapter<Character>(new CharacterConverter()));
- DEFAULT_CONVERTERS.put(new AdapterKey(Double.class, String.class), new ConverterAdapter<Double>(new DoubleConverter()));
- DEFAULT_CONVERTERS.put(new AdapterKey(Float.class, String.class), new ConverterAdapter<Float>(new FloatConverter()));
- DEFAULT_CONVERTERS.put(new AdapterKey(Integer.class, String.class), new ConverterAdapter<Integer>(new IntegerConverter()));
- DEFAULT_CONVERTERS.put(new AdapterKey(Long.class, String.class), new ConverterAdapter<Long>(new LongConverter()));
- DEFAULT_CONVERTERS.put(new AdapterKey(Short.class, String.class), new ConverterAdapter<Short>(new ShortConverter()));
- DEFAULT_CONVERTERS.put(new AdapterKey(Boolean.class, String.class), new ConverterAdapter<Boolean>(new CachedDelegateConverter<Boolean>(new BooleanConverter())));
- DEFAULT_CONVERTERS.put(new AdapterKey(byte.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Byte.class, String.class)));
- DEFAULT_CONVERTERS.put(new AdapterKey(char.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Character.class, String.class)));
- DEFAULT_CONVERTERS.put(new AdapterKey(double.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Double.class, String.class)));
- DEFAULT_CONVERTERS.put(new AdapterKey(float.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Float.class, String.class)));
- DEFAULT_CONVERTERS.put(new AdapterKey(int.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Integer.class, String.class)));
- DEFAULT_CONVERTERS.put(new AdapterKey(long.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Long.class, String.class)));
- DEFAULT_CONVERTERS.put(new AdapterKey(short.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Short.class, String.class)));
- DEFAULT_CONVERTERS.put(new AdapterKey(boolean.class, String.class), DEFAULT_CONVERTERS.get(new AdapterKey(Boolean.class, String.class)));
- */
- DEFAULT_CONVERTERS.put(new AdapterKey(Locale.class, String.class), new LocaleConverter());
- }
-
private JsonReaderFactory readerFactory;
private JsonGeneratorFactory generatorFactory;
private JsonProvider provider;
@@ -134,7 +89,7 @@
private boolean enforceQuoteString;
private AccessMode accessMode;
private Charset encoding = Charset.forName(System.getProperty("johnzon.mapper.encoding", "UTF-8"));
- private ConcurrentMap<AdapterKey, Adapter<?, ?>> adapters = new ConcurrentHashMap<AdapterKey, Adapter<?, ?>>(DEFAULT_CONVERTERS);
+ private LazyConverterMap adapters = new LazyConverterMap();
private Map<Class<?>, ObjectConverter.Reader<?>> objectConverterReaders = new HashMap<Class<?>, ObjectConverter.Reader<?>>();
private Map<Class<?>, ObjectConverter.Writer<?>> objectConverterWriters = new HashMap<Class<?>, ObjectConverter.Writer<?>>();
private Map<Class<?>, String[]> ignoredForFields = new HashMap<Class<?>, String[]>();
@@ -244,14 +199,14 @@
}
if (primitiveConverters) {
- adapters.put(new AdapterKey(Byte.class, String.class), new ConverterAdapter<Byte>(new CachedDelegateConverter<Byte>(new ByteConverter())));
- adapters.put(new AdapterKey(Character.class, String.class), new ConverterAdapter<Character>(new CharacterConverter()));
- adapters.put(new AdapterKey(Double.class, String.class), new ConverterAdapter<Double>(new DoubleConverter()));
- adapters.put(new AdapterKey(Float.class, String.class), new ConverterAdapter<Float>(new FloatConverter()));
- adapters.put(new AdapterKey(Integer.class, String.class), new ConverterAdapter<Integer>(new IntegerConverter()));
- adapters.put(new AdapterKey(Long.class, String.class), new ConverterAdapter<Long>(new LongConverter()));
- adapters.put(new AdapterKey(Short.class, String.class), new ConverterAdapter<Short>(new ShortConverter()));
- adapters.put(new AdapterKey(Boolean.class, String.class), new ConverterAdapter<Boolean>(new CachedDelegateConverter<Boolean>(new BooleanConverter())));
+ adapters.put(new AdapterKey(Byte.class, String.class), new ConverterAdapter<>(new CachedDelegateConverter<>(new ByteConverter()), Byte.class));
+ adapters.put(new AdapterKey(Character.class, String.class), new ConverterAdapter<>(new CharacterConverter(), Character.class));
+ adapters.put(new AdapterKey(Double.class, String.class), new ConverterAdapter<>(new DoubleConverter(), Double.class));
+ adapters.put(new AdapterKey(Float.class, String.class), new ConverterAdapter<>(new FloatConverter(), Float.class));
+ adapters.put(new AdapterKey(Integer.class, String.class), new ConverterAdapter<>(new IntegerConverter(), Integer.class));
+ adapters.put(new AdapterKey(Long.class, String.class), new ConverterAdapter<>(new LongConverter(), Long.class));
+ adapters.put(new AdapterKey(Short.class, String.class), new ConverterAdapter<>(new ShortConverter(), Short.class));
+ adapters.put(new AdapterKey(Boolean.class, String.class), new ConverterAdapter<>(new CachedDelegateConverter<>(new BooleanConverter()), Boolean.class));
adapters.put(new AdapterKey(byte.class, String.class), adapters.get(new AdapterKey(Byte.class, String.class)));
adapters.put(new AdapterKey(char.class, String.class), adapters.get(new AdapterKey(Character.class, String.class)));
adapters.put(new AdapterKey(double.class, String.class), adapters.get(new AdapterKey(Double.class, String.class)));
@@ -278,6 +233,25 @@
closeables);
}
+ public ConcurrentHashMap<AdapterKey, Adapter<?,?>> getAdapters() {
+ return adapters;
+ }
+
+ public MapperBuilder setUseShortISO8601Format(final boolean useShortISO8601Format) {
+ adapters.setUseShortISO8601Format(useShortISO8601Format);
+ return this;
+ }
+
+ public MapperBuilder setAdaptersDateTimeFormatter(final DateTimeFormatter dateTimeFormatter) {
+ adapters.setDateTimeFormatter(dateTimeFormatter);
+ return this;
+ }
+
+ public MapperBuilder setAdaptersDateTimeFormatterString(final String dateTimeFormatter) {
+ adapters.setDateTimeFormatter(DateTimeFormatter.ofPattern(dateTimeFormatter));
+ return this;
+ }
+
public MapperBuilder setInterfaceImplementationMapping(final Map<Class<?>, Class<?>> interfaceImplementationMapping) {
this.interfaceImplementationMapping = interfaceImplementationMapping;
return this;
@@ -395,13 +369,13 @@
@Deprecated // use addAdapter
public MapperBuilder addPropertyEditor(final Class<?> clazz, final Converter<?> converter) {
- adapters.put(new AdapterKey(clazz, String.class), new ConverterAdapter(converter));
+ adapters.put(new AdapterKey(clazz, String.class), new ConverterAdapter(converter, clazz));
return this;
}
@Deprecated // use addAdapter
public MapperBuilder addConverter(final Type clazz, final Converter<?> converter) {
- adapters.put(new AdapterKey(clazz, String.class), new ConverterAdapter(converter));
+ adapters.put(new AdapterKey(clazz, String.class), new ConverterAdapter(converter, clazz));
return this;
}
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java
index 3046376..d0b17d5 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperConfig.java
@@ -22,6 +22,7 @@
import org.apache.johnzon.mapper.converter.EnumConverter;
import org.apache.johnzon.mapper.internal.AdapterKey;
import org.apache.johnzon.mapper.internal.ConverterAdapter;
+import org.apache.johnzon.mapper.map.LazyConverterMap;
import javax.json.JsonValue;
import java.lang.reflect.Type;
@@ -67,7 +68,7 @@
private final boolean supportEnumMapDeserialization; // for tck
private final AccessMode accessMode;
private final Charset encoding;
- private final ConcurrentMap<AdapterKey, Adapter<?, ?>> adapters;
+ private final LazyConverterMap adapters;
private final ConcurrentMap<Adapter<?, ?>, AdapterKey> reverseAdapters;
private final Map<Class<?>, ObjectConverter.Writer<?>> objectConverterWriters;
@@ -95,7 +96,7 @@
//disable checkstyle for 10+ parameters
//CHECKSTYLE:OFF
- public MapperConfig(final ConcurrentMap<AdapterKey, Adapter<?, ?>> adapters,
+ public MapperConfig(final LazyConverterMap adapters,
final Map<Class<?>, ObjectConverter.Writer<?>> objectConverterWriters,
final Map<Class<?>, ObjectConverter.Reader<?>> objectConverterReaders,
final int version, final boolean close,
@@ -218,12 +219,12 @@
if (Class.class.isInstance(aClass)) {
final Class<?> clazz = Class.class.cast(aClass);
if (Enum.class.isAssignableFrom(clazz)) {
- final Adapter<?, ?> enumConverter = new ConverterAdapter(new EnumConverter(clazz));
+ final Adapter<?, ?> enumConverter = new ConverterAdapter(new EnumConverter(clazz), clazz);
adapters.putIfAbsent(new AdapterKey(String.class, aClass), enumConverter);
return enumConverter;
}
}
- final List<AdapterKey> matched = adapters.keySet().stream()
+ final List<AdapterKey> matched = adapters.adapterKeys().stream()
.filter(k -> k.isAssignableFrom(aClass))
.collect(toList());
if (matched.size() == 1) {
@@ -382,7 +383,7 @@
return encoding;
}
- public ConcurrentMap<AdapterKey, Adapter<?, ?>> getAdapters() {
+ public LazyConverterMap getAdapters() {
return adapters;
}
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
index 1e90acf..25a5134 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingParserImpl.java
@@ -503,15 +503,9 @@
if (JsonObject.class == key.getTo() || JsonStructure.class == key.getTo()) {
return converter.to(jsonValue.asJsonObject());
}
-
- //X TODO maybe we can put this into MapperConfig?
- //X config.getAdapter(AdapterKey)
- //X config.getAdapterKey(Adapter)
- final AdapterKey adapterKey = getAdapterKey(converter);
-
final Object param;
try {
- Type to = adapterKey.getTo();
+ Type to = key.getTo();
param = buildObject(to, JsonObject.class.cast(jsonValue), to instanceof Class, jsonPointer);
} catch (final Exception e) {
throw new MapperException(e);
@@ -575,9 +569,7 @@
if (adapterKey == null) {
if (converter instanceof TypeAwareAdapter) {
- TypeAwareAdapter typeAwareAdapter = (TypeAwareAdapter) converter;
- adapterKey = new AdapterKey(typeAwareAdapter.getFrom(), typeAwareAdapter.getTo());
- config.getReverseAdapters().putIfAbsent(converter, adapterKey);
+ return TypeAwareAdapter.class.cast(converter).getKey();
} else {
Class<?> current = converter.getClass();
@@ -655,7 +647,7 @@
if (JsonObject.class == type || JsonStructure.class == type) {
return jsonValue;
}
- final boolean typedAdapter = TypeAwareAdapter.class.isInstance(itemConverter);
+ final boolean typedAdapter = !ConverterAdapter.class.isInstance(itemConverter) && TypeAwareAdapter.class.isInstance(itemConverter);
final Object object = buildObject(
baseInstance != null ? baseInstance.getClass() : (
typedAdapter ? TypeAwareAdapter.class.cast(itemConverter).getTo() : type),
@@ -1136,12 +1128,12 @@
if (Class.class.isInstance(aClass)) {
final Class<?> clazz = Class.class.cast(aClass);
if (Enum.class.isAssignableFrom(clazz)) {
- final Adapter<?, ?> enumConverter = new ConverterAdapter(new EnumConverter(clazz));
+ final Adapter<?, ?> enumConverter = new ConverterAdapter(new EnumConverter(clazz), clazz);
config.getAdapters().putIfAbsent(new AdapterKey(String.class, aClass), enumConverter);
return enumConverter;
}
}
- final List<AdapterKey> matched = config.getAdapters().keySet().stream()
+ final List<AdapterKey> matched = config.getAdapters().adapterKeys().stream()
.filter(k -> k.isAssignableFrom(aClass))
.collect(toList());
if (matched.size() == 1) {
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
index 1c5db42..1c5b045 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
@@ -160,16 +160,27 @@
}
if (theObjectConverter == null) {
Adapter adapter;
+ final Type readerType = reader.getType();
if (converter instanceof Converter) {
- adapter = new ConverterAdapter((Converter) converter);
+ adapter = new ConverterAdapter((Converter) converter, readerType);
} else {
adapter = (Adapter) converter;
}
- if (matches(reader.getType(), adapter)) {
+ if (matches(readerType, adapter)) {
theConverter = adapter;
} else {
- theItemConverter = adapter;
+ if (converter instanceof Converter) {
+ if (ParameterizedType.class.isInstance(readerType) && ParameterizedType.class.cast(readerType).getActualTypeArguments().length > 0) {
+ final Type[] args = ParameterizedType.class.cast(readerType).getActualTypeArguments();
+ // List<A> or Map<String, A> lead to read the last arg in all cases
+ theItemConverter = new ConverterAdapter((Converter) converter, args[args.length - 1]);
+ } else {
+ theItemConverter = adapter;
+ }
+ } else {
+ theItemConverter = adapter;
+ }
}
}
}
@@ -237,18 +248,29 @@
if (converter instanceof ObjectConverter.Reader) {
theObjectConverter = (ObjectConverter.Reader) converter;
}
+ final Type writerType = writer.getType();
if (theObjectConverter == null) {
Adapter adapter;
if (converter instanceof Converter) {
- adapter = new ConverterAdapter((Converter) converter);
+ adapter = new ConverterAdapter((Converter) converter, writerType);
} else {
adapter = (Adapter) converter;
}
- if (matches(writer.getType(), adapter)) {
+ if (matches(writerType, adapter)) {
theConverter = adapter;
} else {
- theItemConverter = adapter;
+ if (converter instanceof Converter) {
+ if (ParameterizedType.class.isInstance(writerType) && ParameterizedType.class.cast(writerType).getActualTypeArguments().length > 0) {
+ final Type[] args = ParameterizedType.class.cast(writerType).getActualTypeArguments();
+ // List<A> or Map<String, A> lead to read the last arg in all cases
+ theItemConverter = new ConverterAdapter((Converter) converter, args[args.length - 1]);
+ } else {
+ theItemConverter = adapter;
+ }
+ } else {
+ theItemConverter = adapter;
+ }
}
}
}
@@ -677,7 +699,7 @@
final AdapterKey key = new AdapterKey(String.class, type);
converter = adapters.get(key); // first ensure user didnt override it
if (converter == null) {
- converter = new ConverterAdapter(new EnumConverter(type));
+ converter = new ConverterAdapter(new EnumConverter(type), type);
adapters.put(key, (Adapter<?, ?>) converter);
}
}
@@ -694,12 +716,12 @@
public MapBuilderReader(final Map<String, Getter> objectGetters, final String[] paths, final int version) {
this.getters = objectGetters;
this.paths = paths;
- this.template = new LinkedHashMap<String, Object>();
+ this.template = new LinkedHashMap<>();
this.version = version;
Map<String, Object> last = this.template;
for (int i = 1; i < paths.length; i++) {
- final Map<String, Object> newLast = new LinkedHashMap<String, Object>();
+ final Map<String, Object> newLast = new LinkedHashMap<>();
last.put(paths[i], newLast);
last = newLast;
}
@@ -707,7 +729,7 @@
@Override
public Object read(final Object instance) {
- final Map<String, Object> map = new LinkedHashMap<String, Object>(template);
+ final Map<String, Object> map = new LinkedHashMap<>(template);
Map<String, Object> nested = map;
for (int i = 1; i < paths.length; i++) {
nested = Map.class.cast(nested.get(paths[i]));
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/TypeAwareAdapter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/TypeAwareAdapter.java
index cff2fc0..b956803 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/TypeAwareAdapter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/TypeAwareAdapter.java
@@ -18,9 +18,15 @@
*/
package org.apache.johnzon.mapper;
+import org.apache.johnzon.mapper.internal.AdapterKey;
+
import java.lang.reflect.Type;
public interface TypeAwareAdapter<A, B> extends Adapter<A, B> {
Type getTo();
Type getFrom();
+
+ default AdapterKey getKey() {
+ return new AdapterKey(getFrom(), getTo());
+ }
}
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
index edbc379..7bd011e 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/BaseAccessMode.java
@@ -197,7 +197,7 @@
try {
MapperConverter mapperConverter = JohnzonConverter.class.cast(a).value().newInstance();
if (mapperConverter instanceof Converter) {
- final Adapter<?, ?> converter = new ConverterAdapter((Converter) mapperConverter);
+ final Adapter<?, ?> converter = new ConverterAdapter((Converter) mapperConverter, constructor.getGenericParameterTypes()[i]);
if (matches(constructor.getParameterTypes()[i], converter)) {
constructorParameterConverters[i] = converter;
constructorItemParameterConverters[i] = null;
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/DateWithCopyConverter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/DateWithCopyConverter.java
index 431f133..85abb26 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/DateWithCopyConverter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/DateWithCopyConverter.java
@@ -28,7 +28,7 @@
private final Adapter<Date, String> delegate;
public DateWithCopyConverter(final Adapter<Date, String> delegate) {
- this.delegate = delegate == null ? new ConverterAdapter<Date>(new DateConverter("yyyyMMddHHmmssZ")) : delegate;
+ this.delegate = delegate == null ? new ConverterAdapter<>(new DateConverter("yyyyMMddHHmmssZ"), Date.class) : delegate;
}
@Override
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/LocaleConverter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/LocaleConverter.java
index a03b2a1..6dd330f 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/LocaleConverter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/converter/LocaleConverter.java
@@ -18,12 +18,31 @@
*/
package org.apache.johnzon.mapper.converter;
-import org.apache.johnzon.mapper.Adapter;
+import org.apache.johnzon.mapper.TypeAwareAdapter;
+import org.apache.johnzon.mapper.internal.AdapterKey;
+import java.lang.reflect.Type;
import java.util.Locale;
// from [lang]
-public class LocaleConverter implements Adapter<Locale, String> {
+public class LocaleConverter implements TypeAwareAdapter<Locale, String> {
+ private final AdapterKey key = new AdapterKey(Locale.class, String.class);
+
+ @Override
+ public Type getTo() {
+ return key.getTo();
+ }
+
+ @Override
+ public Type getFrom() {
+ return key.getFrom();
+ }
+
+ @Override
+ public AdapterKey getKey() {
+ return key;
+ }
+
@Override
public String from(final Locale instance) {
return instance.toString();
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/ConverterAdapter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/ConverterAdapter.java
index c3bb90e..afea76b 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/ConverterAdapter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/ConverterAdapter.java
@@ -18,14 +18,18 @@
*/
package org.apache.johnzon.mapper.internal;
-import org.apache.johnzon.mapper.Adapter;
import org.apache.johnzon.mapper.Converter;
+import org.apache.johnzon.mapper.TypeAwareAdapter;
-public class ConverterAdapter<A> implements Adapter<A, String> {
+import java.lang.reflect.Type;
+
+public class ConverterAdapter<A> implements TypeAwareAdapter<A, String> {
private final Converter<A> converter;
+ private final AdapterKey key;
- public ConverterAdapter(final Converter<A> converter) {
+ public ConverterAdapter(final Converter<A> converter, final Type from) {
this.converter = converter;
+ this.key = new AdapterKey(from, String.class);
}
public Converter<A> getConverter() {
@@ -41,4 +45,19 @@
public String from(final A a) {
return converter.toString(a);
}
+
+ @Override
+ public Type getTo() {
+ return key.getTo();
+ }
+
+ @Override
+ public Type getFrom() {
+ return key.getFrom();
+ }
+
+ @Override
+ public AdapterKey getKey() {
+ return key;
+ }
}
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/map/LazyConverterMap.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/map/LazyConverterMap.java
new file mode 100644
index 0000000..f1a4e42
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/map/LazyConverterMap.java
@@ -0,0 +1,637 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+package org.apache.johnzon.mapper.map;
+
+import org.apache.johnzon.mapper.Adapter;
+import org.apache.johnzon.mapper.Converter;
+import org.apache.johnzon.mapper.MapperException;
+import org.apache.johnzon.mapper.converter.BigDecimalConverter;
+import org.apache.johnzon.mapper.converter.BigIntegerConverter;
+import org.apache.johnzon.mapper.converter.ClassConverter;
+import org.apache.johnzon.mapper.converter.DateConverter;
+import org.apache.johnzon.mapper.converter.LocaleConverter;
+import org.apache.johnzon.mapper.converter.StringConverter;
+import org.apache.johnzon.mapper.converter.URIConverter;
+import org.apache.johnzon.mapper.converter.URLConverter;
+import org.apache.johnzon.mapper.internal.AdapterKey;
+import org.apache.johnzon.mapper.internal.ConverterAdapter;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.URI;
+import java.net.URL;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.OffsetTime;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.TemporalAccessor;
+import java.time.temporal.TemporalQueries;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.Set;
+import java.util.SimpleTimeZone;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import static java.time.temporal.ChronoField.DAY_OF_MONTH;
+import static java.time.temporal.ChronoField.HOUR_OF_DAY;
+import static java.time.temporal.ChronoField.MILLI_OF_SECOND;
+import static java.time.temporal.ChronoField.MINUTE_OF_HOUR;
+import static java.time.temporal.ChronoField.MONTH_OF_YEAR;
+import static java.time.temporal.ChronoField.SECOND_OF_MINUTE;
+import static java.time.temporal.ChronoField.YEAR;
+import static java.util.stream.Collectors.toSet;
+
+// important: override all usages,
+// mainly org.apache.johnzon.mapper.MapperConfig.findAdapter and
+// org.apache.johnzon.mapper.MappingParserImpl.findAdapter today
+public class LazyConverterMap extends ConcurrentHashMap<AdapterKey, Adapter<?, ?>> {
+ private static final Adapter<?, ?> NO_ADAPTER = new Adapter<Object, Object>() {
+ @Override
+ public Object to(final Object b) {
+ throw new UnsupportedOperationException("shouldn't be called");
+ }
+
+ @Override
+ public Object from(final Object a) {
+ return to(null); // just fail
+ }
+ };
+
+ private boolean useShortISO8601Format = true;
+ private DateTimeFormatter dateTimeFormatter;
+
+ public void setUseShortISO8601Format(final boolean useShortISO8601Format) {
+ this.useShortISO8601Format = useShortISO8601Format;
+ }
+
+ public void setDateTimeFormatter(final DateTimeFormatter dateTimeFormatter) {
+ this.dateTimeFormatter = dateTimeFormatter;
+ }
+
+ @Override
+ public Adapter<?, ?> get(final Object key) {
+ final Adapter<?, ?> found = super.get(key);
+ if (found == NO_ADAPTER) {
+ return null;
+ }
+ if (found == null) {
+ if (!AdapterKey.class.isInstance(key)) {
+ return null;
+ }
+ final AdapterKey k = AdapterKey.class.cast(key);
+ if (k.getTo() == String.class) {
+ final Adapter<?, ?> adapter = doLazyLookup(k);
+ if (adapter != null) {
+ return adapter;
+ } // else let's cache we don't need to go through lazy lookups
+ }
+ add(k, NO_ADAPTER);
+ return null;
+ }
+ return found;
+ }
+
+ @Override
+ public Set<Entry<AdapterKey, Adapter<?, ?>>> entrySet() {
+ return super.entrySet().stream()
+ .filter(it -> it.getValue() != NO_ADAPTER)
+ .collect(toSet());
+ }
+
+ public Set<AdapterKey> adapterKeys() {
+ return Stream.concat(
+ super.keySet().stream()
+ .filter(it -> super.get(it) != NO_ADAPTER),
+ Stream.of(Date.class, URI.class, URL.class, Class.class, String.class, BigDecimal.class, BigInteger.class,
+ Locale.class, Period.class, Duration.class, Calendar.class, GregorianCalendar.class, TimeZone.class,
+ ZoneId.class, ZoneOffset.class, SimpleTimeZone.class, Instant.class, LocalDateTime.class, LocalDate.class,
+ ZonedDateTime.class, OffsetDateTime.class, OffsetTime.class)
+ .map(it -> new AdapterKey(it, String.class, true)))
+ .collect(toSet());
+ }
+
+ private Adapter<?, ?> doLazyLookup(final AdapterKey key) {
+ final Type from = key.getFrom();
+ if (from == Date.class) {
+ return addDateConverter(key);
+ }
+ if (from == URI.class) {
+ return add(key, new ConverterAdapter<>(new URIConverter(), URI.class));
+ }
+ if (from == URL.class) {
+ return add(key, new ConverterAdapter<>(new URLConverter(), URL.class));
+ }
+ if (from == Class.class) {
+ return add(key, new ConverterAdapter<>(new ClassConverter(), Class.class));
+ }
+ if (from == String.class) {
+ return add(key, new ConverterAdapter<>(new StringConverter(), String.class));
+ }
+ if (from == BigDecimal.class) {
+ return add(key, new ConverterAdapter<>(new BigDecimalConverter(), BigDecimal.class));
+ }
+ if (from == BigInteger.class) {
+ return add(key, new ConverterAdapter<>(new BigIntegerConverter(), BigInteger.class));
+ }
+ if (from == Locale.class) {
+ return add(key, new LocaleConverter());
+ }
+ if (from == Period.class) {
+ return add(key, new ConverterAdapter<>(new Converter<Period>() {
+ @Override
+ public String toString(final Period instance) {
+ return instance.toString();
+ }
+
+ @Override
+ public Period fromString(final String text) {
+ return Period.parse(text);
+ }
+ }, Period.class));
+ }
+ if (from == Duration.class) {
+ return add(key, new ConverterAdapter<>(new Converter<Duration>() {
+ @Override
+ public String toString(final Duration instance) {
+ return instance.toString();
+ }
+
+ @Override
+ public Duration fromString(final String text) {
+ return Duration.parse(text);
+ }
+ }, Duration.class));
+ }
+ if (from == Calendar.class) {
+ return addCalendarConverter(key);
+ }
+ if (from == GregorianCalendar.class) {
+ return addGregorianCalendar(key);
+ }
+ if (from == TimeZone.class) {
+ return add(key, new ConverterAdapter<>(new Converter<TimeZone>() {
+ @Override
+ public String toString(final TimeZone instance) {
+ return instance.getID();
+ }
+
+ @Override
+ public TimeZone fromString(final String text) {
+ checkForDeprecatedTimeZone(text);
+ return TimeZone.getTimeZone(text);
+ }
+ }, TimeZone.class));
+ }
+ if (from == ZoneId.class) {
+ return add(key, new ConverterAdapter<>(new Converter<ZoneId>() {
+ @Override
+ public String toString(final ZoneId instance) {
+ return instance.getId();
+ }
+
+ @Override
+ public ZoneId fromString(final String text) {
+ return ZoneId.of(text);
+ }
+ }, ZoneId.class));
+ }
+ if (from == ZoneOffset.class) {
+ return add(key, new ConverterAdapter<>(new Converter<ZoneOffset>() {
+ @Override
+ public String toString(final ZoneOffset instance) {
+ return instance.getId();
+ }
+
+ @Override
+ public ZoneOffset fromString(final String text) {
+ return ZoneOffset.of(text);
+ }
+ }, ZoneOffset.class));
+ }
+ if (from == SimpleTimeZone.class) {
+ return add(key, new ConverterAdapter<>(new Converter<SimpleTimeZone>() {
+ @Override
+ public String toString(final SimpleTimeZone instance) {
+ return instance.getID();
+ }
+
+ @Override
+ public SimpleTimeZone fromString(final String text) {
+ checkForDeprecatedTimeZone(text);
+ final TimeZone timeZone = TimeZone.getTimeZone(text);
+ return new SimpleTimeZone(timeZone.getRawOffset(), timeZone.getID());
+ }
+ }, SimpleTimeZone.class));
+ }
+ if (from == Instant.class) {
+ return addInstantConverter(key);
+ }
+ if (from == LocalDate.class) {
+ return addLocalDateConverter(key);
+ }
+ if (from == LocalTime.class) {
+ return add(key, new ConverterAdapter<>(new Converter<LocalTime>() {
+ @Override
+ public String toString(final LocalTime instance) {
+ return instance.toString();
+ }
+
+ @Override
+ public LocalTime fromString(final String text) {
+ return LocalTime.parse(text);
+ }
+ }, LocalTime.class));
+ }
+ if (from == LocalDateTime.class) {
+ return addLocalDateTimeConverter(key);
+ }
+ if (from == ZonedDateTime.class) {
+ return addZonedDateTimeConverter(key);
+ }
+ if (from == OffsetDateTime.class) {
+ return addOffsetDateTimeConverter(key);
+ }
+ if (from == OffsetTime.class) {
+ return add(key, new ConverterAdapter<>(new Converter<OffsetTime>() {
+ @Override
+ public String toString(final OffsetTime instance) {
+ return instance.toString();
+ }
+
+ @Override
+ public OffsetTime fromString(final String text) {
+ return OffsetTime.parse(text);
+ }
+ }, OffsetTime.class));
+ }
+ return null;
+ }
+
+ private Adapter<?, ?> addOffsetDateTimeConverter(final AdapterKey key) {
+ if (dateTimeFormatter != null) {
+ final ZoneId zoneIDUTC = ZoneId.of("UTC");
+ return add(key, new ConverterAdapter<>(new Converter<OffsetDateTime>() {
+ @Override
+ public String toString(final OffsetDateTime instance) {
+ return dateTimeFormatter.format(ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC));
+ }
+
+ @Override
+ public OffsetDateTime fromString(final String text) {
+ try {
+ return parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toOffsetDateTime();
+ } catch (final DateTimeParseException dpe) {
+ return OffsetDateTime.parse(text);
+ }
+ }
+ }, OffsetDateTime.class));
+ }
+ return add(key, new ConverterAdapter<>(new Converter<OffsetDateTime>() {
+ @Override
+ public String toString(final OffsetDateTime instance) {
+ return instance.toString();
+ }
+
+ @Override
+ public OffsetDateTime fromString(final String text) {
+ return OffsetDateTime.parse(text);
+ }
+ }, OffsetDateTime.class));
+ }
+
+ private Adapter<?, ?> addZonedDateTimeConverter(final AdapterKey key) {
+ if (dateTimeFormatter != null) {
+ final ZoneId zoneIDUTC = ZoneId.of("UTC");
+ return add(key, new ConverterAdapter<>(new Converter<ZonedDateTime>() {
+ @Override
+ public String toString(final ZonedDateTime instance) {
+ return dateTimeFormatter.format(ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC));
+ }
+
+ @Override
+ public ZonedDateTime fromString(final String text) {
+ try {
+ return parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC);
+ } catch (final DateTimeParseException dpe) {
+ return ZonedDateTime.parse(text);
+ }
+ }
+ }, ZonedDateTime.class));
+ }
+ return add(key, new ConverterAdapter<>(new Converter<ZonedDateTime>() {
+ @Override
+ public String toString(final ZonedDateTime instance) {
+ return instance.toString();
+ }
+
+ @Override
+ public ZonedDateTime fromString(final String text) {
+ return ZonedDateTime.parse(text);
+ }
+ }, ZonedDateTime.class));
+ }
+
+ private Adapter<?, ?> addLocalDateTimeConverter(final AdapterKey key) {
+ if (dateTimeFormatter != null) {
+ final ZoneId zoneIDUTC = ZoneId.of("UTC");
+ return add(key, new ConverterAdapter<>(new Converter<LocalDateTime>() {
+
+ @Override
+ public String toString(final LocalDateTime instance) {
+ return dateTimeFormatter.format(ZonedDateTime.ofInstant(instance.toInstant(ZoneOffset.UTC), zoneIDUTC));
+ }
+
+ @Override
+ public LocalDateTime fromString(final String text) {
+ try {
+ return parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toLocalDateTime();
+ } catch (final DateTimeParseException dpe) {
+ return LocalDateTime.parse(text);
+ }
+ }
+ }, LocalDateTime.class));
+ }
+ return add(key, new ConverterAdapter<>(new Converter<LocalDateTime>() {
+ @Override
+ public String toString(final LocalDateTime instance) {
+ return instance.toString();
+ }
+
+ @Override
+ public LocalDateTime fromString(final String text) {
+ return LocalDateTime.parse(text);
+ }
+ }, LocalDateTime.class));
+ }
+
+ private Adapter<?, ?> addLocalDateConverter(final AdapterKey key) {
+ if (dateTimeFormatter != null) {
+ final ZoneId zoneIDUTC = ZoneId.of("UTC");
+ return add(key, new ConverterAdapter<>(new Converter<LocalDate>() {
+ @Override
+ public String toString(final LocalDate instance) {
+ return dateTimeFormatter.format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(TimeUnit.DAYS.toMillis(instance.toEpochDay())), zoneIDUTC));
+ }
+
+ @Override
+ public LocalDate fromString(final String text) {
+ try {
+ return parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toLocalDate();
+ } catch (final DateTimeParseException dpe) {
+ return LocalDate.parse(text);
+ }
+ }
+ }, LocalDate.class));
+ }
+ return add(key, new ConverterAdapter<>(new Converter<LocalDate>() {
+ @Override
+ public String toString(final LocalDate instance) {
+ return instance.toString();
+ }
+
+ @Override
+ public LocalDate fromString(final String text) {
+ return LocalDate.parse(text);
+ }
+ }, LocalDate.class));
+ }
+
+ private Adapter<?, ?> addInstantConverter(final AdapterKey key) {
+ if (dateTimeFormatter != null) {
+ final ZoneId zoneIDUTC = ZoneId.of("UTC");
+ return add(key, new ConverterAdapter<>(new Converter<Instant>() {
+ @Override
+ public String toString(final Instant instance) {
+ return dateTimeFormatter.format(ZonedDateTime.ofInstant(instance, zoneIDUTC));
+ }
+
+ @Override
+ public Instant fromString(final String text) {
+ return parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toInstant();
+ }
+ }, Instant.class));
+ }
+ return add(key, new ConverterAdapter<>(new Converter<Instant>() {
+ @Override
+ public String toString(final Instant instance) {
+ return instance.toString();
+ }
+
+ @Override
+ public Instant fromString(final String text) {
+ return Instant.parse(text);
+ }
+ }, Instant.class));
+ }
+
+ private Adapter<?, ?> addGregorianCalendar(final AdapterKey key) {
+ if (dateTimeFormatter != null) {
+ final ZoneId zoneIDUTC = ZoneId.of("UTC");
+ return add(key, new ConverterAdapter<>(new Converter<GregorianCalendar>() {
+ @Override
+ public String toString(final GregorianCalendar instance) {
+ return dateTimeFormatter.format(ZonedDateTime.ofInstant(instance.toInstant(), instance.getTimeZone().toZoneId()));
+ }
+
+ @Override
+ public GregorianCalendar fromString(final String text) {
+ final ZonedDateTime zonedDateTime = parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC);
+ final Calendar instance = GregorianCalendar.getInstance();
+ instance.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone()));
+ instance.setTime(Date.from(zonedDateTime.toInstant()));
+ return GregorianCalendar.class.cast(instance);
+ }
+ }, GregorianCalendar.class));
+ }
+ return add(key, new ConverterAdapter<>(new Converter<GregorianCalendar>() {
+ @Override
+ public String toString(final GregorianCalendar instance) {
+ return toStringCalendar(instance);
+ }
+
+ @Override
+ public GregorianCalendar fromString(final String text) {
+ return fromCalendar(text, GregorianCalendar::from);
+ }
+ }, GregorianCalendar.class));
+ }
+
+ private Adapter<?, ?> addCalendarConverter(final AdapterKey key) {
+ if (dateTimeFormatter != null) {
+ final ZoneId zoneIDUTC = ZoneId.of("UTC");
+ return add(key, new ConverterAdapter<>(new Converter<Calendar>() {
+ @Override
+ public String toString(final Calendar instance) {
+ return dateTimeFormatter.format(ZonedDateTime.ofInstant(instance.toInstant(), instance.getTimeZone().toZoneId()));
+ }
+
+ @Override
+ public Calendar fromString(final String text) {
+ final ZonedDateTime zonedDateTime = parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC);
+ final Calendar instance = Calendar.getInstance();
+ instance.setTimeZone(TimeZone.getTimeZone(zonedDateTime.getZone()));
+ instance.setTime(Date.from(zonedDateTime.toInstant()));
+ return instance;
+ }
+ }, Calendar.class));
+ }
+ return add(key, new ConverterAdapter<>(new Converter<Calendar>() {
+ @Override
+ public String toString(final Calendar instance) {
+ return toStringCalendar(instance);
+ }
+
+ @Override
+ public Calendar fromString(final String text) {
+ return fromCalendar(text, zdt -> {
+ final Calendar instance = Calendar.getInstance();
+ instance.clear();
+ instance.setTimeZone(TimeZone.getTimeZone(zdt.getZone()));
+ instance.setTimeInMillis(zdt.toInstant().toEpochMilli());
+ return instance;
+ });
+ }
+ }, Calendar.class));
+ }
+
+ private Adapter<?, ?> addDateConverter(final AdapterKey key) {
+ if (useShortISO8601Format) {
+ return add(key, new ConverterAdapter<>(new DateConverter("yyyyMMddHHmmssZ"), Date.class));
+ }
+ final ZoneId zoneIDUTC = ZoneId.of("UTC");
+ if (dateTimeFormatter != null) {
+ return add(key, new ConverterAdapter<>(new Converter<Date>() {
+
+ @Override
+ public String toString(final Date instance) {
+ return dateTimeFormatter.format(ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC));
+ }
+
+ @Override
+ public Date fromString(final String text) {
+ try {
+ return Date.from(parseZonedDateTime(text, dateTimeFormatter, zoneIDUTC).toInstant());
+ } catch (final DateTimeParseException dpe) {
+ return Date.from(LocalDateTime.parse(text).toInstant(ZoneOffset.UTC));
+ }
+ }
+ }, Date.class));
+ }
+ return add(key, new ConverterAdapter<>(new Converter<Date>() {
+ @Override
+ public String toString(final Date instance) {
+ return ZonedDateTime.ofInstant(instance.toInstant(), zoneIDUTC)
+ .format(DateTimeFormatter.ISO_ZONED_DATE_TIME);
+ }
+
+ @Override
+ public Date fromString(final String text) {
+ try {
+ return Date.from(ZonedDateTime.parse(text).toInstant());
+ } catch (final DateTimeParseException dte) {
+ return Date.from(LocalDateTime.parse(text).toInstant(ZoneOffset.UTC));
+ }
+ }
+ }, Date.class));
+ }
+
+ private static ZonedDateTime parseZonedDateTime(final String text, final DateTimeFormatter formatter, final ZoneId defaultZone) {
+ final TemporalAccessor parse = formatter.parse(text);
+ ZoneId zone = parse.query(TemporalQueries.zone());
+ if (Objects.isNull(zone)) {
+ zone = defaultZone;
+ }
+ final int year = parse.isSupported(YEAR) ? parse.get(YEAR) : 0;
+ final int month = parse.isSupported(MONTH_OF_YEAR) ? parse.get(MONTH_OF_YEAR) : 0;
+ final int day = parse.isSupported(DAY_OF_MONTH) ? parse.get(DAY_OF_MONTH) : 0;
+ final int hour = parse.isSupported(HOUR_OF_DAY) ? parse.get(HOUR_OF_DAY) : 0;
+ final int minute = parse.isSupported(MINUTE_OF_HOUR) ? parse.get(MINUTE_OF_HOUR) : 0;
+ final int second = parse.isSupported(SECOND_OF_MINUTE) ? parse.get(SECOND_OF_MINUTE) : 0;
+ final int millisecond = parse.isSupported(MILLI_OF_SECOND) ? parse.get(MILLI_OF_SECOND) : 0;
+ return ZonedDateTime.of(year, month, day, hour, minute, second, millisecond, zone);
+ }
+
+ private static void checkForDeprecatedTimeZone(final String text) {
+ switch (text) {
+ case "CST": // really for TCK, this sucks for end users so we don't fail for all deprecated zones
+ throw new MapperException("Deprecated timezone: '" + text + '"');
+ default:
+ }
+ }
+
+ private String toStringCalendar(final Calendar instance) {
+ if (!hasTime(instance)) { // spec
+ final LocalDate localDate = LocalDate.of(
+ instance.get(Calendar.YEAR),
+ instance.get(Calendar.MONTH) + 1,
+ instance.get(Calendar.DAY_OF_MONTH));
+ return localDate.toString() +
+ (instance.getTimeZone() != null ?
+ instance.getTimeZone().toZoneId().getRules()
+ .getOffset(Instant.ofEpochMilli(TimeUnit.DAYS.toMillis(localDate.toEpochDay()))) : "");
+ }
+ return ZonedDateTime.ofInstant(instance.toInstant(), instance.getTimeZone().toZoneId())
+ .format(DateTimeFormatter.ISO_DATE_TIME);
+ }
+
+ private boolean hasTime(final Calendar instance) {
+ if (!instance.isSet(Calendar.HOUR_OF_DAY)) {
+ return false;
+ }
+ return instance.get(Calendar.HOUR_OF_DAY) != 0 ||
+ (instance.isSet(Calendar.MINUTE) && instance.get(Calendar.MINUTE) != 0) ||
+ (instance.isSet(Calendar.SECOND) && instance.get(Calendar.SECOND) != 0);
+ }
+
+ private <T extends Calendar> T fromCalendar(final String text, final Function<ZonedDateTime, T> calendarSupplier) {
+ switch (text.length()) {
+ case 10: {
+ final ZonedDateTime date = LocalDate.parse(text)
+ .atTime(0, 0, 0)
+ .atZone(ZoneId.of("UTC"));
+ return calendarSupplier.apply(date);
+ }
+ default:
+ final ZonedDateTime zonedDateTime = ZonedDateTime.parse(text);
+ return calendarSupplier.apply(zonedDateTime);
+ }
+ }
+
+ private Adapter<?, ?> add(final AdapterKey key, final Adapter<?, ?> converter) {
+ final Adapter<?, ?> existing = putIfAbsent(key, converter);
+ return existing == null ? converter : existing;
+ }
+}
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java
index f0f4020..4044781 100644
--- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MapperConfigTest.java
@@ -21,6 +21,7 @@
import static java.util.Collections.emptyMap;
import org.apache.johnzon.mapper.access.FieldAccessMode;
+import org.apache.johnzon.mapper.map.LazyConverterMap;
import org.junit.Assert;
import org.junit.Test;
@@ -31,7 +32,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
public class MapperConfigTest {
@@ -156,7 +156,7 @@
private MapperConfig createConfig(Map<Class<?>, ObjectConverter.Codec<?>> converter) {
- return new MapperConfig(new ConcurrentHashMap<>(0),
+ return new MapperConfig(new LazyConverterMap(),
Map.class.cast(converter), Map.class.cast(converter),
-1,
true,
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java
index 62e9495..8e5e059 100644
--- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/ObjectConverterTest.java
@@ -108,6 +108,15 @@
result = 31 * result + Arrays.hashCode(linkedPersonsArray);
return result;
}
+
+ @Override
+ public String toString() {
+ return "Contact{" +
+ "linkedPersons=" + linkedPersons +
+ ", linkedPersonsArray=" + Arrays.toString(linkedPersonsArray) +
+ ", personMap=" + personMap +
+ '}';
+ }
}
public static class Person {
@@ -156,5 +165,13 @@
result = 31 * result + (lastName != null ? lastName.hashCode() : 0);
return result;
}
+
+ @Override
+ public String toString() {
+ return "Person{" +
+ "firstName='" + firstName + '\'' +
+ ", lastName='" + lastName + '\'' +
+ '}';
+ }
}
}
\ No newline at end of file
diff --git a/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java b/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java
index 8e5264c..27fdd0d 100644
--- a/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java
+++ b/johnzon-mapper/src/test/java/org/superbiz/ExtendMappingTest.java
@@ -21,13 +21,13 @@
import org.apache.johnzon.mapper.MapperConfig;
import org.apache.johnzon.mapper.Mappings;
import org.apache.johnzon.mapper.access.FieldAccessMode;
+import org.apache.johnzon.mapper.map.LazyConverterMap;
import org.junit.Test;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
import static java.util.Collections.emptyMap;
import static org.junit.Assert.assertEquals;
@@ -57,7 +57,7 @@
public static class MyMappings extends Mappings {
public MyMappings() {
super(new MapperConfig(
- new ConcurrentHashMap<>(), new HashMap<>(), new HashMap<>(),
+ new LazyConverterMap(), new HashMap<>(), new HashMap<>(),
-1, true, true, true, false, false, false,
new FieldAccessMode(false, false),
StandardCharsets.UTF_8, String::compareTo, false, false, null, false, false,