From c81cc0f721cec43f7e955fff2fc19d0a7f192efb Mon Sep 17 00:00:00 2001 From: Michael Dombrowski Date: Tue, 11 May 2021 10:42:49 -0700 Subject: [PATCH 1/2] feature: support JsonProperty annotation on enum values --- .../AnnotationBasedIntrospector.java | 28 ++++----- .../AnnotationBasedValueRWModifier.java | 63 +++++++++++++++---- .../jr/annotationsupport/ASTestBase.java | 5 +- .../jr/annotationsupport/BasicRenameTest.java | 36 +++++++++++ .../jr/ob/impl/ValueReaderLocator.java | 37 ++++++++--- .../jr/ob/impl/ValueWriterLocator.java | 14 ++++- 6 files changed, 145 insertions(+), 38 deletions(-) diff --git a/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java b/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java index de88dbef..79811d88 100644 --- a/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java +++ b/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java @@ -31,9 +31,9 @@ public class AnnotationBasedIntrospector * Visibility settings to use for auto-detecting accessors. */ protected final JsonAutoDetect.Value _visibility; - + // // // State (collected properties, related) - + protected final Map _props = new HashMap(); // // // State only for deserialization: @@ -237,7 +237,7 @@ protected void _findFields(final Class currType) for (Field f : currType.getDeclaredFields()) { // Does not include static fields, but there are couple of things we do // not include regardless: - if (f.isEnumConstant() || f.isSynthetic()) { + if (f.isSynthetic()) { continue; } // otherwise, first things first; explicit ignoral? @@ -273,7 +273,7 @@ protected void _findMethods() { protected void _findMethods(final Class currType) { - if (currType == null || currType == Object.class) { + if (currType == null || currType == Object.class || currType == Enum.class) { return; } // Start with base type methods (so overrides work) @@ -350,7 +350,7 @@ protected void _checkGetterMethod(Method m) acc = APropAccessor.createVisible(implName, m); } else { acc = APropAccessor.createExplicit(explName, m); - } + } } } _propBuilder(implName).getter = acc; @@ -397,7 +397,7 @@ protected void _checkSetterMethod(Method m) acc = APropAccessor.createVisible(implName, m); } else { acc = APropAccessor.createExplicit(explName, m); - } + } } } _propBuilder(implName).setter = acc; @@ -426,7 +426,7 @@ protected boolean _isGetterVisible(Method m, boolean isIsGetter) { protected boolean _isSetterVisible(Method m) { return _visibility.getSetterVisibility().isVisible(m); } - + /* /********************************************************************** /* Internal methods, annotation introspection @@ -449,7 +449,7 @@ protected String _findExplicitName(AnnotatedElement m) { * Lookup method for finding possible annotated order of property names * for the type this introspector is to introspect * - * @return List of property names that defines order (possibly partial); if + * @return List of property names that defines order (possibly partial); if * none, empty List (but never null) */ protected List _findNameSortOrder() { @@ -465,7 +465,7 @@ protected List _findNameSortOrder() { * for the type this introspector is to introspect that should be ignored * (both for serialization and deserialization). * - * @return List of property names that defines order (possibly partial); if + * @return List of property names that defines order (possibly partial); if * none, empty List (but never null) */ protected Collection _findIgnorableNames() { @@ -480,13 +480,13 @@ protected Collection _findIgnorableNames() { protected ANN _find(AnnotatedElement elem, Class annotationType) { return elem.getAnnotation(annotationType); } - + /* /********************************************************************** /* Internal methods, other /********************************************************************** */ - + protected APropBuilder _propBuilder(String name) { APropBuilder b = _props.get(name); if (b == null) { @@ -546,7 +546,7 @@ protected static String _decap(String name) { /* Helper classes /********************************************************************** */ - + protected static class APropBuilder implements Comparable { @@ -615,7 +615,7 @@ private static APropAccessor _merge(APropAccesso // should be fine to take first one return a1; } - + public APropBuilder withName(String newName) { APropBuilder newB = new APropBuilder(this, newName); newB.field = field; @@ -676,7 +676,7 @@ private static Set _collectAliases(APropAccessor acc, Set col } return collectedAliases; } - + private String _firstExplicit(APropAccessor acc1, APropAccessor acc2, APropAccessor acc3) { diff --git a/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedValueRWModifier.java b/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedValueRWModifier.java index ef575d10..24035b6b 100644 --- a/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedValueRWModifier.java +++ b/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedValueRWModifier.java @@ -1,14 +1,22 @@ package com.fasterxml.jackson.jr.annotationsupport; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; + import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.jr.ob.api.ReaderWriterModifier; +import com.fasterxml.jackson.jr.ob.api.ValueWriter; import com.fasterxml.jackson.jr.ob.impl.JSONReader; import com.fasterxml.jackson.jr.ob.impl.JSONWriter; import com.fasterxml.jackson.jr.ob.impl.POJODefinition; -public class AnnotationBasedValueRWModifier - extends ReaderWriterModifier -{ +public class AnnotationBasedValueRWModifier extends ReaderWriterModifier { + // Matches SER_ENUM code in ValueLocatorBase + protected static final int SER_ENUM = 23; + /** * Visibility settings to use for auto-detecting accessors. */ @@ -19,17 +27,48 @@ public AnnotationBasedValueRWModifier(JsonAutoDetect.Value visibility) { } @Override - public POJODefinition pojoDefinitionForDeserialization(JSONReader readContext, - Class pojoType) - { - return AnnotationBasedIntrospector.pojoDefinitionForDeserialization(readContext, - pojoType, _visibility); + public POJODefinition pojoDefinitionForDeserialization(JSONReader readContext, Class pojoType) { + return AnnotationBasedIntrospector.pojoDefinitionForDeserialization(readContext, pojoType, _visibility); + } + + @Override + public POJODefinition pojoDefinitionForSerialization(JSONWriter writeContext, Class pojoType) { + return AnnotationBasedIntrospector.pojoDefinitionForSerialization(writeContext, pojoType, _visibility); } @Override - public POJODefinition pojoDefinitionForSerialization(JSONWriter writeContext, - Class pojoType) { - return AnnotationBasedIntrospector.pojoDefinitionForSerialization(writeContext, - pojoType, _visibility); + public ValueWriter overrideStandardValueWriter(JSONWriter writeContext, Class type, int stdTypeId) { + if (stdTypeId == SER_ENUM) { + return new EnumWriter(type); + } + return null; + } + + private static class EnumWriter implements ValueWriter { + private final Class _valueType; + private final Map enumMap; + + public EnumWriter(Class type) { + _valueType = type; + enumMap = new HashMap(); + Field[] fields = type.getDeclaredFields(); + for (Field field : fields) { + if (field.isAnnotationPresent(JsonProperty.class)) { + enumMap.put(field.getName(), field.getAnnotation(JsonProperty.class).value()); + } else { + enumMap.put(field.getName(), field.getName()); + } + } + } + + @Override + public void writeValue(JSONWriter context, JsonGenerator g, Object value) throws IOException { + context.writeValue(enumMap.get(((Enum) value).name())); + } + + @Override + public Class valueType() { + return _valueType; + } } } diff --git a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/ASTestBase.java b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/ASTestBase.java index d66aa95e..00a803a6 100644 --- a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/ASTestBase.java +++ b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/ASTestBase.java @@ -2,6 +2,7 @@ import java.util.Arrays; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.jr.ob.JSON; import junit.framework.TestCase; @@ -10,6 +11,8 @@ public abstract class ASTestBase extends TestCase { protected enum ABC { A, B, C; } + protected enum ABCRename { @JsonProperty("A1") A, @JsonProperty("B1") B, C; } + protected static class NameBean { protected String first, last; @@ -25,7 +28,7 @@ public NameBean(String f, String l) { public void setFirst(String n) { first = n; } public void setLast(String n) { last = n; } } - + protected void verifyException(Throwable e, String... matches) { String msg = e.getMessage(); diff --git a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicRenameTest.java b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicRenameTest.java index cc25943f..bfde917a 100644 --- a/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicRenameTest.java +++ b/jr-annotation-support/src/test/java/com/fasterxml/jackson/jr/annotationsupport/BasicRenameTest.java @@ -60,4 +60,40 @@ public void testBasicRenameOnDeserialize() throws Exception assertEquals("Bob", result._first); assertEquals("Burger", result._last); } + + public void testEnumRenameOnSerialize() throws Exception + { + ABCRename inputA = ABCRename.A; + // default + assertEquals(a2q("\"A\""), JSON.std.asString(inputA)); + // with annotations + assertEquals(a2q("\"A1\""), JSON_WITH_ANNO.asString(inputA)); + + ABCRename inputB = ABCRename.B; + // default + assertEquals(a2q("\"B\""), JSON.std.asString(inputB)); + // with annotations + assertEquals(a2q("\"B1\""), JSON_WITH_ANNO.asString(inputB)); + + ABCRename inputC = ABCRename.C; + // default + assertEquals(a2q("\"C\""), JSON.std.asString(inputC)); + // with annotations + assertEquals(a2q("\"C\""), JSON_WITH_ANNO.asString(inputC)); + } + + public void testEnumRenameOnDeserialize() throws Exception + { + String jsonA = a2q("\"A1\""); + ABCRename resultA = JSON_WITH_ANNO.beanFrom(ABCRename.class, jsonA); + assertEquals(ABCRename.A, resultA); + + String jsonB = a2q("\"B1\""); + ABCRename resultB = JSON_WITH_ANNO.beanFrom(ABCRename.class, jsonB); + assertEquals(ABCRename.B, resultB); + + String jsonC = a2q("\"C\""); + ABCRename resultC = JSON_WITH_ANNO.beanFrom(ABCRename.class, jsonC); + assertEquals(ABCRename.C, resultC); + } } diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java index 47069306..b4a2e4c0 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueReaderLocator.java @@ -64,7 +64,7 @@ public class ValueReaderLocator /* Caching /********************************************************************** */ - + /** * Set of {@link ValueReader}s that we have resolved */ @@ -100,7 +100,7 @@ public class ValueReaderLocator /* Instance state, caching /********************************************************************** */ - + /** * Reusable lookup key; only used by per-thread instances. */ @@ -143,14 +143,14 @@ protected ValueReaderLocator(ValueReaderLocator base, // create new cache as there may be custom writers: _knownReaders = new ConcurrentHashMap(10, 0.75f, 2); _readerLock = new Object(); - + _features = base._features; _readContext = base._readContext; _readerProvider = rwp; _readerModifier = rwm; _typeResolver = base._typeResolver; } - + public final static ValueReaderLocator blueprint(ReaderWriterProvider rwp, ReaderWriterModifier rwm) { return new ValueReaderLocator(rwp, rwm); } @@ -168,7 +168,7 @@ public ValueReaderLocator with(ReaderWriterModifier rwm) { } return new ValueReaderLocator(this, _readerProvider, rwm); } - + public ValueReaderLocator perOperationInstance(JSONReader r, int features) { return new ValueReaderLocator(this, features & CACHE_FLAGS, r); } @@ -188,7 +188,7 @@ public ValueReaderLocator perOperationInstance(JSONReader r, int features) { /* Public API, operations /********************************************************************** */ - + /** * Method used during deserialization to find handler for given * non-generic type: will first check for already resolved (and cached) readers @@ -295,10 +295,29 @@ protected ValueReader arrayReader(Class contextType, Class arrayType) { } protected ValueReader enumReader(Class enumType) { + // Call pojoDefinitionForDeserialization so that the annotation support extension can get custom names for + // enum values + POJODefinition def = null; + if (_readerModifier != null) { + def = _readerModifier.pojoDefinitionForDeserialization(_readContext, enumType); + } + Map byName = new HashMap(); Object[] enums = enumType.getEnumConstants(); - Map byName = new HashMap(); - for (Object e : enums) { - byName.put(e.toString(), e); + if (def == null) { + for (Object e : enums) { + byName.put(e.toString(), e); + } + } else { + for (POJODefinition.Prop e : def.getProperties()) { + if (e.field != null && e.field.isEnumConstant()) { + try { + byName.put(e.name, e.field.get(null)); + } catch (IllegalAccessException ex) { + // Don't believe that this should be possible, but raise it up just in case + throw new RuntimeException(ex); + } + } + } } return new EnumReader(enumType, enums, byName); } diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueWriterLocator.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueWriterLocator.java index b14e2633..aa3decc9 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueWriterLocator.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/ValueWriterLocator.java @@ -251,7 +251,7 @@ private int _modifyAndRegisterWriter(Class rawType, ValueWriter w) { } return _registerWriter(rawType, w); } - + private int _registerWriter(Class rawType, ValueWriter valueWriter) { // Due to concurrent access, possible that someone might have added it synchronized (_knownWriters) { @@ -269,7 +269,7 @@ private int _registerWriter(Class rawType, ValueWriter valueWriter) { return typeId; } } - + protected BeanPropertyWriter[] _resolveBeanForSer(Class raw, POJODefinition beanDef) { final List rawProps = beanDef.getProperties(); @@ -310,6 +310,16 @@ protected BeanPropertyWriter[] _resolveBeanForSer(Class raw, POJODefinition b } } int typeId = _findSimpleType(type, true); + // Give plugin the opportunity to override standard value writer + if (_writerModifier != null && typeId != 0) { + Integer I = _knownSerTypes.get(new ClassKey(type, _features)); + if (I == null) { + ValueWriter w = _writerModifier.overrideStandardValueWriter(_writeContext, type, typeId); + if (w != null) { + typeId = _registerWriter(type, w); + } + } + } props.add(new BeanPropertyWriter(typeId, rawProp.name, rawProp.field, m)); } int plen = props.size(); From 7805a6864266708e3c59a983451aa499aeecc94e Mon Sep 17 00:00:00 2001 From: Michael Dombrowski Date: Thu, 13 May 2021 21:19:53 -0700 Subject: [PATCH 2/2] fix build --- .../jr/annotationsupport/AnnotationBasedIntrospector.java | 3 ++- .../fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java b/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java index d4289c7a..3d87184f 100644 --- a/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java +++ b/jr-annotation-support/src/main/java/com/fasterxml/jackson/jr/annotationsupport/AnnotationBasedIntrospector.java @@ -11,6 +11,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.fasterxml.jackson.jr.ob.JSON; import com.fasterxml.jackson.jr.ob.impl.JSONReader; import com.fasterxml.jackson.jr.ob.impl.JSONWriter; import com.fasterxml.jackson.jr.ob.impl.POJODefinition; @@ -414,7 +415,7 @@ protected void _checkSetterMethod(Method m) protected boolean _isFieldVisible(Field f) { // Consider transient and static-final to be non-visible // TODO: (maybe?) final - return !(Modifier.isFinal(f.getModifiers()) && Modifier.isStatic(f.getModifiers())) + return !(Modifier.isFinal(f.getModifiers()) && Modifier.isStatic(f.getModifiers()) && !f.isEnumConstant()) && !Modifier.isTransient(f.getModifiers()) && _visibility.getFieldVisibility().isVisible(f); } diff --git a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java index a9fc87fd..86a12767 100644 --- a/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java +++ b/jr-objects/src/main/java/com/fasterxml/jackson/jr/ob/impl/BeanPropertyIntrospector.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.TreeMap; +import com.fasterxml.jackson.jr.ob.JSON; import com.fasterxml.jackson.jr.ob.impl.POJODefinition.Prop; import com.fasterxml.jackson.jr.ob.impl.POJODefinition.PropBuilder;