This is an automated email from the ASF dual-hosted git repository. rmannibucau pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/johnzon.git
The following commit(s) were added to refs/heads/master by this push: new ef9fc08 JOHNZON-309 more tolerance of adapters (string) ef9fc08 is described below commit ef9fc08a3d4846534449eb37833470837d808da7 Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Fri Mar 27 10:41:51 2020 +0100 JOHNZON-309 more tolerance of adapters (string) --- .../java/org/apache/johnzon/jsonb/AdapterTest.java | 37 ++++++++++++++++++++++ .../java/org/apache/johnzon/mapper/Mapper.java | 8 ++++- .../org/apache/johnzon/mapper/MapperConfig.java | 32 ++++++++++++++++++- .../apache/johnzon/mapper/MappingParserImpl.java | 34 ++++++++++++++++++-- .../apache/johnzon/mapper/internal/AdapterKey.java | 18 +++++++++++ 5 files changed, 125 insertions(+), 4 deletions(-) diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/AdapterTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/AdapterTest.java index dca4576..9a69bb7 100644 --- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/AdapterTest.java +++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/AdapterTest.java @@ -29,6 +29,8 @@ import javax.json.bind.JsonbConfig; import javax.json.bind.adapter.JsonbAdapter; import javax.json.bind.annotation.JsonbTypeAdapter; import javax.json.bind.config.PropertyOrderStrategy; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @@ -41,6 +43,41 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; public class AdapterTest { + public static class PathAdapter implements JsonbAdapter<Path, JsonString> { + @Override + public JsonString adaptToJson(final Path path) { + return Json.createValue(path.toString()); + } + + @Override + public Path adaptFromJson(final JsonString jsonString) { + return Paths.get(jsonString.getString()); + } + } + + public static class PathWrapper { + public Path path; + } + + @Test + public void testSerialize() throws Exception { + try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withAdapters(new PathAdapter()))) { + final PathWrapper wrapper = new PathWrapper(); + wrapper.path = Paths.get("/example/file.txt"); + assertEquals("{\"path\":\"/example/file.txt\"}", jsonb.toJson(wrapper)); + assertEquals("\"/example/file.txt\"", jsonb.toJson(wrapper.path)); + } + } + + @Test + public void testDeserialize() throws Exception { + try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withAdapters(new PathAdapter()))) { + final Path expected = Paths.get("/example/file.txt"); + assertEquals(expected, jsonb.fromJson("{\"path\":\"/example/file.txt\"}", PathWrapper.class).path); + assertEquals(expected, jsonb.fromJson("\"/example/file.txt\"", Path.class)); + } + } + @Test public void adapt() throws Exception { try (final Jsonb jsonb = JsonbBuilder.create(new JsonbConfig().withAdapters(new BarAdapter()))) { diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java index 3616bf7..391c60f 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mapper.java @@ -44,6 +44,7 @@ import javax.json.JsonBuilderFactory; import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonReaderFactory; +import javax.json.JsonString; import javax.json.JsonStructure; import javax.json.JsonValue; import javax.json.spi.JsonProvider; @@ -195,8 +196,13 @@ public class Mapper implements Closeable { return; } + final Adapter adapter = config.findAdapter(object.getClass()); + if (adapter != null && TypeAwareAdapter.class.isInstance(adapter) && TypeAwareAdapter.class.cast(adapter).getTo() == JsonString.class) { + writeObject(adapter.from(object), stream); + return; + } try (final JsonGenerator generator = generatorFactory.createGenerator(stream(stream))) { - writeObjectWithGenerator(object, generator); + writeObjectWithGenerator(adapter == null ? object : adapter.from(object), generator); } } 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 d3ef464..3046376 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 @@ -26,14 +26,18 @@ import org.apache.johnzon.mapper.internal.ConverterAdapter; import javax.json.JsonValue; import java.lang.reflect.Type; import java.nio.charset.Charset; +import java.util.Collection; import java.util.Comparator; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.function.Function; import java.util.function.Predicate; +import static java.util.stream.Collectors.toList; + /** * Contains internal configuration for all the mapper stuff. * It needs to be immutable and 100% runtime oriented. @@ -86,6 +90,9 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { private final Map<Class<?>, ObjectConverter.Writer<?>> objectConverterWriterCache; private final Map<Class<?>, ObjectConverter.Reader<?>> objectConverterReaderCache; + private final Collection<Type> noParserAdapterTypes = new ConcurrentHashMap<Type, Boolean>().keySet(true); + private final Collection<Type> noGeneratorAdapterTypes = new ConcurrentHashMap<Type, Boolean>().keySet(true); + //disable checkstyle for 10+ parameters //CHECKSTYLE:OFF public MapperConfig(final ConcurrentMap<AdapterKey, Adapter<?, ?>> adapters, @@ -149,6 +156,14 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { this.deduplicateObjects = deduplicateObjects; } + public Collection<Type> getNoParserAdapterTypes() { + return noParserAdapterTypes; + } + + public Collection<Type> getNoGeneratorAdapterTypes() { + return noGeneratorAdapterTypes; + } + public Function<String, Class<?>> getTypeLoader() { return typeLoader; } @@ -186,7 +201,11 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { } public Adapter findAdapter(final Type aClass) { - final Adapter<?, ?> converter = adapters.get(new AdapterKey(aClass, String.class)); + if (getNoGeneratorAdapterTypes().contains(aClass)) { // avoid to create a key for nothing + return null; + } + + final Adapter<?, ?> converter = adapters.get(new AdapterKey(aClass, String.class, true)); if (converter != null) { return converter; } @@ -204,6 +223,17 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { return enumConverter; } } + final List<AdapterKey> matched = adapters.keySet().stream() + .filter(k -> k.isAssignableFrom(aClass)) + .collect(toList()); + if (matched.size() == 1) { + final Adapter<?, ?> adapter = adapters.get(matched.iterator().next()); + if (TypeAwareAdapter.class.isInstance(adapter)) { + adapters.put(new AdapterKey(aClass, TypeAwareAdapter.class.cast(adapter).getTo()), adapter); + } + return adapter; + } + getNoGeneratorAdapterTypes().add(aClass); return null; } 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 0ec10bc..26d3e12 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 @@ -77,6 +77,7 @@ import java.util.stream.LongStream; import java.util.stream.Stream; import static java.util.Arrays.asList; +import static java.util.stream.Collectors.toList; import static javax.json.JsonValue.ValueType.ARRAY; import static javax.json.JsonValue.ValueType.FALSE; import static javax.json.JsonValue.ValueType.NULL; @@ -164,6 +165,20 @@ public class MappingParserImpl implements MappingParser { if (classMapping != null && classMapping.adapter != null) { return (T) classMapping.adapter.to(JsonString.class.cast(jsonValue).getString()); } + + final Adapter adapter = findAdapter(targetType); + if (adapter != null && TypeAwareAdapter.class.isInstance(adapter)) { + final TypeAwareAdapter typeAwareAdapter = TypeAwareAdapter.class.cast(adapter); + if (typeAwareAdapter.getTo() == String.class) { + return (T) adapter.to(JsonString.class.cast(jsonValue).getString()); + } + if (typeAwareAdapter.getTo() == JsonString.class) { + return (T) adapter.to(JsonString.class.cast(jsonValue)); + } + if (typeAwareAdapter.getTo() == CharSequence.class) { + return (T) adapter.to(JsonString.class.cast(jsonValue).getChars()); + } + } } if (JsonNumber.class.isInstance(jsonValue)) { final JsonNumber number = JsonNumber.class.cast(jsonValue); @@ -235,6 +250,7 @@ public class MappingParserImpl implements MappingParser { if (FALSE == valueType && (Boolean.class == targetType || boolean.class == targetType || Object.class == targetType)) { return (T) Boolean.FALSE; } + throw new IllegalArgumentException("Unsupported " + jsonValue + " for type " + targetType); } @@ -1093,10 +1109,13 @@ public class MappingParserImpl implements MappingParser { } /** - * @deprecated see MapperConfig + * @deprecated see MapperConfig - it is acually reversed so maybe not deprecated after all? */ private Adapter findAdapter(final Type aClass) { - final Adapter<?, ?> converter = config.getAdapters().get(new AdapterKey(aClass, String.class)); + if (config.getNoParserAdapterTypes().contains(aClass)) { + return null; + } + final Adapter<?, ?> converter = config.getAdapters().get(new AdapterKey(aClass, String.class, true)); if (converter != null) { return converter; } @@ -1108,6 +1127,17 @@ public class MappingParserImpl implements MappingParser { return enumConverter; } } + final List<AdapterKey> matched = config.getAdapters().keySet().stream() + .filter(k -> k.isAssignableFrom(aClass)) + .collect(toList()); + if (matched.size() == 1) { + final Adapter<?, ?> adapter = config.getAdapters().get(matched.iterator().next()); + if (TypeAwareAdapter.class.isInstance(adapter)) { + config.getAdapters().put(new AdapterKey(aClass, TypeAwareAdapter.class.cast(adapter).getTo()), adapter); + } + return adapter; + } + config.getNoParserAdapterTypes().add(aClass); return null; } diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/AdapterKey.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/AdapterKey.java index 9494ca2..27085c7 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/AdapterKey.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/AdapterKey.java @@ -24,10 +24,20 @@ public class AdapterKey { private final Type from; private final Type to; private final int hash; + private Class valueAsClass; + private Class<?> keyAsClass; public AdapterKey(final Type from, final Type to) { + this(from, to, false); + } + + public AdapterKey(final Type from, final Type to, final boolean lookup) { this.from = from; this.to = to; + if (!lookup) { + this.keyAsClass = Class.class.isInstance(from) ? Class.class.cast(from) : null; + this.valueAsClass = Class.class.isInstance(to) ? Class.class.cast(to) : null; + } int result = from.hashCode(); result = 31 * result + to.hashCode(); @@ -56,6 +66,14 @@ public class AdapterKey { } + public boolean isAssignableFrom(final Type type) { + return keyAsClass != null && Class.class.isInstance(type) && keyAsClass.isAssignableFrom(Class.class.cast(type)); + } + + public boolean isAssignableTo(final Type type) { + return valueAsClass != null && Class.class.isInstance(type) && valueAsClass.isAssignableFrom(Class.class.cast(type)); + } + @Override public int hashCode() { return hash;