blob: 93c2c25290871cf0bc6e8312d83f72e66214cf75 [file] [log] [blame]
/*
* 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);
}
}