PropertyInfoImpl.java revision 524:dcaa586ab756
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.util.Collection;
29import java.lang.annotation.Annotation;
30
31import javax.activation.MimeType;
32import javax.xml.bind.annotation.XmlAttachmentRef;
33import javax.xml.bind.annotation.XmlElement;
34import javax.xml.bind.annotation.XmlElementWrapper;
35import javax.xml.bind.annotation.XmlID;
36import javax.xml.bind.annotation.XmlIDREF;
37import javax.xml.bind.annotation.XmlInlineBinaryData;
38import javax.xml.bind.annotation.XmlMimeType;
39import javax.xml.bind.annotation.XmlSchema;
40import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
41import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
42import javax.xml.bind.annotation.adapters.XmlAdapter;
43import javax.xml.namespace.QName;
44
45import com.sun.xml.internal.bind.v2.TODO;
46import com.sun.xml.internal.bind.v2.model.annotation.AnnotationReader;
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.ID;
50import com.sun.xml.internal.bind.v2.model.core.PropertyInfo;
51import com.sun.xml.internal.bind.v2.model.core.TypeInfo;
52import com.sun.xml.internal.bind.v2.model.core.TypeInfoSet;
53import com.sun.xml.internal.bind.v2.model.nav.Navigator;
54import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException;
55import com.sun.xml.internal.bind.v2.runtime.Location;
56import com.sun.xml.internal.bind.v2.runtime.SwaRefAdapter;
57
58/**
59 * Default partial implementation for {@link PropertyInfo}.
60 *
61 * @author Kohsuke Kawaguchi
62 */
63abstract class PropertyInfoImpl<T,C,F,M>
64    implements PropertyInfo<T,C>, Locatable, Comparable<PropertyInfoImpl> /*by their names*/ {
65
66    /**
67     * Object that reads annotations.
68     */
69    protected final PropertySeed<T,C,F,M> seed;
70
71    private final boolean isCollection;
72
73    private final ID id;
74
75    private final MimeType expectedMimeType;
76    private final boolean inlineBinary;
77    private final QName schemaType;
78
79    protected final ClassInfoImpl<T,C,F,M> parent;
80
81    private final Adapter<T,C> adapter;
82
83    protected PropertyInfoImpl(ClassInfoImpl<T,C,F,M> parent, PropertySeed<T,C,F,M> spi) {
84        this.seed = spi;
85        this.parent = parent;
86
87        if(parent==null)
88            /*
89                Various people reported a bug where this parameter is somehow null.
90                In an attempt to catch the error better, let's do an explicit check here.
91
92                http://forums.java.net/jive/thread.jspa?threadID=18479
93                http://forums.java.net/jive/thread.jspa?messageID=165946
94             */
95            throw new AssertionError();
96
97        MimeType mt = Util.calcExpectedMediaType(seed,parent.builder);
98        if(mt!=null && !kind().canHaveXmlMimeType) {
99            parent.builder.reportError(new IllegalAnnotationException(
100                Messages.ILLEGAL_ANNOTATION.format(XmlMimeType.class.getName()),
101                seed.readAnnotation(XmlMimeType.class)
102            ));
103            mt = null;
104        }
105        this.expectedMimeType = mt;
106        this.inlineBinary = seed.hasAnnotation(XmlInlineBinaryData.class);
107
108        T t = seed.getRawType();
109
110        // check if there's an adapter applicable to the whole property
111        XmlJavaTypeAdapter xjta = getApplicableAdapter(t);
112        if(xjta!=null) {
113            isCollection = false;
114            adapter = new Adapter<T,C>(xjta,reader(),nav());
115        } else {
116            // check if the adapter is applicable to the individual item in the property
117
118            this.isCollection = nav().isSubClassOf(t, nav().ref(Collection.class))
119                             || nav().isArrayButNotByteArray(t);
120
121            xjta = getApplicableAdapter(getIndividualType());
122            if(xjta==null) {
123                // ugly ugly hack, but we implement swaRef as adapter
124                XmlAttachmentRef xsa = seed.readAnnotation(XmlAttachmentRef.class);
125                if(xsa!=null) {
126                    parent.builder.hasSwaRef = true;
127                    adapter = new Adapter<T,C>(nav().asDecl(SwaRefAdapter.class),nav());
128                } else {
129                    adapter = null;
130
131                    // if this field has adapter annotation but not applicable,
132                    // that must be an error of the user
133                    xjta = seed.readAnnotation(XmlJavaTypeAdapter.class);
134                    if(xjta!=null) {
135                        T ad = reader().getClassValue(xjta,"value");
136                        parent.builder.reportError(new IllegalAnnotationException(
137                            Messages.UNMATCHABLE_ADAPTER.format(
138                                    nav().getTypeName(ad), nav().getTypeName(t)),
139                            xjta
140                        ));
141                    }
142                }
143            } else {
144                adapter = new Adapter<T,C>(xjta,reader(),nav());
145            }
146        }
147
148        this.id = calcId();
149        this.schemaType = Util.calcSchemaType(reader(),seed,parent.clazz,
150                getIndividualType(),this);
151    }
152
153
154    public ClassInfoImpl<T,C,F,M> parent() {
155        return parent;
156    }
157
158    protected final Navigator<T,C,F,M> nav() {
159        return parent.nav();
160    }
161    protected final AnnotationReader<T,C,F,M> reader() {
162        return parent.reader();
163    }
164
165    public T getRawType() {
166        return seed.getRawType();
167    }
168
169    public T getIndividualType() {
170        if(adapter!=null)
171            return adapter.defaultType;
172        T raw = getRawType();
173        if(!isCollection()) {
174            return raw;
175        } else {
176            if(nav().isArrayButNotByteArray(raw))
177                return nav().getComponentType(raw);
178
179            T bt = nav().getBaseClass(raw, nav().asDecl(Collection.class) );
180            if(nav().isParameterizedType(bt))
181                return nav().getTypeArgument(bt,0);
182            else
183                return nav().ref(Object.class);
184        }
185    }
186
187    public final String getName() {
188        return seed.getName();
189    }
190
191    /**
192     * Checks if the given adapter is applicable to the declared property type.
193     */
194    private boolean isApplicable(XmlJavaTypeAdapter jta, T declaredType ) {
195        if(jta==null)   return false;
196
197        T type = reader().getClassValue(jta,"type");
198        if(nav().isSameType(declaredType, type))
199            return true;    // for types explicitly marked in XmlJavaTypeAdapter.type()
200
201        T ad = reader().getClassValue(jta,"value");
202        T ba = nav().getBaseClass(ad, nav().asDecl(XmlAdapter.class));
203        if(!nav().isParameterizedType(ba))
204            return true;   // can't check type applicability. assume Object, which means applicable to any.
205        T inMemType = nav().getTypeArgument(ba, 1);
206
207        return nav().isSubClassOf(declaredType,inMemType);
208    }
209
210    private XmlJavaTypeAdapter getApplicableAdapter(T type) {
211        XmlJavaTypeAdapter jta = seed.readAnnotation(XmlJavaTypeAdapter.class);
212        if(jta!=null && isApplicable(jta,type))
213            return jta;
214
215        // check the applicable adapters on the package
216        XmlJavaTypeAdapters jtas = reader().getPackageAnnotation(XmlJavaTypeAdapters.class, parent.clazz, seed );
217        if(jtas!=null) {
218            for (XmlJavaTypeAdapter xjta : jtas.value()) {
219                if(isApplicable(xjta,type))
220                    return xjta;
221            }
222        }
223        jta = reader().getPackageAnnotation(XmlJavaTypeAdapter.class, parent.clazz, seed );
224        if(isApplicable(jta,type))
225            return jta;
226
227        // then on the target class
228        C refType = nav().asDecl(type);
229        if(refType!=null) {
230            jta = reader().getClassAnnotation(XmlJavaTypeAdapter.class, refType, seed );
231            if(jta!=null && isApplicable(jta,type)) // the one on the type always apply.
232                return jta;
233        }
234
235        return null;
236    }
237
238    /**
239     * This is the default implementation of the getAdapter method
240     * defined on many of the {@link PropertyInfo}-derived classes.
241     */
242    public Adapter<T,C> getAdapter() {
243        return adapter;
244    }
245
246
247    public final String displayName() {
248        return nav().getClassName(parent.getClazz())+'#'+getName();
249    }
250
251    public final ID id() {
252        return id;
253    }
254
255    private ID calcId() {
256        if(seed.hasAnnotation(XmlID.class)) {
257            // check the type
258            if(!nav().isSameType(getIndividualType(), nav().ref(String.class)))
259                parent.builder.reportError(new IllegalAnnotationException(
260                    Messages.ID_MUST_BE_STRING.format(getName()), seed )
261                );
262            return ID.ID;
263        } else
264        if(seed.hasAnnotation(XmlIDREF.class)) {
265            return ID.IDREF;
266        } else {
267            return ID.NONE;
268        }
269    }
270
271    public final MimeType getExpectedMimeType() {
272        return expectedMimeType;
273    }
274
275    public final boolean inlineBinaryData() {
276        return inlineBinary;
277    }
278
279    public final QName getSchemaType() {
280        return schemaType;
281    }
282
283    public final boolean isCollection() {
284        return isCollection;
285    }
286
287    /**
288     * Called after all the {@link TypeInfo}s are collected into the governing {@link TypeInfoSet}.
289     *
290     * Derived class can do additional actions to complete the model.
291     */
292    protected void link() {
293        if(id==ID.IDREF) {
294            // make sure that the refereced type has ID
295            for (TypeInfo<T,C> ti : ref()) {
296                if(!ti.canBeReferencedByIDREF())
297                    parent.builder.reportError(new IllegalAnnotationException(
298                    Messages.INVALID_IDREF.format(
299                        parent.builder.nav.getTypeName(ti.getType())), this ));
300            }
301        }
302    }
303
304    /**
305     * A {@link PropertyInfoImpl} is always referenced by its enclosing class,
306     * so return that as the upstream.
307     */
308    public Locatable getUpstream() {
309        return parent;
310    }
311
312    public Location getLocation() {
313        return seed.getLocation();
314    }
315
316
317//
318//
319// convenience methods for derived classes
320//
321//
322
323
324    /**
325     * Computes the tag name from a {@link XmlElement} by taking the defaulting into account.
326     */
327    protected final QName calcXmlName(XmlElement e) {
328        if(e!=null)
329            return calcXmlName(e.namespace(),e.name());
330        else
331            return calcXmlName("##default","##default");
332    }
333
334    /**
335     * Computes the tag name from a {@link XmlElementWrapper} by taking the defaulting into account.
336     */
337    protected final QName calcXmlName(XmlElementWrapper e) {
338        if(e!=null)
339            return calcXmlName(e.namespace(),e.name());
340        else
341            return calcXmlName("##default","##default");
342    }
343
344    private QName calcXmlName(String uri,String local) {
345        // compute the default
346        TODO.checkSpec();
347        if(local.length()==0 || local.equals("##default"))
348            local = seed.getName();
349        if(uri.equals("##default")) {
350            XmlSchema xs = reader().getPackageAnnotation( XmlSchema.class, parent.getClazz(), this );
351            // JAX-RPC doesn't want the default namespace URI swapping to take effect to
352            // local "unqualified" elements. UGLY.
353            if(xs!=null) {
354                switch(xs.elementFormDefault()) {
355                case QUALIFIED:
356                    QName typeName = parent.getTypeName();
357                    if(typeName!=null)
358                        uri = typeName.getNamespaceURI();
359                    else
360                        uri = xs.namespace();
361                    if(uri.length()==0)
362                        uri = parent.builder.defaultNsUri;
363                    break;
364                case UNQUALIFIED:
365                case UNSET:
366                    uri = "";
367                }
368            } else {
369                uri = "";
370            }
371        }
372        return new QName(uri.intern(),local.intern());
373    }
374
375    public int compareTo(PropertyInfoImpl that) {
376        return this.getName().compareTo(that.getName());
377    }
378
379    public final <A extends Annotation> A readAnnotation(Class<A> annotationType) {
380        return seed.readAnnotation(annotationType);
381    }
382
383    public final boolean hasAnnotation(Class<? extends Annotation> annotationType) {
384        return seed.hasAnnotation(annotationType);
385    }
386}
387