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: }