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
26package com.sun.xml.internal.bind.v2.model.impl;
27
28import java.lang.annotation.Annotation;
29import java.util.Collection;
30import java.util.Collections;
31import java.util.List;
32
33import javax.activation.MimeType;
34import javax.xml.bind.JAXBElement;
35import javax.xml.bind.annotation.XmlAttachmentRef;
36import javax.xml.bind.annotation.XmlElementDecl;
37import javax.xml.bind.annotation.XmlID;
38import javax.xml.bind.annotation.XmlIDREF;
39import javax.xml.bind.annotation.XmlInlineBinaryData;
40import javax.xml.bind.annotation.XmlSchema;
41import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
42import javax.xml.namespace.QName;
43
44import com.sun.istack.internal.FinalArrayList;
45import com.sun.xml.internal.bind.v2.TODO;
46import com.sun.xml.internal.bind.v2.model.annotation.AnnotationSource;
47import com.sun.xml.internal.bind.v2.model.annotation.Locatable;
48import com.sun.xml.internal.bind.v2.model.core.Adapter;
49import com.sun.xml.internal.bind.v2.model.core.ClassInfo;
50import com.sun.xml.internal.bind.v2.model.core.ElementInfo;
51import com.sun.xml.internal.bind.v2.model.core.ElementPropertyInfo;
52import com.sun.xml.internal.bind.v2.model.core.ID;
53import com.sun.xml.internal.bind.v2.model.core.NonElement;
54import com.sun.xml.internal.bind.v2.model.core.PropertyInfo;
55import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
56import com.sun.xml.internal.bind.v2.model.core.TypeInfo;
57import com.sun.xml.internal.bind.v2.model.core.TypeRef;
58import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException;
59import com.sun.xml.internal.bind.v2.runtime.Location;
60import com.sun.xml.internal.bind.v2.runtime.SwaRefAdapter;
61
62/**
63 * {@link ElementInfo} implementation.
64 *
65 * @author Kohsuke Kawaguchi
66 */
67class ElementInfoImpl<T,C,F,M> extends TypeInfoImpl<T,C,F,M> implements ElementInfo<T,C> {
68
69    private final QName tagName;
70
71    private final NonElement<T,C> contentType;
72
73    private final T tOfJAXBElementT;
74
75    private final T elementType;
76
77    private final ClassInfo<T,C> scope;
78
79    /**
80     * Annotation that controls the binding.
81     */
82    private final XmlElementDecl anno;
83
84    /**
85     * If this element can substitute another element, the element name.
86     * @see #link()
87     */
88    private ElementInfoImpl<T,C,F,M> substitutionHead;
89
90    /**
91     * Lazily constructed list of {@link ElementInfo}s that can substitute this element.
92     * This could be null.
93     * @see #link()
94     */
95    private FinalArrayList<ElementInfoImpl<T,C,F,M>> substitutionMembers;
96
97    /**
98     * The factory method from which this mapping was created.
99     */
100    private final M method;
101
102    /**
103     * If the content type is adapter, return that adapter.
104     */
105    private final Adapter<T,C> adapter;
106
107    private final boolean isCollection;
108
109    private final ID id;
110
111    private final PropertyImpl property;
112    private final MimeType expectedMimeType;
113    private final boolean inlineBinary;
114    private final QName schemaType;
115
116    /**
117     * Singleton instance of {@link ElementPropertyInfo} for this element.
118     */
119    protected class PropertyImpl implements
120            ElementPropertyInfo<T,C>,
121            TypeRef<T,C>,
122            AnnotationSource {
123        //
124        // TypeRef impl
125        //
126        public NonElement<T,C> getTarget() {
127            return contentType;
128        }
129        public QName getTagName() {
130            return tagName;
131        }
132
133        public List<? extends TypeRef<T,C>> getTypes() {
134            return Collections.singletonList(this);
135        }
136
137        public List<? extends NonElement<T,C>> ref() {
138            return Collections.singletonList(contentType);
139        }
140
141        public QName getXmlName() {
142            return tagName;
143        }
144
145        public boolean isCollectionRequired() {
146            return false;
147        }
148
149        public boolean isCollectionNillable() {
150            return true;
151        }
152
153        public boolean isNillable() {
154            return true;
155        }
156
157        public String getDefaultValue() {
158            String v = anno.defaultValue();
159            if(v.equals("\u0000"))
160                return null;
161            else
162                return v;
163        }
164
165        public ElementInfoImpl<T,C,F,M> parent() {
166            return ElementInfoImpl.this;
167        }
168
169        public String getName() {
170            return "value";
171        }
172
173        public String displayName() {
174            return "JAXBElement#value";
175        }
176
177        public boolean isCollection() {
178            return isCollection;
179        }
180
181        /**
182         * For {@link ElementInfo}s, a collection always means a list of values.
183         */
184        public boolean isValueList() {
185            return isCollection;
186        }
187
188        public boolean isRequired() {
189            return true;
190        }
191
192        public PropertyKind kind() {
193            return PropertyKind.ELEMENT;
194        }
195
196        public Adapter<T,C> getAdapter() {
197            return adapter;
198        }
199
200        public ID id() {
201            return id;
202        }
203
204        public MimeType getExpectedMimeType() {
205            return expectedMimeType;
206        }
207
208        public QName getSchemaType() {
209            return schemaType;
210        }
211
212        public boolean inlineBinaryData() {
213            return inlineBinary;
214        }
215
216        public PropertyInfo<T,C> getSource() {
217            return this;
218        }
219
220        //
221        //
222        // AnnotationSource impl
223        //
224        //
225        public <A extends Annotation> A readAnnotation(Class<A> annotationType) {
226            return reader().getMethodAnnotation(annotationType,method,ElementInfoImpl.this);
227        }
228
229        public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
230            return reader().hasMethodAnnotation(annotationType,method);
231        }
232    }
233
234    /**
235     * @param m
236     *      The factory method on ObjectFactory that comes with {@link XmlElementDecl}.
237     */
238    public ElementInfoImpl(ModelBuilder<T,C,F,M> builder,
239                           RegistryInfoImpl<T,C,F,M> registry, M m ) throws IllegalAnnotationException {
240        super(builder,registry);
241
242        this.method = m;
243        anno = reader().getMethodAnnotation( XmlElementDecl.class, m, this );
244        assert anno!=null;  // the caller should check this
245        assert anno instanceof Locatable;
246
247        elementType = nav().getReturnType(m);
248        T baseClass = nav().getBaseClass(elementType,nav().asDecl(JAXBElement.class));
249        if(baseClass==null)
250            throw new IllegalAnnotationException(
251                Messages.XML_ELEMENT_MAPPING_ON_NON_IXMLELEMENT_METHOD.format(nav().getMethodName(m)),
252                anno );
253
254        tagName = parseElementName(anno);
255        T[] methodParams = nav().getMethodParameters(m);
256
257        // adapter
258        Adapter<T,C> a = null;
259        if(methodParams.length>0) {
260            XmlJavaTypeAdapter adapter = reader().getMethodAnnotation(XmlJavaTypeAdapter.class,m,this);
261            if(adapter!=null)
262                a = new Adapter<T,C>(adapter,reader(),nav());
263            else {
264                XmlAttachmentRef xsa = reader().getMethodAnnotation(XmlAttachmentRef.class,m,this);
265                if(xsa!=null) {
266                    TODO.prototype("in Annotation Processing swaRefAdapter isn't avaialble, so this returns null");
267                    a = new Adapter<T,C>(owner.nav.asDecl(SwaRefAdapter.class),owner.nav);
268                }
269            }
270        }
271        this.adapter = a;
272
273        // T of JAXBElement<T>
274        tOfJAXBElementT =
275            methodParams.length>0 ? methodParams[0] // this is more reliable, as it works even for ObjectFactory that sometimes have to return public types
276            : nav().getTypeArgument(baseClass,0); // fall back to infer from the return type if no parameter.
277
278        if(adapter==null) {
279            T list = nav().getBaseClass(tOfJAXBElementT,nav().asDecl(List.class));
280            if(list==null) {
281                isCollection = false;
282                contentType = builder.getTypeInfo(tOfJAXBElementT,this);  // suck this type into the current set.
283            } else {
284                isCollection = true;
285                contentType = builder.getTypeInfo(nav().getTypeArgument(list,0),this);
286            }
287        } else {
288            // but if adapted, use the adapted type
289            contentType = builder.getTypeInfo(this.adapter.defaultType,this);
290            isCollection = false;
291        }
292
293        // scope
294        T s = reader().getClassValue(anno,"scope");
295        if(nav().isSameType(s, nav().ref(XmlElementDecl.GLOBAL.class)))
296            scope = null;
297        else {
298            // TODO: what happens if there's an error?
299            NonElement<T,C> scp = builder.getClassInfo(nav().asDecl(s),this);
300            if(!(scp instanceof ClassInfo)) {
301                throw new IllegalAnnotationException(
302                    Messages.SCOPE_IS_NOT_COMPLEXTYPE.format(nav().getTypeName(s)),
303                    anno );
304            }
305            scope = (ClassInfo<T,C>)scp;
306        }
307
308        id = calcId();
309
310        property = createPropertyImpl();
311
312        this.expectedMimeType = Util.calcExpectedMediaType(property,builder);
313        this.inlineBinary = reader().hasMethodAnnotation(XmlInlineBinaryData.class,method);
314        this.schemaType = Util.calcSchemaType(reader(),property,registry.registryClass,
315                getContentInMemoryType(),this);
316    }
317
318    final QName parseElementName(XmlElementDecl e) {
319        String local = e.name();
320        String nsUri = e.namespace();
321        if(nsUri.equals("##default")) {
322            // if defaulted ...
323            XmlSchema xs = reader().getPackageAnnotation(XmlSchema.class,
324                nav().getDeclaringClassForMethod(method),this);
325            if(xs!=null)
326                nsUri = xs.namespace();
327            else {
328                nsUri = builder.defaultNsUri;
329            }
330        }
331
332        return new QName(nsUri.intern(),local.intern());
333    }
334
335    protected PropertyImpl createPropertyImpl() {
336        return new PropertyImpl();
337    }
338
339    public ElementPropertyInfo<T,C> getProperty() {
340        return property;
341    }
342
343    public NonElement<T,C> getContentType() {
344        return contentType;
345    }
346
347    public T getContentInMemoryType() {
348        if(adapter==null) {
349            return tOfJAXBElementT;
350        } else {
351            return adapter.customType;
352        }
353    }
354
355    public QName getElementName() {
356        return tagName;
357    }
358
359    public T getType() {
360        return elementType;
361    }
362
363    /**
364     * Leaf-type cannot be referenced from IDREF.
365     *
366     * @deprecated
367     *      why are you calling a method whose return value is always known?
368     */
369    public final boolean canBeReferencedByIDREF() {
370        return false;
371    }
372
373    private ID calcId() {
374        // TODO: share code with PropertyInfoImpl
375        if(reader().hasMethodAnnotation(XmlID.class,method)) {
376            return ID.ID;
377        } else
378        if(reader().hasMethodAnnotation(XmlIDREF.class,method)) {
379            return ID.IDREF;
380        } else {
381            return ID.NONE;
382        }
383    }
384
385    public ClassInfo<T, C> getScope() {
386        return scope;
387    }
388
389    public ElementInfo<T,C> getSubstitutionHead() {
390        return substitutionHead;
391    }
392
393    public Collection<? extends ElementInfoImpl<T,C,F,M>> getSubstitutionMembers() {
394        if(substitutionMembers==null)
395            return Collections.emptyList();
396        else
397            return substitutionMembers;
398    }
399
400    /**
401     * Called after all the {@link TypeInfo}s are collected into the {@link #owner}.
402     */
403    /*package*/ void link() {
404        // substitution head
405        if(anno.substitutionHeadName().length()!=0) {
406            QName name = new QName(
407                anno.substitutionHeadNamespace(), anno.substitutionHeadName() );
408            substitutionHead = owner.getElementInfo(null,name);
409            if(substitutionHead==null) {
410                builder.reportError(
411                    new IllegalAnnotationException(Messages.NON_EXISTENT_ELEMENT_MAPPING.format(
412                        name.getNamespaceURI(),name.getLocalPart()), anno));
413                // recover by ignoring this substitution declaration
414            } else
415                substitutionHead.addSubstitutionMember(this);
416        } else
417            substitutionHead = null;
418        super.link();
419    }
420
421    private void addSubstitutionMember(ElementInfoImpl<T,C,F,M> child) {
422        if(substitutionMembers==null)
423            substitutionMembers = new FinalArrayList<ElementInfoImpl<T,C,F,M>>();
424        substitutionMembers.add(child);
425    }
426
427    public Location getLocation() {
428        return nav().getMethodLocation(method);
429    }
430}
431