1 /*
2  * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Distribution License v. 1.0, which is available at
6  * http://www.eclipse.org/org/documents/edl-v10.php.
7  *
8  * SPDX-License-Identifier: BSD-3-Clause
9  */

10
11 package com.sun.xml.bind.v2.model.impl;
12
13 import java.lang.annotation.Annotation;
14 import java.lang.reflect.Method;
15 import java.util.ArrayList;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.Comparator;
19 import java.util.HashMap;
20 import java.util.HashSet;
21 import java.util.LinkedHashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.TreeSet;
26 import java.util.AbstractList;
27
28 import javax.xml.bind.annotation.XmlAccessOrder;
29 import javax.xml.bind.annotation.XmlAccessType;
30 import javax.xml.bind.annotation.XmlAccessorOrder;
31 import javax.xml.bind.annotation.XmlAccessorType;
32 import javax.xml.bind.annotation.XmlAnyAttribute;
33 import javax.xml.bind.annotation.XmlAnyElement;
34 import javax.xml.bind.annotation.XmlAttachmentRef;
35 import javax.xml.bind.annotation.XmlAttribute;
36 import javax.xml.bind.annotation.XmlElement;
37 import javax.xml.bind.annotation.XmlElementRef;
38 import javax.xml.bind.annotation.XmlElementRefs;
39 import javax.xml.bind.annotation.XmlElementWrapper;
40 import javax.xml.bind.annotation.XmlElements;
41 import javax.xml.bind.annotation.XmlID;
42 import javax.xml.bind.annotation.XmlIDREF;
43 import javax.xml.bind.annotation.XmlInlineBinaryData;
44 import javax.xml.bind.annotation.XmlList;
45 import javax.xml.bind.annotation.XmlMimeType;
46 import javax.xml.bind.annotation.XmlMixed;
47 import javax.xml.bind.annotation.XmlRootElement;
48 import javax.xml.bind.annotation.XmlSchemaType;
49 import javax.xml.bind.annotation.XmlTransient;
50 import javax.xml.bind.annotation.XmlType;
51 import javax.xml.bind.annotation.XmlValue;
52 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
53 import javax.xml.namespace.QName;
54
55 import com.sun.istack.FinalArrayList;
56 import com.sun.xml.bind.annotation.OverrideAnnotationOf;
57 import com.sun.xml.bind.v2.model.annotation.Locatable;
58 import com.sun.xml.bind.v2.model.annotation.MethodLocatable;
59 import com.sun.xml.bind.v2.model.core.ClassInfo;
60 import com.sun.xml.bind.v2.model.core.Element;
61 import com.sun.xml.bind.v2.model.core.ID;
62 import com.sun.xml.bind.v2.model.core.NonElement;
63 import com.sun.xml.bind.v2.model.core.PropertyInfo;
64 import com.sun.xml.bind.v2.model.core.PropertyKind;
65 import com.sun.xml.bind.v2.model.core.ValuePropertyInfo;
66 import com.sun.xml.bind.v2.runtime.IllegalAnnotationException;
67 import com.sun.xml.bind.v2.runtime.Location;
68 import com.sun.xml.bind.v2.util.EditDistance;
69
70
71 /**
72  * A part of the {@link ClassInfo} that doesn't depend on a particular
73  * reflection library.
74  *
75  * @author Kohsuke Kawaguchi (kk@kohsuke.org)
76  */

