0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common Development
0008: * and Distribution License("CDDL") (collectively, the "License"). You
0009: * may not use this file except in compliance with the License. You can obtain
0010: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
0011: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
0012: * language governing permissions and limitations under the License.
0013: *
0014: * When distributing the software, include this License Header Notice in each
0015: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
0016: * Sun designates this particular file as subject to the "Classpath" exception
0017: * as provided by Sun in the GPL Version 2 section of the License file that
0018: * accompanied this code. If applicable, add the following below the License
0019: * Header, with the fields enclosed by brackets [] replaced by your own
0020: * identifying information: "Portions Copyrighted [year]
0021: * [name of copyright owner]"
0022: *
0023: * Contributor(s):
0024: *
0025: * If you wish your version of this file to be governed by only the CDDL or
0026: * only the GPL Version 2, indicate your decision by adding "[Contributor]
0027: * elects to include this software in this distribution under the [CDDL or GPL
0028: * Version 2] license." If you don't indicate a single choice of license, a
0029: * recipient has the option to distribute your version of this file under
0030: * either the CDDL, the GPL Version 2 or to extend the choice of license to
0031: * its licensees as provided above. However, if you add GPL Version 2 code
0032: * and therefore, elected the GPL Version 2 license, then the option applies
0033: * only if the new code is made subject to such option by the copyright
0034: * holder.
0035: */
0036:
0037: package com.sun.xml.bind.v2.model.impl;
0038:
0039: import java.lang.annotation.Annotation;
0040: import java.lang.reflect.Method;
0041: import java.util.ArrayList;
0042: import java.util.Collection;
0043: import java.util.Collections;
0044: import java.util.Comparator;
0045: import java.util.HashMap;
0046: import java.util.HashSet;
0047: import java.util.LinkedHashMap;
0048: import java.util.List;
0049: import java.util.Map;
0050: import java.util.Set;
0051: import java.util.TreeSet;
0052: import java.util.AbstractList;
0053:
0054: import javax.xml.bind.annotation.XmlAccessOrder;
0055: import javax.xml.bind.annotation.XmlAccessType;
0056: import javax.xml.bind.annotation.XmlAccessorOrder;
0057: import javax.xml.bind.annotation.XmlAccessorType;
0058: import javax.xml.bind.annotation.XmlAnyAttribute;
0059: import javax.xml.bind.annotation.XmlAnyElement;
0060: import javax.xml.bind.annotation.XmlAttachmentRef;
0061: import javax.xml.bind.annotation.XmlAttribute;
0062: import javax.xml.bind.annotation.XmlElement;
0063: import javax.xml.bind.annotation.XmlElementRef;
0064: import javax.xml.bind.annotation.XmlElementRefs;
0065: import javax.xml.bind.annotation.XmlElementWrapper;
0066: import javax.xml.bind.annotation.XmlElements;
0067: import javax.xml.bind.annotation.XmlID;
0068: import javax.xml.bind.annotation.XmlIDREF;
0069: import javax.xml.bind.annotation.XmlInlineBinaryData;
0070: import javax.xml.bind.annotation.XmlList;
0071: import javax.xml.bind.annotation.XmlMimeType;
0072: import javax.xml.bind.annotation.XmlMixed;
0073: import javax.xml.bind.annotation.XmlRootElement;
0074: import javax.xml.bind.annotation.XmlSchemaType;
0075: import javax.xml.bind.annotation.XmlTransient;
0076: import javax.xml.bind.annotation.XmlType;
0077: import javax.xml.bind.annotation.XmlValue;
0078: import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
0079: import javax.xml.namespace.QName;
0080:
0081: import com.sun.istack.FinalArrayList;
0082: import com.sun.xml.bind.annotation.XmlLocation;
0083: import com.sun.xml.bind.v2.model.annotation.Locatable;
0084: import com.sun.xml.bind.v2.model.annotation.MethodLocatable;
0085: import com.sun.xml.bind.v2.model.core.ClassInfo;
0086: import com.sun.xml.bind.v2.model.core.Element;
0087: import com.sun.xml.bind.v2.model.core.ID;
0088: import com.sun.xml.bind.v2.model.core.NonElement;
0089: import com.sun.xml.bind.v2.model.core.PropertyInfo;
0090: import com.sun.xml.bind.v2.model.core.PropertyKind;
0091: import com.sun.xml.bind.v2.model.core.TypeInfo;
0092: import com.sun.xml.bind.v2.model.core.ValuePropertyInfo;
0093: import com.sun.xml.bind.v2.runtime.IllegalAnnotationException;
0094: import com.sun.xml.bind.v2.runtime.Location;
0095: import com.sun.xml.bind.v2.util.EditDistance;
0096:
0097: /**
0098: * A part of the {@link ClassInfo} that doesn't depend on a particular
0099: * reflection library.
0100: *
0101: * @author Kohsuke Kawaguchi (kk@kohsuke.org)
0102: */
0103: class ClassInfoImpl<T, C, F, M> extends TypeInfoImpl<T, C, F, M>
0104: implements ClassInfo<T, C>, Element<T, C> {
0105:
0106: protected final C clazz;
0107:
0108: /**
0109: * @see #getElementName()
0110: */
0111: private final QName elementName;
0112:
0113: /**
0114: * @see #getTypeName()
0115: */
0116: private final QName typeName;
0117:
0118: /**
0119: * Lazily created.
0120: *
0121: * @see #getProperties()
0122: */
0123: private FinalArrayList<PropertyInfoImpl<T, C, F, M>> properties;
0124:
0125: /**
0126: * The property order.
0127: *
0128: * null if unordered. {@link #DEFAULT_ORDER} if ordered but the order is defaulted
0129: *
0130: * @see #isOrdered()
0131: */
0132: private final String[] propOrder;
0133:
0134: /**
0135: * Lazily computed.
0136: *
0137: * To avoid the cyclic references of the form C1 --base--> C2 --property--> C1.
0138: */
0139: private ClassInfoImpl<T, C, F, M> baseClass;
0140:
0141: private boolean baseClassComputed = false;
0142:
0143: private boolean hasSubClasses = false;
0144:
0145: /**
0146: * If this class has a declared (not inherited) attribute wildcard, keep the reference
0147: * to it.
0148: *
0149: * This parameter is initialized at the construction time and never change.
0150: */
0151: protected/*final*/PropertySeed<T, C, F, M> attributeWildcard;
0152:
0153: /**
0154: * @see #getFactoryMethod()
0155: */
0156: private M factoryMethod = null;
0157:
0158: ClassInfoImpl(ModelBuilder<T, C, F, M> builder, Locatable upstream,
0159: C clazz) {
0160: super (builder, upstream);
0161: this .clazz = clazz;
0162: assert clazz != null;
0163:
0164: // compute the element name
0165: elementName = parseElementName(clazz);
0166:
0167: // compute the type name
0168: XmlType t = reader().getClassAnnotation(XmlType.class, clazz,
0169: this );
0170: typeName = parseTypeName(clazz, t);
0171:
0172: if (t != null) {
0173: String[] propOrder = t.propOrder();
0174: if (propOrder.length == 0)
0175: this .propOrder = null; // unordered
0176: else {
0177: if (propOrder[0].length() == 0)
0178: this .propOrder = DEFAULT_ORDER;
0179: else
0180: this .propOrder = propOrder;
0181: }
0182: } else {
0183: propOrder = DEFAULT_ORDER;
0184: }
0185:
0186: if (nav().isInterface(clazz)) {
0187: builder.reportError(new IllegalAnnotationException(
0188: Messages.CANT_HANDLE_INTERFACE.format(nav()
0189: .getClassName(clazz)), this ));
0190: }
0191:
0192: // the class must have the default constructor
0193: if (!hasFactoryConstructor(t)) {
0194: if (!nav().hasDefaultConstructor(clazz)) {
0195: Messages msg;
0196: if (nav().isInnerClass(clazz))
0197: msg = Messages.CANT_HANDLE_INNER_CLASS;
0198: else
0199: msg = Messages.NO_DEFAULT_CONSTRUCTOR;
0200:
0201: builder.reportError(new IllegalAnnotationException(msg
0202: .format(nav().getClassName(clazz)), this ));
0203: }
0204: }
0205: }
0206:
0207: public ClassInfoImpl<T, C, F, M> getBaseClass() {
0208: if (!baseClassComputed) {
0209: baseClassComputed = true;
0210: // compute the base class
0211: C s = nav().getSuperClass(clazz);
0212: if (s == null || s == nav().asDecl(Object.class))
0213: baseClass = null;
0214: else {
0215: NonElement<T, C> b = builder
0216: .getClassInfo(s, true, this );
0217: if (b instanceof ClassInfoImpl) {
0218: baseClass = (ClassInfoImpl<T, C, F, M>) b;
0219: baseClass.hasSubClasses = true;
0220: } else {
0221: baseClass = null;
0222: }
0223: }
0224: }
0225: return baseClass;
0226: }
0227:
0228: /**
0229: * {@inheritDoc}
0230: *
0231: * The substitution hierarchy is the same as the inheritance hierarchy.
0232: */
0233: public final Element<T, C> getSubstitutionHead() {
0234: ClassInfoImpl<T, C, F, M> c = getBaseClass();
0235: while (c != null && !c.isElement())
0236: c = c.getBaseClass();
0237: return c;
0238: }
0239:
0240: public final C getClazz() {
0241: return clazz;
0242: }
0243:
0244: /**
0245: * When a bean binds to an element, it's always through {@link XmlRootElement},
0246: * so this method always return null.
0247: *
0248: * @deprecated
0249: * you shouldn't be invoking this method on {@link ClassInfoImpl}.
0250: */
0251: public ClassInfoImpl<T, C, F, M> getScope() {
0252: return null;
0253: }
0254:
0255: public final T getType() {
0256: return nav().use(clazz);
0257: }
0258:
0259: /**
0260: * A {@link ClassInfo} can be referenced by {@link XmlIDREF} if
0261: * it has an ID property.
0262: */
0263: public boolean canBeReferencedByIDREF() {
0264: for (PropertyInfo<T, C> p : getProperties()) {
0265: if (p.id() == ID.ID)
0266: return true;
0267: }
0268: ClassInfoImpl<T, C, F, M> base = getBaseClass();
0269: if (base != null)
0270: return base.canBeReferencedByIDREF();
0271: else
0272: return false;
0273: }
0274:
0275: public final String getName() {
0276: return nav().getClassName(clazz);
0277: }
0278:
0279: public <A extends Annotation> A readAnnotation(Class<A> a) {
0280: return reader().getClassAnnotation(a, clazz, this );
0281: }
0282:
0283: public Element<T, C> asElement() {
0284: if (isElement())
0285: return this ;
0286: else
0287: return null;
0288: }
0289:
0290: public List<? extends PropertyInfo<T, C>> getProperties() {
0291: if (properties != null)
0292: return properties;
0293:
0294: // check the access type first
0295: XmlAccessType at = getAccessType();
0296:
0297: properties = new FinalArrayList<PropertyInfoImpl<T, C, F, M>>();
0298:
0299: findFieldProperties(clazz, at);
0300:
0301: findGetterSetterProperties(at);
0302:
0303: if (propOrder == DEFAULT_ORDER || propOrder == null) {
0304: XmlAccessOrder ao = getAccessorOrder();
0305: if (ao == XmlAccessOrder.ALPHABETICAL)
0306: Collections.sort(properties);
0307: } else {
0308: //sort them as specified
0309: PropertySorter sorter = new PropertySorter();
0310: for (PropertyInfoImpl p : properties)
0311: sorter.checkedGet(p); // have it check for errors
0312: Collections.sort(properties, sorter);
0313: sorter.checkUnusedProperties();
0314: }
0315:
0316: {// additional error checks
0317: PropertyInfoImpl vp = null; // existing value property
0318: PropertyInfoImpl ep = null; // existing element property
0319:
0320: for (PropertyInfoImpl p : properties) {
0321: switch (p.kind()) {
0322: case ELEMENT:
0323: case REFERENCE:
0324: case MAP:
0325: ep = p;
0326: break;
0327: case VALUE:
0328: if (vp != null) {
0329: // can't have multiple value properties.
0330: builder
0331: .reportError(new IllegalAnnotationException(
0332: Messages.MULTIPLE_VALUE_PROPERTY
0333: .format(), vp, p));
0334: }
0335: if (getBaseClass() != null) {
0336: builder
0337: .reportError(new IllegalAnnotationException(
0338: Messages.XMLVALUE_IN_DERIVED_TYPE
0339: .format(), p));
0340: }
0341: vp = p;
0342: break;
0343: case ATTRIBUTE:
0344: break; // noop
0345: default:
0346: assert false;
0347: }
0348: }
0349:
0350: if (ep != null && vp != null) {
0351: // can't have element and value property at the same time
0352: builder.reportError(new IllegalAnnotationException(
0353: Messages.ELEMENT_AND_VALUE_PROPERTY.format(),
0354: vp, ep));
0355: }
0356: }
0357:
0358: return properties;
0359: }
0360:
0361: private void findFieldProperties(C c, XmlAccessType at) {
0362: // always find properties from the super class first
0363: C sc = nav().getSuperClass(c);
0364: if (shouldRecurseSuperClass(sc))
0365: findFieldProperties(sc, at);
0366:
0367: for (F f : nav().getDeclaredFields(c)) {
0368: Annotation[] annotations = reader().getAllFieldAnnotations(
0369: f, this );
0370: if (nav().isTransient(f)) {
0371: // it's an error for transient field to have any binding annotation
0372: if (hasJAXBAnnotation(annotations))
0373: builder.reportError(new IllegalAnnotationException(
0374: Messages.TRANSIENT_FIELD_NOT_BINDABLE
0375: .format(nav().getFieldName(f)),
0376: getSomeJAXBAnnotation(annotations)));
0377: } else if (nav().isStaticField(f)) {
0378: // static fields are bound only when there's explicit annotation.
0379: if (hasJAXBAnnotation(annotations))
0380: addProperty(createFieldSeed(f), annotations);
0381: } else {
0382: if (at == XmlAccessType.FIELD
0383: || (at == XmlAccessType.PUBLIC_MEMBER && nav()
0384: .isPublicField(f))
0385: || hasJAXBAnnotation(annotations))
0386: addProperty(createFieldSeed(f), annotations);
0387: checkFieldXmlLocation(f);
0388: }
0389: }
0390: }
0391:
0392: public final boolean hasValueProperty() {
0393: ClassInfoImpl<T, C, F, M> bc = getBaseClass();
0394: if (bc != null && bc.hasValueProperty())
0395: return true;
0396:
0397: for (PropertyInfo p : getProperties()) {
0398: if (p instanceof ValuePropertyInfo)
0399: return true;
0400: }
0401:
0402: return false;
0403: }
0404:
0405: public PropertyInfo<T, C> getProperty(String name) {
0406: for (PropertyInfo<T, C> p : getProperties()) {
0407: if (p.getName().equals(name))
0408: return p;
0409: }
0410: return null;
0411: }
0412:
0413: /**
0414: * This hook is used by {@link RuntimeClassInfoImpl} to look for {@link XmlLocation}.
0415: */
0416: protected void checkFieldXmlLocation(F f) {
0417: }
0418:
0419: /**
0420: * Gets an annotation that are allowed on both class and type.
0421: */
0422: private <T extends Annotation> T getClassOrPackageAnnotation(
0423: Class<T> type) {
0424: T t = reader().getClassAnnotation(type, clazz, this );
0425: if (t != null)
0426: return t;
0427: // defaults to the package level
0428: return reader().getPackageAnnotation(type, clazz, this );
0429: }
0430:
0431: /**
0432: * Computes the {@link XmlAccessType} on this class by looking at {@link XmlAccessorType}
0433: * annotations.
0434: */
0435: private XmlAccessType getAccessType() {
0436: XmlAccessorType xat = getClassOrPackageAnnotation(XmlAccessorType.class);
0437: if (xat != null)
0438: return xat.value();
0439: else
0440: return XmlAccessType.PUBLIC_MEMBER;
0441: }
0442:
0443: /**
0444: * Gets the accessor order for this class by consulting {@link XmlAccessorOrder}.
0445: */
0446: private XmlAccessOrder getAccessorOrder() {
0447: XmlAccessorOrder xao = getClassOrPackageAnnotation(XmlAccessorOrder.class);
0448: if (xao != null)
0449: return xao.value();
0450: else
0451: return XmlAccessOrder.UNDEFINED;
0452: }
0453:
0454: /**
0455: * Compares orders among {@link PropertyInfoImpl} according to {@link ClassInfoImpl#propOrder}.
0456: *
0457: * <p>
0458: * extends {@link HashMap} to save memory.
0459: */
0460: private final class PropertySorter extends HashMap<String, Integer>
0461: implements Comparator<PropertyInfoImpl> {
0462: /**
0463: * Mark property names that are used, so that we can report unused property names in the propOrder array.
0464: */
0465: PropertyInfoImpl[] used = new PropertyInfoImpl[propOrder.length];
0466:
0467: /**
0468: * If any name collides, it will be added to this set.
0469: * This is used to avoid repeating the same error message.
0470: */
0471: private Set<String> collidedNames;
0472:
0473: PropertySorter() {
0474: super (propOrder.length);
0475: for (String name : propOrder)
0476: if (put(name, size()) != null) {
0477: // two properties with the same name
0478: builder.reportError(new IllegalAnnotationException(
0479: Messages.DUPLICATE_ENTRY_IN_PROP_ORDER
0480: .format(name), ClassInfoImpl.this ));
0481: }
0482: }
0483:
0484: public int compare(PropertyInfoImpl o1, PropertyInfoImpl o2) {
0485: int lhs = checkedGet(o1);
0486: int rhs = checkedGet(o2);
0487:
0488: return lhs - rhs;
0489: }
0490:
0491: private int checkedGet(PropertyInfoImpl p) {
0492: Integer i = get(p.getName());
0493: if (i == null) {
0494: // missing
0495: if ((p.kind().isOrdered))
0496: builder.reportError(new IllegalAnnotationException(
0497: Messages.PROPERTY_MISSING_FROM_ORDER
0498: .format(p.getName()), p));
0499:
0500: // give it an order to recover from an error
0501: i = size();
0502: put(p.getName(), i);
0503: }
0504:
0505: // mark the used field
0506: int ii = i;
0507: if (ii < used.length) {
0508: if (used[ii] != null && used[ii] != p) {
0509: if (collidedNames == null)
0510: collidedNames = new HashSet<String>();
0511:
0512: if (collidedNames.add(p.getName()))
0513: // report the error only on the first time
0514: builder
0515: .reportError(new IllegalAnnotationException(
0516: Messages.DUPLICATE_PROPERTIES
0517: .format(p.getName()),
0518: p, used[ii]));
0519: }
0520: used[ii] = p;
0521: }
0522:
0523: return i;
0524: }
0525:
0526: /**
0527: * Report errors for unused propOrder entries.
0528: */
0529: public void checkUnusedProperties() {
0530: for (int i = 0; i < used.length; i++)
0531: if (used[i] == null) {
0532: String unusedName = propOrder[i];
0533: String nearest = EditDistance.findNearest(
0534: unusedName, new AbstractList<String>() {
0535: public String get(int index) {
0536: return properties.get(index)
0537: .getName();
0538: }
0539:
0540: public int size() {
0541: return properties.size();
0542: }
0543: });
0544: builder
0545: .reportError(new IllegalAnnotationException(
0546: Messages.PROPERTY_ORDER_CONTAINS_UNUSED_ENTRY
0547: .format(unusedName, nearest),
0548: ClassInfoImpl.this ));
0549: }
0550: }
0551: }
0552:
0553: public boolean hasProperties() {
0554: return !properties.isEmpty();
0555: }
0556:
0557: /**
0558: * Picks the first non-null argument, or null if all arguments are null.
0559: */
0560: private static <T> T pickOne(T... args) {
0561: for (T arg : args)
0562: if (arg != null)
0563: return arg;
0564: return null;
0565: }
0566:
0567: private static <T> List<T> makeSet(T... args) {
0568: List<T> l = new FinalArrayList<T>();
0569: for (T arg : args)
0570: if (arg != null)
0571: l.add(arg);
0572: return l;
0573: }
0574:
0575: private static final class ConflictException extends Exception {
0576: final List<Annotation> annotations;
0577:
0578: public ConflictException(List<Annotation> one) {
0579: this .annotations = one;
0580: }
0581: }
0582:
0583: private static final class DupliateException extends Exception {
0584: final Annotation a1, a2;
0585:
0586: public DupliateException(Annotation a1, Annotation a2) {
0587: this .a1 = a1;
0588: this .a2 = a2;
0589: }
0590: }
0591:
0592: /**
0593: * Represents 6 groups of secondary annotations
0594: */
0595: private static enum SecondaryAnnotation {
0596: JAVA_TYPE(0x01, XmlJavaTypeAdapter.class), ID_IDREF(0x02,
0597: XmlID.class, XmlIDREF.class), BINARY(0x04,
0598: XmlInlineBinaryData.class, XmlMimeType.class,
0599: XmlAttachmentRef.class), ELEMENT_WRAPPER(0x08,
0600: XmlElementWrapper.class), LIST(0x10, XmlList.class), SCHEMA_TYPE(
0601: 0x20, XmlSchemaType.class);
0602:
0603: /**
0604: * Each constant gets an unique bit mask so that the presence/absence
0605: * of them can be represented in a single byte.
0606: */
0607: final int bitMask;
0608: /**
0609: * List of annotations that belong to this member.
0610: */
0611: final Class<? extends Annotation>[] members;
0612:
0613: SecondaryAnnotation(int bitMask,
0614: Class<? extends Annotation>... members) {
0615: this .bitMask = bitMask;
0616: this .members = members;
0617: }
0618: }
0619:
0620: private static final SecondaryAnnotation[] SECONDARY_ANNOTATIONS = SecondaryAnnotation
0621: .values();
0622:
0623: /**
0624: * Represents 7 groups of properties.
0625: *
0626: * Each instance is also responsible for rejecting annotations
0627: * that are not allowed on that kind.
0628: */
0629: private static enum PropertyGroup {
0630: TRANSIENT(false, false, false, false, false, false), ANY_ATTRIBUTE(
0631: true, false, false, false, false, false), ATTRIBUTE(
0632: true, true, true, false, true, true), VALUE(true, true,
0633: true, false, true, true), ELEMENT(true, true, true,
0634: true, true, true), ELEMENT_REF(true, false, false,
0635: true, false, false), MAP(false, false, false, true,
0636: false, false);
0637:
0638: /**
0639: * Bit mask that represents secondary annotations that are allowed on this group.
0640: *
0641: * T = not allowed, F = allowed
0642: */
0643: final int allowedsecondaryAnnotations;
0644:
0645: PropertyGroup(boolean... bits) {
0646: int mask = 0;
0647: assert bits.length == SECONDARY_ANNOTATIONS.length;
0648: for (int i = 0; i < bits.length; i++) {
0649: if (bits[i])
0650: mask |= SECONDARY_ANNOTATIONS[i].bitMask;
0651: }
0652: allowedsecondaryAnnotations = ~mask;
0653: }
0654:
0655: boolean allows(SecondaryAnnotation a) {
0656: return (allowedsecondaryAnnotations & a.bitMask) == 0;
0657: }
0658: }
0659:
0660: private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0];
0661:
0662: /**
0663: * All the annotations in JAXB to their internal index.
0664: */
0665: private static final HashMap<Class, Integer> ANNOTATION_NUMBER_MAP = new HashMap<Class, Integer>();
0666: static {
0667: Class[] annotations = { XmlTransient.class, // 0
0668: XmlAnyAttribute.class, // 1
0669: XmlAttribute.class, // 2
0670: XmlValue.class, // 3
0671: XmlElement.class, // 4
0672: XmlElements.class, // 5
0673: XmlElementRef.class, // 6
0674: XmlElementRefs.class, // 7
0675: XmlAnyElement.class, // 8
0676: XmlMixed.class, // 9
0677: };
0678:
0679: HashMap<Class, Integer> m = ANNOTATION_NUMBER_MAP;
0680:
0681: // characterizing annotations
0682: for (Class c : annotations)
0683: m.put(c, m.size());
0684:
0685: // secondary annotations
0686: int index = 20;
0687: for (SecondaryAnnotation sa : SECONDARY_ANNOTATIONS) {
0688: for (Class member : sa.members)
0689: m.put(member, index);
0690: index++;
0691: }
0692: }
0693:
0694: private void checkConflict(Annotation a, Annotation b)
0695: throws DupliateException {
0696: assert b != null;
0697: if (a != null)
0698: throw new DupliateException(a, b);
0699: }
0700:
0701: /**
0702: * Called only from {@link #getProperties()}.
0703: *
0704: * <p>
0705: * This is where we decide the type of the property and checks for annotations
0706: * that are not allowed.
0707: *
0708: * @param annotations
0709: * all annotations on this property. It's the same as
0710: * {@code seed.readAllAnnotation()}, but taken as a parameter
0711: * because the caller should know it already.
0712: */
0713: private void addProperty(PropertySeed<T, C, F, M> seed,
0714: Annotation[] annotations) {
0715: // since typically there's a very few annotations on a method,
0716: // this runs faster than checking for each annotation via readAnnotation(A)
0717:
0718: // characterizing annotations. these annotations (or lack thereof) decides
0719: // the kind of the property it goes to.
0720: // I wish I could use an array...
0721: XmlTransient t = null;
0722: XmlAnyAttribute aa = null;
0723: XmlAttribute a = null;
0724: XmlValue v = null;
0725: XmlElement e1 = null;
0726: XmlElements e2 = null;
0727: XmlElementRef r1 = null;
0728: XmlElementRefs r2 = null;
0729: XmlAnyElement xae = null;
0730: XmlMixed mx = null;
0731:
0732: // encountered secondary annotations are accumulated into a bit mask
0733: int secondaryAnnotations = 0;
0734:
0735: try {
0736: for (Annotation ann : annotations) {
0737: Integer index = ANNOTATION_NUMBER_MAP.get(ann
0738: .annotationType());
0739: if (index == null)
0740: continue;
0741: switch (index) {
0742: case 0:
0743: checkConflict(t, ann);
0744: t = (XmlTransient) ann;
0745: break;
0746: case 1:
0747: checkConflict(aa, ann);
0748: aa = (XmlAnyAttribute) ann;
0749: break;
0750: case 2:
0751: checkConflict(a, ann);
0752: a = (XmlAttribute) ann;
0753: break;
0754: case 3:
0755: checkConflict(v, ann);
0756: v = (XmlValue) ann;
0757: break;
0758: case 4:
0759: checkConflict(e1, ann);
0760: e1 = (XmlElement) ann;
0761: break;
0762: case 5:
0763: checkConflict(e2, ann);
0764: e2 = (XmlElements) ann;
0765: break;
0766: case 6:
0767: checkConflict(r1, ann);
0768: r1 = (XmlElementRef) ann;
0769: break;
0770: case 7:
0771: checkConflict(r2, ann);
0772: r2 = (XmlElementRefs) ann;
0773: break;
0774: case 8:
0775: checkConflict(xae, ann);
0776: xae = (XmlAnyElement) ann;
0777: break;
0778: case 9:
0779: checkConflict(mx, ann);
0780: mx = (XmlMixed) ann;
0781: break;
0782: default:
0783: // secondary annotations
0784: secondaryAnnotations |= (1 << (index - 20));
0785: break;
0786: }
0787: }
0788:
0789: // determine the group kind, and also count the numbers, since
0790: // characterizing annotations are mutually exclusive.
0791: PropertyGroup group = null;
0792: int groupCount = 0;
0793:
0794: if (t != null) {
0795: group = PropertyGroup.TRANSIENT;
0796: groupCount++;
0797: }
0798: if (aa != null) {
0799: group = PropertyGroup.ANY_ATTRIBUTE;
0800: groupCount++;
0801: }
0802: if (a != null) {
0803: group = PropertyGroup.ATTRIBUTE;
0804: groupCount++;
0805: }
0806: if (v != null) {
0807: group = PropertyGroup.VALUE;
0808: groupCount++;
0809: }
0810: if (e1 != null || e2 != null) {
0811: group = PropertyGroup.ELEMENT;
0812: groupCount++;
0813: }
0814: if (r1 != null || r2 != null || xae != null || mx != null) {
0815: group = PropertyGroup.ELEMENT_REF;
0816: groupCount++;
0817: }
0818:
0819: if (groupCount > 1) {
0820: // collision between groups
0821: List<Annotation> err = makeSet(t, aa, a, v, pickOne(e1,
0822: e2), pickOne(r1, r2, xae));
0823: throw new ConflictException(err);
0824: }
0825:
0826: if (group == null) {
0827: // if no characterizing annotation was found, it's either element or map
0828: // sniff the signature and then decide.
0829: assert groupCount == 0;
0830:
0831: // UGLY: the presence of XmlJavaTypeAdapter makes it an element property. ARGH.
0832: if (nav().isSubClassOf(seed.getRawType(),
0833: nav().ref(Map.class))
0834: && !seed
0835: .hasAnnotation(XmlJavaTypeAdapter.class))
0836: group = PropertyGroup.MAP;
0837: else
0838: group = PropertyGroup.ELEMENT;
0839: }
0840:
0841: // group determined by now
0842: // make sure that there are no prohibited secondary annotations
0843: if ((secondaryAnnotations & group.allowedsecondaryAnnotations) != 0) {
0844: // uh oh. find the offending annotation
0845: for (SecondaryAnnotation sa : SECONDARY_ANNOTATIONS) {
0846: if (group.allows(sa))
0847: continue;
0848: for (Class<? extends Annotation> m : sa.members) {
0849: Annotation offender = seed.readAnnotation(m);
0850: if (offender != null) {
0851: // found it
0852: builder
0853: .reportError(new IllegalAnnotationException(
0854: Messages.ANNOTATION_NOT_ALLOWED
0855: .format(m
0856: .getSimpleName()),
0857: offender));
0858: return;
0859: }
0860: }
0861: }
0862: // there must have been an offender
0863: assert false;
0864: }
0865:
0866: // actually create annotations
0867: switch (group) {
0868: case TRANSIENT:
0869: return;
0870: case ANY_ATTRIBUTE:
0871: // an attribute wildcard property
0872: if (attributeWildcard != null) {
0873: builder.reportError(new IllegalAnnotationException(
0874: Messages.TWO_ATTRIBUTE_WILDCARDS
0875: .format(nav().getClassName(
0876: getClazz())), aa,
0877: attributeWildcard));
0878: return; // recover by ignore
0879: }
0880: attributeWildcard = seed;
0881:
0882: if (inheritsAttributeWildcard()) {
0883: builder.reportError(new IllegalAnnotationException(
0884: Messages.SUPER_CLASS_HAS_WILDCARD.format(),
0885: aa, getInheritedAttributeWildcard()));
0886: return;
0887: }
0888:
0889: // check the signature and make sure it's assignable to Map
0890: if (!nav().isSubClassOf(seed.getRawType(),
0891: nav().ref(Map.class))) {
0892: builder.reportError(new IllegalAnnotationException(
0893: Messages.INVALID_ATTRIBUTE_WILDCARD_TYPE
0894: .format(nav().getTypeName(
0895: seed.getRawType())), aa,
0896: getInheritedAttributeWildcard()));
0897: return;
0898: }
0899:
0900: return;
0901: case ATTRIBUTE:
0902: properties.add(createAttributeProperty(seed));
0903: return;
0904: case VALUE:
0905: properties.add(createValueProperty(seed));
0906: return;
0907: case ELEMENT:
0908: properties.add(createElementProperty(seed));
0909: return;
0910: case ELEMENT_REF:
0911: properties.add(createReferenceProperty(seed));
0912: return;
0913: case MAP:
0914: properties.add(createMapProperty(seed));
0915: return;
0916: default:
0917: assert false;
0918: }
0919: } catch (ConflictException x) {
0920: // report a conflicting annotation
0921: List<Annotation> err = x.annotations;
0922:
0923: builder
0924: .reportError(new IllegalAnnotationException(
0925: Messages.MUTUALLY_EXCLUSIVE_ANNOTATIONS
0926: .format(nav().getClassName(
0927: getClazz())
0928: + '#' + seed.getName(), err
0929: .get(0).annotationType()
0930: .getName(), err.get(1)
0931: .annotationType().getName()),
0932: err.get(0), err.get(1)));
0933:
0934: // recover by ignoring this property
0935: } catch (DupliateException e) {
0936: // both are present
0937: builder.reportError(new IllegalAnnotationException(
0938: Messages.DUPLICATE_ANNOTATIONS.format(e.a1
0939: .annotationType().getName()), e.a1, e.a2));
0940: // recover by ignoring this property
0941:
0942: }
0943: }
0944:
0945: protected ReferencePropertyInfoImpl<T, C, F, M> createReferenceProperty(
0946: PropertySeed<T, C, F, M> seed) {
0947: return new ReferencePropertyInfoImpl<T, C, F, M>(this , seed);
0948: }
0949:
0950: protected AttributePropertyInfoImpl<T, C, F, M> createAttributeProperty(
0951: PropertySeed<T, C, F, M> seed) {
0952: return new AttributePropertyInfoImpl<T, C, F, M>(this , seed);
0953: }
0954:
0955: protected ValuePropertyInfoImpl<T, C, F, M> createValueProperty(
0956: PropertySeed<T, C, F, M> seed) {
0957: return new ValuePropertyInfoImpl<T, C, F, M>(this , seed);
0958: }
0959:
0960: protected ElementPropertyInfoImpl<T, C, F, M> createElementProperty(
0961: PropertySeed<T, C, F, M> seed) {
0962: return new ElementPropertyInfoImpl<T, C, F, M>(this , seed);
0963: }
0964:
0965: protected MapPropertyInfoImpl<T, C, F, M> createMapProperty(
0966: PropertySeed<T, C, F, M> seed) {
0967: return new MapPropertyInfoImpl<T, C, F, M>(this , seed);
0968: }
0969:
0970: /**
0971: * Adds properties that consists of accessors.
0972: */
0973: private void findGetterSetterProperties(XmlAccessType at) {
0974: // in the first step we accumulate getters and setters
0975: // into this map keyed by the property name.
0976: Map<String, M> getters = new LinkedHashMap<String, M>();
0977: Map<String, M> setters = new LinkedHashMap<String, M>();
0978:
0979: C c = clazz;
0980: do {
0981: collectGetterSetters(clazz, getters, setters);
0982:
0983: // take super classes into account if they have @XmlTransient
0984: c = nav().getSuperClass(c);
0985: } while (shouldRecurseSuperClass(c));
0986:
0987: // compute the intersection
0988: Set<String> complete = new TreeSet<String>(getters.keySet());
0989: complete.retainAll(setters.keySet());
0990:
0991: resurrect(getters, complete);
0992: resurrect(setters, complete);
0993:
0994: // then look for read/write properties.
0995: for (String name : complete) {
0996: M getter = getters.get(name);
0997: M setter = setters.get(name);
0998:
0999: Annotation[] ga = getter != null ? reader()
1000: .getAllMethodAnnotations(getter,
1001: new MethodLocatable<M>(this , getter, nav()))
1002: : EMPTY_ANNOTATIONS;
1003: Annotation[] sa = setter != null ? reader()
1004: .getAllMethodAnnotations(setter,
1005: new MethodLocatable<M>(this , setter, nav()))
1006: : EMPTY_ANNOTATIONS;
1007:
1008: boolean hasAnnotation = hasJAXBAnnotation(ga)
1009: || hasJAXBAnnotation(sa);
1010: boolean isOverriding = false;
1011: if (!hasAnnotation) {
1012: // checking if the method is overriding others isn't free,
1013: // so we don't compute it if it's not necessary.
1014: isOverriding = (getter != null && nav().isOverriding(
1015: getter, c))
1016: || (setter != null && nav().isOverriding(
1017: setter, c));
1018: }
1019:
1020: if ((at == XmlAccessType.PROPERTY && !isOverriding)
1021: || (at == XmlAccessType.PUBLIC_MEMBER
1022: && isConsideredPublic(getter)
1023: && isConsideredPublic(setter) && !isOverriding)
1024: || hasAnnotation) {
1025: // make sure that the type is consistent
1026: if (getter != null
1027: && setter != null
1028: && !nav().getReturnType(getter).equals(
1029: nav().getMethodParameters(setter)[0])) {
1030: // inconsistent
1031: builder
1032: .reportError(new IllegalAnnotationException(
1033: Messages.GETTER_SETTER_INCOMPATIBLE_TYPE
1034: .format(
1035: nav()
1036: .getTypeName(
1037: nav()
1038: .getReturnType(
1039: getter)),
1040: nav()
1041: .getTypeName(
1042: nav()
1043: .getMethodParameters(
1044: setter)[0])),
1045: new MethodLocatable<M>(this ,
1046: getter, nav()),
1047: new MethodLocatable<M>(this ,
1048: setter, nav())));
1049: continue;
1050: }
1051:
1052: // merge annotations from two list
1053: Annotation[] r;
1054: if (ga.length == 0) {
1055: r = sa;
1056: } else if (sa.length == 0) {
1057: r = ga;
1058: } else {
1059: r = new Annotation[ga.length + sa.length];
1060: System.arraycopy(ga, 0, r, 0, ga.length);
1061: System.arraycopy(sa, 0, r, ga.length, sa.length);
1062: }
1063:
1064: addProperty(createAccessorSeed(getter, setter), r);
1065: }
1066: }
1067: // done with complete pairs
1068: getters.keySet().removeAll(complete);
1069: setters.keySet().removeAll(complete);
1070:
1071: // TODO: think about
1072: // class Foo {
1073: // int getFoo();
1074: // }
1075: // class Bar extends Foo {
1076: // void setFoo(int x);
1077: // }
1078: // and how it will be XML-ized.
1079: }
1080:
1081: private void collectGetterSetters(C c, Map<String, M> getters,
1082: Map<String, M> setters) {
1083: // take super classes into account if they have @XmlTransient.
1084: // always visit them first so that
1085: // 1) order is right
1086: // 2) overriden properties are handled accordingly
1087: C sc = nav().getSuperClass(c);
1088: if (shouldRecurseSuperClass(sc))
1089: collectGetterSetters(sc, getters, setters);
1090:
1091: Collection<? extends M> methods = nav().getDeclaredMethods(c);
1092: Map<String, List<M>> allSetters = new LinkedHashMap<String, List<M>>();
1093: for (M method : methods) {
1094: boolean used = false; // if this method is added to getters or setters
1095:
1096: if (nav().isBridgeMethod(method))
1097: continue; // ignore
1098:
1099: String name = nav().getMethodName(method);
1100: int arity = nav().getMethodParameters(method).length;
1101:
1102: if (nav().isStaticMethod(method)) {
1103: ensureNoAnnotation(method);
1104: continue;
1105: }
1106:
1107: // don't look at XmlTransient on properties. We'll deal with that later.
1108:
1109: // is this a get method?
1110: String propName = getPropertyNameFromGetMethod(name);
1111: if (propName != null && arity == 0) {
1112: getters.put(propName, method);
1113: used = true;
1114: }
1115:
1116: // is this a set method?
1117: propName = getPropertyNameFromSetMethod(name);
1118: if (propName != null && arity == 1) {
1119: List<M> propSetters = allSetters.get(propName);
1120: if (null == propSetters) {
1121: propSetters = new ArrayList<M>();
1122: allSetters.put(propName, propSetters);
1123: }
1124: propSetters.add(method);
1125: used = true; // used check performed later
1126: }
1127:
1128: if (!used)
1129: ensureNoAnnotation(method);
1130: }
1131:
1132: // Match getter with setters by comparing getter return type to setter param
1133: for (Map.Entry<String, M> entry : getters.entrySet()) {
1134: String propName = entry.getKey();
1135: M getter = entry.getValue();
1136: List<M> propSetters = allSetters.remove(propName);
1137: if (null == propSetters) {
1138: //no matching setter
1139: continue;
1140: }
1141: T getterType = nav().getReturnType(getter);
1142: for (M setter : propSetters) {
1143: T setterType = nav().getMethodParameters(setter)[0];
1144: if (setterType.equals(getterType)) {
1145: setters.put(propName, setter);
1146: break;
1147: }
1148: }
1149: }
1150:
1151: // also allow set-only properties
1152: for (Map.Entry<String, List<M>> e : allSetters.entrySet()) {
1153: setters.put(e.getKey(), e.getValue().get(0));
1154: }
1155: }
1156:
1157: /**
1158: * Checks if the properties in this given super class should be aggregated into this class.
1159: */
1160: private boolean shouldRecurseSuperClass(C sc) {
1161: return sc != null
1162: && (builder.isReplaced(sc) || reader()
1163: .hasClassAnnotation(sc, XmlTransient.class));
1164: }
1165:
1166: /**
1167: * Returns true if the method is considered 'public'.
1168: */
1169: private boolean isConsideredPublic(M m) {
1170: return m == null || nav().isPublicMethod(m);
1171: }
1172:
1173: /**
1174: * If the method has an explicit annotation, allow it to participate
1175: * to the processing even if it lacks the setter or the getter.
1176: */
1177: private void resurrect(Map<String, M> methods, Set<String> complete) {
1178: for (Map.Entry<String, M> e : methods.entrySet()) {
1179: if (complete.contains(e.getKey()))
1180: continue;
1181: if (hasJAXBAnnotation(reader().getAllMethodAnnotations(
1182: e.getValue(), this )))
1183: complete.add(e.getKey());
1184: }
1185: }
1186:
1187: /**
1188: * Makes sure that the method doesn't have any annotation, if it does,
1189: * report it as an error
1190: */
1191: private void ensureNoAnnotation(M method) {
1192: Annotation[] annotations = reader().getAllMethodAnnotations(
1193: method, this );
1194: for (Annotation a : annotations) {
1195: if (isJAXBAnnotation(a)) {
1196: builder
1197: .reportError(new IllegalAnnotationException(
1198: Messages.ANNOTATION_ON_WRONG_METHOD
1199: .format(), a));
1200: return;
1201: }
1202: }
1203: }
1204:
1205: /**
1206: * Returns true if a given annotation is a JAXB annotation.
1207: */
1208: private static boolean isJAXBAnnotation(Annotation a) {
1209: return ANNOTATION_NUMBER_MAP.containsKey(a.annotationType());
1210: }
1211:
1212: /**
1213: * Returns true if the array contains a JAXB annotation.
1214: */
1215: private static boolean hasJAXBAnnotation(Annotation[] annotations) {
1216: return getSomeJAXBAnnotation(annotations) != null;
1217: }
1218:
1219: private static Annotation getSomeJAXBAnnotation(
1220: Annotation[] annotations) {
1221: for (Annotation a : annotations)
1222: if (isJAXBAnnotation(a))
1223: return a;
1224: return null;
1225: }
1226:
1227: /**
1228: * Returns "Foo" from "getFoo" or "isFoo".
1229: *
1230: * @return null
1231: * if the method name doesn't look like a getter.
1232: */
1233: private static String getPropertyNameFromGetMethod(String name) {
1234: if (name.startsWith("get") && name.length() > 3)
1235: return name.substring(3);
1236: if (name.startsWith("is") && name.length() > 2)
1237: return name.substring(2);
1238: return null;
1239: }
1240:
1241: /**
1242: * Returns "Foo" from "setFoo".
1243: *
1244: * @return null
1245: * if the method name doesn't look like a setter.
1246: */
1247: private static String getPropertyNameFromSetMethod(String name) {
1248: if (name.startsWith("set") && name.length() > 3)
1249: return name.substring(3);
1250: return null;
1251: }
1252:
1253: /**
1254: * Creates a new {@link FieldPropertySeed} object.
1255: *
1256: * <p>
1257: * Derived class can override this method to create a sub-class.
1258: */
1259: protected PropertySeed<T, C, F, M> createFieldSeed(F f) {
1260: return new FieldPropertySeed<T, C, F, M>(this , f);
1261: }
1262:
1263: /**
1264: * Creates a new {@link GetterSetterPropertySeed} object.
1265: */
1266: protected PropertySeed<T, C, F, M> createAccessorSeed(M getter,
1267: M setter) {
1268: return new GetterSetterPropertySeed<T, C, F, M>(this , getter,
1269: setter);
1270: }
1271:
1272: public final boolean isElement() {
1273: return elementName != null;
1274: }
1275:
1276: public boolean isAbstract() {
1277: return nav().isAbstract(clazz);
1278: }
1279:
1280: public boolean isOrdered() {
1281: return propOrder != null;
1282: }
1283:
1284: public final boolean isFinal() {
1285: return nav().isFinal(clazz);
1286: }
1287:
1288: public final boolean hasSubClasses() {
1289: return hasSubClasses;
1290: }
1291:
1292: public final boolean hasAttributeWildcard() {
1293: return declaresAttributeWildcard()
1294: || inheritsAttributeWildcard();
1295: }
1296:
1297: public final boolean inheritsAttributeWildcard() {
1298: return getInheritedAttributeWildcard() != null;
1299: }
1300:
1301: public final boolean declaresAttributeWildcard() {
1302: return attributeWildcard != null;
1303: }
1304:
1305: /**
1306: * Gets the {@link PropertySeed} object for the inherited attribute wildcard.
1307: */
1308: private PropertySeed<T, C, F, M> getInheritedAttributeWildcard() {
1309: for (ClassInfoImpl<T, C, F, M> c = getBaseClass(); c != null; c = c
1310: .getBaseClass())
1311: if (c.attributeWildcard != null)
1312: return c.attributeWildcard;
1313: return null;
1314: }
1315:
1316: public final QName getElementName() {
1317: return elementName;
1318: }
1319:
1320: public final QName getTypeName() {
1321: return typeName;
1322: }
1323:
1324: public final boolean isSimpleType() {
1325: List<? extends PropertyInfo> props = getProperties();
1326: if (props.size() != 1)
1327: return false;
1328: return props.get(0).kind() == PropertyKind.VALUE;
1329: }
1330:
1331: /**
1332: * Called after all the {@link TypeInfo}s are collected into the {@link #owner}.
1333: */
1334: @Override
1335: /*package*/void link() {
1336: getProperties(); // make sure properties!=null
1337:
1338: // property name collision cehck
1339: Map<String, PropertyInfoImpl> names = new HashMap<String, PropertyInfoImpl>();
1340: for (PropertyInfoImpl<T, C, F, M> p : properties) {
1341: p.link();
1342: PropertyInfoImpl old = names.put(p.getName(), p);
1343: if (old != null) {
1344: builder
1345: .reportError(new IllegalAnnotationException(
1346: Messages.PROPERTY_COLLISION.format(p
1347: .getName()), p, old));
1348: }
1349: }
1350: super .link();
1351: }
1352:
1353: public Location getLocation() {
1354: return nav().getClassLocation(clazz);
1355: }
1356:
1357: /**
1358: * XmlType allows specification of factoryClass and
1359: * factoryMethod. There are to be used if no default
1360: * constructor is found.
1361: *
1362: * @return
1363: * true if the factory method was found. False if not.
1364: */
1365: private boolean hasFactoryConstructor(XmlType t) {
1366: if (t == null)
1367: return false;
1368:
1369: String method = t.factoryMethod();
1370: T fClass = reader().getClassValue(t, "factoryClass");
1371: if (method.length() > 0) {
1372: if (fClass.equals(nav().ref(XmlType.DEFAULT.class))) {
1373: fClass = nav().use(clazz);
1374: }
1375: for (M m : nav().getDeclaredMethods(nav().asDecl(fClass))) {
1376: //- Find the zero-arg public static method with the required return type
1377: if (nav().getMethodName(m).equals(method)
1378: && nav().getReturnType(m).equals(
1379: nav().use(clazz))
1380: && nav().getMethodParameters(m).length == 0
1381: && nav().isStaticMethod(m)) {
1382: factoryMethod = m;
1383: break;
1384: }
1385: }
1386: if (factoryMethod == null) {
1387: builder.reportError(new IllegalAnnotationException(
1388: Messages.NO_FACTORY_METHOD.format(nav()
1389: .getClassName(nav().asDecl(fClass)),
1390: method), this ));
1391: }
1392: } else if (!fClass.equals(nav().ref(XmlType.DEFAULT.class))) {
1393: builder.reportError(new IllegalAnnotationException(
1394: Messages.FACTORY_CLASS_NEEDS_FACTORY_METHOD
1395: .format(nav().getClassName(
1396: nav().asDecl(fClass))), this ));
1397: }
1398: return factoryMethod != null;
1399: }
1400:
1401: public Method getFactoryMethod() {
1402: return (Method) factoryMethod;
1403: }
1404:
1405: public String toString() {
1406: return "ClassInfo(" + clazz + ')';
1407: }
1408:
1409: private static final String[] DEFAULT_ORDER = new String[0];
1410: }
|