Repository: incubator-juneau Updated Branches: refs/heads/master 4b2719c0d -> 4fb5136f8
Simplify @Bean(subTypes) annotation. Project: http://git-wip-us.apache.org/repos/asf/incubator-juneau/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-juneau/commit/4fb5136f Tree: http://git-wip-us.apache.org/repos/asf/incubator-juneau/tree/4fb5136f Diff: http://git-wip-us.apache.org/repos/asf/incubator-juneau/diff/4fb5136f Branch: refs/heads/master Commit: 4fb5136f8fe6fa806c86a5779d52c37185e2c3ea Parents: 4b2719c Author: jamesbognar <[email protected]> Authored: Tue Aug 30 16:11:57 2016 -0400 Committer: jamesbognar <[email protected]> Committed: Tue Aug 30 16:11:57 2016 -0400 ---------------------------------------------------------------------- .../java/org/apache/juneau/BeanContext.java | 11 +++ .../main/java/org/apache/juneau/ClassMeta.java | 12 +++- .../main/java/org/apache/juneau/ObjectMap.java | 13 +++- .../java/org/apache/juneau/annotation/Bean.java | 73 +++++++++++++++++++- .../java/org/apache/juneau/annotation/Pojo.java | 72 +++++++++++++++++++ .../apache/juneau/internal/ReflectionUtils.java | 13 ++++ .../juneau/transform/AnnotationBeanFilter.java | 14 +++- .../java/org/apache/juneau/BeanFilterTest.java | 14 ++-- .../juneau/a/rttests/RoundTripBeanMapsTest.java | 16 ++--- .../a/rttests/RoundTripTransformBeansTest.java | 5 +- 10 files changed, 217 insertions(+), 26 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/4fb5136f/juneau-core/src/main/java/org/apache/juneau/BeanContext.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanContext.java b/juneau-core/src/main/java/org/apache/juneau/BeanContext.java index 8b260bc..9b9ce4a 100644 --- a/juneau-core/src/main/java/org/apache/juneau/BeanContext.java +++ b/juneau-core/src/main/java/org/apache/juneau/BeanContext.java @@ -446,6 +446,10 @@ public class BeanContext extends Context { */ public static final String BEAN_implClasses_put = "BeanContext.implClasses.map.put"; + public static final String BEAN_classLexicon = "BeanContext.pojoSwaps.list"; + public static final String BEAN_classLexicon_add = "BeanContext.pojoSwaps.list.add"; + public static final String BEAN_classLexicon_remove = "BeanContext.pojoSwaps.list.remove"; + /** * Specifies the default parser to use when converting <code>Strings</code> to POJOs in the {@link BeanContext#convertToType(Object, Class)} method (<code>Class</code>). */ @@ -525,6 +529,7 @@ public class BeanContext extends Context { final String[] notBeanPackageNames, notBeanPackagePrefixes; final BeanFilter<?>[] beanFilters; final PojoSwap<?,?>[] pojoSwaps; + final ClassLexicon classLexicon; final Map<Class<?>,Class<?>> implClasses; final Class<?>[] implKeyClasses, implValueClasses; final ClassLoader classLoader; @@ -615,6 +620,8 @@ public class BeanContext extends Context { } pojoSwaps = lpf.toArray(new PojoSwap[0]); + classLexicon = new ClassLexicon(pm.get(BEAN_pojoSwaps, Class[].class, new Class[0])); + implClasses = new TreeMap<Class<?>,Class<?>>(new ClassComparator()); Map<Class,Class> m = pm.getMap(BEAN_implClasses, Class.class, Class.class, null); if (m != null) @@ -1456,6 +1463,10 @@ public class BeanContext extends Context { return null; } + protected ClassLexicon getClassLexicon() { + return classLexicon; + } + /** * Gets the no-arg constructor for the specified class. * http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/4fb5136f/juneau-core/src/main/java/org/apache/juneau/ClassMeta.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/ClassMeta.java b/juneau-core/src/main/java/org/apache/juneau/ClassMeta.java index eb60730..ba31b41 100644 --- a/juneau-core/src/main/java/org/apache/juneau/ClassMeta.java +++ b/juneau-core/src/main/java/org/apache/juneau/ClassMeta.java @@ -83,6 +83,7 @@ public final class ClassMeta<T> implements Type { isMemberClass; // True if this is a non-static member class. private MetadataMap extMeta = new MetadataMap(); // Extended metadata + private ClassLexicon classLexicon; private Throwable initException; // Any exceptions thrown in the init() method. private boolean hasChildPojoSwaps; // True if this class or any subclass of this class has a PojoSwap associated with it. @@ -128,6 +129,15 @@ public final class ClassMeta<T> implements Type { try { beanFilter = findBeanFilter(beanContext); pojoSwap = findPojoSwap(beanContext); + + classLexicon = beanContext.getClassLexicon(); + for (Pojo p : ReflectionUtils.findAnnotationsParentFirst(Pojo.class, innerClass)) + if (p.lexicon().length > 0) + classLexicon = new ClassLexicon(classLexicon, p.lexicon()); + for (Bean b : ReflectionUtils.findAnnotationsParentFirst(Bean.class, innerClass)) + if (b.lexicon().length > 0) + classLexicon = new ClassLexicon(classLexicon, b.lexicon()); + serializedClassMeta = (pojoSwap == null ? this : beanContext.getClassMeta(pojoSwap.getSwapClass())); if (serializedClassMeta == null) serializedClassMeta = this; @@ -394,7 +404,7 @@ public final class ClassMeta<T> implements Type { } /** - * Returns <jk>true</jk> if this class as subtypes defined through {@link Bean#subTypes} or {@link BeanFilter#getSubTypes()}. + * Returns <jk>true</jk> if this class as subtypes defined through {@link Bean#subTypes}. * * @return <jk>true</jk> if this class has subtypes. */ http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/4fb5136f/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java b/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java index 93dad9d..6edebf5 100644 --- a/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java +++ b/juneau-core/src/main/java/org/apache/juneau/ObjectMap.java @@ -1150,12 +1150,21 @@ public class ObjectMap extends LinkedHashMap<String,Object> { * same object if entry does not exist. */ public Object cast() { + return cast((ClassLexicon)null); + } + + public Object cast(ClassLexicon classLexicon) { String c = (String)get("_class"); if (c == null) { if (containsKey("_value")) return get("_value"); return this; } + if (classLexicon != null) { + Class<?> c2 = classLexicon.getClassForName(c); + if (c2 != null) + return cast2(beanContext.getClassMeta(c2)); + } return cast2(beanContext.getClassMetaFromString(c)); } @@ -1249,7 +1258,7 @@ public class ObjectMap extends LinkedHashMap<String,Object> { // Attempt to recursively cast child maps. if (v instanceof ObjectMap) - v = ((ObjectMap)v).cast(); + v = ((ObjectMap)v).cast(beanContext.classLexicon); k = (kType.isString() ? k : beanContext.convertToType(k, kType)); v = (vType.isObject() ? v : beanContext.convertToType(v, vType)); @@ -1270,7 +1279,7 @@ public class ObjectMap extends LinkedHashMap<String,Object> { // Attempt to recursively cast child maps. if (v instanceof ObjectMap) - v = ((ObjectMap)v).cast(); + v = ((ObjectMap)v).cast(beanContext.classLexicon); bm.put(k, v); } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/4fb5136f/juneau-core/src/main/java/org/apache/juneau/annotation/Bean.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/annotation/Bean.java b/juneau-core/src/main/java/org/apache/juneau/annotation/Bean.java index dd436a2..34b6414 100644 --- a/juneau-core/src/main/java/org/apache/juneau/annotation/Bean.java +++ b/juneau-core/src/main/java/org/apache/juneau/annotation/Bean.java @@ -42,6 +42,77 @@ import org.apache.juneau.transform.*; public @interface Bean { /** + * An identifying name for this class. + * <p> + * The name is used to identify the class type during parsing when it cannot be inferred through reflection. + * For example, if a bean property is of type <code>Object</code>, then the serializer will add the name to the + * output so that the class can be determined during parsing. + * It is also used to specify element names in XML. + * <p> + * The name is used in combination with the lexicon defined through {@link #lexicon()}. Together, they make up + * a simple name/value mapping of names to classes. + * Names do not need to be universally unique. However, they must be unique within a lexicon. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * <ja>@Bean</ja>(name=<js>"foo"</js>) + * <jk>public class</jk> Foo { + * <jc>// A bean property where the object types cannot be inferred since it's an Object[].</jc> + * <ja>@BeanProperty</ja>(lexicon={Bar.<jk>class</jk>,Baz.<jk>class</jk>}) + * <jk>public</jk> Object[] x = <jk>new</jk> Object[]{<jk>new</jk> Bar(), <jk>new</jk> Baz()}; + * } + * + * <ja>@Bean</ja>(name=<js>"bar"</js>) + * <jk>public class</jk> Bar {} + * + * <ja>@Bean</ja>(name=<js>"baz"</js>) + * <jk>public class</jk> Baz {} + * </p> + * <p> + * When serialized as XML, the bean is rendered as: + * </p> + * <p class='bcode'> + * <xt><foo></xt> + * <xt><x></xt> + * <xt><bar/>v + * <xt><baz/></xt> + * <xt></x></xt> + * <xt></foo></xt> + * </p> + * <p> + * When serialized as JSON, <js>'n'</js> attributes would be added when needed to infer the type during parsing: + * </p> + * <p class='bcode'> + * { + * <jsa>x</jsa>: [ + * {<jsa>n</jsa>:<jss>'bar'</jss>}, + * {<jsa>n</jsa>:<jss>'baz'</jss>} + * ] + * } * + * </dd> + * </dl> + */ + String name() default ""; + + /** + * The list of classes that make up the class lexicon for this class. + * <p> + * The lexicon is a name/class mapping used to find class types during parsing when they cannot be inferred through reflection. + * The names are defined through the {@link #name()} annotation defined on the bean or POJO classes. + * <p> + * This list can consist of the following class types: + * <ul> + * <li>Any bean class that specifies a value for {@link Bean#name() @Bean.name()}; + * <li>Any POJO class that specifies a value for {@link Pojo#name() @Pojo.name()}; + * <li>Any subclass of {@link ClassLexicon} that defines an entire set of mappings. + * Note that the subclass MUST implement a no-arg constructor so that it can be instantiated. + * </ul> + */ + Class<?>[] lexicon() default {}; + + /** * The set and order of names of properties associated with a bean class. * <p> * The order specified is the same order that the entries will be returned by the {@link BeanMap#entrySet()} and related methods. @@ -177,7 +248,7 @@ public @interface Bean { /** * Used in conjunction with {@link #subTypeProperty()} to set up bean subtypes. */ - BeanSubType[] subTypes() default {}; + Class<?>[] subTypes() default {}; /** * Identifies a class to be used as the interface class for this and all subclasses. http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/4fb5136f/juneau-core/src/main/java/org/apache/juneau/annotation/Pojo.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/annotation/Pojo.java b/juneau-core/src/main/java/org/apache/juneau/annotation/Pojo.java index f200622..9d615c1 100644 --- a/juneau-core/src/main/java/org/apache/juneau/annotation/Pojo.java +++ b/juneau-core/src/main/java/org/apache/juneau/annotation/Pojo.java @@ -17,6 +17,7 @@ import static java.lang.annotation.RetentionPolicy.*; import java.lang.annotation.*; +import org.apache.juneau.*; import org.apache.juneau.internal.*; import org.apache.juneau.transform.*; @@ -32,6 +33,77 @@ import org.apache.juneau.transform.*; public @interface Pojo { /** + * An identifying name for this class. + * <p> + * The name is used to identify the class type during parsing when it cannot be inferred through reflection. + * For example, if a bean property is of type <code>Object</code>, then the serializer will add the name to the + * output so that the class can be determined during parsing. + * It is also used to specify element names in XML. + * <p> + * The name is used in combination with the lexicon defined through {@link #lexicon()}. Together, they make up + * a simple name/value mapping of names to classes. + * Names do not need to be universally unique. However, they must be unique within a lexicon. + * + * <dl> + * <dt>Example:</dt> + * <dd> + * <p class='bcode'> + * <ja>@Bean</ja>(name=<js>"foo"</js>) + * <jk>public class</jk> Foo { + * <jc>// A bean property where the object types cannot be inferred since it's an Object[].</jc> + * <ja>@BeanProperty</ja>(lexicon={Bar.<jk>class</jk>,Baz.<jk>class</jk>}) + * <jk>public</jk> Object[] x = <jk>new</jk> Object[]{<jk>new</jk> Bar(), <jk>new</jk> Baz()}; + * } + * + * <ja>@Pojo</ja>(name=<js>"bar"</js>) + * <jk>public class</jk> Bar <jk>extends</jk> HashMap {} + * + * <ja>@Pojo</ja>(name=<js>"baz"</js>) + * <jk>public class</jk> Baz <jk>extends</jk> HashMap {} + * </p> + * <p> + * When serialized as XML, the bean is rendered as: + * </p> + * <p class='bcode'> + * <xt><foo></xt> + * <xt><x></xt> + * <xt><bar/>v + * <xt><baz/></xt> + * <xt></x></xt> + * <xt></foo></xt> + * </p> + * <p> + * When serialized as JSON, <js>'n'</js> attributes would be added when needed to infer the type during parsing: + * </p> + * <p class='bcode'> + * { + * <jsa>x</jsa>: [ + * {<jsa>n</jsa>:<jss>'bar'</jss>}, + * {<jsa>n</jsa>:<jss>'baz'</jss>} + * ] + * } * + * </dd> + * </dl> + */ + String name() default ""; + + /** + * The list of classes that make up the class lexicon for this class. + * <p> + * The lexicon is a name/class mapping used to find class types during parsing when they cannot be inferred through reflection. + * The names are defined through the {@link #name()} annotation defined on the bean or POJO classes. + * <p> + * This list can consist of the following class types: + * <ul> + * <li>Any bean class that specifies a value for {@link Bean#name() @Bean.name()}; + * <li>Any POJO class that specifies a value for {@link Pojo#name() @Pojo.name()}; + * <li>Any subclass of {@link ClassLexicon} that defines an entire set of mappings. + * Note that the subclass MUST implement a no-arg constructor so that it can be instantiated. + * </ul> + */ + Class<?>[] lexicon() default {}; + + /** * Associate a {@link PojoSwap} or {@link SurrogateSwap} with this class type. * * <p> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/4fb5136f/juneau-core/src/main/java/org/apache/juneau/internal/ReflectionUtils.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/internal/ReflectionUtils.java b/juneau-core/src/main/java/org/apache/juneau/internal/ReflectionUtils.java index 0936d91..c1686ab 100644 --- a/juneau-core/src/main/java/org/apache/juneau/internal/ReflectionUtils.java +++ b/juneau-core/src/main/java/org/apache/juneau/internal/ReflectionUtils.java @@ -91,6 +91,19 @@ public final class ReflectionUtils { } /** + * Same as {@link #findAnnotations(Class, Class)} but returns the list in parent-to-child order. + * + * @param a The annotation class type. + * @param c The class being searched. + * @return The found matches, or an empty array if annotation was not found. + */ + public static <T extends Annotation> List<T> findAnnotationsParentFirst(Class<T> a, Class<?> c) { + List<T> l = findAnnotations(a, c); + Collections.reverse(l); + return l; + } + + /** * Sames as {@link #findAnnotations(Class, Class)} except returns the annotations as a map * with the keys being the class on which the annotation was found. * <p> http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/4fb5136f/juneau-core/src/main/java/org/apache/juneau/transform/AnnotationBeanFilter.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/transform/AnnotationBeanFilter.java b/juneau-core/src/main/java/org/apache/juneau/transform/AnnotationBeanFilter.java index dd8cb4d..24c6d5a 100644 --- a/juneau-core/src/main/java/org/apache/juneau/transform/AnnotationBeanFilter.java +++ b/juneau-core/src/main/java/org/apache/juneau/transform/AnnotationBeanFilter.java @@ -88,8 +88,18 @@ public final class AnnotationBeanFilter<T> extends BeanFilter<T> { if (! b.subTypeProperty().isEmpty()) { subTypeProperty = b.subTypeProperty(); - for (BeanSubType bst : b.subTypes()) - subTypes.put(bst.type(), bst.id()); + for (Class<?> bst : b.subTypes()) { + Bean b2 = bst.getAnnotation(Bean.class); + Pojo p2 = bst.getAnnotation(Pojo.class); + String name = null; + if (! b2.name().isEmpty()) + name = b2.name(); + else if (! p2.name().isEmpty()) + name = p2.name(); + else + name = bst.getName(); + subTypes.put(bst, name); + } } } } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/4fb5136f/juneau-core/src/test/java/org/apache/juneau/BeanFilterTest.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/test/java/org/apache/juneau/BeanFilterTest.java b/juneau-core/src/test/java/org/apache/juneau/BeanFilterTest.java index 78fe620..c2da6ab 100755 --- a/juneau-core/src/test/java/org/apache/juneau/BeanFilterTest.java +++ b/juneau-core/src/test/java/org/apache/juneau/BeanFilterTest.java @@ -55,39 +55,37 @@ public class BeanFilterTest { @Bean( subTypeProperty="subType", - subTypes={ - @BeanSubType(type=A1.class, id="A1"), - @BeanSubType(type=A2.class, id="A2") - } + subTypes={A1.class,A2.class} ) public static abstract class A { public String f0 = "f0"; public B fb; } + @Bean(name="A1") public static class A1 extends A { public String f1; } + @Bean(name="A2") public static class A2 extends A { public String f2; } @Bean( subTypeProperty="subType", - subTypes={ - @BeanSubType(type=B1.class, id="B1"), - @BeanSubType(type=B2.class, id="B2") - } + subTypes={B1.class,B2.class} ) public static abstract class B { public String f0b = "f0b"; } + @Bean(name="B1") public static class B1 extends B { public String f1; } + @Bean(name="B2") public static class B2 extends B { public String f2; } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/4fb5136f/juneau-core/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanMapsTest.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanMapsTest.java b/juneau-core/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanMapsTest.java index 143c677..060f52a 100755 --- a/juneau-core/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanMapsTest.java +++ b/juneau-core/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanMapsTest.java @@ -249,16 +249,13 @@ public class RoundTripBeanMapsTest extends RoundTripTest { @Bean( subTypeProperty="subType", - subTypes={ - @BeanSubType(type=B1.class, id="B1"), - @BeanSubType(type=B2.class, id="B2"), - @BeanSubType(type=B3.class, id="B3") - } + subTypes={B1.class,B2.class,B3.class} ) public abstract static class B { public String f0 = "f0"; } + @Bean(name="B1") public static class B1 extends B { public String f1; public static B1 create() { @@ -269,6 +266,7 @@ public class RoundTripBeanMapsTest extends RoundTripTest { } } + @Bean(name="B2") public static class B2 extends B { public int f2; public static B2 create() { @@ -279,6 +277,7 @@ public class RoundTripBeanMapsTest extends RoundTripTest { } } + @Bean(name="B3") public static class B3 extends B { public XMLGregorianCalendar f3; public static B3 create() throws Exception { @@ -387,15 +386,13 @@ public class RoundTripBeanMapsTest extends RoundTripTest { @Bean( subTypeProperty="subType", - subTypes={ - @BeanSubType(type=BA1.class, id="BA1"), - @BeanSubType(type=BA2.class, id="BA2") - } + subTypes={BA1.class,BA2.class} ) public abstract static class BA { public String f0a, subType, f0b; } + @Bean(name="BA1") public static class BA1 extends BA { public String f1; public static BA1 create() { @@ -408,6 +405,7 @@ public class RoundTripBeanMapsTest extends RoundTripTest { } } + @Bean(name="BA2") public static class BA2 extends BA { public String f2; } http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/4fb5136f/juneau-core/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeansTest.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeansTest.java b/juneau-core/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeansTest.java index 14414e8..4ac7270 100755 --- a/juneau-core/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeansTest.java +++ b/juneau-core/src/test/java/org/apache/juneau/a/rttests/RoundTripTransformBeansTest.java @@ -281,9 +281,7 @@ public class RoundTripTransformBeansTest extends RoundTripTest { @Bean( subTypeProperty="type", - subTypes={ - @BeanSubType(id="C3", type=C3.class) - } + subTypes={C3.class} ) public static interface C1<T> extends Serializable { void setF1(T f1); @@ -304,6 +302,7 @@ public class RoundTripTransformBeansTest extends RoundTripTest { } } + @Bean(name="C3") public static class C3<T> extends C2<T> { public static C3 create() {