77 public class ClassInfoImpl<T,C,F,M> extends TypeInfoImpl<T,C,F,M>
78     implements ClassInfo<T,C>, Element<T,C> {
79
80     protected final C clazz;
81
82     /**
83      * @see #getElementName()
84      */

85     private final QName elementName;
86
87     /**
88      * @see #getTypeName()
89      */

90     private final QName typeName;
91
92     /**
93      * Lazily created.
94      *
95      * @see #getProperties()
96      */

97     private FinalArrayList<PropertyInfoImpl<T,C,F,M>> properties;
98
99     /**
100      * The property order.
101      *
102      * null if unordered. {@link #DEFAULT_ORDER} if ordered but the order is defaulted
103      *
104      * @see #isOrdered()
105      */

106     private /*final*/ String[] propOrder;
107
108     /**
109      * Lazily computed.
110      *
111      * To avoid the cyclic references of the form C1 --base--> C2 --property--> C1.
112      */

113     private ClassInfoImpl<T,C,F,M> baseClass;
114
115     private boolean baseClassComputed = false;
116
117     private boolean hasSubClasses = false;
118
119     /**
120      * If this class has a declared (not inherited) attribute wildcard,  keep the reference
121      * to it.
122      *
123      * This parameter is initialized at the construction time and never change.
124      */

125     protected /*final*/ PropertySeed<T,C,F,M> attributeWildcard;
126
127
128     /**
129      * @see #getFactoryMethod()
130      */

131     private M factoryMethod = null;
132     
133     ClassInfoImpl(ModelBuilder<T,C,F,M> builder, Locatable upstream, C clazz) {
134         super(builder,upstream);
135         this.clazz = clazz;
136         assert clazz!=null;
137
138         // compute the element name
139         elementName = parseElementName(clazz);
140
141         // compute the type name
142         XmlType t = reader().getClassAnnotation(XmlType.class,clazz,this);
143         typeName = parseTypeName(clazz,t);
144
145         if(t!=null) {
146             String[] propOrder = t.propOrder();
147             if(propOrder.length==0)
148                 this.propOrder = null;   // unordered
149             else {
150                 if(propOrder[0].length()==0)
151                     this.propOrder = DEFAULT_ORDER;
152                 else
153                     this.propOrder = propOrder;
154             }
155         } else {
156             propOrder = DEFAULT_ORDER;
157         }
158
159         // obtain XmlAccessorOrder and  set proporder (XmlAccessorOrder can be defined for whole package)
160         // (<xs:all> vs <xs:sequence>)
161         XmlAccessorOrder xao = reader().getPackageAnnotation(XmlAccessorOrder.class, clazz, this);
162         if((xao != null) && (xao.value() == XmlAccessOrder.UNDEFINED)) {
163             propOrder = null;
164         }
165
166         // obtain XmlAccessorOrder and  set proporder (<xs:all> vs <xs:sequence>)
167         xao = reader().getClassAnnotation(XmlAccessorOrder.class, clazz, this);
168         if((xao != null) && (xao.value() == XmlAccessOrder.UNDEFINED)) {
169             propOrder = null;
170         }
171
172         if(nav().isInterface(clazz)) {
173             builder.reportError(new IllegalAnnotationException(
174                 Messages.CANT_HANDLE_INTERFACE.format(nav().getClassName(clazz)), this ));
175         }
176
177         // the class must have the default constructor
178         if (!hasFactoryConstructor(t)){
179             if(!nav().hasDefaultConstructor(clazz)){
180                 if(nav().isInnerClass(clazz)) {
181                     builder.reportError(new IllegalAnnotationException(
182                         Messages.CANT_HANDLE_INNER_CLASS.format(nav().getClassName(clazz)), this ));
183                 } else if (elementName != null) {
184                     builder.reportError(new IllegalAnnotationException(
185                         Messages.NO_DEFAULT_CONSTRUCTOR.format(nav().getClassName(clazz)), this ));
186                 }
187             }
188         }
189     }        
190
191     public ClassInfoImpl<T,C,F,M> getBaseClass() {
192         if (!baseClassComputed) {
193             // compute the base class
194             C s = nav().getSuperClass(clazz);
195             if(s==null || s==nav().asDecl(Object.class)) {
196                 baseClass = null;
197             } else {
198                 NonElement<T,C> b = builder.getClassInfo(s, truethis);
199                 if(b instanceof ClassInfoImpl) {
200                     baseClass = (ClassInfoImpl<T,C,F,M>) b;
201                     baseClass.hasSubClasses = true;
202                 } else {
203                     baseClass = null;
204                 }
205             }
206             baseClassComputed = true;
207         }
208         return baseClass;
209     }
210
211     /**
212      * {@inheritDoc}
213      *
214      * The substitution hierarchy is the same as the inheritance hierarchy.
215      */

216     public final Element<T,C> getSubstitutionHead() {
217         ClassInfoImpl<T,C,F,M> c = getBaseClass();
218         while(c!=null && !c.isElement())
219             c = c.getBaseClass();
220         return c;
221     }
222
223     public final C getClazz() {
224         return clazz;
225     }
226
227     /**
228      * When a bean binds to an element, it's always through {@link XmlRootElement},
229      * so this method always return null.
230      *
231      * @deprecated
232      *      you shouldn't be invoking this method on {@link ClassInfoImpl}.
233      */

234     public ClassInfoImpl<T,C,F,M> getScope() {
235         return null;
236     }
237
238     public final T getType() {
239         return nav().use(clazz);
240     }
241
242     /**
243      * A {@link ClassInfo} can be referenced by {@link XmlIDREF} if
244      * it has an ID property.
245      */

246     public boolean canBeReferencedByIDREF() {
247         for (PropertyInfo<T,C> p : getProperties()) {
248             if(p.id()== ID.ID)
249                 return true;
250         }
251         ClassInfoImpl<T,C,F,M> base = getBaseClass();
252         if(base!=null)
253             return base.canBeReferencedByIDREF();
254         else
255             return false;
256     }
257
258     public final String getName() {
259         return nav().getClassName(clazz);
260     }
261
262     public <A extends Annotation> A readAnnotation(Class<A> a) {
263         return reader().getClassAnnotation(a,clazz,this);
264     }
265
266     public Element<T,C> asElement() {
267         if(isElement())
268             return this;
269         else
270             return null;
271     }
272
273     public List<? extends PropertyInfo<T,C>> getProperties() {
274         if(properties!=null)    return properties;
275
276         // check the access type first
277         XmlAccessType at = getAccessType();
278
279         properties = new FinalArrayList<PropertyInfoImpl<T,C,F,M>>();
280
281         findFieldProperties(clazz,at);
282
283         findGetterSetterProperties(at);
284
285         if(propOrder==DEFAULT_ORDER || propOrder==null) {
286             XmlAccessOrder ao = getAccessorOrder();
287             if(ao==XmlAccessOrder.ALPHABETICAL)
288                 Collections.sort(properties);
289         } else {
290             //sort them as specified
291             PropertySorter sorter = new PropertySorter();
292             for (PropertyInfoImpl p : properties) {
293                 sorter.checkedGet(p);   // have it check for errors
294             }
295             Collections.sort(properties,sorter);
296             sorter.checkUnusedProperties();
297         }
298
299         {// additional error checks
300             PropertyInfoImpl vp=null// existing value property
301             PropertyInfoImpl ep=null// existing element property
302
303             for (PropertyInfoImpl p : properties) {
304                 switch(p.kind()) {
305                 case ELEMENT:
306                 case REFERENCE:
307                 case MAP:
308                     ep = p;
309                     break;
310                 case VALUE:
311                     if(vp!=null) {
312                         // can't have multiple value properties.
313                         builder.reportError(new IllegalAnnotationException(
314                             Messages.MULTIPLE_VALUE_PROPERTY.format(),
315                             vp, p ));
316                     }
317                     if(getBaseClass()!=null) {
318                         builder.reportError(new IllegalAnnotationException(
319                             Messages.XMLVALUE_IN_DERIVED_TYPE.format(), p ));
320                     }
321                     vp = p;
322                     break;
323                 case ATTRIBUTE:
324                     break;  // noop
325                 default:
326                     assert false;
327                 }
328             }
329
330             if(ep!=null && vp!=null) {
331                 // can't have element and value property at the same time
332                 builder.reportError(new IllegalAnnotationException(
333                     Messages.ELEMENT_AND_VALUE_PROPERTY.format(),
334                     vp, ep
335                 ));
336             }
337         }
338
339         return properties;
340     }
341
342     private void findFieldProperties(C c, XmlAccessType at) {
343
344         // always find properties from the super class first
345         C sc = nav().getSuperClass(c);
346         if (shouldRecurseSuperClass(sc)) {
347             findFieldProperties(sc,at);
348         }
349
350         for( F f : nav().getDeclaredFields(c) ) {
351             Annotation[] annotations = reader().getAllFieldAnnotations(f,this);
352             boolean isDummy = reader().hasFieldAnnotation(OverrideAnnotationOf.class, f);
353
354             if( nav().isTransient(f) ) {
355                 // it's an error for transient field to have any binding annotation
356                 if(hasJAXBAnnotation(annotations))
357                     builder.reportError(new IllegalAnnotationException(
358                         Messages.TRANSIENT_FIELD_NOT_BINDABLE.format(nav().getFieldName(f)),
359                             getSomeJAXBAnnotation(annotations)));
360             } else
361             if( nav().isStaticField(f) ) {
362                 // static fields are bound only when there's explicit annotation.
363                 if(hasJAXBAnnotation(annotations))
364                     addProperty(createFieldSeed(f),annotations, false);
365             } else {
366                 if(at==XmlAccessType.FIELD
367                 ||(at==XmlAccessType.PUBLIC_MEMBER && nav().isPublicField(f))
368                 || hasJAXBAnnotation(annotations)) {
369                     if (isDummy) {
370                         ClassInfo<T, C> top = getBaseClass();
371                         while ((top != null) && (top.getProperty("content") == null)) {
372                             top = top.getBaseClass();
373                         }
374                         DummyPropertyInfo prop = (DummyPropertyInfo) top.getProperty("content");
375                         PropertySeed seed = createFieldSeed(f);
376                         ((DummyPropertyInfo)prop).addType(createReferenceProperty(seed));
377                     } else {
378                         addProperty(createFieldSeed(f), annotations, false);
379                     }
380                 }
381                 checkFieldXmlLocation(f);
382             }
383         }
384     }
385
386     public final boolean hasValueProperty() {
387         ClassInfoImpl<T, C, F, M> bc = getBaseClass();
388         if(bc!=null && bc.hasValueProperty())
389             return true;
390
391         for (PropertyInfo p : getProperties()) {
392             if (p instanceof ValuePropertyInfo) return true;
393         }
394
395         return false;
396         }
397
398     public PropertyInfo<T,C> getProperty(String name) {
399         for( PropertyInfo<T,C> p: getProperties() ) {
400             if(p.getName().equals(name))
401                 return p;
402         }
403         return null;
404     }
405
406     /**
407      * This hook is used by {@link RuntimeClassInfoImpl} to look for {@link com.sun.xml.bind.annotation.XmlLocation}.
408      */

409     protected void checkFieldXmlLocation(F f) {
410     }
411
412     /**
413      * Gets an annotation that are allowed on both class and type.
414      */

415     private <T extends Annotation> T getClassOrPackageAnnotation(Class<T> type) {
416         T t = reader().getClassAnnotation(type,clazz,this);
417         if(t!=null)
418             return t;
419         // defaults to the package level
420         return reader().getPackageAnnotation(type,clazz,this);
421     }
422
423     /**
424      * Computes the {@link XmlAccessType} on this class by looking at {@link XmlAccessorType}
425      * annotations.
426      */

427     private XmlAccessType getAccessType() {
428         XmlAccessorType xat = getClassOrPackageAnnotation(XmlAccessorType.class);
429         if(xat!=null)
430             return xat.value();
431         else
432             return XmlAccessType.PUBLIC_MEMBER;
433     }
434
435     /**
436      * Gets the accessor order for this class by consulting {@link XmlAccessorOrder}.
437      */

438     private XmlAccessOrder getAccessorOrder() {
439         XmlAccessorOrder xao = getClassOrPackageAnnotation(XmlAccessorOrder.class);
440         if(xao!=null)
441             return xao.value();
442         else
443             return XmlAccessOrder.UNDEFINED;
444     }
445
446     /**
447      * Compares orders among {@link PropertyInfoImpl} according to {@link ClassInfoImpl#propOrder}.
448      *
449      * <p>
450      * extends {@link HashMap} to save memory.
451      */

452     private final class PropertySorter extends HashMap<String,Integer> implements Comparator<PropertyInfoImpl> {
453         /**
454          * Mark property names that are used, so that we can report unused property names in the propOrder array.
455          */

456         PropertyInfoImpl[] used = new PropertyInfoImpl[propOrder.length];
457
458         /**
459          * If any name collides, it will be added to this set.
460          * This is used to avoid repeating the same error message.
461          */

462         private Set<String> collidedNames;
463
464         PropertySorter() {
465             super(propOrder.length);
466             for( String name : propOrder )
467                 if(put(name,size())!=null) {
468                     // two properties with the same name
469                     builder.reportError(new IllegalAnnotationException(
470                         Messages.DUPLICATE_ENTRY_IN_PROP_ORDER.format(name),ClassInfoImpl.this));
471                 }
472         }
473
474         public int compare(PropertyInfoImpl o1, PropertyInfoImpl o2) {
475             int lhs = checkedGet(o1);
476             int rhs = checkedGet(o2);
477
478             return lhs-rhs;
479         }
480
481         private int checkedGet(PropertyInfoImpl p) {
482             Integer i = get(p.getName());
483             if(i==null) {
484                 // missing
485                 if (p.kind().isOrdered)
486                     builder.reportError(new IllegalAnnotationException(
487                         Messages.PROPERTY_MISSING_FROM_ORDER.format(p.getName()),p));
488
489                 // give it an order to recover from an error
490                 i = size();
491                 put(p.getName(),i);
492             }
493
494             // mark the used field
495             int ii = i;
496             if(ii<used.length) {
497                 if(used[ii]!=null && used[ii]!=p) {
498                     if(collidedNames==null) collidedNames = new HashSet<String>();
499
500                     if(collidedNames.add(p.getName()))
501                         // report the error only on the first time
502                         builder.reportError(new IllegalAnnotationException(
503                             Messages.DUPLICATE_PROPERTIES.format(p.getName()),p,used[ii]));
504                 }
505                 used[ii] = p;
506             }
507
508             return i;
509         }
510
511         /**
512          * Report errors for unused propOrder entries.
513          */

514         public void checkUnusedProperties() {
515             forint i=0; i<used.length; i++ )
516                 if(used[i]==null) {
517                     String unusedName = propOrder[i];
518                     String nearest = EditDistance.findNearest(unusedName, new AbstractList<String>() {
519                         public String get(int index) {
520                             return properties.get(index).getName();
521                         }
522
523                         public int size() {
524                             return properties.size();
525                         }
526                     });
527                     boolean isOverriding = (i > (properties.size()-1)) ? false : properties.get(i).hasAnnotation(OverrideAnnotationOf.class);
528                     if (!isOverriding) {
529                         builder.reportError(new IllegalAnnotationException(
530                         Messages.PROPERTY_ORDER_CONTAINS_UNUSED_ENTRY.format(unusedName,nearest),ClassInfoImpl.this));
531                     }
532                 }
533         }
534     }
535
536     public boolean hasProperties() {
537         return !properties.isEmpty();
538     }
539
540
541     /**
542      * Picks the first non-null argument, or null if all arguments are null.
543      */

