/* * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.xml.internal.bind.v2.model.impl; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.AbstractList; import javax.xml.bind.annotation.XmlAccessOrder; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorOrder; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAnyAttribute; import javax.xml.bind.annotation.XmlAnyElement; import javax.xml.bind.annotation.XmlAttachmentRef; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlElementRefs; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlElements; import javax.xml.bind.annotation.XmlID; import javax.xml.bind.annotation.XmlIDREF; import javax.xml.bind.annotation.XmlInlineBinaryData; import javax.xml.bind.annotation.XmlList; import javax.xml.bind.annotation.XmlMimeType; import javax.xml.bind.annotation.XmlMixed; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlSchemaType; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.XmlValue; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import javax.xml.namespace.QName; import com.sun.istack.internal.FinalArrayList; import com.sun.xml.internal.bind.annotation.OverrideAnnotationOf; import com.sun.xml.internal.bind.v2.model.annotation.Locatable; import com.sun.xml.internal.bind.v2.model.annotation.MethodLocatable; import com.sun.xml.internal.bind.v2.model.core.ClassInfo; import com.sun.xml.internal.bind.v2.model.core.Element; import com.sun.xml.internal.bind.v2.model.core.ID; import com.sun.xml.internal.bind.v2.model.core.NonElement; import com.sun.xml.internal.bind.v2.model.core.PropertyInfo; import com.sun.xml.internal.bind.v2.model.core.PropertyKind; import com.sun.xml.internal.bind.v2.model.core.ValuePropertyInfo; import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException; import com.sun.xml.internal.bind.v2.runtime.Location; import com.sun.xml.internal.bind.v2.util.EditDistance; /** * A part of the {@link ClassInfo} that doesn't depend on a particular * reflection library. * * @author Kohsuke Kawaguchi (kk@kohsuke.org) */ public class ClassInfoImpl extends TypeInfoImpl implements ClassInfo, Element { protected final C clazz; /** * @see #getElementName() */ private final QName elementName; /** * @see #getTypeName() */ private final QName typeName; /** * Lazily created. * * @see #getProperties() */ private FinalArrayList> properties; /** * The property order. * * null if unordered. {@link #DEFAULT_ORDER} if ordered but the order is defaulted * * @see #isOrdered() */ private /*final*/ String[] propOrder; /** * Lazily computed. * * To avoid the cyclic references of the form C1 --base--> C2 --property--> C1. */ private ClassInfoImpl baseClass; private boolean baseClassComputed = false; private boolean hasSubClasses = false; /** * If this class has a declared (not inherited) attribute wildcard, keep the reference * to it. * * This parameter is initialized at the construction time and never change. */ protected /*final*/ PropertySeed attributeWildcard; /** * @see #getFactoryMethod() */ private M factoryMethod = null; ClassInfoImpl(ModelBuilder builder, Locatable upstream, C clazz) { super(builder,upstream); this.clazz = clazz; assert clazz!=null; // compute the element name elementName = parseElementName(clazz); // compute the type name XmlType t = reader().getClassAnnotation(XmlType.class,clazz,this); typeName = parseTypeName(clazz,t); if(t!=null) { String[] propOrder = t.propOrder(); if(propOrder.length==0) this.propOrder = null; // unordered else { if(propOrder[0].length()==0) this.propOrder = DEFAULT_ORDER; else this.propOrder = propOrder; } } else { propOrder = DEFAULT_ORDER; } // obtain XmlAccessorOrder and set proporder (XmlAccessorOrder can be defined for whole package) // ( vs ) XmlAccessorOrder xao = reader().getPackageAnnotation(XmlAccessorOrder.class, clazz, this); if((xao != null) && (xao.value() == XmlAccessOrder.UNDEFINED)) { propOrder = null; } // obtain XmlAccessorOrder and set proporder ( vs ) xao = reader().getClassAnnotation(XmlAccessorOrder.class, clazz, this); if((xao != null) && (xao.value() == XmlAccessOrder.UNDEFINED)) { propOrder = null; } if(nav().isInterface(clazz)) { builder.reportError(new IllegalAnnotationException( Messages.CANT_HANDLE_INTERFACE.format(nav().getClassName(clazz)), this )); } // the class must have the default constructor if (!hasFactoryConstructor(t)){ if(!nav().hasDefaultConstructor(clazz)){ if(nav().isInnerClass(clazz)) { builder.reportError(new IllegalAnnotationException( Messages.CANT_HANDLE_INNER_CLASS.format(nav().getClassName(clazz)), this )); } else if (elementName != null) { builder.reportError(new IllegalAnnotationException( Messages.NO_DEFAULT_CONSTRUCTOR.format(nav().getClassName(clazz)), this )); } } } } public ClassInfoImpl getBaseClass() { if (!baseClassComputed) { // compute the base class C s = nav().getSuperClass(clazz); if(s==null || s==nav().asDecl(Object.class)) { baseClass = null; } else { NonElement b = builder.getClassInfo(s, true, this); if(b instanceof ClassInfoImpl) { baseClass = (ClassInfoImpl) b; baseClass.hasSubClasses = true; } else { baseClass = null; } } baseClassComputed = true; } return baseClass; } /** * {@inheritDoc} * * The substitution hierarchy is the same as the inheritance hierarchy. */ public final Element getSubstitutionHead() { ClassInfoImpl c = getBaseClass(); while(c!=null && !c.isElement()) c = c.getBaseClass(); return c; } public final C getClazz() { return clazz; } /** * When a bean binds to an element, it's always through {@link XmlRootElement}, * so this method always return null. * * @deprecated * you shouldn't be invoking this method on {@link ClassInfoImpl}. */ public ClassInfoImpl getScope() { return null; } public final T getType() { return nav().use(clazz); } /** * A {@link ClassInfo} can be referenced by {@link XmlIDREF} if * it has an ID property. */ public boolean canBeReferencedByIDREF() { for (PropertyInfo p : getProperties()) { if(p.id()== ID.ID) return true; } ClassInfoImpl base = getBaseClass(); if(base!=null) return base.canBeReferencedByIDREF(); else return false; } public final String getName() { return nav().getClassName(clazz); } public A readAnnotation(Class a) { return reader().getClassAnnotation(a,clazz,this); } public Element asElement() { if(isElement()) return this; else return null; } public List> getProperties() { if(properties!=null) return properties; // check the access type first XmlAccessType at = getAccessType(); properties = new FinalArrayList>(); findFieldProperties(clazz,at); findGetterSetterProperties(at); if(propOrder==DEFAULT_ORDER || propOrder==null) { XmlAccessOrder ao = getAccessorOrder(); if(ao==XmlAccessOrder.ALPHABETICAL) Collections.sort(properties); } else { //sort them as specified PropertySorter sorter = new PropertySorter(); for (PropertyInfoImpl p : properties) { sorter.checkedGet(p); // have it check for errors } Collections.sort(properties,sorter); sorter.checkUnusedProperties(); } {// additional error checks PropertyInfoImpl vp=null; // existing value property PropertyInfoImpl ep=null; // existing element property for (PropertyInfoImpl p : properties) { switch(p.kind()) { case ELEMENT: case REFERENCE: case MAP: ep = p; break; case VALUE: if(vp!=null) { // can't have multiple value properties. builder.reportError(new IllegalAnnotationException( Messages.MULTIPLE_VALUE_PROPERTY.format(), vp, p )); } if(getBaseClass()!=null) { builder.reportError(new IllegalAnnotationException( Messages.XMLVALUE_IN_DERIVED_TYPE.format(), p )); } vp = p; break; case ATTRIBUTE: break; // noop default: assert false; } } if(ep!=null && vp!=null) { // can't have element and value property at the same time builder.reportError(new IllegalAnnotationException( Messages.ELEMENT_AND_VALUE_PROPERTY.format(), vp, ep )); } } return properties; } private void findFieldProperties(C c, XmlAccessType at) { // always find properties from the super class first C sc = nav().getSuperClass(c); if (shouldRecurseSuperClass(sc)) { findFieldProperties(sc,at); } for( F f : nav().getDeclaredFields(c) ) { Annotation[] annotations = reader().getAllFieldAnnotations(f,this); boolean isDummy = reader().hasFieldAnnotation(OverrideAnnotationOf.class, f); if( nav().isTransient(f) ) { // it's an error for transient field to have any binding annotation if(hasJAXBAnnotation(annotations)) builder.reportError(new IllegalAnnotationException( Messages.TRANSIENT_FIELD_NOT_BINDABLE.format(nav().getFieldName(f)), getSomeJAXBAnnotation(annotations))); } else if( nav().isStaticField(f) ) { // static fields are bound only when there's explicit annotation. if(hasJAXBAnnotation(annotations)) addProperty(createFieldSeed(f),annotations, false); } else { if(at==XmlAccessType.FIELD ||(at==XmlAccessType.PUBLIC_MEMBER && nav().isPublicField(f)) || hasJAXBAnnotation(annotations)) { if (isDummy) { ClassInfo top = getBaseClass(); while ((top != null) && (top.getProperty("content") == null)) { top = top.getBaseClass(); } DummyPropertyInfo prop = (DummyPropertyInfo) top.getProperty("content"); PropertySeed seed = createFieldSeed(f); ((DummyPropertyInfo)prop).addType(createReferenceProperty(seed)); } else { addProperty(createFieldSeed(f), annotations, false); } } checkFieldXmlLocation(f); } } } public final boolean hasValueProperty() { ClassInfoImpl bc = getBaseClass(); if(bc!=null && bc.hasValueProperty()) return true; for (PropertyInfo p : getProperties()) { if (p instanceof ValuePropertyInfo) return true; } return false; } public PropertyInfo getProperty(String name) { for( PropertyInfo p: getProperties() ) { if(p.getName().equals(name)) return p; } return null; } /** * This hook is used by {@link RuntimeClassInfoImpl} to look for {@link com.sun.xml.internal.bind.annotation.XmlLocation}. */ protected void checkFieldXmlLocation(F f) { } /** * Gets an annotation that are allowed on both class and type. */ private T getClassOrPackageAnnotation(Class type) { T t = reader().getClassAnnotation(type,clazz,this); if(t!=null) return t; // defaults to the package level return reader().getPackageAnnotation(type,clazz,this); } /** * Computes the {@link XmlAccessType} on this class by looking at {@link XmlAccessorType} * annotations. */ private XmlAccessType getAccessType() { XmlAccessorType xat = getClassOrPackageAnnotation(XmlAccessorType.class); if(xat!=null) return xat.value(); else return XmlAccessType.PUBLIC_MEMBER; } /** * Gets the accessor order for this class by consulting {@link XmlAccessorOrder}. */ private XmlAccessOrder getAccessorOrder() { XmlAccessorOrder xao = getClassOrPackageAnnotation(XmlAccessorOrder.class); if(xao!=null) return xao.value(); else return XmlAccessOrder.UNDEFINED; } /** * Compares orders among {@link PropertyInfoImpl} according to {@link ClassInfoImpl#propOrder}. * *

* extends {@link HashMap} to save memory. */ private final class PropertySorter extends HashMap implements Comparator { /** * Mark property names that are used, so that we can report unused property names in the propOrder array. */ PropertyInfoImpl[] used = new PropertyInfoImpl[propOrder.length]; /** * If any name collides, it will be added to this set. * This is used to avoid repeating the same error message. */ private Set collidedNames; PropertySorter() { super(propOrder.length); for( String name : propOrder ) if(put(name,size())!=null) { // two properties with the same name builder.reportError(new IllegalAnnotationException( Messages.DUPLICATE_ENTRY_IN_PROP_ORDER.format(name),ClassInfoImpl.this)); } } public int compare(PropertyInfoImpl o1, PropertyInfoImpl o2) { int lhs = checkedGet(o1); int rhs = checkedGet(o2); return lhs-rhs; } private int checkedGet(PropertyInfoImpl p) { Integer i = get(p.getName()); if(i==null) { // missing if (p.kind().isOrdered) builder.reportError(new IllegalAnnotationException( Messages.PROPERTY_MISSING_FROM_ORDER.format(p.getName()),p)); // give it an order to recover from an error i = size(); put(p.getName(),i); } // mark the used field int ii = i; if(ii(); if(collidedNames.add(p.getName())) // report the error only on the first time builder.reportError(new IllegalAnnotationException( Messages.DUPLICATE_PROPERTIES.format(p.getName()),p,used[ii])); } used[ii] = p; } return i; } /** * Report errors for unused propOrder entries. */ public void checkUnusedProperties() { for( int i=0; i() { public String get(int index) { return properties.get(index).getName(); } public int size() { return properties.size(); } }); boolean isOverriding = (i > (properties.size()-1)) ? false : properties.get(i).hasAnnotation(OverrideAnnotationOf.class); if (!isOverriding) { builder.reportError(new IllegalAnnotationException( Messages.PROPERTY_ORDER_CONTAINS_UNUSED_ENTRY.format(unusedName,nearest),ClassInfoImpl.this)); } } } } public boolean hasProperties() { return !properties.isEmpty(); } /** * Picks the first non-null argument, or null if all arguments are null. */ private static T pickOne( T... args ) { for( T arg : args ) if(arg!=null) return arg; return null; } private static List makeSet( T... args ) { List l = new FinalArrayList(); for( T arg : args ) if(arg!=null) l.add(arg); return l; } private static final class ConflictException extends Exception { final List annotations; public ConflictException(List one) { this.annotations = one; } } private static final class DuplicateException extends Exception { final Annotation a1,a2; public DuplicateException(Annotation a1, Annotation a2) { this.a1 = a1; this.a2 = a2; } } /** * Represents 6 groups of secondary annotations */ private static enum SecondaryAnnotation { JAVA_TYPE (0x01, XmlJavaTypeAdapter.class), ID_IDREF (0x02, XmlID.class, XmlIDREF.class), BINARY (0x04, XmlInlineBinaryData.class, XmlMimeType.class, XmlAttachmentRef.class), ELEMENT_WRAPPER (0x08, XmlElementWrapper.class), LIST (0x10, XmlList.class), SCHEMA_TYPE (0x20, XmlSchemaType.class); /** * Each constant gets an unique bit mask so that the presence/absence * of them can be represented in a single byte. */ final int bitMask; /** * List of annotations that belong to this member. */ final Class[] members; SecondaryAnnotation(int bitMask, Class... members) { this.bitMask = bitMask; this.members = members; } } private static final SecondaryAnnotation[] SECONDARY_ANNOTATIONS = SecondaryAnnotation.values(); /** * Represents 7 groups of properties. * * Each instance is also responsible for rejecting annotations * that are not allowed on that kind. */ private static enum PropertyGroup { TRANSIENT (false,false,false,false,false,false), ANY_ATTRIBUTE (true, false,false,false,false,false), ATTRIBUTE (true, true, true, false,true, true ), VALUE (true, true, true, false,true, true ), ELEMENT (true, true, true, true, true, true ), ELEMENT_REF (true, false,false,true, false,false), MAP (false,false,false,true, false,false); /** * Bit mask that represents secondary annotations that are allowed on this group. * * T = not allowed, F = allowed */ final int allowedsecondaryAnnotations; PropertyGroup(boolean... bits) { int mask = 0; assert bits.length==SECONDARY_ANNOTATIONS.length; for( int i=0; i ANNOTATION_NUMBER_MAP = new HashMap(); static { Class[] annotations = { XmlTransient.class, // 0 XmlAnyAttribute.class, // 1 XmlAttribute.class, // 2 XmlValue.class, // 3 XmlElement.class, // 4 XmlElements.class, // 5 XmlElementRef.class, // 6 XmlElementRefs.class, // 7 XmlAnyElement.class, // 8 XmlMixed.class, // 9 OverrideAnnotationOf.class,// 10 }; HashMap m = ANNOTATION_NUMBER_MAP; // characterizing annotations for( Class c : annotations ) m.put(c, m.size() ); // secondary annotations int index = 20; for( SecondaryAnnotation sa : SECONDARY_ANNOTATIONS ) { for( Class member : sa.members ) m.put(member,index); index++; } } private void checkConflict(Annotation a, Annotation b) throws DuplicateException { assert b!=null; if(a!=null) throw new DuplicateException(a,b); } /** * Called only from {@link #getProperties()}. * *

* This is where we decide the type of the property and checks for annotations * that are not allowed. * * @param annotations * all annotations on this property. It's the same as * {@code seed.readAllAnnotation()}, but taken as a parameter * because the caller should know it already. */ private void addProperty( PropertySeed seed, Annotation[] annotations, boolean dummy ) { // since typically there's a very few annotations on a method, // this runs faster than checking for each annotation via readAnnotation(A) // characterizing annotations. these annotations (or lack thereof) decides // the kind of the property it goes to. // I wish I could use an array... XmlTransient t = null; XmlAnyAttribute aa = null; XmlAttribute a = null; XmlValue v = null; XmlElement e1 = null; XmlElements e2 = null; XmlElementRef r1 = null; XmlElementRefs r2 = null; XmlAnyElement xae = null; XmlMixed mx = null; OverrideAnnotationOf ov = null; // encountered secondary annotations are accumulated into a bit mask int secondaryAnnotations = 0; try { for( Annotation ann : annotations ) { Integer index = ANNOTATION_NUMBER_MAP.get(ann.annotationType()); if(index==null) continue; switch(index) { case 0: checkConflict(t ,ann); t = (XmlTransient) ann; break; case 1: checkConflict(aa ,ann); aa = (XmlAnyAttribute) ann; break; case 2: checkConflict(a ,ann); a = (XmlAttribute) ann; break; case 3: checkConflict(v ,ann); v = (XmlValue) ann; break; case 4: checkConflict(e1 ,ann); e1 = (XmlElement) ann; break; case 5: checkConflict(e2 ,ann); e2 = (XmlElements) ann; break; case 6: checkConflict(r1 ,ann); r1 = (XmlElementRef) ann; break; case 7: checkConflict(r2 ,ann); r2 = (XmlElementRefs) ann; break; case 8: checkConflict(xae,ann); xae = (XmlAnyElement) ann; break; case 9: checkConflict(mx, ann); mx = (XmlMixed) ann; break; case 10: checkConflict(ov, ann); ov = (OverrideAnnotationOf) ann; break; default: // secondary annotations secondaryAnnotations |= (1<<(index-20)); break; } } // determine the group kind, and also count the numbers, since // characterizing annotations are mutually exclusive. PropertyGroup group = null; int groupCount = 0; if(t!=null) { group = PropertyGroup.TRANSIENT; groupCount++; } if(aa!=null) { group = PropertyGroup.ANY_ATTRIBUTE; groupCount++; } if(a!=null) { group = PropertyGroup.ATTRIBUTE; groupCount++; } if(v!=null) { group = PropertyGroup.VALUE; groupCount++; } if(e1!=null || e2!=null) { group = PropertyGroup.ELEMENT; groupCount++; } if(r1!=null || r2!=null || xae!=null || mx!=null || ov != null) { group = PropertyGroup.ELEMENT_REF; groupCount++; } if(groupCount>1) { // collision between groups List err = makeSet(t,aa,a,v,pickOne(e1,e2),pickOne(r1,r2,xae)); throw new ConflictException(err); } if(group==null) { // if no characterizing annotation was found, it's either element or map // sniff the signature and then decide. assert groupCount==0; // UGLY: the presence of XmlJavaTypeAdapter makes it an element property. ARGH. if(nav().isSubClassOf( seed.getRawType(), nav().ref(Map.class) ) && !seed.hasAnnotation(XmlJavaTypeAdapter.class)) group = PropertyGroup.MAP; else group = PropertyGroup.ELEMENT; } else if (group.equals(PropertyGroup.ELEMENT)) { // see issue 791 - make sure @XmlElement annotated map property is mapped to map if (nav().isSubClassOf( seed.getRawType(), nav().ref(Map.class)) && !seed.hasAnnotation(XmlJavaTypeAdapter.class)) { group = PropertyGroup.MAP; } } // group determined by now // make sure that there are no prohibited secondary annotations if( (secondaryAnnotations&group.allowedsecondaryAnnotations)!=0 ) { // uh oh. find the offending annotation for( SecondaryAnnotation sa : SECONDARY_ANNOTATIONS ) { if(group.allows(sa)) continue; for( Class m : sa.members ) { Annotation offender = seed.readAnnotation(m); if(offender!=null) { // found it builder.reportError(new IllegalAnnotationException( Messages.ANNOTATION_NOT_ALLOWED.format(m.getSimpleName()),offender)); return; } } } // there must have been an offender assert false; } // actually create annotations switch(group) { case TRANSIENT: return; case ANY_ATTRIBUTE: // an attribute wildcard property if(attributeWildcard!=null) { builder.reportError(new IllegalAnnotationException( Messages.TWO_ATTRIBUTE_WILDCARDS.format( nav().getClassName(getClazz())),aa,attributeWildcard)); return; // recover by ignore } attributeWildcard = seed; if(inheritsAttributeWildcard()) { builder.reportError(new IllegalAnnotationException( Messages.SUPER_CLASS_HAS_WILDCARD.format(), aa,getInheritedAttributeWildcard())); return; } // check the signature and make sure it's assignable to Map if(!nav().isSubClassOf(seed.getRawType(),nav().ref(Map.class))) { builder.reportError(new IllegalAnnotationException( Messages.INVALID_ATTRIBUTE_WILDCARD_TYPE.format(nav().getTypeName(seed.getRawType())), aa,getInheritedAttributeWildcard())); return; } return; case ATTRIBUTE: properties.add(createAttributeProperty(seed)); return; case VALUE: properties.add(createValueProperty(seed)); return; case ELEMENT: properties.add(createElementProperty(seed)); return; case ELEMENT_REF: properties.add(createReferenceProperty(seed)); return; case MAP: properties.add(createMapProperty(seed)); return; default: assert false; } } catch( ConflictException x ) { // report a conflicting annotation List err = x.annotations; builder.reportError(new IllegalAnnotationException( Messages.MUTUALLY_EXCLUSIVE_ANNOTATIONS.format( nav().getClassName(getClazz())+'#'+seed.getName(), err.get(0).annotationType().getName(), err.get(1).annotationType().getName()), err.get(0), err.get(1) )); // recover by ignoring this property } catch( DuplicateException e ) { // both are present builder.reportError(new IllegalAnnotationException( Messages.DUPLICATE_ANNOTATIONS.format(e.a1.annotationType().getName()), e.a1, e.a2 )); // recover by ignoring this property } } protected ReferencePropertyInfoImpl createReferenceProperty(PropertySeed seed) { return new ReferencePropertyInfoImpl(this,seed); } protected AttributePropertyInfoImpl createAttributeProperty(PropertySeed seed) { return new AttributePropertyInfoImpl(this,seed); } protected ValuePropertyInfoImpl createValueProperty(PropertySeed seed) { return new ValuePropertyInfoImpl(this,seed); } protected ElementPropertyInfoImpl createElementProperty(PropertySeed seed) { return new ElementPropertyInfoImpl(this,seed); } protected MapPropertyInfoImpl createMapProperty(PropertySeed seed) { return new MapPropertyInfoImpl(this,seed); } /** * Adds properties that consists of accessors. */ private void findGetterSetterProperties(XmlAccessType at) { // in the first step we accumulate getters and setters // into this map keyed by the property name. Map getters = new LinkedHashMap(); Map setters = new LinkedHashMap(); C c = clazz; do { collectGetterSetters(clazz, getters, setters); // take super classes into account if they have @XmlTransient c = nav().getSuperClass(c); } while(shouldRecurseSuperClass(c)); // compute the intersection Set complete = new TreeSet(getters.keySet()); complete.retainAll(setters.keySet()); resurrect(getters, complete); resurrect(setters, complete); // then look for read/write properties. for (String name : complete) { M getter = getters.get(name); M setter = setters.get(name); Annotation[] ga = getter!=null ? reader().getAllMethodAnnotations(getter,new MethodLocatable(this,getter,nav())) : EMPTY_ANNOTATIONS; Annotation[] sa = setter!=null ? reader().getAllMethodAnnotations(setter,new MethodLocatable(this,setter,nav())) : EMPTY_ANNOTATIONS; boolean hasAnnotation = hasJAXBAnnotation(ga) || hasJAXBAnnotation(sa); boolean isOverriding = false; if(!hasAnnotation) { // checking if the method is overriding others isn't free, // so we don't compute it if it's not necessary. isOverriding = (getter!=null && nav().isOverriding(getter,c)) && (setter!=null && nav().isOverriding(setter,c)); } if((at==XmlAccessType.PROPERTY && !isOverriding) || (at==XmlAccessType.PUBLIC_MEMBER && isConsideredPublic(getter) && isConsideredPublic(setter) && !isOverriding) || hasAnnotation) { // make sure that the type is consistent if(getter!=null && setter!=null && !nav().isSameType(nav().getReturnType(getter), nav().getMethodParameters(setter)[0])) { // inconsistent builder.reportError(new IllegalAnnotationException( Messages.GETTER_SETTER_INCOMPATIBLE_TYPE.format( nav().getTypeName(nav().getReturnType(getter)), nav().getTypeName(nav().getMethodParameters(setter)[0]) ), new MethodLocatable( this, getter, nav()), new MethodLocatable( this, setter, nav()))); continue; } // merge annotations from two list Annotation[] r; if(ga.length==0) { r = sa; } else if(sa.length==0) { r = ga; } else { r = new Annotation[ga.length+sa.length]; System.arraycopy(ga,0,r,0,ga.length); System.arraycopy(sa,0,r,ga.length,sa.length); } addProperty(createAccessorSeed(getter, setter), r, false); } } // done with complete pairs getters.keySet().removeAll(complete); setters.keySet().removeAll(complete); // TODO: think about // class Foo { // int getFoo(); // } // class Bar extends Foo { // void setFoo(int x); // } // and how it will be XML-ized. } private void collectGetterSetters(C c, Map getters, Map setters) { // take super classes into account if they have @XmlTransient. // always visit them first so that // 1) order is right // 2) overriden properties are handled accordingly C sc = nav().getSuperClass(c); if(shouldRecurseSuperClass(sc)) collectGetterSetters(sc,getters,setters); Collection methods = nav().getDeclaredMethods(c); Map> allSetters = new LinkedHashMap>(); for( M method : methods ) { boolean used = false; // if this method is added to getters or setters if(nav().isBridgeMethod(method)) continue; // ignore String name = nav().getMethodName(method); int arity = nav().getMethodParameters(method).length; if(nav().isStaticMethod(method)) { ensureNoAnnotation(method); continue; } // is this a get method? String propName = getPropertyNameFromGetMethod(name); if(propName!=null && arity==0) { getters.put(propName,method); used = true; } // is this a set method? propName = getPropertyNameFromSetMethod(name); if(propName!=null && arity==1) { List propSetters = allSetters.get(propName); if(null == propSetters){ propSetters = new ArrayList(); allSetters.put(propName, propSetters); } propSetters.add(method); used = true; // used check performed later } if(!used) ensureNoAnnotation(method); } // Match getter with setters by comparing getter return type to setter param for (Map.Entry entry : getters.entrySet()) { String propName = entry.getKey(); M getter = entry.getValue(); List propSetters = allSetters.remove(propName); if (null == propSetters) { //no matching setter continue; } T getterType = nav().getReturnType(getter); for (M setter : propSetters) { T setterType = nav().getMethodParameters(setter)[0]; if (nav().isSameType(setterType, getterType)) { setters.put(propName, setter); break; } } } // also allow set-only properties for (Map.Entry> e : allSetters.entrySet()) { setters.put(e.getKey(),e.getValue().get(0)); } } /** * Checks if the properties in this given super class should be aggregated into this class. */ private boolean shouldRecurseSuperClass(C sc) { return sc!=null && (builder.isReplaced(sc) || reader().hasClassAnnotation(sc, XmlTransient.class)); } /** * Returns true if the method is considered 'public'. */ private boolean isConsideredPublic(M m) { return m ==null || nav().isPublicMethod(m); } /** * If the method has an explicit annotation, allow it to participate * to the processing even if it lacks the setter or the getter. */ private void resurrect(Map methods, Set complete) { for (Map.Entry e : methods.entrySet()) { if(complete.contains(e.getKey())) continue; if(hasJAXBAnnotation(reader().getAllMethodAnnotations(e.getValue(),this))) complete.add(e.getKey()); } } /** * Makes sure that the method doesn't have any annotation, if it does, * report it as an error */ private void ensureNoAnnotation(M method) { Annotation[] annotations = reader().getAllMethodAnnotations(method,this); for( Annotation a : annotations ) { if(isJAXBAnnotation(a)) { builder.reportError(new IllegalAnnotationException( Messages.ANNOTATION_ON_WRONG_METHOD.format(), a)); return; } } } /** * Returns true if a given annotation is a JAXB annotation. */ private static boolean isJAXBAnnotation(Annotation a) { return ANNOTATION_NUMBER_MAP.containsKey(a.annotationType()); } /** * Returns true if the array contains a JAXB annotation. */ private static boolean hasJAXBAnnotation(Annotation[] annotations) { return getSomeJAXBAnnotation(annotations)!=null; } private static Annotation getSomeJAXBAnnotation(Annotation[] annotations) { for( Annotation a : annotations ) if(isJAXBAnnotation(a)) return a; return null; } /** * Returns "Foo" from "getFoo" or "isFoo". * * @return null * if the method name doesn't look like a getter. */ private static String getPropertyNameFromGetMethod(String name) { if(name.startsWith("get") && name.length()>3) return name.substring(3); if(name.startsWith("is") && name.length()>2) return name.substring(2); return null; } /** * Returns "Foo" from "setFoo". * * @return null * if the method name doesn't look like a setter. */ private static String getPropertyNameFromSetMethod(String name) { if(name.startsWith("set") && name.length()>3) return name.substring(3); return null; } /** * Creates a new {@link FieldPropertySeed} object. * *

* Derived class can override this method to create a sub-class. */ protected PropertySeed createFieldSeed(F f) { return new FieldPropertySeed(this, f); } /** * Creates a new {@link GetterSetterPropertySeed} object. */ protected PropertySeed createAccessorSeed(M getter, M setter) { return new GetterSetterPropertySeed(this, getter,setter); } public final boolean isElement() { return elementName!=null; } public boolean isAbstract() { return nav().isAbstract(clazz); } public boolean isOrdered() { return propOrder!=null; } public final boolean isFinal() { return nav().isFinal(clazz); } public final boolean hasSubClasses() { return hasSubClasses; } public final boolean hasAttributeWildcard() { return declaresAttributeWildcard() || inheritsAttributeWildcard(); } public final boolean inheritsAttributeWildcard() { return getInheritedAttributeWildcard()!=null; } public final boolean declaresAttributeWildcard() { return attributeWildcard!=null; } /** * Gets the {@link PropertySeed} object for the inherited attribute wildcard. */ private PropertySeed getInheritedAttributeWildcard() { for( ClassInfoImpl c=getBaseClass(); c!=null; c=c.getBaseClass() ) if(c.attributeWildcard!=null) return c.attributeWildcard; return null; } public final QName getElementName() { return elementName; } public final QName getTypeName() { return typeName; } public final boolean isSimpleType() { List props = getProperties(); if(props.size()!=1) return false; return props.get(0).kind()==PropertyKind.VALUE; } /** * Called after all the {@link com.sun.xml.internal.bind.v2.model.core.TypeInfo}s are collected into the {@link #owner}. */ @Override /*package*/ void link() { getProperties(); // make sure properties!=null // property name collision cehck Map names = new HashMap(); for( PropertyInfoImpl p : properties ) { p.link(); PropertyInfoImpl old = names.put(p.getName(),p); if(old!=null) { builder.reportError(new IllegalAnnotationException( Messages.PROPERTY_COLLISION.format(p.getName()), p, old )); } } super.link(); } public Location getLocation() { return nav().getClassLocation(clazz); } /** * XmlType allows specification of factoryClass and * factoryMethod. There are to be used if no default * constructor is found. * * @return * true if the factory method was found. False if not. */ private boolean hasFactoryConstructor(XmlType t){ if (t == null) return false; String method = t.factoryMethod(); T fClass = reader().getClassValue(t, "factoryClass"); if (method.length() > 0){ if(nav().isSameType(fClass, nav().ref(XmlType.DEFAULT.class))){ fClass = nav().use(clazz); } for(M m: nav().getDeclaredMethods(nav().asDecl(fClass))){ //- Find the zero-arg public static method with the required return type if (nav().getMethodName(m).equals(method) && nav().isSameType(nav().getReturnType(m), nav().use(clazz)) && nav().getMethodParameters(m).length == 0 && nav().isStaticMethod(m)){ factoryMethod = m; break; } } if (factoryMethod == null){ builder.reportError(new IllegalAnnotationException( Messages.NO_FACTORY_METHOD.format(nav().getClassName(nav().asDecl(fClass)), method), this )); } } else if(!nav().isSameType(fClass, nav().ref(XmlType.DEFAULT.class))){ builder.reportError(new IllegalAnnotationException( Messages.FACTORY_CLASS_NEEDS_FACTORY_METHOD.format(nav().getClassName(nav().asDecl(fClass))), this )); } return factoryMethod != null; } public Method getFactoryMethod(){ return (Method) factoryMethod; } @Override public String toString() { return "ClassInfo("+clazz+')'; } private static final String[] DEFAULT_ORDER = new String[0]; }