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