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
commit c8e233e2f3c54037b8aa5d5d9bf165b550cc9641 Author: Romain Manni-Bucau <rmannibu...@gmail.com> AuthorDate: Sun Jun 20 16:28:42 2021 +0200 [JOHNZON-349] trying to not do reflection until we know we can, dedup annotation case --- .../converter/JsonbOffsetDateTimeConverter.java | 42 +++++++++ .../org/apache/johnzon/jsonb/CdiAdapterTest.java | 99 +++++++++++++++++----- .../jsonschema/JsonSchemaValidatorFactory.java | 11 ++- .../johnzon/jsonschema/regex/JavascriptRegex.java | 3 +- .../java/org/apache/johnzon/mapper/Mapper.java | 42 +++------ .../org/apache/johnzon/mapper/MapperConfig.java | 4 +- .../johnzon/mapper/MappingGeneratorImpl.java | 66 +++++++++------ .../apache/johnzon/mapper/MappingParserImpl.java | 86 +++++++++---------- .../java/org/apache/johnzon/mapper/Mappings.java | 19 ++--- .../mapper/internal/JsonPointerTracker.java | 15 +++- .../apache/johnzon/mapper/CircularObjectsTest.java | 20 +++-- .../mapper/internal/JsonPointerTrackerTest.java | 2 +- pom.xml | 2 +- 13 files changed, 258 insertions(+), 153 deletions(-) diff --git a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbOffsetDateTimeConverter.java b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbOffsetDateTimeConverter.java new file mode 100644 index 0000000..a78c1ac --- /dev/null +++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/converter/JsonbOffsetDateTimeConverter.java @@ -0,0 +1,42 @@ +/* + * 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.jsonb.converter; + +import javax.json.bind.annotation.JsonbDateFormat; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; + +public class JsonbOffsetDateTimeConverter extends JsonbDateConverterBase<OffsetDateTime> { + private static final ZoneId UTC = ZoneId.of("UTC"); + + public JsonbOffsetDateTimeConverter(final JsonbDateFormat dateFormat) { + super(dateFormat); + } + + @Override + public String toString(final OffsetDateTime instance) { + return formatter == null ? instance.toString() : instance.format(formatter); + } + + @Override + public OffsetDateTime fromString(final String text) { + return formatter == null ? OffsetDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(text)), UTC) : OffsetDateTime.parse(text, formatter); + } +} diff --git a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java index d071541..2ef3742 100644 --- a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java +++ b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/CdiAdapterTest.java @@ -18,55 +18,112 @@ */ package org.apache.johnzon.jsonb; +import org.apache.johnzon.jsonb.cdi.JohnzonCdiExtension; import org.apache.webbeans.config.WebBeansContext; import org.apache.webbeans.config.WebBeansFinder; +import org.apache.webbeans.corespi.DefaultSingletonService; +import org.apache.webbeans.corespi.scanner.xbean.CdiArchive; import org.apache.webbeans.corespi.se.DefaultScannerService; import org.apache.webbeans.lifecycle.StandaloneLifeCycle; import org.apache.webbeans.proxy.OwbNormalScopeProxy; +import org.apache.webbeans.service.ClassLoaderProxyService; +import org.apache.webbeans.spi.BeanArchiveService; import org.apache.webbeans.spi.ContainerLifecycle; +import org.apache.webbeans.spi.DefiningClassService; +import org.apache.webbeans.spi.LoaderService; +import org.apache.webbeans.spi.ResourceInjectionService; import org.apache.webbeans.spi.ScannerService; +import org.apache.webbeans.spi.api.ResourceReference; import org.apache.webbeans.util.WebBeansUtil; -import org.apache.xbean.finder.AnnotationFinder; -import org.apache.xbean.finder.archive.ClassesArchive; +import org.apache.webbeans.xml.DefaultBeanArchiveInformation; import org.junit.Test; import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.spi.Extension; import javax.inject.Inject; import javax.json.bind.Jsonb; import javax.json.bind.JsonbBuilder; import javax.json.bind.adapter.JsonbAdapter; import javax.json.bind.annotation.JsonbTypeAdapter; +import java.lang.annotation.Annotation; +import java.net.MalformedURLException; +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.Properties; -import static java.util.Collections.singletonMap; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; public class CdiAdapterTest { @Test - public void run() { - WebBeansFinder.clearInstances(WebBeansUtil.getCurrentClassLoader()); + public void run() throws Exception { + final ClassLoader currentClassLoader = WebBeansUtil.getCurrentClassLoader(); + WebBeansFinder.clearInstances(currentClassLoader); + final Map<Class<?>, Object> services = new HashMap<>(); + services.put(ScannerService.class, new DefaultScannerService() { + @Override + protected void configure() { + initFinder(); + final DefaultBeanArchiveInformation information = new DefaultBeanArchiveInformation("file://foo"); + information.setBeanDiscoveryMode(BeanArchiveService.BeanDiscoveryMode.ALL); + try { + archive.classesByUrl().put(information.getBdaUrl(), new CdiArchive.FoundClasses( + URI.create(information.getBdaUrl()).toURL(), asList( + Service.class.getName(), ModelAdapter.class.getName()), + information)); + } catch (final MalformedURLException e) { + throw new IllegalStateException(e); + } + } + }); + services.put(ResourceInjectionService.class, new ResourceInjectionService() { + @Override + public void injectJavaEEResources(final Object managedBeanInstance) { + System.out.println(); + } + + @Override + public <X, T extends Annotation> X getResourceReference(final ResourceReference<X, T> resourceReference) { + return null; + } + + @Override + public void clear() { + // no-op + } + }); + services.put(LoaderService.class, new LoaderService() { + @Override + public <T> List<T> load(final Class<T> serviceType, final ClassLoader loader) { + if (Extension.class == serviceType) { + return singletonList(serviceType.cast(new JohnzonCdiExtension())); + } + return Collections.emptyList(); + } + + @Override + public <T> List<T> load(final Class<T> serviceType) { + return emptyList(); + } + }); + final Properties properties = new Properties(); + properties.setProperty(DefiningClassService.class.getName(), ClassLoaderProxyService.class.getName()); + final WebBeansContext webBeansContext = new WebBeansContext(services, properties); + DefaultSingletonService.class.cast(WebBeansFinder.getSingletonService()).register( + currentClassLoader, webBeansContext); final ContainerLifecycle testLifecycle = new StandaloneLifeCycle(); - new WebBeansContext(singletonMap( - ScannerService.class, new DefaultScannerService() { - @Override - protected AnnotationFinder initFinder() { - return new AnnotationFinder(new ClassesArchive(Service.class, ModelAdapter.class)); - } - }), new Properties()); testLifecycle.startApplication(null); - try { - Jsonb jsonb = JsonbBuilder.create(); + try (final Jsonb jsonb = JsonbBuilder.create()) { assertEquals("{\"model\":\"5\"}", jsonb.toJson(new Root(new Model(5)))); - try { - AutoCloseable.class.cast(jsonb).close(); - } catch (final Exception e) { - fail(e.getMessage()); - } } finally { testLifecycle.stopApplication(null); - WebBeansFinder.clearInstances(WebBeansUtil.getCurrentClassLoader()); + WebBeansFinder.clearInstances(currentClassLoader); } } diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorFactory.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorFactory.java index 2fe4e27..14103bc 100644 --- a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorFactory.java +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/JsonSchemaValidatorFactory.java @@ -36,6 +36,7 @@ import java.util.stream.StreamSupport; import javax.json.JsonObject; import javax.json.JsonValue; +import org.apache.johnzon.jsonschema.regex.JavaRegex; import org.apache.johnzon.jsonschema.regex.JavascriptRegex; import org.apache.johnzon.jsonschema.spi.ValidationContext; import org.apache.johnzon.jsonschema.spi.ValidationExtension; @@ -76,7 +77,15 @@ public class JsonSchemaValidatorFactory implements AutoCloseable { private final List<ValidationExtension> extensions = new ArrayList<>(); // js is closer to default and actually most used in the industry - private final AtomicReference<Function<String, Predicate<CharSequence>>> regexFactory = new AtomicReference<>(JavascriptRegex::new); + private final AtomicReference<Function<String, Predicate<CharSequence>>> regexFactory = new AtomicReference<>(this::newRegexFactory); + + private Predicate<CharSequence> newRegexFactory(final String regex) { + try { + return new JavascriptRegex(regex); + } catch (final RuntimeException re) { + return new JavaRegex(regex); + } + } public JsonSchemaValidatorFactory() { extensions.addAll(createDefaultValidations()); diff --git a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/regex/JavascriptRegex.java b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/regex/JavascriptRegex.java index 9b83389..6ec93cf 100644 --- a/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/regex/JavascriptRegex.java +++ b/johnzon-jsonschema/src/main/java/org/apache/johnzon/jsonschema/regex/JavascriptRegex.java @@ -18,12 +18,11 @@ */ package org.apache.johnzon.jsonschema.regex; -import java.util.function.Predicate; - import javax.script.Bindings; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; +import java.util.function.Predicate; public class JavascriptRegex implements Predicate<CharSequence> { 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 b74c105..022eaee 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 @@ -114,8 +114,7 @@ public class Mapper implements Closeable { public <T> void writeArray(final Collection<T> object, final Writer stream) { try (final JsonGenerator generator = generatorFactory.createGenerator(stream(stream))) { - boolean dedup = Boolean.TRUE.equals(config.isDeduplicateObjects()); - writeObject(object, generator, null, dedup ? new JsonPointerTracker(null, "/") : null); + writeObject(object, generator, null, config.isDeduplicateObjects() ? JsonPointerTracker.ROOT : null); } } @@ -125,8 +124,7 @@ public class Mapper implements Closeable { public <T> void writeIterable(final Iterable<T> object, final Writer stream) { try (final JsonGenerator generator = generatorFactory.createGenerator(stream(stream))) { - boolean dedup = Boolean.TRUE.equals(config.isDeduplicateObjects()); - writeObject(object, generator, null, dedup ? new JsonPointerTracker(null, "/") : null); + writeObject(object, generator, null, config.isDeduplicateObjects() ? JsonPointerTracker.ROOT : null); } } @@ -162,8 +160,7 @@ public class Mapper implements Closeable { return provider.createValue(BigInteger.class.cast(object)); } final JsonObjectGenerator objectGenerator = new JsonObjectGenerator(builderFactory); - writeObject(object, objectGenerator, null, - isDedup(object.getClass()) ? new JsonPointerTracker(null, "/") : null); + writeObject(object, objectGenerator, null, null); return objectGenerator.getResult(); } @@ -192,8 +189,7 @@ public class Mapper implements Closeable { } public void writeObjectWithGenerator(final Object object, final JsonGenerator generator) { - writeObject(object, generator, null, - isDedup(object.getClass()) ? new JsonPointerTracker(null, "/") : null); + writeObject(object, generator, null, null); } public void writeObject(final Object object, final OutputStream stream) { @@ -205,9 +201,10 @@ public class Mapper implements Closeable { writeObject(object, new OutputStreamWriter(stream, charset)); } - private void writeObject(final Object object, final JsonGenerator generator, final Collection<String> ignored, JsonPointerTracker jsonPointer) { - final MappingGeneratorImpl mappingGenerator = new MappingGeneratorImpl(config, generator, mappings, jsonPointer != null); - mappingGenerator.doWriteObject(object, generator, true, ignored, jsonPointer); + private void writeObject(final Object object, final JsonGenerator generator, final Collection<String> ignored, + final JsonPointerTracker tracker) { + final MappingGeneratorImpl mappingGenerator = new MappingGeneratorImpl(config, generator, mappings); + mappingGenerator.doWriteObject(object, generator, true, ignored, tracker); } public String writeArrayAsString(final Collection<?> instance) { @@ -263,7 +260,7 @@ public class Mapper implements Closeable { public void close() { // no-op } - }, isDedup(clazz)).readObject(clazz); + }, null).readObject(clazz); } public <T> T readObject(final String string, final Type clazz) { @@ -370,26 +367,7 @@ public class Mapper implements Closeable { private <T> T mapObject(final Type clazz, final JsonReader reader) { - return new MappingParserImpl(config, mappings, reader, isDedup(clazz)).readObject(clazz); - } - - private boolean isDedup(final Type clazz) { - if (clazz instanceof Class && - JsonValue.class != clazz && JsonStructure.class != clazz && - JsonObject.class != clazz && JsonArray.class != clazz) { - Boolean dedup = config.isDeduplicateObjects(); - if (dedup == null) { - // TODO: never call it more than once per clazz, should be done after once ClassMapping is obtained, not here! - // -> revisit org.apache.johnzon.mapper.Mappings.findOrCreateClassMapping (isPrimitive should drop) - // -> revisit org.apache.johnzon.mapper.access.FieldAccessMode.isIgnored(java.lang.String, java.lang.Class<?>) - Mappings.ClassMapping classMapping = mappings.findOrCreateClassMapping(clazz); - if (classMapping != null) { - dedup = classMapping.isDeduplicateObjects(); - } - } - return dedup != null ? dedup : false; - } - return false; + return new MappingParserImpl(config, mappings, reader, null).readObject(clazz); } 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 eea1716..4d8bceb 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 @@ -412,8 +412,8 @@ public /* DON'T MAKE IT HIDDEN */ class MapperConfig implements Cloneable { return useBigDecimalForFloats; } - public Boolean isDeduplicateObjects() { - return deduplicateObjects; + public boolean isDeduplicateObjects() { + return Boolean.TRUE.equals(deduplicateObjects); } public boolean isSupportEnumContainerDeserialization() { diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java index d4d103c..bdfba2a 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MappingGeneratorImpl.java @@ -19,6 +19,7 @@ package org.apache.johnzon.mapper; import static java.util.Collections.emptyList; +import static java.util.Collections.emptyMap; import static java.util.stream.Collectors.toList; import org.apache.johnzon.mapper.internal.JsonPointerTracker; @@ -45,19 +46,12 @@ public class MappingGeneratorImpl implements MappingGenerator { private final MapperConfig config; private final JsonGenerator generator; private final Mappings mappings; - - private final Boolean isDeduplicateObjects; private Map<Object, String> jsonPointers; - - MappingGeneratorImpl(MapperConfig config, JsonGenerator jsonGenerator, final Mappings mappings, Boolean isDeduplicateObjects) { + MappingGeneratorImpl(MapperConfig config, JsonGenerator jsonGenerator, final Mappings mappings) { this.config = config; this.generator = jsonGenerator; this.mappings = mappings; - - this.isDeduplicateObjects = isDeduplicateObjects; - - this.jsonPointers = isDeduplicateObjects ? new HashMap<>() : Collections.emptyMap(); } @Override @@ -76,7 +70,7 @@ public class MappingGeneratorImpl implements MappingGenerator { try { if (Map.class.isInstance(object)) { writeValue(Map.class, false, false, false, false, true, null, key, object, - null, emptyList(), isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null, generator); + null, emptyList(), isDedup() ? JsonPointerTracker.ROOT : null, generator); } else if(writePrimitives(key, objectClass, object, generator)) { // no-op } else if (Enum.class.isAssignableFrom(objectClass)) { @@ -85,10 +79,10 @@ public class MappingGeneratorImpl implements MappingGenerator { generator.write(key, adaptedValue); } else if (objectClass.isArray()) { writeValue(Map.class, false, false, true, false, false, null, key, object, - null, emptyList(), isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null, generator); + null, emptyList(), isDedup() ? JsonPointerTracker.ROOT : null, generator); } else if (Iterable.class.isInstance(object)) { writeValue(Map.class, false, false, false, true, false, null, key, object, - null, emptyList(), isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null, generator); + null, emptyList(), isDedup() ? JsonPointerTracker.ROOT : null, generator); } else { final ObjectConverter.Writer objectConverter = config.findObjectConverterWriter(objectClass); if (objectConverter != null) { @@ -98,7 +92,7 @@ public class MappingGeneratorImpl implements MappingGenerator { dynamicMappingGenerator.flushIfNeeded(); } else { writeValue(objectClass, false, false, false, false, false, null, key, object, - null, emptyList(), isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null, generator); + null, emptyList(), isDedup() ? JsonPointerTracker.ROOT : null, generator); } } } catch (final InvocationTargetException | IllegalAccessException e) { @@ -115,11 +109,15 @@ public class MappingGeneratorImpl implements MappingGenerator { } else if (object instanceof JsonValue) { generator.write((JsonValue) object); } else { - doWriteObject(object, generator, false, null, isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null); + doWriteObject(object, generator, false, null, isDedup() ? JsonPointerTracker.ROOT : null); } return this; } + private boolean isDedup() { + return config.isDeduplicateObjects() || (jsonPointers != null && jsonPointers != Collections.<Object, String>emptyMap()); + } + public void doWriteObject(Object object, JsonGenerator generator, boolean writeBody, final Collection<String> ignoredProperties, JsonPointerTracker jsonPointer) { @@ -184,6 +182,14 @@ public class MappingGeneratorImpl implements MappingGenerator { dynamicMappingGenerator.flushIfNeeded(); } } else { + if (classMapping == null) { // will be created anyway now so force it and if it has an adapter respect it + final Mappings.ClassMapping mapping = mappings.findOrCreateClassMapping(objectClass); + if (mapping != null && mapping.adapter != null) { + final Object result = mapping.adapter.from(object); + doWriteObject(result, generator, writeBody, ignoredProperties, jsonPointer); + return; + } + } if (writeBody) { generator.writeStartObject(); } @@ -345,17 +351,23 @@ public class MappingGeneratorImpl implements MappingGenerator { private boolean doWriteObjectBody(final Object object, final Collection<String> ignored, final JsonPointerTracker jsonPointer, final JsonGenerator generator) throws IllegalAccessException, InvocationTargetException { - - if (jsonPointer != null) { - jsonPointers.put(object, jsonPointer.toString()); - } - final Class<?> objectClass = object.getClass(); final Mappings.ClassMapping classMapping = mappings.findOrCreateClassMapping(objectClass); if (classMapping == null) { throw new MapperException("No mapping for " + objectClass.getName()); } + if (jsonPointers == null) { + if (classMapping.deduplicateObjects || config.isDeduplicateObjects()) { + jsonPointers = new HashMap<>(); + jsonPointers.putIfAbsent(object, jsonPointer == null ? "/" : jsonPointer.toString()); + } else { + jsonPointers = emptyMap(); + } + } else if (isDedup()) { + jsonPointers.putIfAbsent(object, jsonPointer == null ? "/" : jsonPointer.toString()); + } + if (classMapping.writer != null) { final DynamicMappingGenerator gen = new DynamicMappingGenerator.SkipEnclosingWriteEnd(this, null, generator); classMapping.writer.writeJson(object, gen); @@ -409,7 +421,7 @@ public class MappingGeneratorImpl implements MappingGenerator { val, getter.objectConverter, getter.ignoreNested, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, getterEntry.getKey()) : null, + isDedup() ? new JsonPointerTracker(jsonPointer, getterEntry.getKey()) : null, generator); } } @@ -527,7 +539,7 @@ public class MappingGeneratorImpl implements MappingGenerator { generator.writeStartArray(key); while (iterator.hasNext()) { final Object o = iterator.next(); - String valJsonPointer = jsonPointers.get(o); + String valJsonPointer = jsonPointers == null ? null : jsonPointers.get(o); if (valJsonPointer != null) { // write JsonPointer instead of the original object writePrimitives(valJsonPointer); @@ -544,7 +556,7 @@ public class MappingGeneratorImpl implements MappingGenerator { dynamicMappingGenerator.flushIfNeeded(); } else { writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null); } } i++; @@ -648,21 +660,23 @@ public class MappingGeneratorImpl implements MappingGenerator { Object[] oArrayValue = (Object[]) arrayValue; for (int i = 0; i < length; i++) { final Object o = oArrayValue[i]; - writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, null); + writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null); } } else { // must be object arrays for (int i = 0; i < length; i++) { Object[] oArrayValue = (Object[]) arrayValue; final Object o = oArrayValue[i]; - String valJsonPointer = jsonPointers.get(o); + String valJsonPointer = jsonPointers == null ? null : jsonPointers.get(o); if (valJsonPointer != null) { // write the JsonPointer as String natively generator.write(valJsonPointer); } else if (o instanceof JsonValue) { generator.write((JsonValue) o); } else { - writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null); + writeItem(itemConverter != null ? itemConverter.from(o) : o, ignoredProperties, + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null); } } } @@ -682,7 +696,7 @@ public class MappingGeneratorImpl implements MappingGenerator { writeArray(o.getClass(), null, null, o, ignoredProperties, jsonPointer); } } else { - String valJsonPointer = jsonPointers.get(o); + String valJsonPointer = jsonPointers == null ? null : jsonPointers.get(o); if (valJsonPointer != null) { // write the JsonPointer instead generator.write(valJsonPointer); @@ -706,7 +720,7 @@ public class MappingGeneratorImpl implements MappingGenerator { if (t == null) { generator.writeNull(); } else { - writeItem(t, ignoredProperties, isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null); + writeItem(t, ignoredProperties, isDedup() ? new JsonPointerTracker(jsonPointer, i) : null); } } i++; 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 80ca4ad..e376e87 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 @@ -98,7 +98,6 @@ public class MappingParserImpl implements MappingParser { private final MapperConfig config; private final Mappings mappings; - private final boolean isDeduplicateObjects; private final JsonReader jsonReader; @@ -110,19 +109,10 @@ public class MappingParserImpl implements MappingParser { private Map<String, Object> jsonPointers; - public MappingParserImpl(MapperConfig config, Mappings mappings, JsonReader jsonReader, boolean isDeduplicateObjects) { + public MappingParserImpl(MapperConfig config, Mappings mappings, JsonReader jsonReader, Map<String, Object> jsonPointers) { this.config = config; this.mappings = mappings; - this.jsonReader = jsonReader; - - this.isDeduplicateObjects = isDeduplicateObjects; - - if (isDeduplicateObjects) { - jsonPointers = new HashMap<>(); - } else { - jsonPointers = Collections.emptyMap(); - } } @@ -150,8 +140,7 @@ public class MappingParserImpl implements MappingParser { if (JsonObject.class.isInstance(jsonValue)) { return (T) buildObject( targetType, JsonObject.class.cast(jsonValue), applyObjectConverter, - isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null, - skippedConverters); + null, skippedConverters); } if (JsonString.class.isInstance(jsonValue)) { if ((targetType == String.class || targetType == Object.class)) { @@ -222,7 +211,7 @@ public class MappingParserImpl implements MappingParser { if (asClass.isArray()) { final Class componentType = asClass.getComponentType(); return (T) buildArrayWithComponentType(jsonArray, componentType, config.findAdapter(componentType), - isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null, Object.class); + isDedup() ? JsonPointerTracker.ROOT : null, Object.class); } if (Collection.class.isAssignableFrom(asClass)) { return readObject(jsonValue, new JohnzonParameterizedType(asClass, Object.class), applyObjectConverter, skippedConverters); @@ -238,11 +227,11 @@ public class MappingParserImpl implements MappingParser { final Type arg = pt.getActualTypeArguments()[0]; return (T) mapCollection(mapping, jsonArray, Class.class.isInstance(arg) ? config.findAdapter(Class.class.cast(arg)) : null, - null, isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null, Object.class); + null, isDedup() ? JsonPointerTracker.ROOT : null, Object.class); } if (Object.class == targetType) { return (T) new ArrayList(asList(Object[].class.cast(buildArrayWithComponentType(jsonArray, Object.class, null, - isDeduplicateObjects ? new JsonPointerTracker(null, "/") : null, Object.class)))); + isDedup() ? JsonPointerTracker.ROOT : null, Object.class)))); } } if (NULL == valueType) { @@ -258,6 +247,9 @@ public class MappingParserImpl implements MappingParser { throw new IllegalArgumentException("Unsupported " + jsonValue + " for type " + targetType); } + private boolean isDedup() { + return jsonPointers != Collections.<String, Object>emptyMap(); + } private Object buildObject(final Type inType, final JsonObject object, final boolean applyObjectConverter, final JsonPointerTracker jsonPointer, final Collection<Class<?>> skippedConverters) { @@ -389,8 +381,15 @@ public class MappingParserImpl implements MappingParser { t = classMapping.factory.create(createParameters(classMapping, object, jsonPointer)); } // store the new object under it's jsonPointer in case it gets referenced later - if (isDeduplicateObjects) { - jsonPointers.put(jsonPointer.toString(), t); + if (jsonPointers == null) { + if (classMapping.deduplicateObjects || config.isDeduplicateObjects()) { + jsonPointers = new HashMap<>(); + jsonPointers.put(jsonPointer == null ? "/" : jsonPointer.toString(), t); + } else { + jsonPointers = Collections.emptyMap(); + } + } else if (isDedup()) { + jsonPointers.put(jsonPointer == null ? "/" : jsonPointer.toString(), t); } for (final Map.Entry<String, JsonValue> jsonEntry : object.entrySet()) { @@ -428,7 +427,7 @@ public class MappingParserImpl implements MappingParser { final Object convertedValue = toValue( existingInstance, jsonValue, value.converter, value.itemConverter, value.paramType, value.objectConverter, - new JsonPointerTracker(jsonPointer, jsonEntry.getKey()), inType); + isDedup() ? new JsonPointerTracker(jsonPointer, jsonEntry.getKey()) : null, inType); if (convertedValue != null) { setterMethod.write(t, convertedValue); } @@ -442,7 +441,7 @@ public class MappingParserImpl implements MappingParser { classMapping.anySetter.invoke(t, key, toValue(null, entry.getValue(), null, null, classMapping.anySetter.getGenericParameterTypes()[1], null, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, entry.getKey()) : null, type)); + isDedup() ? new JsonPointerTracker(jsonPointer, entry.getKey()) : null, type)); } catch (final IllegalAccessException e) { throw new IllegalStateException(e); } catch (final InvocationTargetException e) { @@ -457,7 +456,7 @@ public class MappingParserImpl implements MappingParser { .filter(it -> !classMapping.setters.containsKey(it.getKey())) .collect(toMap(Map.Entry::getKey, e -> toValue(null, e.getValue(), null, null, ParameterizedType.class.cast(classMapping.anyField.getGenericType()).getActualTypeArguments()[1], null, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, e.getKey()) : null, tRef)))); + isDedup() ? new JsonPointerTracker(jsonPointer, e.getKey()) : null, tRef)))); } catch (final IllegalAccessException e) { throw new IllegalStateException(e); } @@ -724,16 +723,15 @@ public class MappingParserImpl implements MappingParser { final String string = JsonString.class.cast(jsonValue).getString(); if (itemConverter == null) { // check whether we have a jsonPointer to a previously deserialised object - if (!String.class.equals(type)) { - Object o = jsonPointers.get(string); + if (isDedup() && !String.class.equals(type)) { + Object o = jsonPointers == null ? null : jsonPointers.get(string); if (o != null) { return o; } } return convertTo(type, string); - } else { - return itemConverter.to(string); } + return itemConverter.to(string); } throw new MapperException("Unable to parse " + jsonValue + " to " + type); @@ -810,7 +808,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (boolean) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -820,7 +818,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (byte) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -830,7 +828,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (char) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -840,7 +838,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (short) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -850,7 +848,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (int) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -860,7 +858,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (long) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -870,7 +868,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (float) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -880,7 +878,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (double) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -892,7 +890,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (Boolean) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -902,7 +900,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (Byte) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -912,7 +910,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (Character) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -922,7 +920,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (Short) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -932,7 +930,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (Integer) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -942,7 +940,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (Long) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -952,7 +950,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (Float) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -962,7 +960,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { array[i] = (Double) toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType); i++; } return array; @@ -973,7 +971,7 @@ public class MappingParserImpl implements MappingParser { int i = 0; for (final JsonValue value : jsonArray) { Array.set(array, i, toObject(null, value, componentType, itemConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType)); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType)); i++; } return array; @@ -1009,7 +1007,7 @@ public class MappingParserImpl implements MappingParser { collection.add(JsonValue.NULL.equals(value) ? null : toValue(null, value, null, itemConverter, mapping.arg, objectConverter, - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, i) : null, rootType)); + isDedup() ? new JsonPointerTracker(jsonPointer, i) : null, rootType)); i++; } @@ -1045,7 +1043,7 @@ public class MappingParserImpl implements MappingParser { mapping.factory.getParameterItemConverter()[i], parameterType, mapping.factory.getObjectConverter()[i], - isDeduplicateObjects ? new JsonPointerTracker(jsonPointer, paramName) : null, + isDedup() ? new JsonPointerTracker(jsonPointer, paramName) : null, mapping.clazz); //X TODO ObjectConverter in @JohnzonConverter with Constructors! if (objects[i] == null) { objects[i] = getPrimitiveDefault(parameterType); 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 23141be..686f285 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 @@ -76,10 +76,7 @@ public class Mappings { public final Field anyField; public final Method mapAdder; public final Class<?> mapAdderType; - - - private Boolean deduplicateObjects; - private boolean deduplicationEvaluated = false; + public boolean deduplicateObjects; protected ClassMapping(final Class<?> clazz, final AccessMode.Factory factory, final Map<String, Getter> getters, final Map<String, Setter> setters, @@ -99,17 +96,15 @@ public class Mappings { this.anyField = anyField; this.mapAdder = mapAdder; this.mapAdderType = mapAdder == null ? null : mapAdder.getParameterTypes()[1]; + this.deduplicateObjects = isDeduplicateObjects(); } - public Boolean isDeduplicateObjects() { - if (!deduplicationEvaluated) { - JohnzonDeduplicateObjects jdo = clazz.getAnnotation(JohnzonDeduplicateObjects.class); - if (jdo != null){ - deduplicateObjects = jdo.value(); - } - deduplicationEvaluated = true; + private Boolean isDeduplicateObjects() { + final JohnzonDeduplicateObjects jdo = clazz.getAnnotation(JohnzonDeduplicateObjects.class); + if (jdo != null){ + return jdo.value(); } - return deduplicateObjects; + return false; } } diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/JsonPointerTracker.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/JsonPointerTracker.java index 201a609..fe376c0 100644 --- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/JsonPointerTracker.java +++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/internal/JsonPointerTracker.java @@ -27,6 +27,13 @@ import org.apache.johnzon.core.JsonPointerUtil; * For use in recursive generator and parser method calls to defer string operations. */ public class JsonPointerTracker { + public static final JsonPointerTracker ROOT = new JsonPointerTracker(null, null) { + @Override + public String toString() { + return "/"; + } + }; + private final JsonPointerTracker parent; private final String currentNode; @@ -55,13 +62,13 @@ public class JsonPointerTracker { public String toString() { if (jsonPointer == null) { if (parent != null) { - if (parent.parent == null) { + jsonPointer = (parent != ROOT ? parent + "/" : "/") + JsonPointerUtil.encode(currentNode); + } else { + if (currentNode != null) { jsonPointer = "/" + JsonPointerUtil.encode(currentNode); } else { - jsonPointer = parent.toString() + "/" + JsonPointerUtil.encode(currentNode); + jsonPointer = "/"; } - } else { - jsonPointer = "/"; } } diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java index b4c8af6..98665a0 100644 --- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java +++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/CircularObjectsTest.java @@ -20,6 +20,7 @@ package org.apache.johnzon.mapper; import java.io.StringReader; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import org.junit.Test; @@ -42,13 +43,13 @@ public class CircularObjectsTest { john.setMarriedTo(marry); marry.setMarriedTo(john); - Mapper mapper = new MapperBuilder().setAccessModeName("field").setDeduplicateObjects(true).build(); + Mapper mapper = new MapperBuilder() + .setAccessModeName("field") + .setDeduplicateObjects(true) + .setAttributeOrder(Comparator.naturalOrder()) + .build(); String ser = mapper.writeObjectAsString(john); - - assertNotNull(ser); - assertTrue(ser.contains("\"name\":\"John\"")); - assertTrue(ser.contains("\"marriedTo\":\"/\"")); - assertTrue(ser.contains("\"name\":\"Marry\"")); + assertEquals("{\"kids\":[],\"marriedTo\":{\"kids\":[],\"marriedTo\":\"/\",\"name\":\"Marry\"},\"name\":\"John\"}", ser); // and now de-serialise it back Person john2 = mapper.readObject(ser, Person.class); @@ -164,7 +165,12 @@ public class CircularObjectsTest { sue.setFather(karl); sue.setMother(andrea); - Mapper mapper = new MapperBuilder().setAccessModeName("field").setDeduplicateObjects(true).build(); + Mapper mapper = new MapperBuilder() + .setAccessModeName("field") + .setDeduplicateObjects(true) + .setAttributeOrder(Comparator.naturalOrder()) + .setPretty(true) + .build(); // test deep array Person[] people = new Person[4]; diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/internal/JsonPointerTrackerTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/internal/JsonPointerTrackerTest.java index 1bcc7f6..15df6d7 100644 --- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/internal/JsonPointerTrackerTest.java +++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/internal/JsonPointerTrackerTest.java @@ -26,7 +26,7 @@ public class JsonPointerTrackerTest { @Test public void testJsonPointerTracker() { - JsonPointerTracker jptRoot = new JsonPointerTracker(null, "/"); + JsonPointerTracker jptRoot = JsonPointerTracker.ROOT; Assert.assertEquals("/", jptRoot.toString()); diff --git a/pom.xml b/pom.xml index b5f776f..4699bc6 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ <checkstyle.version>2.15</checkstyle.version> <!-- checkstyle > 2.15 version do not support java 6 --> <!-- JVM values for surefire plugin --> <surefire.jvm.params>-Xms1024m -Xmx2048m -Dfile.encoding=UTF-8</surefire.jvm.params> - <owb.version>2.0.20</owb.version> + <owb.version>2.0.23</owb.version> </properties> <modules>