544     private static <T> T pickOne( T... args ) {
545         for( T arg : args )
546             if(arg!=null)
547                 return arg;
548         return null;
549     }
550
551     private static <T> List<T> makeSet( T... args ) {
552         List<T> l = new FinalArrayList<T>();
553         for( T arg : args )
554             if(arg!=null)   l.add(arg);
555         return l;
556     }
557
558     private static final class ConflictException extends Exception {
559         final List<Annotation> annotations;
560
561         public ConflictException(List<Annotation> one) {
562             this.annotations = one;
563         }
564     }
565
566     private static final class DuplicateException extends Exception {
567         final Annotation a1,a2;
568         public DuplicateException(Annotation a1, Annotation a2) {
569             this.a1 = a1;
570             this.a2 = a2;
571         }
572     }
573
574     /**
575      * Represents 6 groups of secondary annotations
576      */

577     private static enum SecondaryAnnotation {
578         JAVA_TYPE       (0x01, XmlJavaTypeAdapter.class),
579         ID_IDREF        (0x02, XmlID.class, XmlIDREF.class),
580         BINARY          (0x04, XmlInlineBinaryData.class, XmlMimeType.class, XmlAttachmentRef.class),
581         ELEMENT_WRAPPER (0x08, XmlElementWrapper.class),
582         LIST            (0x10, XmlList.class),
583         SCHEMA_TYPE     (0x20, XmlSchemaType.class);
584
585         /**
586          * Each constant gets an unique bit mask so that the presence/absence
587          * of them can be represented in a single byte.
588          */

589         final int bitMask;
590         /**
591          * List of annotations that belong to this member.
592          */

593         final Class<? extends Annotation>[] members;
594
595         SecondaryAnnotation(int bitMask, Class<? extends Annotation>... members) {
596             this.bitMask = bitMask;
597             this.members = members;
598         }
599     }
600
601     private static final SecondaryAnnotation[] SECONDARY_ANNOTATIONS = SecondaryAnnotation.values();
602
603     /**
604      * Represents 7 groups of properties.
605      *
606      * Each instance is also responsible for rejecting annotations
607      * that are not allowed on that kind.
608      */

