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
26package com.sun.xml.internal.bind.v2.model.impl;
27
28import java.lang.reflect.ParameterizedType;
29import java.lang.reflect.Type;
30import java.util.HashMap;
31import java.util.Map;
32import java.util.logging.Level;
33import java.util.logging.Logger;
34
35import javax.xml.bind.JAXBElement;
36import javax.xml.bind.annotation.XmlAttachmentRef;
37import javax.xml.bind.annotation.XmlRegistry;
38import javax.xml.bind.annotation.XmlSchema;
39import javax.xml.bind.annotation.XmlSeeAlso;
40import javax.xml.bind.annotation.XmlTransient;
41import javax.xml.namespace.QName;
42
43import com.sun.xml.internal.bind.util.Which;
44import com.sun.xml.internal.bind.v2.model.annotation.AnnotationReader;
45import com.sun.xml.internal.bind.v2.model.annotation.ClassLocatable;
46import com.sun.xml.internal.bind.v2.model.annotation.Locatable;
47import com.sun.xml.internal.bind.v2.model.core.ClassInfo;
48import com.sun.xml.internal.bind.v2.model.core.ErrorHandler;
49import com.sun.xml.internal.bind.v2.model.core.LeafInfo;
50import com.sun.xml.internal.bind.v2.model.core.NonElement;
51import com.sun.xml.internal.bind.v2.model.core.PropertyInfo;
52import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
53import com.sun.xml.internal.bind.v2.model.core.Ref;
54import com.sun.xml.internal.bind.v2.model.core.RegistryInfo;
55import com.sun.xml.internal.bind.v2.model.core.TypeInfo;
56import com.sun.xml.internal.bind.v2.model.core.TypeInfoSet;
57import com.sun.xml.internal.bind.v2.model.nav.Navigator;
58import com.sun.xml.internal.bind.v2.model.runtime.RuntimePropertyInfo;
59import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException;
60import com.sun.xml.internal.bind.WhiteSpaceProcessor;
61
62/**
63 * Builds a {@link TypeInfoSet} (a set of JAXB properties)
64 * by using {@link ElementInfoImpl} and {@link ClassInfoImpl}.
65 * from annotated Java classes.
66 *
67 * <p>
68 * This class uses {@link Navigator} and {@link AnnotationReader} to
69 * work with arbitrary annotation source and arbitrary Java model.
70 * For this purpose this class is parameterized.
71 *
72 * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
73 */
74public class ModelBuilder<T,C,F,M> implements ModelBuilderI<T,C,F,M> {
75    private static final Logger logger;
76
77    /**
78     * {@link TypeInfo}s that are built will go into this set.
79     */
80    final TypeInfoSetImpl<T,C,F,M> typeInfoSet;
81
82    public final AnnotationReader<T,C,F,M> reader;
83
84    public final Navigator<T,C,F,M> nav;
85
86    /**
87     * Used to detect collisions among global type names.
88     */
89    private final Map<QName,TypeInfo> typeNames = new HashMap<QName,TypeInfo>();
90
91    /**
92     * JAXB doesn't want to use namespaces unless we are told to, but WS-I BP
93     * conformace requires JAX-RPC to always use a non-empty namespace URI.
94     * (see http://www.ws-i.org/Profiles/BasicProfile-1.0-2004-04-16.html#WSDLTYPES R2105)
95     *
96     * <p>
97     * To work around this issue, we allow the use of the empty namespaces to be
98     * replaced by a particular designated namespace URI.
99     *
100     * <p>
101     * This field keeps the value of that replacing namespace URI.
102     * When there's no replacement, this field is set to "".
103     */
104    public final String defaultNsUri;
105
106
107    /**
108     * Packages whose registries are already added.
109     */
110    /*package*/ final Map<String,RegistryInfoImpl<T,C,F,M>> registries
111            = new HashMap<String,RegistryInfoImpl<T,C,F,M>>();
112
113    private final Map<C,C> subclassReplacements;
114
115    /**
116     * @see #setErrorHandler
117     */
118    private ErrorHandler errorHandler;
119    private boolean hadError;
120
121    /**
122     * Set to true if the model includes {@link XmlAttachmentRef}. JAX-WS
123     * needs to know this information.
124     */
125    public boolean hasSwaRef;
126
127    private final ErrorHandler proxyErrorHandler = new ErrorHandler() {
128        public void error(IllegalAnnotationException e) {
129            reportError(e);
130        }
131    };
132
133    public ModelBuilder(
134            AnnotationReader<T, C, F, M> reader,
135            Navigator<T, C, F, M> navigator,
136            Map<C, C> subclassReplacements,
137            String defaultNamespaceRemap
138    ) {
139
140        this.reader = reader;
141        this.nav = navigator;
142        this.subclassReplacements = subclassReplacements;
143        if(defaultNamespaceRemap==null)
144            defaultNamespaceRemap = "";
145        this.defaultNsUri = defaultNamespaceRemap;
146        reader.setErrorHandler(proxyErrorHandler);
147        typeInfoSet = createTypeInfoSet();
148    }
149
150    /**
151     * Makes sure that we are running with 2.1 JAXB API,
152     * and report an error if not.
153     */
154    static {
155        try {
156            XmlSchema s = null;
157            s.location();
158        } catch (NullPointerException e) {
159            // as epxected
160        } catch (NoSuchMethodError e) {
161            // this is not a 2.1 API. Where is it being loaded from?
162            Messages res;
163            if (SecureLoader.getClassClassLoader(XmlSchema.class) == null) {
164                res = Messages.INCOMPATIBLE_API_VERSION_MUSTANG;
165            } else {
166                res = Messages.INCOMPATIBLE_API_VERSION;
167            }
168
169            throw new LinkageError( res.format(
170                Which.which(XmlSchema.class),
171                Which.which(ModelBuilder.class)
172            ));
173        }
174    }
175
176    /**
177     * Makes sure that we don't have conflicting 1.0 runtime,
178     * and report an error if we do.
179     */
180    static {
181        try {
182            WhiteSpaceProcessor.isWhiteSpace("xyz");
183        } catch (NoSuchMethodError e) {
184            // we seem to be getting 1.0 runtime
185            throw new LinkageError( Messages.RUNNING_WITH_1_0_RUNTIME.format(
186                Which.which(WhiteSpaceProcessor.class),
187                Which.which(ModelBuilder.class)
188            ));
189        }
190    }
191
192    /**
193     * Logger init
194     */
195    static {
196        logger = Logger.getLogger(ModelBuilder.class.getName());
197    }
198
199    protected TypeInfoSetImpl<T,C,F,M> createTypeInfoSet() {
200        return new TypeInfoSetImpl<T,C,F,M>(nav,reader,BuiltinLeafInfoImpl.createLeaves(nav));
201    }
202
203    /**
204     * Builds a JAXB {@link ClassInfo} model from a given class declaration
205     * and adds that to this model owner.
206     *
207     * <p>
208     * Return type is either {@link ClassInfo} or {@link LeafInfo} (for types like
209     * {@link String} or {@link Enum}-derived ones)
210     */
211    public NonElement<T,C> getClassInfo( C clazz, Locatable upstream ) {
212        return getClassInfo(clazz,false,upstream);
213    }
214
215    /**
216     * For limited cases where the caller needs to search for a super class.
217     * This is necessary because we don't want {@link #subclassReplacements}
218     * to kick in for the super class search, which will cause infinite recursion.
219     */
220    public NonElement<T,C> getClassInfo( C clazz, boolean searchForSuperClass, Locatable upstream ) {
221        assert clazz!=null;
222        NonElement<T,C> r = typeInfoSet.getClassInfo(clazz);
223        if(r!=null)
224            return r;
225
226        if(nav.isEnum(clazz)) {
227            EnumLeafInfoImpl<T,C,F,M> li = createEnumLeafInfo(clazz,upstream);
228            typeInfoSet.add(li);
229            r = li;
230            addTypeName(r);
231        } else {
232            boolean isReplaced = subclassReplacements.containsKey(clazz);
233            if(isReplaced && !searchForSuperClass) {
234                // handle it as if the replacement was specified
235                r = getClassInfo(subclassReplacements.get(clazz),upstream);
236            } else
237            if(reader.hasClassAnnotation(clazz,XmlTransient.class) || isReplaced) {
238                // handle it as if the base class was specified
239                r = getClassInfo( nav.getSuperClass(clazz), searchForSuperClass,
240                        new ClassLocatable<C>(upstream,clazz,nav) );
241            } else {
242                ClassInfoImpl<T,C,F,M> ci = createClassInfo(clazz,upstream);
243                typeInfoSet.add(ci);
244
245                // compute the closure by eagerly expanding references
246                for( PropertyInfo<T,C> p : ci.getProperties() ) {
247                    if(p.kind()== PropertyKind.REFERENCE) {
248                        // make sure that we have a registry for this package
249                        addToRegistry(clazz, (Locatable) p);
250                        Class[] prmzdClasses = getParametrizedTypes(p);
251                        if (prmzdClasses != null) {
252                            for (Class prmzdClass : prmzdClasses) {
253                                if (prmzdClass != clazz) {
254                                    addToRegistry((C) prmzdClass, (Locatable) p);
255                                }
256                            }
257                        }
258                    }
259
260                    for( TypeInfo<T,C> t : p.ref() )
261                        ; // just compute a reference should be suffice
262                }
263                ci.getBaseClass(); // same as above.
264
265                r = ci;
266                addTypeName(r);
267            }
268        }
269
270
271        // more reference closure expansion. @XmlSeeAlso
272        XmlSeeAlso sa = reader.getClassAnnotation(XmlSeeAlso.class, clazz, upstream);
273        if(sa!=null) {
274            for( T t : reader.getClassArrayValue(sa,"value") ) {
275                getTypeInfo(t,(Locatable)sa);
276            }
277        }
278
279
280        return r;
281    }
282
283    /**
284     * Adding package's ObjectFactory methods to registry
285     * @param clazz which package will be used
286     * @param p location
287     */
288    private void addToRegistry(C clazz, Locatable p) {
289        String pkg = nav.getPackageName(clazz);
290        if (!registries.containsKey(pkg)) {
291            // insert the package's object factory
292            C c = nav.loadObjectFactory(clazz, pkg);
293            if (c != null)
294                addRegistry(c, p);
295        }
296    }
297
298    /**
299     * Getting parametrized classes of {@code JAXBElement<...>} property
300     * @param p property which parametrized types we will try to get
301     * @return null - if it's not JAXBElement property, or it's not parametrized, and array of parametrized classes in other case
302     */
303    private Class[] getParametrizedTypes(PropertyInfo p) {
304        try {
305            Type pType = ((RuntimePropertyInfo) p).getIndividualType();
306            if (pType instanceof ParameterizedType) {
307                ParameterizedType prmzdType = (ParameterizedType) pType;
308                if (prmzdType.getRawType() == JAXBElement.class) {
309                    Type[] actualTypes = prmzdType.getActualTypeArguments();
310                    Class[] result = new Class[actualTypes.length];
311                    for (int i = 0; i < actualTypes.length; i++) {
312                        result[i] = (Class) actualTypes[i];
313                    }
314                    return result;
315                }
316            }
317        } catch (Exception e) {
318            logger.log(Level.FINE, "Error in ModelBuilder.getParametrizedTypes. " + e.getMessage());
319        }
320        return null;
321    }
322
323    /**
324     * Checks the uniqueness of the type name.
325     */
326    private void addTypeName(NonElement<T, C> r) {
327        QName t = r.getTypeName();
328        if(t==null)     return;
329
330        TypeInfo old = typeNames.put(t,r);
331        if(old!=null) {
332            // collision
333            reportError(new IllegalAnnotationException(
334                    Messages.CONFLICTING_XML_TYPE_MAPPING.format(r.getTypeName()),
335                    old, r ));
336        }
337    }
338
339    /**
340     * Have the builder recognize the type (if it hasn't done so yet),
341     * and returns a {@link NonElement} that represents it.
342     *
343     * @return
344     *      always non-null.
345     */
346    public NonElement<T,C> getTypeInfo(T t,Locatable upstream) {
347        NonElement<T,C> r = typeInfoSet.getTypeInfo(t);
348        if(r!=null)     return r;
349
350        if(nav.isArray(t)) { // no need for checking byte[], because above typeInfoset.getTypeInfo() would return non-null
351            ArrayInfoImpl<T,C,F,M> ai =
352                createArrayInfo(upstream, t);
353            addTypeName(ai);
354            typeInfoSet.add(ai);
355            return ai;
356        }
357
358        C c = nav.asDecl(t);
359        assert c!=null : t.toString()+" must be a leaf, but we failed to recognize it.";
360        return getClassInfo(c,upstream);
361    }
362
363    /**
364     * This method is used to add a root reference to a model.
365     */
366    public NonElement<T,C> getTypeInfo(Ref<T,C> ref) {
367        // TODO: handle XmlValueList
368        assert !ref.valueList;
369        C c = nav.asDecl(ref.type);
370        if(c!=null && reader.getClassAnnotation(XmlRegistry.class,c,null/*TODO: is this right?*/)!=null) {
371            if(!registries.containsKey(nav.getPackageName(c)))
372                addRegistry(c,null);
373            return null;    // TODO: is this correct?
374        } else
375            return getTypeInfo(ref.type,null);
376    }
377
378
379    protected EnumLeafInfoImpl<T,C,F,M> createEnumLeafInfo(C clazz,Locatable upstream) {
380        return new EnumLeafInfoImpl<T,C,F,M>(this,upstream,clazz,nav.use(clazz));
381    }
382
383    protected ClassInfoImpl<T,C,F,M> createClassInfo(C clazz, Locatable upstream ) {
384        return new ClassInfoImpl<T,C,F,M>(this,upstream,clazz);
385    }
386
387    protected ElementInfoImpl<T,C,F,M> createElementInfo(
388        RegistryInfoImpl<T,C,F,M> registryInfo, M m) throws IllegalAnnotationException {
389        return new ElementInfoImpl<T,C,F,M>(this,registryInfo,m);
390    }
391
392    protected ArrayInfoImpl<T,C,F,M> createArrayInfo(Locatable upstream, T arrayType) {
393        return new ArrayInfoImpl<T, C, F, M>(this,upstream,arrayType);
394    }
395
396
397    /**
398     * Visits a class with {@link XmlRegistry} and records all the element mappings
399     * in it.
400     */
401    public RegistryInfo<T,C> addRegistry(C registryClass, Locatable upstream ) {
402        return new RegistryInfoImpl<T,C,F,M>(this,upstream,registryClass);
403    }
404
405    /**
406     * Gets a {@link RegistryInfo} for the given package.
407     *
408     * @return
409     *      null if no registry exists for the package.
410     *      unlike other getXXX methods on this class,
411     *      this method is side-effect free.
412     */
413    public RegistryInfo<T,C> getRegistry(String packageName) {
414        return registries.get(packageName);
415    }
416
417    private boolean linked;
418
419    /**
420     * Called after all the classes are added to the type set
421     * to "link" them together.
422     *
423     * <p>
424     * Don't expose implementation classes in the signature.
425     *
426     * @return
427     *      fully built {@link TypeInfoSet} that represents the model,
428     *      or null if there was an error.
429     */
430    public TypeInfoSet<T,C,F,M> link() {
431
432        assert !linked;
433        linked = true;
434
435        for( ElementInfoImpl ei : typeInfoSet.getAllElements() )
436            ei.link();
437
438        for( ClassInfoImpl ci : typeInfoSet.beans().values() )
439            ci.link();
440
441        for( EnumLeafInfoImpl li : typeInfoSet.enums().values() )
442            li.link();
443
444        if(hadError)
445            return null;
446        else
447            return typeInfoSet;
448    }
449
450//
451//
452// error handling
453//
454//
455
456    /**
457     * Sets the error handler that receives errors discovered during the model building.
458     *
459     * @param errorHandler
460     *      can be null.
461     */
462    public void setErrorHandler(ErrorHandler errorHandler) {
463        this.errorHandler = errorHandler;
464    }
465
466    public final void reportError(IllegalAnnotationException e) {
467        hadError = true;
468        if(errorHandler!=null)
469            errorHandler.error(e);
470    }
471
472    public boolean isReplaced(C sc) {
473        return subclassReplacements.containsKey(sc);
474    }
475
476    @Override
477    public Navigator<T, C, F, M> getNavigator() {
478        return nav;
479    }
480
481    @Override
482    public AnnotationReader<T, C, F, M> getReader() {
483        return reader;
484    }
485}
486