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.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; 84 85 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> { 94 95 protected final C clazz; 96 97 /** 98 * @see #getElementName() 99 */ 100 private final QName elementName; 101 102 /** 103 * @see #getTypeName() 104 */ 105 private final QName typeName; 106 107 /** 108 * Lazily created. 109 * 110 * @see #getProperties() 111 */ 112 private FinalArrayList<PropertyInfoImpl<T,C,F,M>> properties; 113 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; 122 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; 129 130 private boolean baseClassComputed = false; 131 132 private boolean hasSubClasses = false; 133 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; 141 142 143 /** 144 * @see #getFactoryMethod() 145 */ 146 private M factoryMethod = null; 147 148 ClassInfoImpl(ModelBuilder<T,C,F,M> builder, Locatable upstream, C clazz) { 149 super(builder,upstream); 150 this.clazz = clazz; 151 assert clazz!=null; 152 153 // compute the element name 154 elementName = parseElementName(clazz); 155 156 // compute the type name 157 XmlType t = reader().getClassAnnotation(XmlType.class,clazz,this); 158 typeName = parseTypeName(clazz,t); 159 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 } 173 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 } 180 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 } 186 187 if(nav().isInterface(clazz)) { 188 builder.reportError(new IllegalAnnotationException( 189 Messages.CANT_HANDLE_INTERFACE.format(nav().getClassName(clazz)), this )); 190 } 191 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 } 205 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 } 225 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 } 237 238 public final C getClazz() { 239 return clazz; 240 } 241 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 } 252 253 public final T getType() { 254 return nav().use(clazz); 255 } 256 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 } 272 273 public final String getName() { 274 return nav().getClassName(clazz); 275 } 276 277 public <A extends Annotation> A readAnnotation(Class<A> a) { 278 return reader().getClassAnnotation(a,clazz,this); 279 } 280 281 public Element<T,C> asElement() { 282 if(isElement()) 283 return this; 284 else 285 return null; 286 } 287 288 public List<? extends PropertyInfo<T,C>> getProperties() { 289 if(properties!=null) return properties; 290 291 // check the access type first 292 XmlAccessType at = getAccessType(); 293 294 properties = new FinalArrayList<PropertyInfoImpl<T,C,F,M>>(); 295 296 findFieldProperties(clazz,at); 297 298 findGetterSetterProperties(at); 299 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 } 313 314 {// additional error checks 315 PropertyInfoImpl vp=null; // existing value property 316 PropertyInfoImpl ep=null; // existing element property 317 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 } 344 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 } 353 354 return properties; 355 } 356 357 private void findFieldProperties(C c, XmlAccessType at) { 358 359 // always find properties from the super class first 360 C sc = nav().getSuperClass(c); 361 if (shouldRecurseSuperClass(sc)) { 362 findFieldProperties(sc,at); 363 } 364 365 for( F f : nav().getDeclaredFields(c) ) { 366 Annotation[] annotations = reader().getAllFieldAnnotations(f,this); 367 boolean isDummy = reader().hasFieldAnnotation(OverrideAnnotationOf.class, f); 368 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 } 400 401 public final boolean hasValueProperty() { 402 ClassInfoImpl<T, C, F, M> bc = getBaseClass(); 403 if(bc!=null && bc.hasValueProperty()) 404 return true; 405 406 for (PropertyInfo p : getProperties()) { 407 if (p instanceof ValuePropertyInfo) return true; 408 } 409 410 return false; 411 } 412 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 } 420 421 /** 422 * This hook is used by {@link RuntimeClassInfoImpl} to look for {@link com.sun.xml.internal.bind.annotation.XmlLocation}. 423 */ 424 protected void checkFieldXmlLocation(F f) { 425 } 426 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 } 437 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 } 449 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 } 460 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]; 472 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; 478 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 } 488 489 public int compare(PropertyInfoImpl o1, PropertyInfoImpl o2) { 490 int lhs = checkedGet(o1); 491 int rhs = checkedGet(o2); 492 493 return lhs-rhs; 494 } 495 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)); 503 504 // give it an order to recover from an error 505 i = size(); 506 put(p.getName(),i); 507 } 508 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>(); 514 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 } 522 523 return i; 524 } 525 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 } 537 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 } 550 551 public boolean hasProperties() { 552 return !properties.isEmpty(); 553 } 554 555 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 } 565 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 } 572 573 private static final class ConflictException extends Exception { 574 final List<Annotation> annotations; 575 576 public ConflictException(List<Annotation> one) { 577 this.annotations = one; 578 } 579 } 580 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 } 588 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); 599 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; 609 610 SecondaryAnnotation(int bitMask, Class<? extends Annotation>... members) { 611 this.bitMask = bitMask; 612 this.members = members; 613 } 614 } 615 616 private static final SecondaryAnnotation[] SECONDARY_ANNOTATIONS = SecondaryAnnotation.values(); 617 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); 632 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; 639 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 } 649 650 boolean allows(SecondaryAnnotation a) { 651 return (allowedsecondaryAnnotations&a.bitMask)==0; 652 } 653 } 654 655 private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0]; 656 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 }; 675 676 HashMap<Class,Integer> m = ANNOTATION_NUMBER_MAP; 677 678 // characterizing annotations 679 for( Class c : annotations ) 680 m.put(c, m.size() ); 681 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 } 690 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 } 696 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) 712 713 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; 728 729 // encountered secondary annotations are accumulated into a bit mask 730 int secondaryAnnotations = 0; 731 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 } 754 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; 759 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 } 784 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 } 790 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; 795 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 } 807 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 } 828 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; 842 843 if(inheritsAttributeWildcard()) { 844 builder.reportError(new IllegalAnnotationException( 845 Messages.SUPER_CLASS_HAS_WILDCARD.format(), 846 aa,getInheritedAttributeWildcard())); 847 return; 848 } 849 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 } 857 858 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; 881 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) )); 887 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 895 896 } 897 } 898 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 } 902 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 } 906 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 } 910 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 } 914 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 } 918 919 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>(); 928 929 C c = clazz; 930 do { 931 collectGetterSetters(clazz, getters, setters); 932 933 // take super classes into account if they have @XmlTransient 934 c = nav().getSuperClass(c); 935 } while(shouldRecurseSuperClass(c)); 936 937 938 // compute the intersection 939 Set<String> complete = new TreeSet<String>(getters.keySet()); 940 complete.retainAll(setters.keySet()); 941 942 resurrect(getters, complete); 943 resurrect(setters, complete); 944 945 // then look for read/write properties. 946 for (String name : complete) { 947 M getter = getters.get(name); 948 M setter = setters.get(name); 949 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; 952 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 } 961 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 } 978 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 } 991 992 addProperty(createAccessorSeed(getter, setter), r, false); 993 } 994 } 995 // done with complete pairs 996 getters.keySet().removeAll(complete); 997 setters.keySet().removeAll(complete); 998 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 } 1008 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); 1017 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 1022 1023 if(nav().isBridgeMethod(method)) 1024 continue; // ignore 1025 1026 String name = nav().getMethodName(method); 1027 int arity = nav().getMethodParameters(method).length; 1028 1029 if(nav().isStaticMethod(method)) { 1030 ensureNoAnnotation(method); 1031 continue; 1032 } 1033 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 } 1040 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 } 1052 1053 if(!used) 1054 ensureNoAnnotation(method); 1055 } 1056 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 } 1075 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 } 1081 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 } 1089 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 } 1096 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 } 1109 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 } 1125 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 } 1132 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 } 1139 1140 private static Annotation getSomeJAXBAnnotation(Annotation[] annotations) { 1141 for( Annotation a : annotations ) 1142 if(isJAXBAnnotation(a)) 1143 return a; 1144 return null; 1145 } 1146 1147 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 } 1161 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 } 1173 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 } 1183 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 } 1190 1191 public final boolean isElement() { 1192 return elementName!=null; 1193 } 1194 1195 public boolean isAbstract() { 1196 return nav().isAbstract(clazz); 1197 } 1198 1199 public boolean isOrdered() { 1200 return propOrder!=null; 1201 } 1202 1203 public final boolean isFinal() { 1204 return nav().isFinal(clazz); 1205 } 1206 1207 public final boolean hasSubClasses() { 1208 return hasSubClasses; 1209 } 1210 1211 public final boolean hasAttributeWildcard() { 1212 return declaresAttributeWildcard() || inheritsAttributeWildcard(); 1213 } 1214 1215 public final boolean inheritsAttributeWildcard() { 1216 return getInheritedAttributeWildcard()!=null; 1217 } 1218 1219 public final boolean declaresAttributeWildcard() { 1220 return attributeWildcard!=null; 1221 } 1222 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 } 1232 1233 public final QName getElementName() { 1234 return elementName; 1235 } 1236 1237 public final QName getTypeName() { 1238 return typeName; 1239 } 1240 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 } 1246 1247 /** 1248 * Called after all the {@link com.sun.xml.internal.bind.v2.model.core.TypeInfo}s are collected into the {@link #owner}. 1249 */ 1250 @Override 1251 /*package*/ void link() { 1252 getProperties(); // make sure properties!=null 1253 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 } 1267 1268 public Location getLocation() { 1269 return nav().getClassLocation(clazz); 1270 } 1271 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; 1282 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 } 1309 1310 public Method getFactoryMethod(){ 1311 return (Method) factoryMethod; 1312 } 1313 1314 @Override 1315 public String toString() { 1316 return "ClassInfo("+clazz+')'; 1317 } 1318 1319 private static final String[] DEFAULT_ORDER = new String[0]; 1320 }