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 }