aoqi@0: /*
aoqi@0: * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
aoqi@0: *
aoqi@0: * This code is free software; you can redistribute it and/or modify it
aoqi@0: * under the terms of the GNU General Public License version 2 only, as
aoqi@0: * published by the Free Software Foundation. Oracle designates this
aoqi@0: * particular file as subject to the "Classpath" exception as provided
aoqi@0: * by Oracle in the LICENSE file that accompanied this code.
aoqi@0: *
aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT
aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that
aoqi@0: * accompanied this code).
aoqi@0: *
aoqi@0: * You should have received a copy of the GNU General Public License version
aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation,
aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
aoqi@0: *
aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
aoqi@0: * or visit www.oracle.com if you need additional information or have any
aoqi@0: * questions.
aoqi@0: */
aoqi@0:
aoqi@0: package com.sun.xml.internal.bind.v2.model.impl;
aoqi@0:
aoqi@0: import java.lang.reflect.ParameterizedType;
aoqi@0: import java.lang.reflect.Type;
aoqi@0: import java.util.HashMap;
aoqi@0: import java.util.Map;
aoqi@0: import java.util.logging.Level;
aoqi@0: import java.util.logging.Logger;
aoqi@0:
aoqi@0: import javax.xml.bind.JAXBElement;
aoqi@0: import javax.xml.bind.annotation.XmlAttachmentRef;
aoqi@0: import javax.xml.bind.annotation.XmlRegistry;
aoqi@0: import javax.xml.bind.annotation.XmlSchema;
aoqi@0: import javax.xml.bind.annotation.XmlSeeAlso;
aoqi@0: import javax.xml.bind.annotation.XmlTransient;
aoqi@0: import javax.xml.namespace.QName;
aoqi@0:
aoqi@0: import com.sun.xml.internal.bind.util.Which;
aoqi@0: import com.sun.xml.internal.bind.v2.model.annotation.AnnotationReader;
aoqi@0: import com.sun.xml.internal.bind.v2.model.annotation.ClassLocatable;
aoqi@0: import com.sun.xml.internal.bind.v2.model.annotation.Locatable;
aoqi@0: import com.sun.xml.internal.bind.v2.model.core.ClassInfo;
aoqi@0: import com.sun.xml.internal.bind.v2.model.core.ErrorHandler;
aoqi@0: import com.sun.xml.internal.bind.v2.model.core.LeafInfo;
aoqi@0: import com.sun.xml.internal.bind.v2.model.core.NonElement;
aoqi@0: import com.sun.xml.internal.bind.v2.model.core.PropertyInfo;
aoqi@0: import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
aoqi@0: import com.sun.xml.internal.bind.v2.model.core.Ref;
aoqi@0: import com.sun.xml.internal.bind.v2.model.core.RegistryInfo;
aoqi@0: import com.sun.xml.internal.bind.v2.model.core.TypeInfo;
aoqi@0: import com.sun.xml.internal.bind.v2.model.core.TypeInfoSet;
aoqi@0: import com.sun.xml.internal.bind.v2.model.nav.Navigator;
aoqi@0: import com.sun.xml.internal.bind.v2.model.runtime.RuntimePropertyInfo;
aoqi@0: import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException;
aoqi@0: import com.sun.xml.internal.bind.WhiteSpaceProcessor;
aoqi@0:
aoqi@0: /**
aoqi@0: * Builds a {@link TypeInfoSet} (a set of JAXB properties)
aoqi@0: * by using {@link ElementInfoImpl} and {@link ClassInfoImpl}.
aoqi@0: * from annotated Java classes.
aoqi@0: *
aoqi@0: *
aoqi@0: * This class uses {@link Navigator} and {@link AnnotationReader} to
aoqi@0: * work with arbitrary annotation source and arbitrary Java model.
aoqi@0: * For this purpose this class is parameterized.
aoqi@0: *
aoqi@0: * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
aoqi@0: */
aoqi@0: public class ModelBuilder implements ModelBuilderI {
aoqi@0: private static final Logger logger;
aoqi@0:
aoqi@0: /**
aoqi@0: * {@link TypeInfo}s that are built will go into this set.
aoqi@0: */
aoqi@0: final TypeInfoSetImpl typeInfoSet;
aoqi@0:
aoqi@0: public final AnnotationReader reader;
aoqi@0:
aoqi@0: public final Navigator nav;
aoqi@0:
aoqi@0: /**
aoqi@0: * Used to detect collisions among global type names.
aoqi@0: */
aoqi@0: private final Map typeNames = new HashMap();
aoqi@0:
aoqi@0: /**
aoqi@0: * JAXB doesn't want to use namespaces unless we are told to, but WS-I BP
aoqi@0: * conformace requires JAX-RPC to always use a non-empty namespace URI.
aoqi@0: * (see http://www.ws-i.org/Profiles/BasicProfile-1.0-2004-04-16.html#WSDLTYPES R2105)
aoqi@0: *
aoqi@0: *
aoqi@0: * To work around this issue, we allow the use of the empty namespaces to be
aoqi@0: * replaced by a particular designated namespace URI.
aoqi@0: *
aoqi@0: *
aoqi@0: * This field keeps the value of that replacing namespace URI.
aoqi@0: * When there's no replacement, this field is set to "".
aoqi@0: */
aoqi@0: public final String defaultNsUri;
aoqi@0:
aoqi@0:
aoqi@0: /**
aoqi@0: * Packages whose registries are already added.
aoqi@0: */
aoqi@0: /*package*/ final Map> registries
aoqi@0: = new HashMap>();
aoqi@0:
aoqi@0: private final Map subclassReplacements;
aoqi@0:
aoqi@0: /**
aoqi@0: * @see #setErrorHandler
aoqi@0: */
aoqi@0: private ErrorHandler errorHandler;
aoqi@0: private boolean hadError;
aoqi@0:
aoqi@0: /**
aoqi@0: * Set to true if the model includes {@link XmlAttachmentRef}. JAX-WS
aoqi@0: * needs to know this information.
aoqi@0: */
aoqi@0: public boolean hasSwaRef;
aoqi@0:
aoqi@0: private final ErrorHandler proxyErrorHandler = new ErrorHandler() {
aoqi@0: public void error(IllegalAnnotationException e) {
aoqi@0: reportError(e);
aoqi@0: }
aoqi@0: };
aoqi@0:
aoqi@0: public ModelBuilder(
aoqi@0: AnnotationReader reader,
aoqi@0: Navigator navigator,
aoqi@0: Map subclassReplacements,
aoqi@0: String defaultNamespaceRemap
aoqi@0: ) {
aoqi@0:
aoqi@0: this.reader = reader;
aoqi@0: this.nav = navigator;
aoqi@0: this.subclassReplacements = subclassReplacements;
aoqi@0: if(defaultNamespaceRemap==null)
aoqi@0: defaultNamespaceRemap = "";
aoqi@0: this.defaultNsUri = defaultNamespaceRemap;
aoqi@0: reader.setErrorHandler(proxyErrorHandler);
aoqi@0: typeInfoSet = createTypeInfoSet();
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Makes sure that we are running with 2.1 JAXB API,
aoqi@0: * and report an error if not.
aoqi@0: */
aoqi@0: static {
aoqi@0: try {
aoqi@0: XmlSchema s = null;
aoqi@0: s.location();
aoqi@0: } catch (NullPointerException e) {
aoqi@0: // as epxected
aoqi@0: } catch (NoSuchMethodError e) {
aoqi@0: // this is not a 2.1 API. Where is it being loaded from?
aoqi@0: Messages res;
aoqi@0: if (SecureLoader.getClassClassLoader(XmlSchema.class) == null) {
aoqi@0: res = Messages.INCOMPATIBLE_API_VERSION_MUSTANG;
aoqi@0: } else {
aoqi@0: res = Messages.INCOMPATIBLE_API_VERSION;
aoqi@0: }
aoqi@0:
aoqi@0: throw new LinkageError( res.format(
aoqi@0: Which.which(XmlSchema.class),
aoqi@0: Which.which(ModelBuilder.class)
aoqi@0: ));
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Makes sure that we don't have conflicting 1.0 runtime,
aoqi@0: * and report an error if we do.
aoqi@0: */
aoqi@0: static {
aoqi@0: try {
aoqi@0: WhiteSpaceProcessor.isWhiteSpace("xyz");
aoqi@0: } catch (NoSuchMethodError e) {
aoqi@0: // we seem to be getting 1.0 runtime
aoqi@0: throw new LinkageError( Messages.RUNNING_WITH_1_0_RUNTIME.format(
aoqi@0: Which.which(WhiteSpaceProcessor.class),
aoqi@0: Which.which(ModelBuilder.class)
aoqi@0: ));
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Logger init
aoqi@0: */
aoqi@0: static {
aoqi@0: logger = Logger.getLogger(ModelBuilder.class.getName());
aoqi@0: }
aoqi@0:
aoqi@0: protected TypeInfoSetImpl createTypeInfoSet() {
aoqi@0: return new TypeInfoSetImpl(nav,reader,BuiltinLeafInfoImpl.createLeaves(nav));
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Builds a JAXB {@link ClassInfo} model from a given class declaration
aoqi@0: * and adds that to this model owner.
aoqi@0: *
aoqi@0: *
aoqi@0: * Return type is either {@link ClassInfo} or {@link LeafInfo} (for types like
aoqi@0: * {@link String} or {@link Enum}-derived ones)
aoqi@0: */
aoqi@0: public NonElement getClassInfo( C clazz, Locatable upstream ) {
aoqi@0: return getClassInfo(clazz,false,upstream);
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * For limited cases where the caller needs to search for a super class.
aoqi@0: * This is necessary because we don't want {@link #subclassReplacements}
aoqi@0: * to kick in for the super class search, which will cause infinite recursion.
aoqi@0: */
aoqi@0: public NonElement getClassInfo( C clazz, boolean searchForSuperClass, Locatable upstream ) {
aoqi@0: assert clazz!=null;
aoqi@0: NonElement r = typeInfoSet.getClassInfo(clazz);
aoqi@0: if(r!=null)
aoqi@0: return r;
aoqi@0:
aoqi@0: if(nav.isEnum(clazz)) {
aoqi@0: EnumLeafInfoImpl li = createEnumLeafInfo(clazz,upstream);
aoqi@0: typeInfoSet.add(li);
aoqi@0: r = li;
aoqi@0: addTypeName(r);
aoqi@0: } else {
aoqi@0: boolean isReplaced = subclassReplacements.containsKey(clazz);
aoqi@0: if(isReplaced && !searchForSuperClass) {
aoqi@0: // handle it as if the replacement was specified
aoqi@0: r = getClassInfo(subclassReplacements.get(clazz),upstream);
aoqi@0: } else
aoqi@0: if(reader.hasClassAnnotation(clazz,XmlTransient.class) || isReplaced) {
aoqi@0: // handle it as if the base class was specified
aoqi@0: r = getClassInfo( nav.getSuperClass(clazz), searchForSuperClass,
aoqi@0: new ClassLocatable(upstream,clazz,nav) );
aoqi@0: } else {
aoqi@0: ClassInfoImpl ci = createClassInfo(clazz,upstream);
aoqi@0: typeInfoSet.add(ci);
aoqi@0:
aoqi@0: // compute the closure by eagerly expanding references
aoqi@0: for( PropertyInfo p : ci.getProperties() ) {
aoqi@0: if(p.kind()== PropertyKind.REFERENCE) {
aoqi@0: // make sure that we have a registry for this package
aoqi@0: addToRegistry(clazz, (Locatable) p);
aoqi@0: Class[] prmzdClasses = getParametrizedTypes(p);
aoqi@0: if (prmzdClasses != null) {
aoqi@0: for (Class prmzdClass : prmzdClasses) {
aoqi@0: if (prmzdClass != clazz) {
aoqi@0: addToRegistry((C) prmzdClass, (Locatable) p);
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: for( TypeInfo t : p.ref() )
aoqi@0: ; // just compute a reference should be suffice
aoqi@0: }
aoqi@0: ci.getBaseClass(); // same as above.
aoqi@0:
aoqi@0: r = ci;
aoqi@0: addTypeName(r);
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0:
aoqi@0: // more reference closure expansion. @XmlSeeAlso
aoqi@0: XmlSeeAlso sa = reader.getClassAnnotation(XmlSeeAlso.class, clazz, upstream);
aoqi@0: if(sa!=null) {
aoqi@0: for( T t : reader.getClassArrayValue(sa,"value") ) {
aoqi@0: getTypeInfo(t,(Locatable)sa);
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0:
aoqi@0: return r;
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Adding package's ObjectFactory methods to registry
aoqi@0: * @param clazz which package will be used
aoqi@0: * @param p location
aoqi@0: */
aoqi@0: private void addToRegistry(C clazz, Locatable p) {
aoqi@0: String pkg = nav.getPackageName(clazz);
aoqi@0: if (!registries.containsKey(pkg)) {
aoqi@0: // insert the package's object factory
aoqi@0: C c = nav.loadObjectFactory(clazz, pkg);
aoqi@0: if (c != null)
aoqi@0: addRegistry(c, p);
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Getting parametrized classes of {@code JAXBElement<...>} property
aoqi@0: * @param p property which parametrized types we will try to get
aoqi@0: * @return null - if it's not JAXBElement property, or it's not parametrized, and array of parametrized classes in other case
aoqi@0: */
aoqi@0: private Class[] getParametrizedTypes(PropertyInfo p) {
aoqi@0: try {
aoqi@0: Type pType = ((RuntimePropertyInfo) p).getIndividualType();
aoqi@0: if (pType instanceof ParameterizedType) {
aoqi@0: ParameterizedType prmzdType = (ParameterizedType) pType;
aoqi@0: if (prmzdType.getRawType() == JAXBElement.class) {
aoqi@0: Type[] actualTypes = prmzdType.getActualTypeArguments();
aoqi@0: Class[] result = new Class[actualTypes.length];
aoqi@0: for (int i = 0; i < actualTypes.length; i++) {
aoqi@0: result[i] = (Class) actualTypes[i];
aoqi@0: }
aoqi@0: return result;
aoqi@0: }
aoqi@0: }
aoqi@0: } catch (Exception e) {
aoqi@0: logger.log(Level.FINE, "Error in ModelBuilder.getParametrizedTypes. " + e.getMessage());
aoqi@0: }
aoqi@0: return null;
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Checks the uniqueness of the type name.
aoqi@0: */
aoqi@0: private void addTypeName(NonElement r) {
aoqi@0: QName t = r.getTypeName();
aoqi@0: if(t==null) return;
aoqi@0:
aoqi@0: TypeInfo old = typeNames.put(t,r);
aoqi@0: if(old!=null) {
aoqi@0: // collision
aoqi@0: reportError(new IllegalAnnotationException(
aoqi@0: Messages.CONFLICTING_XML_TYPE_MAPPING.format(r.getTypeName()),
aoqi@0: old, r ));
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Have the builder recognize the type (if it hasn't done so yet),
aoqi@0: * and returns a {@link NonElement} that represents it.
aoqi@0: *
aoqi@0: * @return
aoqi@0: * always non-null.
aoqi@0: */
aoqi@0: public NonElement getTypeInfo(T t,Locatable upstream) {
aoqi@0: NonElement r = typeInfoSet.getTypeInfo(t);
aoqi@0: if(r!=null) return r;
aoqi@0:
aoqi@0: if(nav.isArray(t)) { // no need for checking byte[], because above typeInfoset.getTypeInfo() would return non-null
aoqi@0: ArrayInfoImpl ai =
aoqi@0: createArrayInfo(upstream, t);
aoqi@0: addTypeName(ai);
aoqi@0: typeInfoSet.add(ai);
aoqi@0: return ai;
aoqi@0: }
aoqi@0:
aoqi@0: C c = nav.asDecl(t);
aoqi@0: assert c!=null : t.toString()+" must be a leaf, but we failed to recognize it.";
aoqi@0: return getClassInfo(c,upstream);
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * This method is used to add a root reference to a model.
aoqi@0: */
aoqi@0: public NonElement getTypeInfo(Ref ref) {
aoqi@0: // TODO: handle XmlValueList
aoqi@0: assert !ref.valueList;
aoqi@0: C c = nav.asDecl(ref.type);
aoqi@0: if(c!=null && reader.getClassAnnotation(XmlRegistry.class,c,null/*TODO: is this right?*/)!=null) {
aoqi@0: if(!registries.containsKey(nav.getPackageName(c)))
aoqi@0: addRegistry(c,null);
aoqi@0: return null; // TODO: is this correct?
aoqi@0: } else
aoqi@0: return getTypeInfo(ref.type,null);
aoqi@0: }
aoqi@0:
aoqi@0:
aoqi@0: protected EnumLeafInfoImpl createEnumLeafInfo(C clazz,Locatable upstream) {
aoqi@0: return new EnumLeafInfoImpl(this,upstream,clazz,nav.use(clazz));
aoqi@0: }
aoqi@0:
aoqi@0: protected ClassInfoImpl createClassInfo(C clazz, Locatable upstream ) {
aoqi@0: return new ClassInfoImpl(this,upstream,clazz);
aoqi@0: }
aoqi@0:
aoqi@0: protected ElementInfoImpl createElementInfo(
aoqi@0: RegistryInfoImpl registryInfo, M m) throws IllegalAnnotationException {
aoqi@0: return new ElementInfoImpl(this,registryInfo,m);
aoqi@0: }
aoqi@0:
aoqi@0: protected ArrayInfoImpl createArrayInfo(Locatable upstream, T arrayType) {
aoqi@0: return new ArrayInfoImpl(this,upstream,arrayType);
aoqi@0: }
aoqi@0:
aoqi@0:
aoqi@0: /**
aoqi@0: * Visits a class with {@link XmlRegistry} and records all the element mappings
aoqi@0: * in it.
aoqi@0: */
aoqi@0: public RegistryInfo addRegistry(C registryClass, Locatable upstream ) {
aoqi@0: return new RegistryInfoImpl(this,upstream,registryClass);
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Gets a {@link RegistryInfo} for the given package.
aoqi@0: *
aoqi@0: * @return
aoqi@0: * null if no registry exists for the package.
aoqi@0: * unlike other getXXX methods on this class,
aoqi@0: * this method is side-effect free.
aoqi@0: */
aoqi@0: public RegistryInfo getRegistry(String packageName) {
aoqi@0: return registries.get(packageName);
aoqi@0: }
aoqi@0:
aoqi@0: private boolean linked;
aoqi@0:
aoqi@0: /**
aoqi@0: * Called after all the classes are added to the type set
aoqi@0: * to "link" them together.
aoqi@0: *
aoqi@0: *
aoqi@0: * Don't expose implementation classes in the signature.
aoqi@0: *
aoqi@0: * @return
aoqi@0: * fully built {@link TypeInfoSet} that represents the model,
aoqi@0: * or null if there was an error.
aoqi@0: */
aoqi@0: public TypeInfoSet link() {
aoqi@0:
aoqi@0: assert !linked;
aoqi@0: linked = true;
aoqi@0:
aoqi@0: for( ElementInfoImpl ei : typeInfoSet.getAllElements() )
aoqi@0: ei.link();
aoqi@0:
aoqi@0: for( ClassInfoImpl ci : typeInfoSet.beans().values() )
aoqi@0: ci.link();
aoqi@0:
aoqi@0: for( EnumLeafInfoImpl li : typeInfoSet.enums().values() )
aoqi@0: li.link();
aoqi@0:
aoqi@0: if(hadError)
aoqi@0: return null;
aoqi@0: else
aoqi@0: return typeInfoSet;
aoqi@0: }
aoqi@0:
aoqi@0: //
aoqi@0: //
aoqi@0: // error handling
aoqi@0: //
aoqi@0: //
aoqi@0:
aoqi@0: /**
aoqi@0: * Sets the error handler that receives errors discovered during the model building.
aoqi@0: *
aoqi@0: * @param errorHandler
aoqi@0: * can be null.
aoqi@0: */
aoqi@0: public void setErrorHandler(ErrorHandler errorHandler) {
aoqi@0: this.errorHandler = errorHandler;
aoqi@0: }
aoqi@0:
aoqi@0: public final void reportError(IllegalAnnotationException e) {
aoqi@0: hadError = true;
aoqi@0: if(errorHandler!=null)
aoqi@0: errorHandler.error(e);
aoqi@0: }
aoqi@0:
aoqi@0: public boolean isReplaced(C sc) {
aoqi@0: return subclassReplacements.containsKey(sc);
aoqi@0: }
aoqi@0:
aoqi@0: @Override
aoqi@0: public Navigator getNavigator() {
aoqi@0: return nav;
aoqi@0: }
aoqi@0:
aoqi@0: @Override
aoqi@0: public AnnotationReader getReader() {
aoqi@0: return reader;
aoqi@0: }
aoqi@0: }