609     private static enum PropertyGroup {
610         TRANSIENT       (false,false,false,false,false,false),
611         ANY_ATTRIBUTE   (truefalse,false,false,false,false),
612         ATTRIBUTE       (truetruetruefalse,truetrue ),
613         VALUE           (truetruetruefalse,truetrue ),
614         ELEMENT         (truetruetruetruetruetrue ),
615         ELEMENT_REF     (truefalse,false,truefalse,false),
616         MAP             (false,false,false,truefalse,false);
617
618         /**
619          * Bit mask that represents secondary annotations that are allowed on this group.
620          *
621          * T = not allowed, F = allowed
622          */

623         final int allowedsecondaryAnnotations;
624
625         PropertyGroup(boolean... bits) {
626             int mask = 0;
627             assert bits.length==SECONDARY_ANNOTATIONS.length;
628             forint i=0; i<bits.length; i++ ) {
629                 if(bits[i])
630                     mask |= SECONDARY_ANNOTATIONS[i].bitMask;
631             }
632             allowedsecondaryAnnotations = ~mask;
633         }
634
635         boolean allows(SecondaryAnnotation a) {
636             return (allowedsecondaryAnnotations&a.bitMask)==0;
637         }
638     }
639
640     private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
641
642     /**
643      * All the annotations in JAXB to their internal index.
644      */

645     private static final HashMap<Class,Integer> ANNOTATION_NUMBER_MAP = new HashMap<Class,Integer>();
646     static {
647         Class[] annotations = {
648             XmlTransient.class,     // 0
649             XmlAnyAttribute.class,  // 1
650             XmlAttribute.class,     // 2
651             XmlValue.class,         // 3
652             XmlElement.class,       // 4
653             XmlElements.class,      // 5
654             XmlElementRef.class,    // 6
655             XmlElementRefs.class,   // 7
656             XmlAnyElement.class,    // 8
657             XmlMixed.class,         // 9
658             OverrideAnnotationOf.class,// 10
659         };
660
661         HashMap<Class,Integer> m = ANNOTATION_NUMBER_MAP;
662
663         // characterizing annotations
664         for( Class c : annotations )
665             m.put(c, m.size() );
666
667         // secondary annotations
668         int index = 20;
669         for( SecondaryAnnotation sa : SECONDARY_ANNOTATIONS ) {
670             for( Class member : sa.members )
671                 m.put(member,index);
672             index++;
673         }
674     }
675
676     private void checkConflict(Annotation a, Annotation b) throws DuplicateException {
677         assert b!=null;
678         if(a!=null)
679             throw new DuplicateException(a,b);
680     }
681
682     /**
683      * Called only from {@link #getProperties()}.
684      *
685      * <p>
686      * This is where we decide the type of the property and checks for annotations
687      * that are not allowed.
688      *
689      * @param annotations
690      *      all annotations on this property. It's the same as
691      *      {@code seed.readAllAnnotation()}, but taken as a parameter
692      *      because the caller should know it already.
693      */

