Fri, 24 Oct 2014 15:02:28 +0200
8054367: More references for endpoints
Summary: fix also reviewed by Iaroslav.Savytskyi@oracle.com, Alexander.Fomin@oracle.com
Reviewed-by: mullan, skoivu
1 /*
2 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package com.sun.xml.internal.bind.v2.model.impl;
28 import java.lang.annotation.Annotation;
29 import java.lang.reflect.Method;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.Comparator;
34 import java.util.HashMap;
35 import java.util.HashSet;
36 import java.util.LinkedHashMap;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Set;
40 import java.util.TreeSet;
41 import java.util.AbstractList;
43 import javax.xml.bind.annotation.XmlAccessOrder;
44 import javax.xml.bind.annotation.XmlAccessType;
45 import javax.xml.bind.annotation.XmlAccessorOrder;
46 import javax.xml.bind.annotation.XmlAccessorType;
47 import javax.xml.bind.annotation.XmlAnyAttribute;
48 import javax.xml.bind.annotation.XmlAnyElement;
49 import javax.xml.bind.annotation.XmlAttachmentRef;
50 import javax.xml.bind.annotation.XmlAttribute;
51 import javax.xml.bind.annotation.XmlElement;
52 import javax.xml.bind.annotation.XmlElementRef;
53 import javax.xml.bind.annotation.XmlElementRefs;
54 import javax.xml.bind.annotation.XmlElementWrapper;
55 import javax.xml.bind.annotation.XmlElements;
56 import javax.xml.bind.annotation.XmlID;
57 import javax.xml.bind.annotation.XmlIDREF;
58 import javax.xml.bind.annotation.XmlInlineBinaryData;
59 import javax.xml.bind.annotation.XmlList;
60 import javax.xml.bind.annotation.XmlMimeType;
61 import javax.xml.bind.annotation.XmlMixed;
62 import javax.xml.bind.annotation.XmlRootElement;
63 import javax.xml.bind.annotation.XmlSchemaType;
64 import javax.xml.bind.annotation.XmlTransient;
65 import javax.xml.bind.annotation.XmlType;
66 import javax.xml.bind.annotation.XmlValue;
67 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
68 import javax.xml.namespace.QName;
70 import com.sun.istack.internal.FinalArrayList;
71 import com.sun.xml.internal.bind.annotation.OverrideAnnotationOf;
72 import com.sun.xml.internal.bind.v2.model.annotation.Locatable;
73 import com.sun.xml.internal.bind.v2.model.annotation.MethodLocatable;
74 import com.sun.xml.internal.bind.v2.model.core.ClassInfo;
75 import com.sun.xml.internal.bind.v2.model.core.Element;
76 import com.sun.xml.internal.bind.v2.model.core.ID;
77 import com.sun.xml.internal.bind.v2.model.core.NonElement;
78 import com.sun.xml.internal.bind.v2.model.core.PropertyInfo;
79 import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
80 import com.sun.xml.internal.bind.v2.model.core.ValuePropertyInfo;
81 import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException;
82 import com.sun.xml.internal.bind.v2.runtime.Location;
83 import com.sun.xml.internal.bind.v2.util.EditDistance;
86 /**
87 * A part of the {@link ClassInfo} that doesn't depend on a particular
88 * reflection library.
89 *
90 * @author Kohsuke Kawaguchi (kk@kohsuke.org)
91 */
92 public class ClassInfoImpl<T,C,F,M> extends TypeInfoImpl<T,C,F,M>
93 implements ClassInfo<T,C>, Element<T,C> {
95 protected final C clazz;
97 /**
98 * @see #getElementName()
99 */
100 private final QName elementName;
102 /**
103 * @see #getTypeName()
104 */
105 private final QName typeName;
107 /**
108 * Lazily created.
109 *
110 * @see #getProperties()
111 */
112 private FinalArrayList<PropertyInfoImpl<T,C,F,M>> properties;
114 /**
115 * The property order.
116 *
117 * null if unordered. {@link #DEFAULT_ORDER} if ordered but the order is defaulted
118 *
119 * @see #isOrdered()
120 */
121 private /*final*/ String[] propOrder;
123 /**
124 * Lazily computed.
125 *
126 * To avoid the cyclic references of the form C1 --base--> C2 --property--> C1.
127 */
128 private ClassInfoImpl<T,C,F,M> baseClass;
130 private boolean baseClassComputed = false;
132 private boolean hasSubClasses = false;
134 /**
135 * If this class has a declared (not inherited) attribute wildcard, keep the reference
136 * to it.
137 *
138 * This parameter is initialized at the construction time and never change.
139 */
140 protected /*final*/ PropertySeed<T,C,F,M> attributeWildcard;
143 /**
144 * @see #getFactoryMethod()
145 */
146 private M factoryMethod = null;
148 ClassInfoImpl(ModelBuilder<T,C,F,M> builder, Locatable upstream, C clazz) {
149 super(builder,upstream);
150 this.clazz = clazz;
151 assert clazz!=null;
153 // compute the element name
154 elementName = parseElementName(clazz);
156 // compute the type name
157 XmlType t = reader().getClassAnnotation(XmlType.class,clazz,this);
158 typeName = parseTypeName(clazz,t);
160 if(t!=null) {
161 String[] propOrder = t.propOrder();
162 if(propOrder.length==0)
163 this.propOrder = null; // unordered
164 else {
165 if(propOrder[0].length()==0)
166 this.propOrder = DEFAULT_ORDER;
167 else
168 this.propOrder = propOrder;
169 }
170 } else {
171 propOrder = DEFAULT_ORDER;
172 }
174 // obtain XmlAccessorOrder and set proporder (XmlAccessorOrder can be defined for whole package)
175 // (<xs:all> vs <xs:sequence>)
176 XmlAccessorOrder xao = reader().getPackageAnnotation(XmlAccessorOrder.class, clazz, this);
177 if((xao != null) && (xao.value() == XmlAccessOrder.UNDEFINED)) {
178 propOrder = null;
179 }
181 // obtain XmlAccessorOrder and set proporder (<xs:all> vs <xs:sequence>)
182 xao = reader().getClassAnnotation(XmlAccessorOrder.class, clazz, this);
183 if((xao != null) && (xao.value() == XmlAccessOrder.UNDEFINED)) {
184 propOrder = null;
185 }
187 if(nav().isInterface(clazz)) {
188 builder.reportError(new IllegalAnnotationException(
189 Messages.CANT_HANDLE_INTERFACE.format(nav().getClassName(clazz)), this ));
190 }
192 // the class must have the default constructor
193 if (!hasFactoryConstructor(t)){
194 if(!nav().hasDefaultConstructor(clazz)){
195 if(nav().isInnerClass(clazz)) {
196 builder.reportError(new IllegalAnnotationException(
197 Messages.CANT_HANDLE_INNER_CLASS.format(nav().getClassName(clazz)), this ));
198 } else if (elementName != null) {
199 builder.reportError(new IllegalAnnotationException(
200 Messages.NO_DEFAULT_CONSTRUCTOR.format(nav().getClassName(clazz)), this ));
201 }
202 }
203 }
204 }
206 public ClassInfoImpl<T,C,F,M> getBaseClass() {
207 if (!baseClassComputed) {
208 // compute the base class
209 C s = nav().getSuperClass(clazz);
210 if(s==null || s==nav().asDecl(Object.class)) {
211 baseClass = null;
212 } else {
213 NonElement<T,C> b = builder.getClassInfo(s, true, this);
214 if(b instanceof ClassInfoImpl) {
215 baseClass = (ClassInfoImpl<T,C,F,M>) b;
216 baseClass.hasSubClasses = true;
217 } else {
218 baseClass = null;
219 }
220 }
221 baseClassComputed = true;
222 }
223 return baseClass;
224 }
226 /**
227 * {@inheritDoc}
228 *
229 * The substitution hierarchy is the same as the inheritance hierarchy.
230 */
231 public final Element<T,C> getSubstitutionHead() {
232 ClassInfoImpl<T,C,F,M> c = getBaseClass();
233 while(c!=null && !c.isElement())
234 c = c.getBaseClass();
235 return c;
236 }
238 public final C getClazz() {
239 return clazz;
240 }
242 /**
243 * When a bean binds to an element, it's always through {@link XmlRootElement},
244 * so this method always return null.
245 *
246 * @deprecated
247 * you shouldn't be invoking this method on {@link ClassInfoImpl}.
248 */
249 public ClassInfoImpl<T,C,F,M> getScope() {
250 return null;
251 }
253 public final T getType() {
254 return nav().use(clazz);
255 }
257 /**
258 * A {@link ClassInfo} can be referenced by {@link XmlIDREF} if
259 * it has an ID property.
260 */
261 public boolean canBeReferencedByIDREF() {
262 for (PropertyInfo<T,C> p : getProperties()) {
263 if(p.id()== ID.ID)
264 return true;
265 }
266 ClassInfoImpl<T,C,F,M> base = getBaseClass();
267 if(base!=null)
268 return base.canBeReferencedByIDREF();
269 else
270 return false;
271 }
273 public final String getName() {
274 return nav().getClassName(clazz);
275 }
277 public <A extends Annotation> A readAnnotation(Class<A> a) {
278 return reader().getClassAnnotation(a,clazz,this);
279 }
281 public Element<T,C> asElement() {
282 if(isElement())
283 return this;
284 else
285 return null;
286 }
288 public List<? extends PropertyInfo<T,C>> getProperties() {
289 if(properties!=null) return properties;
291 // check the access type first
292 XmlAccessType at = getAccessType();
294 properties = new FinalArrayList<PropertyInfoImpl<T,C,F,M>>();
296 findFieldProperties(clazz,at);
298 findGetterSetterProperties(at);
300 if(propOrder==DEFAULT_ORDER || propOrder==null) {
301 XmlAccessOrder ao = getAccessorOrder();
302 if(ao==XmlAccessOrder.ALPHABETICAL)
303 Collections.sort(properties);
304 } else {
305 //sort them as specified
306 PropertySorter sorter = new PropertySorter();
307 for (PropertyInfoImpl p : properties) {
308 sorter.checkedGet(p); // have it check for errors
309 }
310 Collections.sort(properties,sorter);
311 sorter.checkUnusedProperties();
312 }
314 {// additional error checks
315 PropertyInfoImpl vp=null; // existing value property
316 PropertyInfoImpl ep=null; // existing element property
318 for (PropertyInfoImpl p : properties) {
319 switch(p.kind()) {
320 case ELEMENT:
321 case REFERENCE:
322 case MAP:
323 ep = p;
324 break;
325 case VALUE:
326 if(vp!=null) {
327 // can't have multiple value properties.
328 builder.reportError(new IllegalAnnotationException(
329 Messages.MULTIPLE_VALUE_PROPERTY.format(),
330 vp, p ));
331 }
332 if(getBaseClass()!=null) {
333 builder.reportError(new IllegalAnnotationException(
334 Messages.XMLVALUE_IN_DERIVED_TYPE.format(), p ));
335 }
336 vp = p;
337 break;
338 case ATTRIBUTE:
339 break; // noop
340 default:
341 assert false;
342 }
343 }
345 if(ep!=null && vp!=null) {
346 // can't have element and value property at the same time
347 builder.reportError(new IllegalAnnotationException(
348 Messages.ELEMENT_AND_VALUE_PROPERTY.format(),
349 vp, ep
350 ));
351 }
352 }
354 return properties;
355 }
357 private void findFieldProperties(C c, XmlAccessType at) {
359 // always find properties from the super class first
360 C sc = nav().getSuperClass(c);
361 if (shouldRecurseSuperClass(sc)) {
362 findFieldProperties(sc,at);
363 }
365 for( F f : nav().getDeclaredFields(c) ) {
366 Annotation[] annotations = reader().getAllFieldAnnotations(f,this);
367 boolean isDummy = reader().hasFieldAnnotation(OverrideAnnotationOf.class, f);
369 if( nav().isTransient(f) ) {
370 // it's an error for transient field to have any binding annotation
371 if(hasJAXBAnnotation(annotations))
372 builder.reportError(new IllegalAnnotationException(
373 Messages.TRANSIENT_FIELD_NOT_BINDABLE.format(nav().getFieldName(f)),
374 getSomeJAXBAnnotation(annotations)));
375 } else
376 if( nav().isStaticField(f) ) {
377 // static fields are bound only when there's explicit annotation.
378 if(hasJAXBAnnotation(annotations))
379 addProperty(createFieldSeed(f),annotations, false);
380 } else {
381 if(at==XmlAccessType.FIELD
382 ||(at==XmlAccessType.PUBLIC_MEMBER && nav().isPublicField(f))
383 || hasJAXBAnnotation(annotations)) {
384 if (isDummy) {
385 ClassInfo<T, C> top = getBaseClass();
386 while ((top != null) && (top.getProperty("content") == null)) {
387 top = top.getBaseClass();
388 }
389 DummyPropertyInfo prop = (DummyPropertyInfo) top.getProperty("content");
390 PropertySeed seed = createFieldSeed(f);
391 ((DummyPropertyInfo)prop).addType(createReferenceProperty(seed));
392 } else {
393 addProperty(createFieldSeed(f), annotations, false);
394 }
395 }
396 checkFieldXmlLocation(f);
397 }
398 }
399 }
401 public final boolean hasValueProperty() {
402 ClassInfoImpl<T, C, F, M> bc = getBaseClass();
403 if(bc!=null && bc.hasValueProperty())
404 return true;
406 for (PropertyInfo p : getProperties()) {
407 if (p instanceof ValuePropertyInfo) return true;
408 }
410 return false;
411 }
413 public PropertyInfo<T,C> getProperty(String name) {
414 for( PropertyInfo<T,C> p: getProperties() ) {
415 if(p.getName().equals(name))
416 return p;
417 }
418 return null;
419 }
421 /**
422 * This hook is used by {@link RuntimeClassInfoImpl} to look for {@link XmlLocation}.
423 */
424 protected void checkFieldXmlLocation(F f) {
425 }
427 /**
428 * Gets an annotation that are allowed on both class and type.
429 */
430 private <T extends Annotation> T getClassOrPackageAnnotation(Class<T> type) {
431 T t = reader().getClassAnnotation(type,clazz,this);
432 if(t!=null)
433 return t;
434 // defaults to the package level
435 return reader().getPackageAnnotation(type,clazz,this);
436 }
438 /**
439 * Computes the {@link XmlAccessType} on this class by looking at {@link XmlAccessorType}
440 * annotations.
441 */
442 private XmlAccessType getAccessType() {
443 XmlAccessorType xat = getClassOrPackageAnnotation(XmlAccessorType.class);
444 if(xat!=null)
445 return xat.value();
446 else
447 return XmlAccessType.PUBLIC_MEMBER;
448 }
450 /**
451 * Gets the accessor order for this class by consulting {@link XmlAccessorOrder}.
452 */
453 private XmlAccessOrder getAccessorOrder() {
454 XmlAccessorOrder xao = getClassOrPackageAnnotation(XmlAccessorOrder.class);
455 if(xao!=null)
456 return xao.value();
457 else
458 return XmlAccessOrder.UNDEFINED;
459 }
461 /**
462 * Compares orders among {@link PropertyInfoImpl} according to {@link ClassInfoImpl#propOrder}.
463 *
464 * <p>
465 * extends {@link HashMap} to save memory.
466 */
467 private final class PropertySorter extends HashMap<String,Integer> implements Comparator<PropertyInfoImpl> {
468 /**
469 * Mark property names that are used, so that we can report unused property names in the propOrder array.
470 */
471 PropertyInfoImpl[] used = new PropertyInfoImpl[propOrder.length];
473 /**
474 * If any name collides, it will be added to this set.
475 * This is used to avoid repeating the same error message.
476 */
477 private Set<String> collidedNames;
479 PropertySorter() {
480 super(propOrder.length);
481 for( String name : propOrder )
482 if(put(name,size())!=null) {
483 // two properties with the same name
484 builder.reportError(new IllegalAnnotationException(
485 Messages.DUPLICATE_ENTRY_IN_PROP_ORDER.format(name),ClassInfoImpl.this));
486 }
487 }
489 public int compare(PropertyInfoImpl o1, PropertyInfoImpl o2) {
490 int lhs = checkedGet(o1);
491 int rhs = checkedGet(o2);
493 return lhs-rhs;
494 }
496 private int checkedGet(PropertyInfoImpl p) {
497 Integer i = get(p.getName());
498 if(i==null) {
499 // missing
500 if (p.kind().isOrdered)
501 builder.reportError(new IllegalAnnotationException(
502 Messages.PROPERTY_MISSING_FROM_ORDER.format(p.getName()),p));
504 // give it an order to recover from an error
505 i = size();
506 put(p.getName(),i);
507 }
509 // mark the used field
510 int ii = i;
511 if(ii<used.length) {
512 if(used[ii]!=null && used[ii]!=p) {
513 if(collidedNames==null) collidedNames = new HashSet<String>();
515 if(collidedNames.add(p.getName()))
516 // report the error only on the first time
517 builder.reportError(new IllegalAnnotationException(
518 Messages.DUPLICATE_PROPERTIES.format(p.getName()),p,used[ii]));
519 }
520 used[ii] = p;
521 }
523 return i;
524 }
526 /**
527 * Report errors for unused propOrder entries.
528 */
529 public void checkUnusedProperties() {
530 for( int i=0; i<used.length; i++ )
531 if(used[i]==null) {
532 String unusedName = propOrder[i];
533 String nearest = EditDistance.findNearest(unusedName, new AbstractList<String>() {
534 public String get(int index) {
535 return properties.get(index).getName();
536 }
538 public int size() {
539 return properties.size();
540 }
541 });
542 boolean isOverriding = (i > (properties.size()-1)) ? false : properties.get(i).hasAnnotation(OverrideAnnotationOf.class);
543 if (!isOverriding) {
544 builder.reportError(new IllegalAnnotationException(
545 Messages.PROPERTY_ORDER_CONTAINS_UNUSED_ENTRY.format(unusedName,nearest),ClassInfoImpl.this));
546 }
547 }
548 }
549 }
551 public boolean hasProperties() {
552 return !properties.isEmpty();
553 }
556 /**
557 * Picks the first non-null argument, or null if all arguments are null.
558 */
559 private static <T> T pickOne( T... args ) {
560 for( T arg : args )
561 if(arg!=null)
562 return arg;
563 return null;
564 }
566 private static <T> List<T> makeSet( T... args ) {
567 List<T> l = new FinalArrayList<T>();
568 for( T arg : args )
569 if(arg!=null) l.add(arg);
570 return l;
571 }
573 private static final class ConflictException extends Exception {
574 final List<Annotation> annotations;
576 public ConflictException(List<Annotation> one) {
577 this.annotations = one;
578 }
579 }
581 private static final class DuplicateException extends Exception {
582 final Annotation a1,a2;
583 public DuplicateException(Annotation a1, Annotation a2) {
584 this.a1 = a1;
585 this.a2 = a2;
586 }
587 }
589 /**
590 * Represents 6 groups of secondary annotations
591 */
592 private static enum SecondaryAnnotation {
593 JAVA_TYPE (0x01, XmlJavaTypeAdapter.class),
594 ID_IDREF (0x02, XmlID.class, XmlIDREF.class),
595 BINARY (0x04, XmlInlineBinaryData.class, XmlMimeType.class, XmlAttachmentRef.class),
596 ELEMENT_WRAPPER (0x08, XmlElementWrapper.class),
597 LIST (0x10, XmlList.class),
598 SCHEMA_TYPE (0x20, XmlSchemaType.class);
600 /**
601 * Each constant gets an unique bit mask so that the presence/absence
602 * of them can be represented in a single byte.
603 */
604 final int bitMask;
605 /**
606 * List of annotations that belong to this member.
607 */
608 final Class<? extends Annotation>[] members;
610 SecondaryAnnotation(int bitMask, Class<? extends Annotation>... members) {
611 this.bitMask = bitMask;
612 this.members = members;
613 }
614 }
616 private static final SecondaryAnnotation[] SECONDARY_ANNOTATIONS = SecondaryAnnotation.values();
618 /**
619 * Represents 7 groups of properties.
620 *
621 * Each instance is also responsible for rejecting annotations
622 * that are not allowed on that kind.
623 */
624 private static enum PropertyGroup {
625 TRANSIENT (false,false,false,false,false,false),
626 ANY_ATTRIBUTE (true, false,false,false,false,false),
627 ATTRIBUTE (true, true, true, false,true, true ),
628 VALUE (true, true, true, false,true, true ),
629 ELEMENT (true, true, true, true, true, true ),
630 ELEMENT_REF (true, false,false,true, false,false),
631 MAP (false,false,false,true, false,false);
633 /**
634 * Bit mask that represents secondary annotations that are allowed on this group.
635 *
636 * T = not allowed, F = allowed
637 */
638 final int allowedsecondaryAnnotations;
640 PropertyGroup(boolean... bits) {
641 int mask = 0;
642 assert bits.length==SECONDARY_ANNOTATIONS.length;
643 for( int i=0; i<bits.length; i++ ) {
644 if(bits[i])
645 mask |= SECONDARY_ANNOTATIONS[i].bitMask;
646 }
647 allowedsecondaryAnnotations = ~mask;
648 }
650 boolean allows(SecondaryAnnotation a) {
651 return (allowedsecondaryAnnotations&a.bitMask)==0;
652 }
653 }
655 private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
657 /**
658 * All the annotations in JAXB to their internal index.
659 */
660 private static final HashMap<Class,Integer> ANNOTATION_NUMBER_MAP = new HashMap<Class,Integer>();
661 static {
662 Class[] annotations = {
663 XmlTransient.class, // 0
664 XmlAnyAttribute.class, // 1
665 XmlAttribute.class, // 2
666 XmlValue.class, // 3
667 XmlElement.class, // 4
668 XmlElements.class, // 5
669 XmlElementRef.class, // 6
670 XmlElementRefs.class, // 7
671 XmlAnyElement.class, // 8
672 XmlMixed.class, // 9
673 OverrideAnnotationOf.class,// 10
674 };
676 HashMap<Class,Integer> m = ANNOTATION_NUMBER_MAP;
678 // characterizing annotations
679 for( Class c : annotations )
680 m.put(c, m.size() );
682 // secondary annotations
683 int index = 20;
684 for( SecondaryAnnotation sa : SECONDARY_ANNOTATIONS ) {
685 for( Class member : sa.members )
686 m.put(member,index);
687 index++;
688 }
689 }
691 private void checkConflict(Annotation a, Annotation b) throws DuplicateException {
692 assert b!=null;
693 if(a!=null)
694 throw new DuplicateException(a,b);
695 }
697 /**
698 * Called only from {@link #getProperties()}.
699 *
700 * <p>
701 * This is where we decide the type of the property and checks for annotations
702 * that are not allowed.
703 *
704 * @param annotations
705 * all annotations on this property. It's the same as
706 * {@code seed.readAllAnnotation()}, but taken as a parameter
707 * because the caller should know it already.
708 */
709 private void addProperty( PropertySeed<T,C,F,M> seed, Annotation[] annotations, boolean dummy ) {
710 // since typically there's a very few annotations on a method,
711 // this runs faster than checking for each annotation via readAnnotation(A)
714 // characterizing annotations. these annotations (or lack thereof) decides
715 // the kind of the property it goes to.
716 // I wish I could use an array...
717 XmlTransient t = null;
718 XmlAnyAttribute aa = null;
719 XmlAttribute a = null;
720 XmlValue v = null;
721 XmlElement e1 = null;
722 XmlElements e2 = null;
723 XmlElementRef r1 = null;
724 XmlElementRefs r2 = null;
725 XmlAnyElement xae = null;
726 XmlMixed mx = null;
727 OverrideAnnotationOf ov = null;
729 // encountered secondary annotations are accumulated into a bit mask
730 int secondaryAnnotations = 0;
732 try {
733 for( Annotation ann : annotations ) {
734 Integer index = ANNOTATION_NUMBER_MAP.get(ann.annotationType());
735 if(index==null) continue;
736 switch(index) {
737 case 0: checkConflict(t ,ann); t = (XmlTransient) ann; break;
738 case 1: checkConflict(aa ,ann); aa = (XmlAnyAttribute) ann; break;
739 case 2: checkConflict(a ,ann); a = (XmlAttribute) ann; break;
740 case 3: checkConflict(v ,ann); v = (XmlValue) ann; break;
741 case 4: checkConflict(e1 ,ann); e1 = (XmlElement) ann; break;
742 case 5: checkConflict(e2 ,ann); e2 = (XmlElements) ann; break;
743 case 6: checkConflict(r1 ,ann); r1 = (XmlElementRef) ann; break;
744 case 7: checkConflict(r2 ,ann); r2 = (XmlElementRefs) ann; break;
745 case 8: checkConflict(xae,ann); xae = (XmlAnyElement) ann; break;
746 case 9: checkConflict(mx, ann); mx = (XmlMixed) ann; break;
747 case 10: checkConflict(ov, ann); ov = (OverrideAnnotationOf) ann; break;
748 default:
749 // secondary annotations
750 secondaryAnnotations |= (1<<(index-20));
751 break;
752 }
753 }
755 // determine the group kind, and also count the numbers, since
756 // characterizing annotations are mutually exclusive.
757 PropertyGroup group = null;
758 int groupCount = 0;
760 if(t!=null) {
761 group = PropertyGroup.TRANSIENT;
762 groupCount++;
763 }
764 if(aa!=null) {
765 group = PropertyGroup.ANY_ATTRIBUTE;
766 groupCount++;
767 }
768 if(a!=null) {
769 group = PropertyGroup.ATTRIBUTE;
770 groupCount++;
771 }
772 if(v!=null) {
773 group = PropertyGroup.VALUE;
774 groupCount++;
775 }
776 if(e1!=null || e2!=null) {
777 group = PropertyGroup.ELEMENT;
778 groupCount++;
779 }
780 if(r1!=null || r2!=null || xae!=null || mx!=null || ov != null) {
781 group = PropertyGroup.ELEMENT_REF;
782 groupCount++;
783 }
785 if(groupCount>1) {
786 // collision between groups
787 List<Annotation> err = makeSet(t,aa,a,v,pickOne(e1,e2),pickOne(r1,r2,xae));
788 throw new ConflictException(err);
789 }
791 if(group==null) {
792 // if no characterizing annotation was found, it's either element or map
793 // sniff the signature and then decide.
794 assert groupCount==0;
796 // UGLY: the presence of XmlJavaTypeAdapter makes it an element property. ARGH.
797 if(nav().isSubClassOf( seed.getRawType(), nav().ref(Map.class) )
798 && !seed.hasAnnotation(XmlJavaTypeAdapter.class))
799 group = PropertyGroup.MAP;
800 else
801 group = PropertyGroup.ELEMENT;
802 } else if (group.equals(PropertyGroup.ELEMENT)) { // see issue 791 - make sure @XmlElement annotated map property is mapped to map
803 if (nav().isSubClassOf( seed.getRawType(), nav().ref(Map.class)) && !seed.hasAnnotation(XmlJavaTypeAdapter.class)) {
804 group = PropertyGroup.MAP;
805 }
806 }
808 // group determined by now
809 // make sure that there are no prohibited secondary annotations
810 if( (secondaryAnnotations&group.allowedsecondaryAnnotations)!=0 ) {
811 // uh oh. find the offending annotation
812 for( SecondaryAnnotation sa : SECONDARY_ANNOTATIONS ) {
813 if(group.allows(sa))
814 continue;
815 for( Class<? extends Annotation> m : sa.members ) {
816 Annotation offender = seed.readAnnotation(m);
817 if(offender!=null) {
818 // found it
819 builder.reportError(new IllegalAnnotationException(
820 Messages.ANNOTATION_NOT_ALLOWED.format(m.getSimpleName()),offender));
821 return;
822 }
823 }
824 }
825 // there must have been an offender
826 assert false;
827 }
829 // actually create annotations
830 switch(group) {
831 case TRANSIENT:
832 return;
833 case ANY_ATTRIBUTE:
834 // an attribute wildcard property
835 if(attributeWildcard!=null) {
836 builder.reportError(new IllegalAnnotationException(
837 Messages.TWO_ATTRIBUTE_WILDCARDS.format(
838 nav().getClassName(getClazz())),aa,attributeWildcard));
839 return; // recover by ignore
840 }
841 attributeWildcard = seed;
843 if(inheritsAttributeWildcard()) {
844 builder.reportError(new IllegalAnnotationException(
845 Messages.SUPER_CLASS_HAS_WILDCARD.format(),
846 aa,getInheritedAttributeWildcard()));
847 return;
848 }
850 // check the signature and make sure it's assignable to Map
851 if(!nav().isSubClassOf(seed.getRawType(),nav().ref(Map.class))) {
852 builder.reportError(new IllegalAnnotationException(
853 Messages.INVALID_ATTRIBUTE_WILDCARD_TYPE.format(nav().getTypeName(seed.getRawType())),
854 aa,getInheritedAttributeWildcard()));
855 return;
856 }
859 return;
860 case ATTRIBUTE:
861 properties.add(createAttributeProperty(seed));
862 return;
863 case VALUE:
864 properties.add(createValueProperty(seed));
865 return;
866 case ELEMENT:
867 properties.add(createElementProperty(seed));
868 return;
869 case ELEMENT_REF:
870 properties.add(createReferenceProperty(seed));
871 return;
872 case MAP:
873 properties.add(createMapProperty(seed));
874 return;
875 default:
876 assert false;
877 }
878 } catch( ConflictException x ) {
879 // report a conflicting annotation
880 List<Annotation> err = x.annotations;
882 builder.reportError(new IllegalAnnotationException(
883 Messages.MUTUALLY_EXCLUSIVE_ANNOTATIONS.format(
884 nav().getClassName(getClazz())+'#'+seed.getName(),
885 err.get(0).annotationType().getName(), err.get(1).annotationType().getName()),
886 err.get(0), err.get(1) ));
888 // recover by ignoring this property
889 } catch( DuplicateException e ) {
890 // both are present
891 builder.reportError(new IllegalAnnotationException(
892 Messages.DUPLICATE_ANNOTATIONS.format(e.a1.annotationType().getName()),
893 e.a1, e.a2 ));
894 // recover by ignoring this property
896 }
897 }
899 protected ReferencePropertyInfoImpl<T,C,F,M> createReferenceProperty(PropertySeed<T,C,F,M> seed) {
900 return new ReferencePropertyInfoImpl<T,C,F,M>(this,seed);
901 }
903 protected AttributePropertyInfoImpl<T,C,F,M> createAttributeProperty(PropertySeed<T,C,F,M> seed) {
904 return new AttributePropertyInfoImpl<T,C,F,M>(this,seed);
905 }
907 protected ValuePropertyInfoImpl<T,C,F,M> createValueProperty(PropertySeed<T,C,F,M> seed) {
908 return new ValuePropertyInfoImpl<T,C,F,M>(this,seed);
909 }
911 protected ElementPropertyInfoImpl<T,C,F,M> createElementProperty(PropertySeed<T,C,F,M> seed) {
912 return new ElementPropertyInfoImpl<T,C,F,M>(this,seed);
913 }
915 protected MapPropertyInfoImpl<T,C,F,M> createMapProperty(PropertySeed<T,C,F,M> seed) {
916 return new MapPropertyInfoImpl<T,C,F,M>(this,seed);
917 }
920 /**
921 * Adds properties that consists of accessors.
922 */
923 private void findGetterSetterProperties(XmlAccessType at) {
924 // in the first step we accumulate getters and setters
925 // into this map keyed by the property name.
926 Map<String,M> getters = new LinkedHashMap<String,M>();
927 Map<String,M> setters = new LinkedHashMap<String,M>();
929 C c = clazz;
930 do {
931 collectGetterSetters(clazz, getters, setters);
933 // take super classes into account if they have @XmlTransient
934 c = nav().getSuperClass(c);
935 } while(shouldRecurseSuperClass(c));
938 // compute the intersection
939 Set<String> complete = new TreeSet<String>(getters.keySet());
940 complete.retainAll(setters.keySet());
942 resurrect(getters, complete);
943 resurrect(setters, complete);
945 // then look for read/write properties.
946 for (String name : complete) {
947 M getter = getters.get(name);
948 M setter = setters.get(name);
950 Annotation[] ga = getter!=null ? reader().getAllMethodAnnotations(getter,new MethodLocatable<M>(this,getter,nav())) : EMPTY_ANNOTATIONS;
951 Annotation[] sa = setter!=null ? reader().getAllMethodAnnotations(setter,new MethodLocatable<M>(this,setter,nav())) : EMPTY_ANNOTATIONS;
953 boolean hasAnnotation = hasJAXBAnnotation(ga) || hasJAXBAnnotation(sa);
954 boolean isOverriding = false;
955 if(!hasAnnotation) {
956 // checking if the method is overriding others isn't free,
957 // so we don't compute it if it's not necessary.
958 isOverriding = (getter!=null && nav().isOverriding(getter,c))
959 && (setter!=null && nav().isOverriding(setter,c));
960 }
962 if((at==XmlAccessType.PROPERTY && !isOverriding)
963 || (at==XmlAccessType.PUBLIC_MEMBER && isConsideredPublic(getter) && isConsideredPublic(setter) && !isOverriding)
964 || hasAnnotation) {
965 // make sure that the type is consistent
966 if(getter!=null && setter!=null
967 && !nav().isSameType(nav().getReturnType(getter), nav().getMethodParameters(setter)[0])) {
968 // inconsistent
969 builder.reportError(new IllegalAnnotationException(
970 Messages.GETTER_SETTER_INCOMPATIBLE_TYPE.format(
971 nav().getTypeName(nav().getReturnType(getter)),
972 nav().getTypeName(nav().getMethodParameters(setter)[0])
973 ),
974 new MethodLocatable<M>( this, getter, nav()),
975 new MethodLocatable<M>( this, setter, nav())));
976 continue;
977 }
979 // merge annotations from two list
980 Annotation[] r;
981 if(ga.length==0) {
982 r = sa;
983 } else
984 if(sa.length==0) {
985 r = ga;
986 } else {
987 r = new Annotation[ga.length+sa.length];
988 System.arraycopy(ga,0,r,0,ga.length);
989 System.arraycopy(sa,0,r,ga.length,sa.length);
990 }
992 addProperty(createAccessorSeed(getter, setter), r, false);
993 }
994 }
995 // done with complete pairs
996 getters.keySet().removeAll(complete);
997 setters.keySet().removeAll(complete);
999 // TODO: think about
1000 // class Foo {
1001 // int getFoo();
1002 // }
1003 // class Bar extends Foo {
1004 // void setFoo(int x);
1005 // }
1006 // and how it will be XML-ized.
1007 }
1009 private void collectGetterSetters(C c, Map<String,M> getters, Map<String,M> setters) {
1010 // take super classes into account if they have @XmlTransient.
1011 // always visit them first so that
1012 // 1) order is right
1013 // 2) overriden properties are handled accordingly
1014 C sc = nav().getSuperClass(c);
1015 if(shouldRecurseSuperClass(sc))
1016 collectGetterSetters(sc,getters,setters);
1018 Collection<? extends M> methods = nav().getDeclaredMethods(c);
1019 Map<String,List<M>> allSetters = new LinkedHashMap<String,List<M>>();
1020 for( M method : methods ) {
1021 boolean used = false; // if this method is added to getters or setters
1023 if(nav().isBridgeMethod(method))
1024 continue; // ignore
1026 String name = nav().getMethodName(method);
1027 int arity = nav().getMethodParameters(method).length;
1029 if(nav().isStaticMethod(method)) {
1030 ensureNoAnnotation(method);
1031 continue;
1032 }
1034 // is this a get method?
1035 String propName = getPropertyNameFromGetMethod(name);
1036 if(propName!=null && arity==0) {
1037 getters.put(propName,method);
1038 used = true;
1039 }
1041 // is this a set method?
1042 propName = getPropertyNameFromSetMethod(name);
1043 if(propName!=null && arity==1) {
1044 List<M> propSetters = allSetters.get(propName);
1045 if(null == propSetters){
1046 propSetters = new ArrayList<M>();
1047 allSetters.put(propName, propSetters);
1048 }
1049 propSetters.add(method);
1050 used = true; // used check performed later
1051 }
1053 if(!used)
1054 ensureNoAnnotation(method);
1055 }
1057 // Match getter with setters by comparing getter return type to setter param
1058 for (Map.Entry<String,M> entry : getters.entrySet()) {
1059 String propName = entry.getKey();
1060 M getter = entry.getValue();
1061 List<M> propSetters = allSetters.remove(propName);
1062 if (null == propSetters) {
1063 //no matching setter
1064 continue;
1065 }
1066 T getterType = nav().getReturnType(getter);
1067 for (M setter : propSetters) {
1068 T setterType = nav().getMethodParameters(setter)[0];
1069 if (nav().isSameType(setterType, getterType)) {
1070 setters.put(propName, setter);
1071 break;
1072 }
1073 }
1074 }
1076 // also allow set-only properties
1077 for (Map.Entry<String,List<M>> e : allSetters.entrySet()) {
1078 setters.put(e.getKey(),e.getValue().get(0));
1079 }
1080 }
1082 /**
1083 * Checks if the properties in this given super class should be aggregated into this class.
1084 */
1085 private boolean shouldRecurseSuperClass(C sc) {
1086 return sc!=null
1087 && (builder.isReplaced(sc) || reader().hasClassAnnotation(sc, XmlTransient.class));
1088 }
1090 /**
1091 * Returns true if the method is considered 'public'.
1092 */
1093 private boolean isConsideredPublic(M m) {
1094 return m ==null || nav().isPublicMethod(m);
1095 }
1097 /**
1098 * If the method has an explicit annotation, allow it to participate
1099 * to the processing even if it lacks the setter or the getter.
1100 */
1101 private void resurrect(Map<String, M> methods, Set<String> complete) {
1102 for (Map.Entry<String, M> e : methods.entrySet()) {
1103 if(complete.contains(e.getKey()))
1104 continue;
1105 if(hasJAXBAnnotation(reader().getAllMethodAnnotations(e.getValue(),this)))
1106 complete.add(e.getKey());
1107 }
1108 }
1110 /**
1111 * Makes sure that the method doesn't have any annotation, if it does,
1112 * report it as an error
1113 */
1114 private void ensureNoAnnotation(M method) {
1115 Annotation[] annotations = reader().getAllMethodAnnotations(method,this);
1116 for( Annotation a : annotations ) {
1117 if(isJAXBAnnotation(a)) {
1118 builder.reportError(new IllegalAnnotationException(
1119 Messages.ANNOTATION_ON_WRONG_METHOD.format(),
1120 a));
1121 return;
1122 }
1123 }
1124 }
1126 /**
1127 * Returns true if a given annotation is a JAXB annotation.
1128 */
1129 private static boolean isJAXBAnnotation(Annotation a) {
1130 return ANNOTATION_NUMBER_MAP.containsKey(a.annotationType());
1131 }
1133 /**
1134 * Returns true if the array contains a JAXB annotation.
1135 */
1136 private static boolean hasJAXBAnnotation(Annotation[] annotations) {
1137 return getSomeJAXBAnnotation(annotations)!=null;
1138 }
1140 private static Annotation getSomeJAXBAnnotation(Annotation[] annotations) {
1141 for( Annotation a : annotations )
1142 if(isJAXBAnnotation(a))
1143 return a;
1144 return null;
1145 }
1148 /**
1149 * Returns "Foo" from "getFoo" or "isFoo".
1150 *
1151 * @return null
1152 * if the method name doesn't look like a getter.
1153 */
1154 private static String getPropertyNameFromGetMethod(String name) {
1155 if(name.startsWith("get") && name.length()>3)
1156 return name.substring(3);
1157 if(name.startsWith("is") && name.length()>2)
1158 return name.substring(2);
1159 return null;
1160 }
1162 /**
1163 * Returns "Foo" from "setFoo".
1164 *
1165 * @return null
1166 * if the method name doesn't look like a setter.
1167 */
1168 private static String getPropertyNameFromSetMethod(String name) {
1169 if(name.startsWith("set") && name.length()>3)
1170 return name.substring(3);
1171 return null;
1172 }
1174 /**
1175 * Creates a new {@link FieldPropertySeed} object.
1176 *
1177 * <p>
1178 * Derived class can override this method to create a sub-class.
1179 */
1180 protected PropertySeed<T,C,F,M> createFieldSeed(F f) {
1181 return new FieldPropertySeed<T,C,F,M>(this, f);
1182 }
1184 /**
1185 * Creates a new {@link GetterSetterPropertySeed} object.
1186 */
1187 protected PropertySeed<T,C,F,M> createAccessorSeed(M getter, M setter) {
1188 return new GetterSetterPropertySeed<T,C,F,M>(this, getter,setter);
1189 }
1191 public final boolean isElement() {
1192 return elementName!=null;
1193 }
1195 public boolean isAbstract() {
1196 return nav().isAbstract(clazz);
1197 }
1199 public boolean isOrdered() {
1200 return propOrder!=null;
1201 }
1203 public final boolean isFinal() {
1204 return nav().isFinal(clazz);
1205 }
1207 public final boolean hasSubClasses() {
1208 return hasSubClasses;
1209 }
1211 public final boolean hasAttributeWildcard() {
1212 return declaresAttributeWildcard() || inheritsAttributeWildcard();
1213 }
1215 public final boolean inheritsAttributeWildcard() {
1216 return getInheritedAttributeWildcard()!=null;
1217 }
1219 public final boolean declaresAttributeWildcard() {
1220 return attributeWildcard!=null;
1221 }
1223 /**
1224 * Gets the {@link PropertySeed} object for the inherited attribute wildcard.
1225 */
1226 private PropertySeed<T,C,F,M> getInheritedAttributeWildcard() {
1227 for( ClassInfoImpl<T,C,F,M> c=getBaseClass(); c!=null; c=c.getBaseClass() )
1228 if(c.attributeWildcard!=null)
1229 return c.attributeWildcard;
1230 return null;
1231 }
1233 public final QName getElementName() {
1234 return elementName;
1235 }
1237 public final QName getTypeName() {
1238 return typeName;
1239 }
1241 public final boolean isSimpleType() {
1242 List<? extends PropertyInfo> props = getProperties();
1243 if(props.size()!=1) return false;
1244 return props.get(0).kind()==PropertyKind.VALUE;
1245 }
1247 /**
1248 * Called after all the {@link TypeInfo}s are collected into the {@link #owner}.
1249 */
1250 @Override
1251 /*package*/ void link() {
1252 getProperties(); // make sure properties!=null
1254 // property name collision cehck
1255 Map<String,PropertyInfoImpl> names = new HashMap<String,PropertyInfoImpl>();
1256 for( PropertyInfoImpl<T,C,F,M> p : properties ) {
1257 p.link();
1258 PropertyInfoImpl old = names.put(p.getName(),p);
1259 if(old!=null) {
1260 builder.reportError(new IllegalAnnotationException(
1261 Messages.PROPERTY_COLLISION.format(p.getName()),
1262 p, old ));
1263 }
1264 }
1265 super.link();
1266 }
1268 public Location getLocation() {
1269 return nav().getClassLocation(clazz);
1270 }
1272 /**
1273 * XmlType allows specification of factoryClass and
1274 * factoryMethod. There are to be used if no default
1275 * constructor is found.
1276 *
1277 * @return
1278 * true if the factory method was found. False if not.
1279 */
1280 private boolean hasFactoryConstructor(XmlType t){
1281 if (t == null) return false;
1283 String method = t.factoryMethod();
1284 T fClass = reader().getClassValue(t, "factoryClass");
1285 if (method.length() > 0){
1286 if(nav().isSameType(fClass, nav().ref(XmlType.DEFAULT.class))){
1287 fClass = nav().use(clazz);
1288 }
1289 for(M m: nav().getDeclaredMethods(nav().asDecl(fClass))){
1290 //- Find the zero-arg public static method with the required return type
1291 if (nav().getMethodName(m).equals(method) &&
1292 nav().isSameType(nav().getReturnType(m), nav().use(clazz)) &&
1293 nav().getMethodParameters(m).length == 0 &&
1294 nav().isStaticMethod(m)){
1295 factoryMethod = m;
1296 break;
1297 }
1298 }
1299 if (factoryMethod == null){
1300 builder.reportError(new IllegalAnnotationException(
1301 Messages.NO_FACTORY_METHOD.format(nav().getClassName(nav().asDecl(fClass)), method), this ));
1302 }
1303 } else if(!nav().isSameType(fClass, nav().ref(XmlType.DEFAULT.class))){
1304 builder.reportError(new IllegalAnnotationException(
1305 Messages.FACTORY_CLASS_NEEDS_FACTORY_METHOD.format(nav().getClassName(nav().asDecl(fClass))), this ));
1306 }
1307 return factoryMethod != null;
1308 }
1310 public Method getFactoryMethod(){
1311 return (Method) factoryMethod;
1312 }
1314 @Override
1315 public String toString() {
1316 return "ClassInfo("+clazz+')';
1317 }
1319 private static final String[] DEFAULT_ORDER = new String[0];
1320 }