/* * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.xml.internal.bind.v2.model.impl; import java.util.Collection; import java.lang.annotation.Annotation; import javax.activation.MimeType; import javax.xml.bind.annotation.XmlAttachmentRef; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlID; import javax.xml.bind.annotation.XmlIDREF; import javax.xml.bind.annotation.XmlInlineBinaryData; import javax.xml.bind.annotation.XmlMimeType; import javax.xml.bind.annotation.XmlSchema; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters; import javax.xml.bind.annotation.adapters.XmlAdapter; import javax.xml.namespace.QName; import com.sun.xml.internal.bind.v2.TODO; import com.sun.xml.internal.bind.v2.model.annotation.AnnotationReader; import com.sun.xml.internal.bind.v2.model.annotation.Locatable; import com.sun.xml.internal.bind.v2.model.core.Adapter; import com.sun.xml.internal.bind.v2.model.core.ID; import com.sun.xml.internal.bind.v2.model.core.PropertyInfo; import com.sun.xml.internal.bind.v2.model.core.TypeInfo; import com.sun.xml.internal.bind.v2.model.core.TypeInfoSet; import com.sun.xml.internal.bind.v2.model.nav.Navigator; import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException; import com.sun.xml.internal.bind.v2.runtime.Location; import com.sun.xml.internal.bind.v2.runtime.SwaRefAdapter; /** * Default partial implementation for {@link PropertyInfo}. * * @author Kohsuke Kawaguchi */ abstract class PropertyInfoImpl<T,C,F,M> implements PropertyInfo<T,C>, Locatable, Comparable<PropertyInfoImpl> /*by their names*/ { /** * Object that reads annotations. */ protected final PropertySeed<T,C,F,M> seed; private final boolean isCollection; private final ID id; private final MimeType expectedMimeType; private final boolean inlineBinary; private final QName schemaType; protected final ClassInfoImpl<T,C,F,M> parent; private final Adapter<T,C> adapter; protected PropertyInfoImpl(ClassInfoImpl<T,C,F,M> parent, PropertySeed<T,C,F,M> spi) { this.seed = spi; this.parent = parent; if(parent==null) /* Various people reported a bug where this parameter is somehow null. In an attempt to catch the error better, let's do an explicit check here. http://forums.java.net/jive/thread.jspa?threadID=18479 http://forums.java.net/jive/thread.jspa?messageID=165946 */ throw new AssertionError(); MimeType mt = Util.calcExpectedMediaType(seed,parent.builder); if(mt!=null && !kind().canHaveXmlMimeType) { parent.builder.reportError(new IllegalAnnotationException( Messages.ILLEGAL_ANNOTATION.format(XmlMimeType.class.getName()), seed.readAnnotation(XmlMimeType.class) )); mt = null; } this.expectedMimeType = mt; this.inlineBinary = seed.hasAnnotation(XmlInlineBinaryData.class); this.schemaType = Util.calcSchemaType(reader(),seed,parent.clazz, getIndividualType(),this); T t = seed.getRawType(); // check if there's an adapter applicable to the whole property XmlJavaTypeAdapter xjta = getApplicableAdapter(t); if(xjta!=null) { isCollection = false; adapter = new Adapter<T,C>(xjta,reader(),nav()); } else { // check if the adapter is applicable to the individual item in the property this.isCollection = nav().isSubClassOf(t, nav().ref(Collection.class)) || nav().isArrayButNotByteArray(t); xjta = getApplicableAdapter(getIndividualType()); if(xjta==null) { // ugly ugly hack, but we implement swaRef as adapter XmlAttachmentRef xsa = seed.readAnnotation(XmlAttachmentRef.class); if(xsa!=null) { parent.builder.hasSwaRef = true; adapter = new Adapter<T,C>(nav().asDecl(SwaRefAdapter.class),nav()); } else { adapter = null; // if this field has adapter annotation but not applicable, // that must be an error of the user xjta = seed.readAnnotation(XmlJavaTypeAdapter.class); if(xjta!=null) { T adapter = reader().getClassValue(xjta,"value"); parent.builder.reportError(new IllegalAnnotationException( Messages.UNMATCHABLE_ADAPTER.format( nav().getTypeName(adapter), nav().getTypeName(t)), xjta )); } } } else { adapter = new Adapter<T,C>(xjta,reader(),nav()); } } this.id = calcId(); } public ClassInfoImpl<T,C,F,M> parent() { return parent; } protected final Navigator<T,C,F,M> nav() { return parent.nav(); } protected final AnnotationReader<T,C,F,M> reader() { return parent.reader(); } public T getRawType() { return seed.getRawType(); } public T getIndividualType() { if(adapter!=null) return adapter.defaultType; T raw = getRawType(); if(!isCollection()) { return raw; } else { if(nav().isArrayButNotByteArray(raw)) return nav().getComponentType(raw); T bt = nav().getBaseClass(raw, nav().asDecl(Collection.class) ); if(nav().isParameterizedType(bt)) return nav().getTypeArgument(bt,0); else return nav().ref(Object.class); } } public final String getName() { return seed.getName(); } /** * Checks if the given adapter is applicable to the declared property type. */ private boolean isApplicable(XmlJavaTypeAdapter jta, T declaredType ) { if(jta==null) return false; T type = reader().getClassValue(jta,"type"); if(declaredType.equals(type)) return true; // for types explicitly marked in XmlJavaTypeAdapter.type() T adapter = reader().getClassValue(jta,"value"); T ba = nav().getBaseClass(adapter, nav().asDecl(XmlAdapter.class)); if(!nav().isParameterizedType(ba)) return true; // can't check type applicability. assume Object, which means applicable to any. T inMemType = nav().getTypeArgument(ba, 1); return nav().isSubClassOf(declaredType,inMemType); } private XmlJavaTypeAdapter getApplicableAdapter(T type) { XmlJavaTypeAdapter jta = seed.readAnnotation(XmlJavaTypeAdapter.class); if(jta!=null && isApplicable(jta,type)) return jta; // check the applicable adapters on the package XmlJavaTypeAdapters jtas = reader().getPackageAnnotation(XmlJavaTypeAdapters.class, parent.clazz, seed ); if(jtas!=null) { for (XmlJavaTypeAdapter xjta : jtas.value()) { if(isApplicable(xjta,type)) return xjta; } } jta = reader().getPackageAnnotation(XmlJavaTypeAdapter.class, parent.clazz, seed ); if(isApplicable(jta,type)) return jta; // then on the target class C refType = nav().asDecl(type); if(refType!=null) { jta = reader().getClassAnnotation(XmlJavaTypeAdapter.class, refType, seed ); if(jta!=null && isApplicable(jta,type)) // the one on the type always apply. return jta; } return null; } /** * This is the default implementation of the getAdapter method * defined on many of the {@link PropertyInfo}-derived classes. */ public Adapter<T,C> getAdapter() { return adapter; } public final String displayName() { return nav().getClassName(parent.getClazz())+'#'+getName(); } public final ID id() { return id; } private ID calcId() { if(seed.hasAnnotation(XmlID.class)) { // check the type if(!getIndividualType().equals(nav().ref(String.class))) parent.builder.reportError(new IllegalAnnotationException( Messages.ID_MUST_BE_STRING.format(getName()), seed ) ); return ID.ID; } else if(seed.hasAnnotation(XmlIDREF.class)) { return ID.IDREF; } else { return ID.NONE; } } public final MimeType getExpectedMimeType() { return expectedMimeType; } public final boolean inlineBinaryData() { return inlineBinary; } public final QName getSchemaType() { return schemaType; } public final boolean isCollection() { return isCollection; } /** * Called after all the {@link TypeInfo}s are collected into the governing {@link TypeInfoSet}. * * Derived class can do additional actions to complete the model. */ protected void link() { if(id==ID.IDREF) { // make sure that the refereced type has ID for (TypeInfo<T,C> ti : ref()) { if(!ti.canBeReferencedByIDREF()) parent.builder.reportError(new IllegalAnnotationException( Messages.INVALID_IDREF.format( parent.builder.nav.getTypeName(ti.getType())), this )); } } } /** * A {@link PropertyInfoImpl} is always referenced by its enclosing class, * so return that as the upstream. */ public Locatable getUpstream() { return parent; } public Location getLocation() { return seed.getLocation(); } // // // convenience methods for derived classes // // /** * Computes the tag name from a {@link XmlElement} by taking the defaulting into account. */ protected final QName calcXmlName(XmlElement e) { if(e!=null) return calcXmlName(e.namespace(),e.name()); else return calcXmlName("##default","##default"); } /** * Computes the tag name from a {@link XmlElementWrapper} by taking the defaulting into account. */ protected final QName calcXmlName(XmlElementWrapper e) { if(e!=null) return calcXmlName(e.namespace(),e.name()); else return calcXmlName("##default","##default"); } private QName calcXmlName(String uri,String local) { // compute the default TODO.checkSpec(); if(local.length()==0 || local.equals("##default")) local = seed.getName(); if(uri.equals("##default")) { XmlSchema xs = reader().getPackageAnnotation( XmlSchema.class, parent.getClazz(), this ); // JAX-RPC doesn't want the default namespace URI swapping to take effect to // local "unqualified" elements. UGLY. if(xs!=null) { switch(xs.elementFormDefault()) { case QUALIFIED: QName typeName = parent.getTypeName(); if(typeName!=null) uri = typeName.getNamespaceURI(); else uri = xs.namespace(); if(uri.length()==0) uri = parent.builder.defaultNsUri; break; case UNQUALIFIED: case UNSET: uri = ""; } } else { uri = ""; } } return new QName(uri.intern(),local.intern()); } public int compareTo(PropertyInfoImpl that) { return this.getName().compareTo(that.getName()); } public final <A extends Annotation> A readAnnotation(Class<A> annotationType) { return seed.readAnnotation(annotationType); } public final boolean hasAnnotation(Class<? extends Annotation> annotationType) { return seed.hasAnnotation(annotationType); } }