694     private void addProperty( PropertySeed<T,C,F,M> seed, Annotation[] annotations, boolean dummy ) {
695         // since typically there's a very few annotations on a method,
696         // this runs faster than checking for each annotation via readAnnotation(A)
697
698
699         // characterizing annotations. these annotations (or lack thereof) decides
700         // the kind of the property it goes to.
701         // I wish I could use an array...
702         XmlTransient t = null;
703         XmlAnyAttribute aa = null;
704         XmlAttribute a = null;
705         XmlValue v = null;
706         XmlElement e1 = null;
707         XmlElements e2 = null;
708         XmlElementRef r1 = null;
709         XmlElementRefs r2 = null;
710         XmlAnyElement xae = null;
711         XmlMixed mx = null;
712         OverrideAnnotationOf ov = null;
713
714         // encountered secondary annotations are accumulated into a bit mask
715         int secondaryAnnotations = 0;
716
717         try {
718             for( Annotation ann : annotations ) {
719                 Integer index = ANNOTATION_NUMBER_MAP.get(ann.annotationType());
720                 if(index==nullcontinue;
721                 switch(index) {
722                 case 0:     checkConflict(t  ,ann); t   = (XmlTransient) ann; break;
723                 case 1:     checkConflict(aa ,ann); aa  = (XmlAnyAttribute) ann; break;
724                 case 2:     checkConflict(a  ,ann); a   = (XmlAttribute) ann; break;
725                 case 3:     checkConflict(v  ,ann); v   = (XmlValue) ann; break;
726                 case 4:     checkConflict(e1 ,ann); e1  = (XmlElement) ann; break;
727                 case 5:     checkConflict(e2 ,ann); e2  = (XmlElements) ann; break;
728                 case 6:     checkConflict(r1 ,ann); r1  = (XmlElementRef) ann; break;
729                 case 7:     checkConflict(r2 ,ann); r2  = (XmlElementRefs) ann; break;
730                 case 8:     checkConflict(xae,ann); xae = (XmlAnyElement) ann; break;
731                 case 9:     checkConflict(mx, ann); mx  = (XmlMixed) ann; break;
732                 case 10:    checkConflict(ov, ann); ov  = (OverrideAnnotationOf) ann; break;
733                 default:
734                     // secondary annotations
735                     secondaryAnnotations |= (1<<(index-20));
736                     break;
737                 }
738             }
739
740             // determine the group kind, and also count the numbers, since
741             // characterizing annotations are mutually exclusive.
742             PropertyGroup group = null;
743             int groupCount = 0;
744
745             if(t!=null) {
746                 group = PropertyGroup.TRANSIENT;
747                 groupCount++;
748             }
749             if(aa!=null) {
750                 group = PropertyGroup.ANY_ATTRIBUTE;
751                 groupCount++;
752             }
753             if(a!=null) {
754                 group = PropertyGroup.ATTRIBUTE;
755                 groupCount++;
756             }
757             if(v!=null) {
758                 group = PropertyGroup.VALUE;
759                 groupCount++;
760             }
761             if(e1!=null || e2!=null) {
762                 group = PropertyGroup.ELEMENT;
763                 groupCount++;
764             }
765             if(r1!=null || r2!=null || xae!=null || mx!=null || ov != null) {
766                 group = PropertyGroup.ELEMENT_REF;
767                 groupCount++;
768             }
769
770             if(groupCount>1) {
771                 // collision between groups
772                 List<Annotation> err = makeSet(t,aa,a,v,pickOne(e1,e2),pickOne(r1,r2,xae));
773                 throw new ConflictException(err);
774             }
775
776             if(group==null) {
777                 // if no characterizing annotation was found, it's either element or map
778                 // sniff the signature and then decide.
779                 assert groupCount==0;
780
781                 // UGLY: the presence of XmlJavaTypeAdapter makes it an element property. ARGH.
782                 if(nav().isSubClassOf( seed.getRawType(), nav().ref(Map.class) )
783                 && !seed.hasAnnotation(XmlJavaTypeAdapter.class))
784                     group = PropertyGroup.MAP;
785                 else
786                     group = PropertyGroup.ELEMENT;
787             } else if (group.equals(PropertyGroup.ELEMENT)) { // see issue 791 - make sure @XmlElement annotated map property is mapped to map
788                 if (nav().isSubClassOf( seed.getRawType(), nav().ref(Map.class)) && !seed.hasAnnotation(XmlJavaTypeAdapter.class)) {
789                     group = PropertyGroup.MAP;
790                 }
791             }
792
793             // group determined by now
794             // make sure that there are no prohibited secondary annotations
795             if( (secondaryAnnotations&group.allowedsecondaryAnnotations)!=0 ) {
796                 // uh oh. find the offending annotation
797                 for( SecondaryAnnotation sa : SECONDARY_ANNOTATIONS ) {
798                     if(group.allows(sa))
799                         continue;
800                     for( Class<? extends Annotation> m : sa.members ) {
801                         Annotation offender = seed.readAnnotation(m);
802                         if(offender!=null) {
803                             // found it
804                             builder.reportError(new IllegalAnnotationException(
805                                 Messages.ANNOTATION_NOT_ALLOWED.format(m.getSimpleName()),offender));
806                             return;
807                         }
808                     }
809                 }
810                 // there must have been an offender
811                 assert false;
812             }
813
814             // actually create annotations
815             switch(group) {
816             case TRANSIENT:
817                 return;
818             case ANY_ATTRIBUTE:
819                 // an attribute wildcard property
820                 if(attributeWildcard!=null) {
821                     builder.reportError(new IllegalAnnotationException(
822                         Messages.TWO_ATTRIBUTE_WILDCARDS.format(
823                             nav().getClassName(getClazz())),aa,attributeWildcard));
824                     return// recover by ignore
825                 }
826                 attributeWildcard = seed;
827
828                 if(inheritsAttributeWildcard()) {
829                     builder.reportError(new IllegalAnnotationException(
830                         Messages.SUPER_CLASS_HAS_WILDCARD.format(),
831                             aa,getInheritedAttributeWildcard()));
832                     return;
833                 }
834
835                 // check the signature and make sure it's assignable to Map
836                 if(!nav().isSubClassOf(seed.getRawType(),nav().ref(Map.class))) {
837                     builder.reportError(new IllegalAnnotationException(
838                         Messages.INVALID_ATTRIBUTE_WILDCARD_TYPE.format(nav().getTypeName(seed.getRawType())),
839                             aa,getInheritedAttributeWildcard()));
840                     return;
841                 }
842
843
844                 return;
845             case ATTRIBUTE:
846                 properties.add(createAttributeProperty(seed));
847                 return;
848             case VALUE:
849                 properties.add(createValueProperty(seed));
850                 return;
851             case ELEMENT:
852                 properties.add(createElementProperty(seed));
853                 return;
854             case ELEMENT_REF:
855                 properties.add(createReferenceProperty(seed));
856                 return;
857             case MAP:
858                 properties.add(createMapProperty(seed));
859                 return;
860             default:
861                 assert false;
862             }
863         } catch( ConflictException x ) {
864             // report a conflicting annotation
865             List<Annotation> err = x.annotations;
866
867             builder.reportError(new IllegalAnnotationException(
868                 Messages.MUTUALLY_EXCLUSIVE_ANNOTATIONS.format(
869                     nav().getClassName(getClazz())+'#'+seed.getName(),
870                     err.get(0).annotationType().getName(), err.get(1).annotationType().getName()),
871                     err.get(0), err.get(1) ));
872
873             // recover by ignoring this property
874         } catch( DuplicateException e ) {
875             // both are present
876             builder.reportError(new IllegalAnnotationException(
877                 Messages.DUPLICATE_ANNOTATIONS.format(e.a1.annotationType().getName()),
878                 e.a1, e.a2 ));
879             // recover by ignoring this property
880
881         }
882     }
883
884     protected ReferencePropertyInfoImpl<T,C,F,M> createReferenceProperty(PropertySeed<T,C,F,M> seed) {
885         return new ReferencePropertyInfoImpl<T,C,F,M>(this,seed);
886     }
887
888     protected AttributePropertyInfoImpl<T,C,F,M> createAttributeProperty(PropertySeed<T,C,F,M> seed) {
889         return new AttributePropertyInfoImpl<T,C,F,M>(this,seed);
890     }
891
892     protected ValuePropertyInfoImpl<T,C,F,M> createValueProperty(PropertySeed<T,C,F,M> seed) {
893         return new ValuePropertyInfoImpl<T,C,F,M>(this,seed);
894     }
895
896     protected ElementPropertyInfoImpl<T,C,F,M> createElementProperty(PropertySeed<T,C,F,M> seed) {
897         return new ElementPropertyInfoImpl<T,C,F,M>(this,seed);
898     }
899
900     protected MapPropertyInfoImpl<T,C,F,M> createMapProperty(PropertySeed<T,C,F,M> seed) {
901         return new MapPropertyInfoImpl<T,C,F,M>(this,seed);
902     }
903
904
905     /**
906      * Adds properties that consists of accessors.
907      */

908     private void findGetterSetterProperties(XmlAccessType at) {
909         // in the first step we accumulate getters and setters
910         // into this map keyed by the property name.
911         Map<String,M> getters = new LinkedHashMap<String,M>();
912         Map<String,M> setters = new LinkedHashMap<String,M>();
913
914         C c = clazz;
915         do {
916             collectGetterSetters(clazz, getters, setters);
917
918             // take super classes into account if they have @XmlTransient
919             c = nav().getSuperClass(c);
920         } while(shouldRecurseSuperClass(c));
921
922
923         // compute the intersection
924         Set<String> complete = new TreeSet<String>(getters.keySet());
925         complete.retainAll(setters.keySet());
926
927         resurrect(getters, complete);
928         resurrect(setters, complete);
929
930         // then look for read/write properties.
931         for (String name : complete) {
932             M getter = getters.get(name);
933             M setter = setters.get(name);
934
935             Annotation[] ga = getter!=null ? reader().getAllMethodAnnotations(getter,new MethodLocatable<M>(this,getter,nav())) : EMPTY_ANNOTATIONS;
936             Annotation[] sa = setter!=null ? reader().getAllMethodAnnotations(setter,new MethodLocatable<M>(this,setter,nav())) : EMPTY_ANNOTATIONS;
937
938             boolean hasAnnotation = hasJAXBAnnotation(ga) || hasJAXBAnnotation(sa);
939             boolean isOverriding = false;
940             if(!hasAnnotation) {
941                 // checking if the method is overriding others isn't free,
942                 // so we don't compute it if it's not necessary.
943                 isOverriding = (getter!=null && nav().isOverriding(getter,c))
944                             && (setter!=null && nav().isOverriding(setter,c));
945             }
946
947             if((at==XmlAccessType.PROPERTY && !isOverriding)
948                 || (at==XmlAccessType.PUBLIC_MEMBER && isConsideredPublic(getter) && isConsideredPublic(setter) && !isOverriding)
949             || hasAnnotation) {
950                 // make sure that the type is consistent
951                 if(getter!=null && setter!=null
952                 && !nav().isSameType(nav().getReturnType(getter), nav().getMethodParameters(setter)[0])) {
953                     // inconsistent
954                     builder.reportError(new IllegalAnnotationException(
955                         Messages.GETTER_SETTER_INCOMPATIBLE_TYPE.format(
956                             nav().getTypeName(nav().getReturnType(getter)),
957                             nav().getTypeName(nav().getMethodParameters(setter)[0])
958                         ),
959                         new MethodLocatable<M>( this, getter, nav()),
960                         new MethodLocatable<M>( this, setter, nav())));
961                     continue;
962                 }
963
964                 // merge annotations from two list
965                 Annotation[] r;
966                 if(ga.length==0) {
967                     r = sa;
968                 } else
969                 if(sa.length==0) {
970                     r = ga;
971                 } else {
972                     r = new Annotation[ga.length+sa.length];
973                     System.arraycopy(ga,0,r,0,ga.length);
974                     System.arraycopy(sa,0,r,ga.length,sa.length);
975                 }
976
977                 addProperty(createAccessorSeed(getter, setter), r, false);
978             }
979         }
980         // done with complete pairs
981         getters.keySet().removeAll(complete);
982         setters.keySet().removeAll(complete);
983
984         // TODO: think about
985         // class Foo {
986         //   int getFoo();
987         // }
988         // class Bar extends Foo {
989         //   void setFoo(int x);
990         // }
991         // and how it will be XML-ized.
992     }
993
994     private void collectGetterSetters(C c, Map<String,M> getters, Map<String,M> setters) {
995         // take super classes into account if they have @XmlTransient.
996         // always visit them first so that
997         //   1) order is right
998         //   2) overriden properties are handled accordingly
999         C sc = nav().getSuperClass(c);
1000         if(shouldRecurseSuperClass(sc))
1001             collectGetterSetters(sc,getters,setters);
1002
1003         Collection<? extends M> methods = nav().getDeclaredMethods(c);
1004         Map<String,List<M>> allSetters = new LinkedHashMap<String,List<M>>();
1005         for( M method : methods ) {
1006             boolean used = false;   // if this method is added to getters or setters
1007
1008             if(nav().isBridgeMethod(method))
1009                 continue;   // ignore
1010
1011             String name = nav().getMethodName(method);
1012             int arity = nav().getMethodParameters(method).length;
1013
1014             if(nav().isStaticMethod(method)) {
1015                 ensureNoAnnotation(method);
1016                 continue;
1017             }
1018
1019             // is this a get method?
1020             String propName = getPropertyNameFromGetMethod(name);
1021             if(propName!=null && arity==0) {
1022                     getters.put(propName,method);
1023                 used = true;
1024             }
1025
1026             // is this a set method?
1027             propName = getPropertyNameFromSetMethod(name);
1028             if(propName!=null && arity==1) {
1029                     List<M> propSetters = allSetters.get(propName);
1030                     if(null == propSetters){
1031                         propSetters = new ArrayList<M>();
1032                         allSetters.put(propName, propSetters);
1033                     }
1034                     propSetters.add(method);
1035                 used = true// used check performed later
1036             }
1037
1038             if(!used)
1039                 ensureNoAnnotation(method);
1040         }
1041
1042         // Match getter with setters by comparing getter return type to setter param
1043         for (Map.Entry<String,M> entry : getters.entrySet()) {
1044             String propName = entry.getKey();
1045             M getter = entry.getValue();
1046             List<M> propSetters = allSetters.remove(propName);
1047             if (null == propSetters) {
1048                 //no matching setter
1049                 continue;
1050             }
1051             T getterType = nav().getReturnType(getter);
1052             for (M setter : propSetters) {
1053                 T setterType = nav().getMethodParameters(setter)[0];
1054                 if (nav().isSameType(setterType, getterType)) {
1055                     setters.put(propName, setter);
1056                     break;
1057                 }
1058             }
1059         }
1060
1061         // also allow set-only properties
1062         for (Map.Entry<String,List<M>> e : allSetters.entrySet()) {
1063             setters.put(e.getKey(),e.getValue().get(0));
1064         }
1065     }
1066     
1067     /**
1068      * Checks if the properties in this given super class should be aggregated into this class.
1069      */

1070     private boolean shouldRecurseSuperClass(C sc) {
1071         return sc!=null
1072             && (builder.isReplaced(sc) || reader().hasClassAnnotation(sc, XmlTransient.class));
1073     }
1074
1075     /**
1076      * Returns true if the method is considered 'public'.
1077      */

1078     private boolean isConsideredPublic(M m) {
1079         return m ==null || nav().isPublicMethod(m);
1080     }
1081
1082     /**
1083      * If the method has an explicit annotation, allow it to participate
1084      * to the processing even if it lacks the setter or the getter.
1085      */

1086     private void resurrect(Map<String, M> methods, Set<String> complete) {
1087         for (Map.Entry<String, M> e : methods.entrySet()) {
1088             if(complete.contains(e.getKey()))
1089                 continue;
1090             if(hasJAXBAnnotation(reader().getAllMethodAnnotations(e.getValue(),this)))
1091                 complete.add(e.getKey());
1092         }
1093     }
1094
1095     /**
1096      * Makes sure that the method doesn't have any annotation, if it does,
1097      * report it as an error
1098      */

1099     private void ensureNoAnnotation(M method) {
1100         Annotation[] annotations = reader().getAllMethodAnnotations(method,this);
1101         for( Annotation a : annotations ) {
1102             if(isJAXBAnnotation(a)) {
1103                 builder.reportError(new IllegalAnnotationException(
1104                     Messages.ANNOTATION_ON_WRONG_METHOD.format(),
1105                     a));
1106                 return;
1107             }
1108         }
1109     }
1110
1111     /**
1112      * Returns true if a given annotation is a JAXB annotation.
1113      */

1114     private static boolean isJAXBAnnotation(Annotation a) {
1115         return ANNOTATION_NUMBER_MAP.containsKey(a.annotationType());
1116     }
1117
1118     /**
1119      * Returns true if the array contains a JAXB annotation.
1120      */

1121     private static boolean hasJAXBAnnotation(Annotation[] annotations) {
1122         return getSomeJAXBAnnotation(annotations)!=null;
1123     }
1124
1125     private static Annotation getSomeJAXBAnnotation(Annotation[] annotations) {
1126         for( Annotation a : annotations )
1127             if(isJAXBAnnotation(a))
1128                 return a;
1129         return null;
1130     }
1131
1132
1133     /**
1134      * Returns "Foo" from "getFoo" or "isFoo".
1135      *
1136      * @return null
1137      *      if the method name doesn't look like a getter.
1138      */

1139     private static String getPropertyNameFromGetMethod(String name) {
1140         if(name.startsWith("get") && name.length()>3)
1141             return name.substring(3);
1142         if(name.startsWith("is") && name.length()>2)
1143             return name.substring(2);
1144         return null;
1145     }
1146
1147     /**
1148      * Returns "Foo" from "setFoo".
1149      *
1150      * @return null
1151      *      if the method name doesn't look like a setter.
1152      */

1153     private static String getPropertyNameFromSetMethod(String name) {
1154         if(name.startsWith("set") && name.length()>3)
1155             return name.substring(3);
1156         return null;
1157     }
1158
1159     /**
1160      * Creates a new {@link FieldPropertySeed} object.
1161      *
1162      * <p>
1163      * Derived class can override this method to create a sub-class.
1164      */

1165     protected PropertySeed<T,C,F,M> createFieldSeed(F f) {
1166         return new FieldPropertySeed<T,C,F,M>(this, f);
1167     }
1168
1169     /**
1170      * Creates a new {@link GetterSetterPropertySeed} object.
1171      */

1172     protected PropertySeed<T,C,F,M> createAccessorSeed(M getter, M setter) {
1173         return new GetterSetterPropertySeed<T,C,F,M>(this, getter,setter);
1174     }
1175
1176     public final boolean isElement() {
1177         return elementName!=null;
1178     }
1179
1180     public boolean isAbstract() {
1181         return nav().isAbstract(clazz);
1182     }
1183
1184     public boolean isOrdered() {
1185         return propOrder!=null;
1186     }
1187
1188     public final boolean isFinal() {
1189         return nav().isFinal(clazz);
1190     }
1191
1192     public final boolean hasSubClasses() {
1193         return hasSubClasses;
1194     }
1195
1196     public final boolean hasAttributeWildcard() {
1197         return declaresAttributeWildcard() || inheritsAttributeWildcard();
1198     }
1199
1200     public final boolean inheritsAttributeWildcard() {
1201         return getInheritedAttributeWildcard()!=null;
1202     }
1203
1204     public final boolean declaresAttributeWildcard() {
1205         return attributeWildcard!=null;
1206     }
1207
1208     /**
1209      * Gets the {@link PropertySeed} object for the inherited attribute wildcard.
1210      */

1211     private PropertySeed<T,C,F,M> getInheritedAttributeWildcard() {
1212         for( ClassInfoImpl<T,C,F,M> c=getBaseClass(); c!=null; c=c.getBaseClass() )
1213             if(c.attributeWildcard!=null)
1214                 return c.attributeWildcard;
1215         return null;
1216     }
1217
1218     public final QName getElementName() {
1219         return elementName;
1220     }
1221
1222     public final QName getTypeName() {
1223         return typeName;
1224     }
1225
1226     public final boolean isSimpleType() {
1227         List<? extends PropertyInfo> props = getProperties();
1228         if(props.size()!=1)     return false;
1229         return props.get(0).kind()==PropertyKind.VALUE;
1230     }
1231
1232     /**
1233      * Called after all the {@link com.sun.xml.bind.v2.model.core.TypeInfo}s are collected into the {@link #owner}.
1234      */

1235     @Override
1236     /*package*/ void link() {
1237         getProperties();    // make sure properties!=null
1238
1239         // property name collision cehck
1240         Map<String,PropertyInfoImpl> names = new HashMap<String,PropertyInfoImpl>();
1241         for( PropertyInfoImpl<T,C,F,M> p : properties ) {
1242             p.link();
1243             PropertyInfoImpl old = names.put(p.getName(),p);
1244             if(old!=null) {
1245                 builder.reportError(new IllegalAnnotationException(
1246                     Messages.PROPERTY_COLLISION.format(p.getName()),
1247                     p, old ));
1248             }
1249         }
1250         super.link();
1251     }
1252
1253     public Location getLocation() {
1254         return nav().getClassLocation(clazz);
1255     }
1256
1257     /**
1258      *  XmlType allows specification of factoryClass and
1259      *  factoryMethod.  There are to be used if no default
1260      *  constructor is found.
1261      *
1262      * @return
1263      *      true if the factory method was found. False if not.
1264      */

1265     private  boolean hasFactoryConstructor(XmlType t){
1266         if (t == nullreturn false;
1267
1268         String method = t.factoryMethod();
1269         T fClass = reader().getClassValue(t, "factoryClass");
1270         if (method.length() > 0){
1271             if(nav().isSameType(fClass, nav().ref(XmlType.DEFAULT.class))){
1272                 fClass = nav().use(clazz);
1273             }
1274             for(M m: nav().getDeclaredMethods(nav().asDecl(fClass))){
1275                 //- Find the zero-arg public static method with the required return type
1276                 if (nav().getMethodName(m).equals(method) &&
1277                     nav().isSameType(nav().getReturnType(m), nav().use(clazz)) &&
1278                     nav().getMethodParameters(m).length == 0 &&
1279                     nav().isStaticMethod(m)){
1280                     factoryMethod = m;
1281                     break;
1282                 }
1283             }
1284             if (factoryMethod == null){
1285                 builder.reportError(new IllegalAnnotationException(
1286                 Messages.NO_FACTORY_METHOD.format(nav().getClassName(nav().asDecl(fClass)), method), this ));
1287             }
1288         } else if(!nav().isSameType(fClass, nav().ref(XmlType.DEFAULT.class))){
1289             builder.reportError(new IllegalAnnotationException(
1290                 Messages.FACTORY_CLASS_NEEDS_FACTORY_METHOD.format(nav().getClassName(nav().asDecl(fClass))), this ));
1291         }
1292         return factoryMethod != null;
1293     }
1294
1295     public Method getFactoryMethod(){
1296         return (Method) factoryMethod;
1297     }
1298
1299     @Override
1300     public String toString() {
1301         return "ClassInfo("+clazz+')';
1302     }
1303
1304     private static final String[] DEFAULT_ORDER = new String[0];
1305 }
1306