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.util.Collections; aoqi@0: import java.util.LinkedHashSet; aoqi@0: import java.util.Set; aoqi@0: aoqi@0: import javax.xml.bind.JAXBElement; aoqi@0: import javax.xml.bind.annotation.XmlAnyElement; aoqi@0: import javax.xml.bind.annotation.XmlElementRef; aoqi@0: import javax.xml.bind.annotation.XmlElementRefs; aoqi@0: import javax.xml.bind.annotation.XmlMixed; aoqi@0: import javax.xml.bind.annotation.XmlSchema; aoqi@0: import javax.xml.bind.annotation.XmlNsForm; aoqi@0: import javax.xml.namespace.QName; aoqi@0: aoqi@0: import com.sun.xml.internal.bind.v2.model.annotation.AnnotationReader; aoqi@0: import com.sun.xml.internal.bind.v2.model.core.ClassInfo; aoqi@0: import com.sun.xml.internal.bind.v2.model.core.Element; aoqi@0: import com.sun.xml.internal.bind.v2.model.core.ElementInfo; aoqi@0: import com.sun.xml.internal.bind.v2.model.core.NonElement; aoqi@0: import com.sun.xml.internal.bind.v2.model.core.PropertyKind; aoqi@0: import com.sun.xml.internal.bind.v2.model.core.ReferencePropertyInfo; aoqi@0: import com.sun.xml.internal.bind.v2.model.core.WildcardMode; aoqi@0: import com.sun.xml.internal.bind.v2.model.nav.Navigator; aoqi@0: import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException; aoqi@0: import java.util.Iterator; aoqi@0: aoqi@0: /** aoqi@0: * Implementation of {@link ReferencePropertyInfo}. aoqi@0: * aoqi@0: * @author Kohsuke Kawaguchi aoqi@0: */ aoqi@0: class ReferencePropertyInfoImpl aoqi@0: extends ERPropertyInfoImpl aoqi@0: implements ReferencePropertyInfo, DummyPropertyInfo aoqi@0: { aoqi@0: /** aoqi@0: * Lazily computed. aoqi@0: * @see #getElements() aoqi@0: */ aoqi@0: private Set> types; aoqi@0: private Set> subTypes = new LinkedHashSet>(); aoqi@0: aoqi@0: private final boolean isMixed; aoqi@0: aoqi@0: private final WildcardMode wildcard; aoqi@0: private final C domHandler; aoqi@0: /** aoqi@0: * Lazily computed. aoqi@0: * @see #isRequired() aoqi@0: */ aoqi@0: private Boolean isRequired; aoqi@0: aoqi@0: public ReferencePropertyInfoImpl( aoqi@0: ClassInfoImpl classInfo, aoqi@0: PropertySeed seed) { aoqi@0: aoqi@0: super(classInfo, seed); aoqi@0: aoqi@0: isMixed = seed.readAnnotation(XmlMixed.class) != null; aoqi@0: aoqi@0: XmlAnyElement xae = seed.readAnnotation(XmlAnyElement.class); aoqi@0: if(xae==null) { aoqi@0: wildcard = null; aoqi@0: domHandler = null; aoqi@0: } else { aoqi@0: wildcard = xae.lax()?WildcardMode.LAX:WildcardMode.SKIP; aoqi@0: domHandler = nav().asDecl(reader().getClassValue(xae,"value")); aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: public Set> ref() { aoqi@0: return getElements(); aoqi@0: } aoqi@0: aoqi@0: public PropertyKind kind() { aoqi@0: return PropertyKind.REFERENCE; aoqi@0: } aoqi@0: aoqi@0: public Set> getElements() { aoqi@0: if(types==null) aoqi@0: calcTypes(false); aoqi@0: assert types!=null; aoqi@0: return types; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * Compute {@link #types}. aoqi@0: * aoqi@0: * @param last aoqi@0: * if true, every {@link XmlElementRef} must yield at least one type. aoqi@0: */ aoqi@0: private void calcTypes(boolean last) { aoqi@0: XmlElementRef[] ann; aoqi@0: types = new LinkedHashSet>(); aoqi@0: XmlElementRefs refs = seed.readAnnotation(XmlElementRefs.class); aoqi@0: XmlElementRef ref = seed.readAnnotation(XmlElementRef.class); aoqi@0: aoqi@0: if(refs!=null && ref!=null) { aoqi@0: parent.builder.reportError(new IllegalAnnotationException( aoqi@0: Messages.MUTUALLY_EXCLUSIVE_ANNOTATIONS.format( aoqi@0: nav().getClassName(parent.getClazz())+'#'+seed.getName(), aoqi@0: ref.annotationType().getName(), refs.annotationType().getName()), aoqi@0: ref, refs )); aoqi@0: } aoqi@0: aoqi@0: if(refs!=null) aoqi@0: ann = refs.value(); aoqi@0: else { aoqi@0: if(ref!=null) aoqi@0: ann = new XmlElementRef[]{ref}; aoqi@0: else aoqi@0: ann = null; aoqi@0: } aoqi@0: aoqi@0: isRequired = !isCollection(); // this is by default, to remain compatible with 2.1 aoqi@0: aoqi@0: if(ann!=null) { aoqi@0: Navigator nav = nav(); aoqi@0: AnnotationReader reader = reader(); aoqi@0: aoqi@0: final T defaultType = nav.ref(XmlElementRef.DEFAULT.class); aoqi@0: final C je = nav.asDecl(JAXBElement.class); aoqi@0: aoqi@0: for( XmlElementRef r : ann ) { aoqi@0: boolean yield; aoqi@0: T type = reader.getClassValue(r,"type"); aoqi@0: if(nav().isSameType(type, defaultType)) aoqi@0: type = nav.erasure(getIndividualType()); aoqi@0: if(nav.getBaseClass(type,je)!=null) aoqi@0: yield = addGenericElement(r); aoqi@0: else aoqi@0: yield = addAllSubtypes(type); aoqi@0: aoqi@0: // essentially "isRequired &= isRequired(r)" except that we'd like to skip evaluating isRequird(r) aoqi@0: // if the value is already false. aoqi@0: if(isRequired && !isRequired(r)) aoqi@0: isRequired = false; aoqi@0: aoqi@0: if(last && !yield) { aoqi@0: // a reference didn't produce any type. aoqi@0: // diagnose the problem aoqi@0: if(nav().isSameType(type, nav.ref(JAXBElement.class))) { aoqi@0: // no XmlElementDecl aoqi@0: parent.builder.reportError(new IllegalAnnotationException( aoqi@0: Messages.NO_XML_ELEMENT_DECL.format( aoqi@0: getEffectiveNamespaceFor(r), r.name()), aoqi@0: this aoqi@0: )); aoqi@0: } else { aoqi@0: parent.builder.reportError(new IllegalAnnotationException( aoqi@0: Messages.INVALID_XML_ELEMENT_REF.format(type),this)); aoqi@0: } aoqi@0: aoqi@0: // reporting one error would do. aoqi@0: // often the element ref field is using @XmlElementRefs aoqi@0: // to point to multiple JAXBElements. aoqi@0: // reporting one error for each @XmlElemetnRef is thus often redundant. aoqi@0: return; aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: for (ReferencePropertyInfoImpl info : subTypes) { aoqi@0: PropertySeed sd = info.seed; aoqi@0: refs = sd.readAnnotation(XmlElementRefs.class); aoqi@0: ref = sd.readAnnotation(XmlElementRef.class); aoqi@0: aoqi@0: if (refs != null && ref != null) { aoqi@0: parent.builder.reportError(new IllegalAnnotationException( aoqi@0: Messages.MUTUALLY_EXCLUSIVE_ANNOTATIONS.format( aoqi@0: nav().getClassName(parent.getClazz())+'#'+seed.getName(), aoqi@0: ref.annotationType().getName(), refs.annotationType().getName()), aoqi@0: ref, refs )); aoqi@0: } aoqi@0: aoqi@0: if (refs != null) { aoqi@0: ann = refs.value(); aoqi@0: } else { aoqi@0: if (ref != null) { aoqi@0: ann = new XmlElementRef[]{ref}; aoqi@0: } else { aoqi@0: ann = null; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: if (ann != null) { aoqi@0: Navigator nav = nav(); aoqi@0: AnnotationReader reader = reader(); aoqi@0: aoqi@0: final T defaultType = nav.ref(XmlElementRef.DEFAULT.class); aoqi@0: final C je = nav.asDecl(JAXBElement.class); aoqi@0: aoqi@0: for( XmlElementRef r : ann ) { aoqi@0: boolean yield; aoqi@0: T type = reader.getClassValue(r,"type"); aoqi@0: if (nav().isSameType(type, defaultType)) { aoqi@0: type = nav.erasure(getIndividualType()); aoqi@0: } aoqi@0: if (nav.getBaseClass(type,je) != null) { aoqi@0: yield = addGenericElement(r, info); aoqi@0: aoqi@0: } else { aoqi@0: yield = addAllSubtypes(type); aoqi@0: } aoqi@0: aoqi@0: if(last && !yield) { aoqi@0: // a reference didn't produce any type. aoqi@0: // diagnose the problem aoqi@0: if(nav().isSameType(type, nav.ref(JAXBElement.class))) { aoqi@0: // no XmlElementDecl aoqi@0: parent.builder.reportError(new IllegalAnnotationException( aoqi@0: Messages.NO_XML_ELEMENT_DECL.format( aoqi@0: getEffectiveNamespaceFor(r), r.name()), aoqi@0: this aoqi@0: )); aoqi@0: } else { aoqi@0: parent.builder.reportError(new IllegalAnnotationException( aoqi@0: Messages.INVALID_XML_ELEMENT_REF.format(),this)); aoqi@0: } aoqi@0: aoqi@0: // reporting one error would do. aoqi@0: // often the element ref field is using @XmlElementRefs aoqi@0: // to point to multiple JAXBElements. aoqi@0: // reporting one error for each @XmlElemetnRef is thus often redundant. aoqi@0: return; aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: types = Collections.unmodifiableSet(types); aoqi@0: } aoqi@0: aoqi@0: public boolean isRequired() { aoqi@0: if(isRequired==null) aoqi@0: calcTypes(false); aoqi@0: return isRequired; aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * If we find out that we are working with 2.1 API, remember the fact so that aoqi@0: * we don't waste time generating exceptions every time we call {@link #isRequired(XmlElementRef)}. aoqi@0: */ aoqi@0: private static boolean is2_2 = true; aoqi@0: aoqi@0: /** aoqi@0: * Reads the value of {@code XmlElementRef.required()}. aoqi@0: * aoqi@0: * If we are working as 2.1 RI, this defaults to true. aoqi@0: */ aoqi@0: private boolean isRequired(XmlElementRef ref) { aoqi@0: if(!is2_2) return true; aoqi@0: aoqi@0: try { aoqi@0: return ref.required(); aoqi@0: } catch(LinkageError e) { aoqi@0: is2_2 = false; aoqi@0: return true; // the value defaults to true aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: /** aoqi@0: * @return aoqi@0: * true if the reference yields at least one type aoqi@0: */ aoqi@0: private boolean addGenericElement(XmlElementRef r) { aoqi@0: String nsUri = getEffectiveNamespaceFor(r); aoqi@0: // TODO: check spec. defaulting of localName. aoqi@0: return addGenericElement(parent.owner.getElementInfo(parent.getClazz(),new QName(nsUri,r.name()))); aoqi@0: } aoqi@0: aoqi@0: private boolean addGenericElement(XmlElementRef r, ReferencePropertyInfoImpl info) { aoqi@0: String nsUri = info.getEffectiveNamespaceFor(r); aoqi@0: ElementInfo ei = parent.owner.getElementInfo(info.parent.getClazz(), new QName(nsUri, r.name())); aoqi@0: types.add(ei); aoqi@0: return true; aoqi@0: } aoqi@0: aoqi@0: private String getEffectiveNamespaceFor(XmlElementRef r) { aoqi@0: String nsUri = r.namespace(); aoqi@0: aoqi@0: XmlSchema xs = reader().getPackageAnnotation( XmlSchema.class, parent.getClazz(), this ); aoqi@0: if(xs!=null && xs.attributeFormDefault()== XmlNsForm.QUALIFIED) { aoqi@0: // JAX-RPC doesn't want the default namespace URI swapping to take effect to aoqi@0: // local "unqualified" elements. UGLY. aoqi@0: if(nsUri.length()==0) aoqi@0: nsUri = parent.builder.defaultNsUri; aoqi@0: } aoqi@0: aoqi@0: return nsUri; aoqi@0: } aoqi@0: aoqi@0: private boolean addGenericElement(ElementInfo ei) { aoqi@0: if(ei==null) aoqi@0: return false; aoqi@0: types.add(ei); aoqi@0: for( ElementInfo subst : ei.getSubstitutionMembers() ) aoqi@0: addGenericElement(subst); aoqi@0: return true; aoqi@0: } aoqi@0: aoqi@0: private boolean addAllSubtypes(T type) { aoqi@0: Navigator nav = nav(); aoqi@0: aoqi@0: // this allows the explicitly referenced type to be sucked in to the model aoqi@0: NonElement t = parent.builder.getClassInfo(nav.asDecl(type),this); aoqi@0: if(!(t instanceof ClassInfo)) aoqi@0: // this is leaf. aoqi@0: return false; aoqi@0: aoqi@0: boolean result = false; aoqi@0: aoqi@0: ClassInfo c = (ClassInfo) t; aoqi@0: if(c.isElement()) { aoqi@0: types.add(c.asElement()); aoqi@0: result = true; aoqi@0: } aoqi@0: aoqi@0: // look for other possible types aoqi@0: for( ClassInfo ci : parent.owner.beans().values() ) { aoqi@0: if(ci.isElement() && nav.isSubClassOf(ci.getType(),type)) { aoqi@0: types.add(ci.asElement()); aoqi@0: result = true; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: // don't allow local elements to substitute. aoqi@0: for( ElementInfo ei : parent.owner.getElementMappings(null).values()) { aoqi@0: if(nav.isSubClassOf(ei.getType(),type)) { aoqi@0: types.add(ei); aoqi@0: result = true; aoqi@0: } aoqi@0: } aoqi@0: aoqi@0: return result; aoqi@0: } aoqi@0: aoqi@0: aoqi@0: @Override aoqi@0: protected void link() { aoqi@0: super.link(); aoqi@0: aoqi@0: // until we get the whole thing into TypeInfoSet, aoqi@0: // we never really know what are all the possible types that can be assigned on this field. aoqi@0: // so recompute this value when we have all the information. aoqi@0: calcTypes(true); aoqi@0: aoqi@0: } aoqi@0: aoqi@0: public final void addType(PropertyInfoImpl info) { aoqi@0: //noinspection unchecked aoqi@0: subTypes.add((ReferencePropertyInfoImpl)info); aoqi@0: } aoqi@0: aoqi@0: public final boolean isMixed() { aoqi@0: return isMixed; aoqi@0: } aoqi@0: aoqi@0: public final WildcardMode getWildcard() { aoqi@0: return wildcard; aoqi@0: } aoqi@0: aoqi@0: public final C getDOMHandler() { aoqi@0: return domHandler; aoqi@0: } aoqi@0: }