1 /*
   2  * Copyright (c) 1997, 2012, 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.HashMap;
  30 import java.util.Iterator;
  31 import java.util.LinkedHashMap;
  32 import java.util.Map;
  33 
  34 import javax.xml.bind.JAXBContext;
  35 import javax.xml.bind.JAXBException;
  36 import javax.xml.bind.Marshaller;
  37 import javax.xml.bind.annotation.XmlNs;
  38 import javax.xml.bind.annotation.XmlNsForm;
  39 import javax.xml.bind.annotation.XmlRegistry;
  40 import javax.xml.bind.annotation.XmlSchema;
  41 import javax.xml.bind.annotation.XmlTransient;
  42 import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
  43 import javax.xml.namespace.QName;
  44 import javax.xml.transform.Result;
  45 
  46 import com.sun.xml.internal.bind.v2.model.annotation.AnnotationReader;
  47 import com.sun.xml.internal.bind.v2.model.core.BuiltinLeafInfo;
  48 import com.sun.xml.internal.bind.v2.model.core.ClassInfo;
  49 import com.sun.xml.internal.bind.v2.model.core.LeafInfo;
  50 import com.sun.xml.internal.bind.v2.model.core.NonElement;
  51 import com.sun.xml.internal.bind.v2.model.core.Ref;
  52 import com.sun.xml.internal.bind.v2.model.core.TypeInfo;
  53 import com.sun.xml.internal.bind.v2.model.core.TypeInfoSet;
  54 import com.sun.xml.internal.bind.v2.model.nav.Navigator;
  55 import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException;
  56 import com.sun.xml.internal.bind.v2.runtime.RuntimeUtil;
  57 import com.sun.xml.internal.bind.v2.util.FlattenIterator;
  58 
  59 /**
  60  * Set of {@link TypeInfo}s.
  61  *
  62  * <p>
  63  * This contains a fixed set of {@link LeafInfo}s and arbitrary set of {@link ClassInfo}s.
  64  *
  65  * <p>
  66  * Members are annotated with JAXB annotations so that we can dump it easily.
  67  *
  68  * @author Kohsuke Kawaguchi
  69  */
  70 class TypeInfoSetImpl<T,C,F,M> implements TypeInfoSet<T,C,F,M> {
  71 
  72     @XmlTransient
  73     public final Navigator<T,C,F,M> nav;
  74 
  75     @XmlTransient
  76     public final AnnotationReader<T,C,F,M> reader;
  77 
  78     /**
  79      * All the leaves.
  80      */
  81     private final Map<T,BuiltinLeafInfo<T,C>> builtins =
  82             new LinkedHashMap<T,BuiltinLeafInfo<T,C>>();
  83 
  84     /** All {@link EnumLeafInfoImpl}s. */
  85     private final Map<C,EnumLeafInfoImpl<T,C,F,M>> enums =
  86             new LinkedHashMap<C,EnumLeafInfoImpl<T,C,F,M>>();
  87 
  88     /** All {@link ArrayInfoImpl}s. */
  89     private final Map<T,ArrayInfoImpl<T,C,F,M>> arrays =
  90             new LinkedHashMap<T,ArrayInfoImpl<T,C,F,M>>();
  91 
  92     /**
  93      * All the user-defined classes.
  94      *
  95      * Using {@link LinkedHashMap} allows us to process classes
  96      * in the order they are given to us. When the user incorrectly
  97      * puts an unexpected class into a reference graph, this causes
  98      * an error to be reported on a class closer to the user's code.
  99      */
 100     @XmlJavaTypeAdapter(RuntimeUtil.ToStringAdapter.class)
 101     private final Map<C,ClassInfoImpl<T,C,F,M>> beans
 102             = new LinkedHashMap<C,ClassInfoImpl<T,C,F,M>>();
 103 
 104     @XmlTransient
 105     private final Map<C,ClassInfoImpl<T,C,F,M>> beansView =
 106         Collections.unmodifiableMap(beans);
 107 
 108     /**
 109      * The element mapping.
 110      */
 111     private final Map<C,Map<QName,ElementInfoImpl<T,C,F,M>>> elementMappings =
 112         new LinkedHashMap<C,Map<QName,ElementInfoImpl<T,C,F,M>>>();
 113 
 114     private final Iterable<? extends ElementInfoImpl<T,C,F,M>> allElements =
 115         new Iterable<ElementInfoImpl<T,C,F,M>>() {
 116             public Iterator<ElementInfoImpl<T,C,F,M>> iterator() {
 117                 return new FlattenIterator<ElementInfoImpl<T,C,F,M>>(elementMappings.values());
 118             }
 119         };
 120 
 121     /**
 122      * {@link TypeInfo} for <tt>xs:anyType</tt>.
 123      *
 124      * anyType is the only {@link TypeInfo} that works with an interface,
 125      * and accordingly it requires a lot of special casing.
 126      */
 127     private final NonElement<T,C> anyType;
 128 
 129     /**
 130      * Lazily parsed set of {@link XmlNs}s.
 131      *
 132      * @see #getXmlNs(String)
 133      */
 134     private Map<String,Map<String,String>> xmlNsCache;
 135 
 136     public TypeInfoSetImpl(Navigator<T,C,F,M> nav,
 137                            AnnotationReader<T,C,F,M> reader,
 138                            Map<T,? extends BuiltinLeafInfoImpl<T,C>> leaves) {
 139         this.nav = nav;
 140         this.reader = reader;
 141         this.builtins.putAll(leaves);
 142 
 143         this.anyType = createAnyType();
 144 
 145         // register primitive types.
 146         for (Map.Entry<Class, Class> e : RuntimeUtil.primitiveToBox.entrySet()) {
 147             this.builtins.put( nav.getPrimitive(e.getKey()), leaves.get(nav.ref(e.getValue())) );
 148         }
 149 
 150         // make sure at lease we got a map for global ones.
 151         elementMappings.put(null,new LinkedHashMap<QName,ElementInfoImpl<T,C,F,M>>());
 152     }
 153 
 154     protected NonElement<T,C> createAnyType() {
 155         return new AnyTypeImpl<T,C>(nav);
 156     }
 157 
 158     public Navigator<T,C,F,M> getNavigator() {
 159         return nav;
 160     }
 161 
 162     /**
 163      * Adds a new {@link ClassInfo} to the set.
 164      */
 165     public void add( ClassInfoImpl<T,C,F,M> ci ) {
 166         beans.put( ci.getClazz(), ci );
 167     }
 168 
 169     /**
 170      * Adds a new {@link LeafInfo} to the set.
 171      */
 172     public void add( EnumLeafInfoImpl<T,C,F,M> li ) {
 173         enums.put( li.clazz,  li );
 174     }
 175 
 176     public void add(ArrayInfoImpl<T, C, F, M> ai) {
 177         arrays.put( ai.getType(), ai );
 178     }
 179 
 180     /**
 181      * Returns a {@link TypeInfo} for the given type.
 182      *
 183      * @return
 184      *      null if the specified type cannot be bound by JAXB, or
 185      *      not known to this set.
 186      */
 187     public NonElement<T,C> getTypeInfo( T type ) {
 188         type = nav.erasure(type);   // replace type variables by their bounds
 189 
 190         LeafInfo<T,C> l = builtins.get(type);
 191         if(l!=null)     return l;
 192 
 193         if( nav.isArray(type) ) {
 194             return arrays.get(type);
 195         }
 196 
 197         C d = nav.asDecl(type);
 198         if(d==null)     return null;
 199         return getClassInfo(d);
 200     }
 201 
 202     public NonElement<T,C> getAnyTypeInfo() {
 203         return anyType;
 204     }
 205 
 206     /**
 207      * This method is used to add a root reference to a model.
 208      */
 209     public NonElement<T,C> getTypeInfo(Ref<T,C> ref) {
 210         // TODO: handle XmlValueList
 211         assert !ref.valueList;
 212         C c = nav.asDecl(ref.type);
 213         if(c!=null && reader.getClassAnnotation(XmlRegistry.class,c,null/*TODO: is this right?*/)!=null) {
 214             return null;    // TODO: is this correct?
 215         } else
 216             return getTypeInfo(ref.type);
 217     }
 218 
 219     /**
 220      * Returns all the {@link ClassInfo}s known to this set.
 221      */
 222     public Map<C,? extends ClassInfoImpl<T,C,F,M>> beans() {
 223         return beansView;
 224     }
 225 
 226     public Map<T, ? extends BuiltinLeafInfo<T,C>> builtins() {
 227         return builtins;
 228     }
 229 
 230     public Map<C, ? extends EnumLeafInfoImpl<T,C,F,M>> enums() {
 231         return enums;
 232     }
 233 
 234     public Map<? extends T, ? extends ArrayInfoImpl<T,C,F,M>> arrays() {
 235         return arrays;
 236     }
 237 
 238     /**
 239      * Returns a {@link ClassInfo} for the given bean.
 240      *
 241      * <p>
 242      * This method is almost like refinement of {@link #getTypeInfo(Object)} except
 243      * our C cannot derive from T.
 244      *
 245      * @return
 246      *      null if the specified type is not bound by JAXB or otherwise
 247      *      unknown to this set.
 248      */
 249     public NonElement<T,C> getClassInfo( C type ) {
 250         LeafInfo<T,C> l = builtins.get(nav.use(type));
 251         if(l!=null)     return l;
 252 
 253         l = enums.get(type);
 254         if(l!=null)     return l;
 255 
 256         if(nav.asDecl(Object.class).equals(type))
 257             return anyType;
 258 
 259         return beans.get(type);
 260     }
 261 
 262     public ElementInfoImpl<T,C,F,M> getElementInfo( C scope, QName name ) {
 263         while(scope!=null) {
 264             Map<QName,ElementInfoImpl<T,C,F,M>> m = elementMappings.get(scope);
 265             if(m!=null) {
 266                 ElementInfoImpl<T,C,F,M> r = m.get(name);
 267                 if(r!=null)     return r;
 268             }
 269             scope = nav.getSuperClass(scope);
 270         }
 271         return elementMappings.get(null).get(name);
 272     }
 273 
 274     /**
 275      * @param builder
 276      *      used for reporting errors.
 277      */
 278     public final void add( ElementInfoImpl<T,C,F,M> ei, ModelBuilder<T,C,F,M> builder ) {
 279         C scope = null;
 280         if(ei.getScope()!=null)
 281             scope = ei.getScope().getClazz();
 282 
 283         Map<QName,ElementInfoImpl<T,C,F,M>> m = elementMappings.get(scope);
 284         if(m==null)
 285             elementMappings.put(scope,m=new LinkedHashMap<QName,ElementInfoImpl<T,C,F,M>>());
 286 
 287         ElementInfoImpl<T,C,F,M> existing = m.put(ei.getElementName(),ei);
 288 
 289         if(existing!=null) {
 290             QName en = ei.getElementName();
 291             builder.reportError(
 292                 new IllegalAnnotationException(
 293                     Messages.CONFLICTING_XML_ELEMENT_MAPPING.format(en.getNamespaceURI(),en.getLocalPart()),
 294                     ei, existing ));
 295         }
 296     }
 297 
 298     public Map<QName,? extends ElementInfoImpl<T,C,F,M>> getElementMappings( C scope ) {
 299         return elementMappings.get(scope);
 300     }
 301 
 302     public Iterable<? extends ElementInfoImpl<T,C,F,M>> getAllElements() {
 303         return allElements;
 304     }
 305 
 306     public Map<String,String> getXmlNs(String namespaceUri) {
 307         if(xmlNsCache==null) {
 308             xmlNsCache = new HashMap<String,Map<String,String>>();
 309 
 310             for (ClassInfoImpl<T, C, F, M> ci : beans().values()) {
 311                 XmlSchema xs = reader.getPackageAnnotation( XmlSchema.class, ci.getClazz(), null );
 312                 if(xs==null)
 313                     continue;
 314 
 315                 String uri = xs.namespace();
 316                 Map<String,String> m = xmlNsCache.get(uri);
 317                 if(m==null)
 318                     xmlNsCache.put(uri,m=new HashMap<String, String>());
 319 
 320                 for( XmlNs xns : xs.xmlns() ) {
 321                     m.put(xns.prefix(),xns.namespaceURI());
 322                 }
 323             }
 324         }
 325 
 326         Map<String,String> r = xmlNsCache.get(namespaceUri);
 327         if(r!=null)     return r;
 328         else            return Collections.emptyMap();
 329     }
 330 
 331     public Map<String,String> getSchemaLocations() {
 332         Map<String, String> r = new HashMap<String,String>();
 333         for (ClassInfoImpl<T, C, F, M> ci : beans().values()) {
 334             XmlSchema xs = reader.getPackageAnnotation( XmlSchema.class, ci.getClazz(), null );
 335             if(xs==null)
 336                 continue;
 337 
 338             String loc = xs.location();
 339             if(loc.equals(XmlSchema.NO_LOCATION))
 340                 continue;   // unspecified
 341 
 342             r.put(xs.namespace(),loc);
 343         }
 344         return r;
 345     }
 346 
 347     public final XmlNsForm getElementFormDefault(String nsUri) {
 348         for (ClassInfoImpl<T, C, F, M> ci : beans().values()) {
 349             XmlSchema xs = reader.getPackageAnnotation( XmlSchema.class, ci.getClazz(), null );
 350             if(xs==null)
 351                 continue;
 352 
 353             if(!xs.namespace().equals(nsUri))
 354                 continue;
 355 
 356             XmlNsForm xnf = xs.elementFormDefault();
 357             if(xnf!=XmlNsForm.UNSET)
 358                 return xnf;
 359         }
 360         return XmlNsForm.UNSET;
 361     }
 362 
 363     public final XmlNsForm getAttributeFormDefault(String nsUri) {
 364         for (ClassInfoImpl<T,C,F,M> ci : beans().values()) {
 365             XmlSchema xs = reader.getPackageAnnotation( XmlSchema.class, ci.getClazz(), null );
 366             if(xs==null)
 367                 continue;
 368 
 369             if(!xs.namespace().equals(nsUri))
 370                 continue;
 371 
 372             XmlNsForm xnf = xs.attributeFormDefault();
 373             if(xnf!=XmlNsForm.UNSET)
 374                 return xnf;
 375         }
 376         return XmlNsForm.UNSET;
 377     }
 378 
 379     /**
 380      * Dumps this model into XML.
 381      *
 382      * For debug only.
 383      *
 384      * TODO: not sure if this actually works. We don't really know what are T,C.
 385      */
 386     public void dump( Result out ) throws JAXBException {
 387         JAXBContext context = JAXBContext.newInstance(this.getClass());
 388         Marshaller m = context.createMarshaller();
 389         m.marshal(this,out);
 390     }
 391 }