1 /*
   2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.bind.v2.model.impl;
  27 
  28 import java.util.Collections;
  29 import java.util.LinkedHashSet;
  30 import java.util.Set;
  31 
  32 import javax.xml.bind.JAXBElement;
  33 import javax.xml.bind.annotation.XmlAnyElement;
  34 import javax.xml.bind.annotation.XmlElementRef;
  35 import javax.xml.bind.annotation.XmlElementRefs;
  36 import javax.xml.bind.annotation.XmlMixed;
  37 import javax.xml.bind.annotation.XmlSchema;
  38 import javax.xml.bind.annotation.XmlNsForm;
  39 import javax.xml.namespace.QName;
  40 
  41 import com.sun.xml.internal.bind.v2.model.annotation.AnnotationReader;
  42 import com.sun.xml.internal.bind.v2.model.core.ClassInfo;
  43 import com.sun.xml.internal.bind.v2.model.core.Element;
  44 import com.sun.xml.internal.bind.v2.model.core.ElementInfo;
  45 import com.sun.xml.internal.bind.v2.model.core.NonElement;
  46 import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
  47 import com.sun.xml.internal.bind.v2.model.core.ReferencePropertyInfo;
  48 import com.sun.xml.internal.bind.v2.model.core.WildcardMode;
  49 import com.sun.xml.internal.bind.v2.model.nav.Navigator;
  50 import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException;
  51 import java.util.Iterator;
  52 
  53 /**
  54  * Implementation of {@link ReferencePropertyInfo}.
  55  *
  56  * @author Kohsuke Kawaguchi
  57  */
  58 class ReferencePropertyInfoImpl<T,C,F,M>
  59     extends ERPropertyInfoImpl<T,C,F,M>
  60     implements ReferencePropertyInfo<T,C>, DummyPropertyInfo<T, C, F, M>
  61 {
  62     /**
  63      * Lazily computed.
  64      * @see #getElements()
  65      */
  66     private Set<Element<T,C>> types;
  67     private Set<ReferencePropertyInfoImpl<T,C,F,M>> subTypes = new LinkedHashSet<ReferencePropertyInfoImpl<T,C,F,M>>();
  68 
  69     private final boolean isMixed;
  70 
  71     private final WildcardMode wildcard;
  72     private final C domHandler;
  73     /**
  74      * Lazily computed.
  75      * @see #isRequired()
  76      */
  77     private Boolean isRequired;
  78 
  79     public ReferencePropertyInfoImpl(
  80         ClassInfoImpl<T,C,F,M> classInfo,
  81         PropertySeed<T,C,F,M> seed) {
  82 
  83         super(classInfo, seed);
  84 
  85         isMixed = seed.readAnnotation(XmlMixed.class) != null;
  86 
  87         XmlAnyElement xae = seed.readAnnotation(XmlAnyElement.class);
  88         if(xae==null) {
  89             wildcard = null;
  90             domHandler = null;
  91         } else {
  92             wildcard = xae.lax()?WildcardMode.LAX:WildcardMode.SKIP;
  93             domHandler = nav().asDecl(reader().getClassValue(xae,"value"));
  94         }
  95     }
  96 
  97     public Set<? extends Element<T,C>> ref() {
  98         return getElements();
  99     }
 100 
 101     public PropertyKind kind() {
 102         return PropertyKind.REFERENCE;
 103     }
 104 
 105     public Set<? extends Element<T,C>> getElements() {
 106         if(types==null)
 107             calcTypes(false);
 108         assert types!=null;
 109         return types;
 110     }
 111 
 112     /**
 113      * Compute {@link #types}.
 114      *
 115      * @param last
 116      *      if true, every {@link XmlElementRef} must yield at least one type.
 117      */
 118     private void calcTypes(boolean last) {
 119         XmlElementRef[] ann;
 120         types = new LinkedHashSet<Element<T,C>>();
 121         XmlElementRefs refs = seed.readAnnotation(XmlElementRefs.class);
 122         XmlElementRef ref = seed.readAnnotation(XmlElementRef.class);
 123 
 124         if(refs!=null && ref!=null) {
 125             parent.builder.reportError(new IllegalAnnotationException(
 126                     Messages.MUTUALLY_EXCLUSIVE_ANNOTATIONS.format(
 127                     nav().getClassName(parent.getClazz())+'#'+seed.getName(),
 128                     ref.annotationType().getName(), refs.annotationType().getName()),
 129                     ref, refs ));
 130         }
 131 
 132         if(refs!=null)
 133             ann = refs.value();
 134         else {
 135             if(ref!=null)
 136                 ann = new XmlElementRef[]{ref};
 137             else
 138                 ann = null;
 139         }
 140 
 141         isRequired = !isCollection();  // this is by default, to remain compatible with 2.1
 142 
 143         if(ann!=null) {
 144             Navigator<T,C,F,M> nav = nav();
 145             AnnotationReader<T,C,F,M> reader = reader();
 146 
 147             final T defaultType = nav.ref(XmlElementRef.DEFAULT.class);
 148             final C je = nav.asDecl(JAXBElement.class);
 149 
 150             for( XmlElementRef r : ann ) {
 151                 boolean yield;
 152                 T type = reader.getClassValue(r,"type");
 153                 if(nav().isSameType(type, defaultType))
 154                     type = nav.erasure(getIndividualType());
 155                 if(nav.getBaseClass(type,je)!=null)
 156                     yield = addGenericElement(r);
 157                 else
 158                     yield = addAllSubtypes(type);
 159 
 160                 // essentially "isRequired &= isRequired(r)" except that we'd like to skip evaluating isRequird(r)
 161                 // if the value is already false.
 162                 if(isRequired && !isRequired(r))
 163                     isRequired = false;
 164 
 165                 if(last && !yield) {
 166                     // a reference didn't produce any type.
 167                     // diagnose the problem
 168                     if(nav().isSameType(type, nav.ref(JAXBElement.class))) {
 169                         // no XmlElementDecl
 170                         parent.builder.reportError(new IllegalAnnotationException(
 171                             Messages.NO_XML_ELEMENT_DECL.format(
 172                                 getEffectiveNamespaceFor(r), r.name()),
 173                             this
 174                         ));
 175                     } else {
 176                         parent.builder.reportError(new IllegalAnnotationException(
 177                             Messages.INVALID_XML_ELEMENT_REF.format(type),this));
 178                     }
 179 
 180                     // reporting one error would do.
 181                     // often the element ref field is using @XmlElementRefs
 182                     // to point to multiple JAXBElements.
 183                     // reporting one error for each @XmlElemetnRef is thus often redundant.
 184                     return;
 185                 }
 186             }
 187         }
 188 
 189         for (ReferencePropertyInfoImpl<T, C, F, M> info : subTypes) {
 190             PropertySeed sd = info.seed;
 191             refs = sd.readAnnotation(XmlElementRefs.class);
 192             ref = sd.readAnnotation(XmlElementRef.class);
 193 
 194             if (refs != null && ref != null) {
 195                 parent.builder.reportError(new IllegalAnnotationException(
 196                         Messages.MUTUALLY_EXCLUSIVE_ANNOTATIONS.format(
 197                         nav().getClassName(parent.getClazz())+'#'+seed.getName(),
 198                         ref.annotationType().getName(), refs.annotationType().getName()),
 199                         ref, refs ));
 200             }
 201 
 202             if (refs != null) {
 203                 ann = refs.value();
 204             } else {
 205                 if (ref != null) {
 206                     ann = new XmlElementRef[]{ref};
 207                 } else {
 208                     ann = null;
 209                 }
 210             }
 211 
 212             if (ann != null) {
 213                 Navigator<T,C,F,M> nav = nav();
 214                 AnnotationReader<T,C,F,M> reader = reader();
 215 
 216                 final T defaultType = nav.ref(XmlElementRef.DEFAULT.class);
 217                 final C je = nav.asDecl(JAXBElement.class);
 218 
 219                 for( XmlElementRef r : ann ) {
 220                     boolean yield;
 221                     T type = reader.getClassValue(r,"type");
 222                     if (nav().isSameType(type, defaultType)) {
 223                         type = nav.erasure(getIndividualType());
 224                     }
 225                     if (nav.getBaseClass(type,je) != null) {
 226                         yield = addGenericElement(r, info);
 227 
 228                     } else {
 229                         yield = addAllSubtypes(type);
 230                     }
 231 
 232                     if(last && !yield) {
 233                         // a reference didn't produce any type.
 234                         // diagnose the problem
 235                         if(nav().isSameType(type, nav.ref(JAXBElement.class))) {
 236                             // no XmlElementDecl
 237                             parent.builder.reportError(new IllegalAnnotationException(
 238                                 Messages.NO_XML_ELEMENT_DECL.format(
 239                                     getEffectiveNamespaceFor(r), r.name()),
 240                                 this
 241                             ));
 242                         } else {
 243                             parent.builder.reportError(new IllegalAnnotationException(
 244                                 Messages.INVALID_XML_ELEMENT_REF.format(),this));
 245                         }
 246 
 247                         // reporting one error would do.
 248                         // often the element ref field is using @XmlElementRefs
 249                         // to point to multiple JAXBElements.
 250                         // reporting one error for each @XmlElemetnRef is thus often redundant.
 251                         return;
 252                     }
 253                 }
 254             }
 255         }
 256 
 257         types = Collections.unmodifiableSet(types);
 258     }
 259 
 260     public boolean isRequired() {
 261         if(isRequired==null)
 262             calcTypes(false);
 263         return isRequired;
 264     }
 265 
 266     /**
 267      * If we find out that we are working with 2.1 API, remember the fact so that
 268      * we don't waste time generating exceptions every time we call {@link #isRequired(XmlElementRef)}.
 269      */
 270     private static boolean is2_2 = true;
 271 
 272     /**
 273      * Reads the value of {@code XmlElementRef.required()}.
 274      *
 275      * If we are working as 2.1 RI, this defaults to true.
 276      */
 277     private boolean isRequired(XmlElementRef ref) {
 278         if(!is2_2)  return true;
 279 
 280         try {
 281             return ref.required();
 282         } catch(LinkageError e) {
 283             is2_2 = false;
 284             return true;    // the value defaults to true
 285         }
 286     }
 287 
 288     /**
 289      * @return
 290      *      true if the reference yields at least one type
 291      */
 292     private boolean addGenericElement(XmlElementRef r) {
 293         String nsUri = getEffectiveNamespaceFor(r);
 294         // TODO: check spec. defaulting of localName.
 295         return addGenericElement(parent.owner.getElementInfo(parent.getClazz(),new QName(nsUri,r.name())));
 296     }
 297 
 298     private boolean addGenericElement(XmlElementRef r, ReferencePropertyInfoImpl<T,C,F,M> info) {
 299         String nsUri = info.getEffectiveNamespaceFor(r);
 300         ElementInfo ei = parent.owner.getElementInfo(info.parent.getClazz(), new QName(nsUri, r.name()));
 301         types.add(ei);
 302         return true;
 303     }
 304 
 305     private String getEffectiveNamespaceFor(XmlElementRef r) {
 306         String nsUri = r.namespace();
 307 
 308         XmlSchema xs = reader().getPackageAnnotation( XmlSchema.class, parent.getClazz(), this );
 309         if(xs!=null && xs.attributeFormDefault()== XmlNsForm.QUALIFIED) {
 310             // JAX-RPC doesn't want the default namespace URI swapping to take effect to
 311             // local "unqualified" elements. UGLY.
 312             if(nsUri.length()==0)
 313                 nsUri = parent.builder.defaultNsUri;
 314         }
 315 
 316         return nsUri;
 317     }
 318 
 319     private boolean addGenericElement(ElementInfo<T,C> ei) {
 320         if(ei==null)
 321             return false;
 322         types.add(ei);
 323         for( ElementInfo<T,C> subst : ei.getSubstitutionMembers() )
 324             addGenericElement(subst);
 325         return true;
 326     }
 327 
 328     private boolean addAllSubtypes(T type) {
 329         Navigator<T,C,F,M> nav = nav();
 330 
 331         // this allows the explicitly referenced type to be sucked in to the model
 332         NonElement<T,C> t = parent.builder.getClassInfo(nav.asDecl(type),this);
 333         if(!(t instanceof ClassInfo))
 334             // this is leaf.
 335             return false;
 336 
 337         boolean result = false;
 338 
 339         ClassInfo<T,C> c = (ClassInfo<T,C>) t;
 340         if(c.isElement()) {
 341             types.add(c.asElement());
 342             result = true;
 343         }
 344 
 345         // look for other possible types
 346         for( ClassInfo<T,C> ci : parent.owner.beans().values() ) {
 347             if(ci.isElement() && nav.isSubClassOf(ci.getType(),type)) {
 348                 types.add(ci.asElement());
 349                 result = true;
 350             }
 351         }
 352 
 353         // don't allow local elements to substitute.
 354         for( ElementInfo<T,C> ei : parent.owner.getElementMappings(null).values()) {
 355             if(nav.isSubClassOf(ei.getType(),type)) {
 356                 types.add(ei);
 357                 result = true;
 358             }
 359         }
 360 
 361         return result;
 362     }
 363 
 364 
 365     @Override
 366     protected void link() {
 367         super.link();
 368 
 369         // until we get the whole thing into TypeInfoSet,
 370         // we never really know what are all the possible types that can be assigned on this field.
 371         // so recompute this value when we have all the information.
 372         calcTypes(true);
 373 
 374     }
 375 
 376     public final void addType(PropertyInfoImpl<T,C,F,M> info) {
 377         //noinspection unchecked
 378         subTypes.add((ReferencePropertyInfoImpl)info);
 379     }
 380 
 381     public final boolean isMixed() {
 382         return isMixed;
 383     }
 384 
 385     public final WildcardMode getWildcard() {
 386         return wildcard;
 387     }
 388 
 389     public final C getDOMHandler() {
 390         return domHandler;
 391     }
 392 }