Repository: incubator-juneau Updated Branches: refs/heads/master 831a2bd9f -> 9caef98a9
"extras" property, part 1. Project: http://git-wip-us.apache.org/repos/asf/incubator-juneau/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-juneau/commit/9caef98a Tree: http://git-wip-us.apache.org/repos/asf/incubator-juneau/tree/9caef98a Diff: http://git-wip-us.apache.org/repos/asf/incubator-juneau/diff/9caef98a Branch: refs/heads/master Commit: 9caef98a90dd2e5ce02541c38d575cb310099b30 Parents: 831a2bd Author: JamesBognar <[email protected]> Authored: Tue May 2 09:58:36 2017 -0400 Committer: JamesBognar <[email protected]> Committed: Tue May 2 09:58:36 2017 -0400 ---------------------------------------------------------------------- .../main/java/org/apache/juneau/BeanMeta.java | 12 +- .../org/apache/juneau/BeanPropertyMeta.java | 184 ++++++++++++++----- 2 files changed, 147 insertions(+), 49 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9caef98a/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java b/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java index e1edb43..a5a894f 100644 --- a/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java +++ b/juneau-core/src/main/java/org/apache/juneau/BeanMeta.java @@ -89,6 +89,7 @@ public class BeanMeta<T> { // Other fields final String typePropertyName; // "_type" property actual name. private final BeanPropertyMeta typeProperty; // "_type" mock bean property. + final BeanPropertyMeta extrasProperty; // "extras" property. private final String dictionaryName; // The @Bean.typeName() annotation defined on this bean class. final String notABeanReason; // Readable string explaining why this class wasn't a bean. final BeanRegistry beanRegistry; @@ -114,6 +115,7 @@ public class BeanMeta<T> { this.properties = b.properties == null ? null : Collections.unmodifiableMap(b.properties); this.getterProps = Collections.unmodifiableMap(b.getterProps); this.setterProps = Collections.unmodifiableMap(b.setterProps); + this.extrasProperty = b.extrasProperty; this.typeVarImpls = b.typeVarImpls == null ? null : Collections.unmodifiableMap(b.typeVarImpls); this.constructor = b.constructor; this.constructorArgs = b.constructorArgs; @@ -131,6 +133,8 @@ public class BeanMeta<T> { Map<String,BeanPropertyMeta> properties; Map<Method,String> getterProps = new HashMap<Method,String>(); Map<Method,String> setterProps = new HashMap<Method,String>(); + BeanPropertyMeta extrasProperty; + Map<Class<?>,Class<?>[]> typeVarImpls; Constructor<T> constructor; String[] constructorArgs = new String[0]; @@ -346,8 +350,12 @@ public class BeanMeta<T> { if (dictionaryName == null) dictionaryName = findDictionaryName(this.classMeta); - for (Map.Entry<String,BeanPropertyMeta.Builder> e : normalProps.entrySet()) - properties.put(e.getKey(), e.getValue().build()); + for (Map.Entry<String,BeanPropertyMeta.Builder> e : normalProps.entrySet()) { + if ("*".equals(e.getValue().name)) + extrasProperty = e.getValue().build(); + else + properties.put(e.getKey(), e.getValue().build()); + } // If a beanFilter is defined, look for inclusion and exclusion lists. if (beanFilter != null) { http://git-wip-us.apache.org/repos/asf/incubator-juneau/blob/9caef98a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java ---------------------------------------------------------------------- diff --git a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java index f16c7fe..99f834c 100644 --- a/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java +++ b/juneau-core/src/main/java/org/apache/juneau/BeanPropertyMeta.java @@ -49,6 +49,7 @@ public class BeanPropertyMeta { private final Field field; // The bean property field (if it has one). private final Method getter, setter; // The bean property getter and setter. private final boolean isUri; // True if this is a URL/URI or annotated with @URI. + private final boolean isPropertyMap; private final ClassMeta<?> rawTypeMeta, // The real class type of the bean property. @@ -57,7 +58,7 @@ public class BeanPropertyMeta { private final String[] properties; // The value of the @BeanProperty(properties) annotation. private final PojoSwap swap; // PojoSwap defined only via @BeanProperty annotation. - private final MetadataMap extMeta = new MetadataMap(); // Extended metadata + private final MetadataMap extMeta; // Extended metadata private final BeanRegistry beanRegistry; private final Object overrideValue; // The bean property value (if it's an overridden delegate). @@ -79,6 +80,7 @@ public class BeanPropertyMeta { private BeanRegistry beanRegistry; private Object overrideValue; private BeanPropertyMeta delegateFor; + private MetadataMap extMeta = new MetadataMap(); Builder(BeanMeta<?> beanMeta, String name) { this.beanMeta = beanMeta; @@ -89,8 +91,6 @@ public class BeanPropertyMeta { Builder(BeanMeta<?> beanMeta, String name, ClassMeta<?> rawTypeMeta, BeanRegistry beanRegistry) { this(beanMeta, name); this.rawTypeMeta = rawTypeMeta; - if (rawTypeMeta == null) - throw new RuntimeException("xxx"); this.typeMeta = rawTypeMeta; this.beanRegistry = beanRegistry; } @@ -151,8 +151,8 @@ public class BeanPropertyMeta { rawTypeMeta = f.resolveClassMeta(p, setter.getGenericParameterTypes()[0], typeVarImpls); isUri |= (rawTypeMeta.isUri() || setter.isAnnotationPresent(org.apache.juneau.annotation.URI.class)); if (p != null) { - if (swap == null) - swap = getPropertyPojoSwap(p); + if (swap == null) + swap = getPropertyPojoSwap(p); if (properties != null && ! p.properties().isEmpty()) properties = StringUtils.split(p.properties(), ','); bdClasses.addAll(Arrays.asList(p.beanDictionary())); @@ -164,14 +164,44 @@ public class BeanPropertyMeta { this.beanRegistry = new BeanRegistry(beanContext, parentBeanRegistry, bdClasses.toArray(new Class<?>[0])); + boolean isAnyProperty = "*".equals(name); + // Do some annotation validation. Class<?> c = rawTypeMeta.getInnerClass(); - if (getter != null && ! isParentClass(getter.getReturnType(), c)) - return false; - if (setter != null && ! isParentClass(setter.getParameterTypes()[0], c)) - return false; - if (field != null && ! isParentClass(field.getType(), c)) - return false; + if (getter != null) { + if (! isParentClass(getter.getReturnType(), c)) + return false; + Class<?>[] pt = getter.getParameterTypes(); + if (isAnyProperty) { + if (pt.length != 1) + return false; + if (! pt[0].equals(String.class)) + return false; + } + } + if (setter != null) { + Class<?>[] pt = setter.getParameterTypes(); + if (pt.length != (isAnyProperty ? 2 : 1)) + return false; + if (isAnyProperty) { + if (pt[0].equals(String.class)) + return false; + if (! isParentClass(pt[1], c)) + return false; + } else { + if (! isParentClass(pt[0], c)) + return false; + } + } + if (field != null) { + if (isAnyProperty) { + if (! isParentClass(field.getType(), Map.class)) + return false; + } else { + if (! isParentClass(field.getType(), c)) + return false; + } + } if (typeMeta == null) typeMeta = (swap != null ? swap.getSwapClassMeta(beanContext) : rawTypeMeta == null ? beanContext.object() : rawTypeMeta.getSerializedClassMeta()); @@ -247,6 +277,35 @@ public class BeanPropertyMeta { this.beanRegistry = b.beanRegistry; this.overrideValue = b.overrideValue; this.delegateFor = b.delegateFor; + this.extMeta = b.extMeta; + this.isPropertyMap = false; + } + + /** + * Creates a BeanPropertyMeta for an "extras" property. + * <p> + * An extras property is one defined with <code><ja>@BeanProperty</ja>(name=<js>"*"</js>)</code> + * + * @param name The bean property name (e.g. the key if this is a Map field, or the value passed to the getter/setter as the property name). + * @param b The real bean property. + */ + protected BeanPropertyMeta(String name, BeanPropertyMeta b) { + this.field = b.field; + this.getter = b.getter; + this.setter = b.setter; + this.isUri = false; + this.beanMeta = b.beanMeta; + this.beanContext = b.beanContext; + this.name = name; + this.rawTypeMeta = b.rawTypeMeta; + this.typeMeta = b.typeMeta; + this.properties = b.properties; + this.swap = b.swap; + this.beanRegistry = b.beanRegistry; + this.overrideValue = b.overrideValue; + this.delegateFor = b.delegateFor; + this.extMeta = b.extMeta; + this.isPropertyMap = true; } /** @@ -381,10 +440,9 @@ public class BeanPropertyMeta { throw new BeanRuntimeException(beanMeta.c, "Getter or public field not defined on property ''{0}''", name); if (getter != null) - o = getter.invoke(bean, (Object[])null); - + o = invokeGetter(bean); else if (field != null) - o = field.get(bean); + o = invokeGetField(bean); return toSerializedForm(m.getBeanSession(), o); @@ -458,8 +516,8 @@ public class BeanPropertyMeta { throw new BeanRuntimeException("Non-existent bean instance on bean."); } - boolean isMap = rawTypeMeta.isMap(); - boolean isCollection = rawTypeMeta.isCollection(); + boolean isMap = rawTypeMeta.isMap(); + boolean isCollection = rawTypeMeta.isCollection(); if (field == null && setter == null && ! (isMap || isCollection)) { if ((value == null && beanContext.ignoreUnknownNullBeanProperties) || beanContext.ignorePropertiesWithoutSetters) @@ -472,14 +530,14 @@ public class BeanPropertyMeta { try { Object r = beanContext.beanMapPutReturnsOldValue || isMap || isCollection ? get(m) : null; - Class<?> propertyClass = rawTypeMeta.getInnerClass(); + Class<?> propertyClass = rawTypeMeta.getInnerClass(); if (value == null && (isMap || isCollection)) { if (setter != null) { - setter.invoke(bean, new Object[] { null }); + invokeSetter(bean, null); return r; } else if (field != null) { - field.set(bean, null); + invokeSetField(bean, null); return r; } throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' to null because no setter or public field is defined", name); @@ -500,7 +558,7 @@ public class BeanPropertyMeta { // If the property type is abstract, then we either need to reuse the existing // map (if it's not null), or try to assign the value directly. - if (! rawTypeMeta.canCreateNewInstance()) { + if (! rawTypeMeta.canCreateNewInstance()) { if (propMap == null) { if (setter == null && field == null) throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter or public field is defined, and the current value is null", name, propertyClass.getName(), findClassName(value)); @@ -514,9 +572,9 @@ public class BeanPropertyMeta { } } if (setter != null) - setter.invoke(bean, valueMap); + invokeSetter(bean, valueMap); else - field.set(bean, valueMap); + invokeSetField(bean, valueMap); return r; } throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{2}'' to object of type ''{2}'' because the assigned map cannot be converted to the specified type because the property type is abstract, and the property value is currently null", name, propertyClass.getName(), findClassName(value)); @@ -525,9 +583,9 @@ public class BeanPropertyMeta { if (propMap == null) { propMap = (Map)propertyClass.newInstance(); if (setter != null) - setter.invoke(bean, propMap); + invokeSetter(bean, propMap); else if (field != null) - field.set(bean, propMap); + invokeSetField(bean, propMap); else throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter or public field is defined on this property, and the existing property value is null", name, propertyClass.getName(), findClassName(value)); } else { @@ -559,7 +617,7 @@ public class BeanPropertyMeta { // If the property type is abstract, then we either need to reuse the existing // collection (if it's not null), or try to assign the value directly. - if (! rawTypeMeta.canCreateNewInstance()) { + if (! rawTypeMeta.canCreateNewInstance()) { if (propList == null) { if (setter == null && field == null) throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter or public field is defined, and the current value is null", name, propertyClass.getName(), findClassName(value)); @@ -576,9 +634,9 @@ public class BeanPropertyMeta { valueList = l; } if (setter != null) - setter.invoke(bean, valueList); + invokeSetter(bean, valueList); else - field.set(bean, valueList); + invokeSetField(bean, valueList); return r; } throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because the assigned map cannot be converted to the specified type because the property type is abstract, and the property value is currently null", name, propertyClass.getName(), findClassName(value)); @@ -588,9 +646,9 @@ public class BeanPropertyMeta { if (propList == null) { propList = (Collection)propertyClass.newInstance(); if (setter != null) - setter.invoke(bean, propList); + invokeSetter(bean, propList); else if (field != null) - field.set(bean, propList); + invokeSetField(bean, propList); else throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter is defined on this property, and the existing property value is null", name, propertyClass.getName(), findClassName(value)); } else { @@ -612,9 +670,9 @@ public class BeanPropertyMeta { value = session.convertToType(value, rawTypeMeta); } if (setter != null) - setter.invoke(bean, new Object[] { value }); + invokeSetter(bean, value); else if (field != null) - field.set(bean, value); + invokeSetField(bean, value); } return r; @@ -635,6 +693,38 @@ public class BeanPropertyMeta { } } + private Object invokeGetter(Object bean) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + if (isPropertyMap) + return getter.invoke(bean, name); + return getter.invoke(bean); + } + + private Object invokeSetter(Object bean, Object val) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { + if (isPropertyMap) + return setter.invoke(bean, name, val); + return setter.invoke(bean, val); + } + + private Object invokeGetField(Object bean) throws IllegalArgumentException, IllegalAccessException { + if (isPropertyMap) { + Map m = (Map)field.get(bean); + if (m != null) + return m.get(name); + return null; + } + return field.get(bean); + } + + private void invokeSetField(Object bean, Object val) throws IllegalArgumentException, IllegalAccessException { + if (isPropertyMap) { + Map m = (Map)field.get(bean); + if (m != null) + m.put(name, val); + } else { + field.set(bean, val); + } + } + /** * Sets an array field on this bean. * Works on both <code>Object</code> and primitive arrays. @@ -648,9 +738,9 @@ public class BeanPropertyMeta { protected void setArray(Object bean, List l) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { Object array = ArrayUtils.toArray(l, this.rawTypeMeta.getElementType().getInnerClass()); if (setter != null) - setter.invoke(bean, array); + invokeSetter(bean, array); else if (field != null) - field.set(bean, array); + invokeSetField(bean, array); else throw new BeanRuntimeException(beanMeta.c, "Attempt to initialize array property ''{0}'', but no setter or field defined.", name); } @@ -692,9 +782,9 @@ public class BeanPropertyMeta { if (isCollection) { Collection c = null; if (getter != null) { - c = (Collection)getter.invoke(bean, (Object[])null); + c = (Collection)invokeGetter(bean); } else if (field != null) { - c = (Collection)field.get(bean); + c = (Collection)invokeGetField(bean); } else { throw new BeanRuntimeException(beanMeta.c, "Attempt to append to collection property ''{0}'', but no getter or field defined.", name); } @@ -712,9 +802,9 @@ public class BeanPropertyMeta { c.add(v); if (setter != null) - setter.invoke(bean, c); + invokeSetter(bean, c); else if (field != null) - field.set(bean, c); + invokeSetField(bean, c); else throw new BeanRuntimeException(beanMeta.c, "Attempt to initialize collection property ''{0}'', but no setter or field defined.", name); @@ -731,9 +821,9 @@ public class BeanPropertyMeta { // Copy any existing array values into the temporary list. Object oldArray; if (getter != null) - oldArray = getter.invoke(bean, (Object[])null); + oldArray = invokeGetter(bean); else if (field != null) - oldArray = field.get(bean); + oldArray = invokeGetField(bean); else throw new BeanRuntimeException(beanMeta.c, "Attempt to append to array property ''{0}'', but no getter or field defined.", name); ArrayUtils.copyToList(oldArray, l); @@ -786,9 +876,9 @@ public class BeanPropertyMeta { if (isMap) { Map map = null; if (getter != null) { - map = (Map)getter.invoke(bean, (Object[])null); + map = (Map)invokeGetter(bean); } else if (field != null) { - map = (Map)field.get(bean); + map = (Map)invokeGetField(bean); } else { throw new BeanRuntimeException(beanMeta.c, "Attempt to append to map property ''{0}'', but no getter or field defined.", name); } @@ -806,9 +896,9 @@ public class BeanPropertyMeta { map.put(key, v); if (setter != null) - setter.invoke(bean, map); + invokeSetter(bean, map); else if (field != null) - field.set(bean, map); + invokeSetField(bean, map); else throw new BeanRuntimeException(beanMeta.c, "Attempt to initialize map property ''{0}'', but no setter or field defined.", name); @@ -816,9 +906,9 @@ public class BeanPropertyMeta { Object b = null; if (getter != null) { - b = getter.invoke(bean, (Object[])null); + b = invokeGetter(bean); } else if (field != null) { - b = field.get(bean); + b = invokeGetField(bean); } else { throw new BeanRuntimeException(beanMeta.c, "Attempt to append to bean property ''{0}'', but no getter or field defined.", name); } @@ -836,9 +926,9 @@ public class BeanPropertyMeta { } if (setter != null) - setter.invoke(bean, b); + invokeSetter(bean, b); else if (field != null) - field.set(bean, b); + invokeSetField(bean, b); else throw new BeanRuntimeException(beanMeta.c, "Attempt to initialize bean property ''{0}'', but no setter or field defined.", name); }
