1 /*
2  * Copyright (c) 1997, 2011, 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.tools.internal.xjc.generator.bean.field;
27 
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.List;
31 
32 import javax.xml.bind.annotation.W3CDomHandler;
33 import javax.xml.bind.annotation.XmlList;
34 import javax.xml.bind.annotation.XmlMixed;
35 import javax.xml.bind.annotation.XmlNsForm;
36 import javax.xml.bind.annotation.XmlValue;
37 import javax.xml.bind.annotation.XmlInlineBinaryData;
38 import javax.xml.namespace.QName;
39 
40 import com.sun.codemodel.internal.JAnnotatable;
41 import com.sun.codemodel.internal.JClass;
42 import com.sun.codemodel.internal.JCodeModel;
43 import com.sun.codemodel.internal.JExpr;
44 import com.sun.codemodel.internal.JExpression;
45 import com.sun.codemodel.internal.JFieldVar;
46 import com.sun.codemodel.internal.JMod;
47 import com.sun.codemodel.internal.JType;
48 import com.sun.tools.internal.xjc.generator.annotation.spec.XmlAnyElementWriter;
49 import com.sun.tools.internal.xjc.generator.annotation.spec.XmlAttributeWriter;
50 import com.sun.tools.internal.xjc.generator.annotation.spec.XmlElementRefWriter;
51 import com.sun.tools.internal.xjc.generator.annotation.spec.XmlElementRefsWriter;
52 import com.sun.tools.internal.xjc.generator.annotation.spec.XmlElementWriter;
53 import com.sun.tools.internal.xjc.generator.annotation.spec.XmlElementsWriter;
54 import com.sun.tools.internal.xjc.generator.annotation.spec.XmlSchemaTypeWriter;
55 import com.sun.tools.internal.xjc.generator.bean.ClassOutlineImpl;
56 import com.sun.tools.internal.xjc.model.CAttributePropertyInfo;
57 import com.sun.tools.internal.xjc.model.CElement;
58 import com.sun.tools.internal.xjc.model.CElementInfo;
59 import com.sun.tools.internal.xjc.model.CElementPropertyInfo;
60 import com.sun.tools.internal.xjc.model.CPropertyInfo;
61 import com.sun.tools.internal.xjc.model.CReferencePropertyInfo;
62 import com.sun.tools.internal.xjc.model.CTypeInfo;
63 import com.sun.tools.internal.xjc.model.CTypeRef;
64 import com.sun.tools.internal.xjc.model.CValuePropertyInfo;
65 import com.sun.tools.internal.xjc.model.nav.NClass;
66 import com.sun.tools.internal.xjc.outline.Aspect;
67 import static com.sun.tools.internal.xjc.outline.Aspect.IMPLEMENTATION;
68 import com.sun.tools.internal.xjc.outline.ClassOutline;
69 import com.sun.tools.internal.xjc.outline.FieldAccessor;
70 import com.sun.tools.internal.xjc.outline.FieldOutline;
71 import com.sun.tools.internal.xjc.reader.TypeUtil;
72 import com.sun.tools.internal.xjc.Options;
73 import com.sun.tools.internal.xjc.api.SpecVersion;
74 import com.sun.xml.internal.bind.api.impl.NameConverter;
75 import com.sun.xml.internal.bind.v2.TODO;
76 
77 /**
78  * Useful base class for implementing {@link FieldOutline}.
79  *
80  * <p>
81  * This class just provides a few utility methods and keep some
82  * important variables so that they can be readily accessed any time.
83  *
84  * @author
85  *     Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
86  */
87 abstract class AbstractField implements FieldOutline {
88 
89     protected final ClassOutlineImpl outline;
90 
91     protected final CPropertyInfo prop;
92 
93     protected final JCodeModel codeModel;
94 
95     /**
96      * The type of this field, which can hold all the possible types.
97      */
98     protected final JType implType;
99 
100     /**
101      * The publicly visible type of this field.
102      * If we are generating value classes implType==exposedType.
103      */
104     protected final JType exposedType;
105 
AbstractField( ClassOutlineImpl outline, CPropertyInfo prop )106     protected AbstractField( ClassOutlineImpl outline, CPropertyInfo prop ) {
107         this.outline = outline;
108         this.prop = prop;
109         this.codeModel = outline.parent().getCodeModel();
110         this.implType = getType(IMPLEMENTATION);
111         this.exposedType = getType(Aspect.EXPOSED);
112     }
113 
parent()114     public final ClassOutline parent() {
115         return outline;
116     }
117 
getPropertyInfo()118     public final CPropertyInfo getPropertyInfo() {
119         return prop;
120     }
121 
122 
123     /**
124      * Annotate the field according to the recipes given as {@link CPropertyInfo}.
125      */
annotate( JAnnotatable field )126     protected void annotate( JAnnotatable field ) {
127 
128         assert(field!=null);
129 
130         /*
131         TODO: consider moving this logic to somewhere else
132         so that it can be better shared, for how a field gets
133         annotated doesn't really depend on how we generate accessors.
134 
135         so perhaps we should separate those two.
136         */
137 
138         // TODO: consider a visitor
139         if (prop instanceof CAttributePropertyInfo) {
140             annotateAttribute(field);
141         } else if (prop instanceof CElementPropertyInfo) {
142             annotateElement(field);
143         } else if (prop instanceof CValuePropertyInfo) {
144             field.annotate(XmlValue.class);
145         } else if (prop instanceof CReferencePropertyInfo) {
146             annotateReference(field);
147         }
148 
149         outline.parent().generateAdapterIfNecessary(prop,field);
150 
151         QName st = prop.getSchemaType();
152         if(st!=null)
153             field.annotate2(XmlSchemaTypeWriter.class)
154                 .name(st.getLocalPart())
155                 .namespace(st.getNamespaceURI());
156 
157         if(prop.inlineBinaryData())
158             field.annotate(XmlInlineBinaryData.class);
159     }
160 
annotateReference(JAnnotatable field)161     private void annotateReference(JAnnotatable field) {
162         CReferencePropertyInfo rp = (CReferencePropertyInfo) prop;
163 
164         TODO.prototype();
165         // this is just a quick hack to get the basic test working
166 
167         Collection<CElement> elements = rp.getElements();
168 
169         XmlElementRefWriter refw;
170         if(elements.size()==1) {
171             refw = field.annotate2(XmlElementRefWriter.class);
172             CElement e = elements.iterator().next();
173             refw.name(e.getElementName().getLocalPart())
174                 .namespace(e.getElementName().getNamespaceURI())
175                 .type(e.getType().toType(outline.parent(),IMPLEMENTATION));
176             if(getOptions().target.isLaterThan(SpecVersion.V2_2))
177                 refw.required(rp.isRequired());
178         } else
179         if(elements.size()>1) {
180             XmlElementRefsWriter refsw = field.annotate2(XmlElementRefsWriter.class);
181             for( CElement e : elements ) {
182                 refw = refsw.value();
183                 refw.name(e.getElementName().getLocalPart())
184                     .namespace(e.getElementName().getNamespaceURI())
185                     .type(e.getType().toType(outline.parent(),IMPLEMENTATION));
186                 if(getOptions().target.isLaterThan(SpecVersion.V2_2))
187                     refw.required(rp.isRequired());
188             }
189         }
190 
191         if(rp.isMixed())
192             field.annotate(XmlMixed.class);
193 
194         NClass dh = rp.getDOMHandler();
195         if(dh!=null) {
196             XmlAnyElementWriter xaew = field.annotate2(XmlAnyElementWriter.class);
197             xaew.lax(rp.getWildcard().allowTypedObject);
198 
199             final JClass value = dh.toType(outline.parent(),IMPLEMENTATION);
200             if(!value.equals(codeModel.ref(W3CDomHandler.class))) {
201                 xaew.value(value);
202             }
203         }
204 
205     }
206 
207     /**
208      * Annotate the element property 'field'
209      */
annotateElement(JAnnotatable field)210     private void annotateElement(JAnnotatable field) {
211         CElementPropertyInfo ep = (CElementPropertyInfo) prop;
212         List<CTypeRef> types = ep.getTypes();
213 
214         if(ep.isValueList()) {
215             field.annotate(XmlList.class);
216         }
217 
218         assert ep.getXmlName()==null;
219 //        if( eName!=null ) { // wrapper
220 //            XmlElementWrapperWriter xcw = field.annotate2(XmlElementWrapperWriter.class);
221 //            xcw.name(eName.getLocalPart())
222 //               .namespace(eName.getNamespaceURI());
223 //        }
224 
225         if (types.size() == 1) {
226             CTypeRef t = types.get(0);
227             writeXmlElementAnnotation(field, t, resolve(t,IMPLEMENTATION), false);
228         } else {
229             for (CTypeRef t : types) {
230                 // generate @XmlElements
231                 writeXmlElementAnnotation(field, t, resolve(t,IMPLEMENTATION), true);
232             }
233             xesw = null;
234         }
235     }
236 
237     /**
238      * Generate the simplest XmlElement annotation possible taking all semantic optimizations
239      * into account.  This method is essentially equivalent to:
240      *
241      *     xew.name(ctype.getTagName().getLocalPart())
242      *        .namespace(ctype.getTagName().getNamespaceURI())
243      *        .type(jtype)
244      *        .defaultValue(ctype.getDefaultValue());
245      *
246      * @param field
247      * @param ctype
248      * @param jtype
249      * @param checkWrapper true if the method might need to generate XmlElements
250      */
writeXmlElementAnnotation( JAnnotatable field, CTypeRef ctype, JType jtype, boolean checkWrapper )251     private void writeXmlElementAnnotation( JAnnotatable field, CTypeRef ctype, JType jtype,
252                                             boolean checkWrapper ) {
253 
254         // lazily create - we don't know if we need to generate anything yet
255         XmlElementWriter xew = null;
256 
257         // these values are used to determine how to optimize the generated annotation
258         XmlNsForm formDefault = parent()._package().getElementFormDefault();
259         String propName = prop.getName(false);
260 
261         String enclosingTypeNS;
262 
263         if(parent().target.getTypeName()==null)
264             enclosingTypeNS = parent()._package().getMostUsedNamespaceURI();
265         else
266             enclosingTypeNS = parent().target.getTypeName().getNamespaceURI();
267 
268         // generate the name property?
269         String generatedName = ctype.getTagName().getLocalPart();
270         if(!generatedName.equals(propName)) {
271             if(xew == null) xew = getXew(checkWrapper, field);
272             xew.name(generatedName);
273         }
274 
275         // generate the namespace property?
276         String generatedNS = ctype.getTagName().getNamespaceURI();
277         if (((formDefault == XmlNsForm.QUALIFIED) && !generatedNS.equals(enclosingTypeNS)) ||
278                 ((formDefault == XmlNsForm.UNQUALIFIED) && !generatedNS.equals(""))) {
279             if(xew == null) xew = getXew(checkWrapper, field);
280             xew.namespace(generatedNS);
281         }
282 
283         // generate the required() property?
284         CElementPropertyInfo ep = (CElementPropertyInfo) prop;
285         if(ep.isRequired() && exposedType.isReference()) {
286             if(xew == null) xew = getXew(checkWrapper, field);
287             xew.required(true);
288         }
289 
290         // generate the type property?
291 
292         // I'm not too sure if this is the right place to handle this, but
293         // if the schema definition is requiring this element, we should point to a primitive type,
294         // not wrapper type (to correctly carry forward the required semantics.)
295         // if it's a collection, we can't use a primitive, however.
296         if(ep.isRequired() && !prop.isCollection())
297             jtype = jtype.unboxify();
298 
299         // when generating code for 1.4, the runtime can't infer that ArrayList<Foo> derives
300         // from Collection<Foo> (because List isn't parameterized), so always expclitly
301         // generate @XmlElement(type=...)
302         if( !jtype.equals(exposedType) || (getOptions().runtime14 && prop.isCollection())) {
303             if(xew == null) xew = getXew(checkWrapper, field);
304             xew.type(jtype);
305         }
306 
307         // generate defaultValue property?
308         final String defaultValue = ctype.getDefaultValue();
309         if (defaultValue!=null) {
310             if(xew == null) xew = getXew(checkWrapper, field);
311             xew.defaultValue(defaultValue);
312         }
313 
314         // generate the nillable property?
315         if (ctype.isNillable()) {
316             if(xew == null) xew = getXew(checkWrapper, field);
317             xew.nillable(true);
318         }
319     }
320 
321     /**
322      * Gets the {@link Options} in the current compilation context.
323      */
getOptions()324     protected final Options getOptions() {
325         return parent().parent().getModel().options;
326     }
327 
328     // ugly hack to lazily create
329     private XmlElementsWriter xesw = null;
330 
getXew(boolean checkWrapper, JAnnotatable field)331     private XmlElementWriter getXew(boolean checkWrapper, JAnnotatable field) {
332         XmlElementWriter xew;
333         if(checkWrapper) {
334             if(xesw==null) {
335                 xesw = field.annotate2(XmlElementsWriter.class);
336             }
337             xew = xesw.value();
338         } else {
339             xew = field.annotate2(XmlElementWriter.class);
340         }
341         return xew;
342     }
343 
344     /**
345      * Annotate the attribute property 'field'
346      */
annotateAttribute(JAnnotatable field)347     private void annotateAttribute(JAnnotatable field) {
348         CAttributePropertyInfo ap = (CAttributePropertyInfo) prop;
349         QName attName = ap.getXmlName();
350 
351         // [RESULT]
352         // @XmlAttribute(name="foo", required=true, namespace="bar://baz")
353         XmlAttributeWriter xaw = field.annotate2(XmlAttributeWriter.class);
354 
355         final String generatedName = attName.getLocalPart();
356         final String generatedNS = attName.getNamespaceURI();
357 
358         // Issue 570; always force generating name="" when do it when globalBindings underscoreBinding is set to non default value
359         // generate name property?
360         if(!generatedName.equals(ap.getName(false)) || !generatedName.equals(ap.getName(true)) || (outline.parent().getModel().getNameConverter() != NameConverter.standard)) {
361             xaw.name(generatedName);
362         }
363 
364         // generate namespace property?
365         if(!generatedNS.equals("")) { // assume attributeFormDefault == unqualified
366             xaw.namespace(generatedNS);
367         }
368 
369         // generate required property?
370         if(ap.isRequired()) {
371             xaw.required(true);
372         }
373     }
374 
375     /**
376      * Useful base class for implementing {@link FieldAccessor}.
377      */
378     protected abstract class Accessor implements FieldAccessor {
379 
380         /**
381          * Evaluates to the target object this accessor should access.
382          */
383         protected final JExpression $target;
384 
Accessor( JExpression $target )385         protected Accessor( JExpression $target ) {
386             this.$target = $target;
387         }
388 
owner()389         public final FieldOutline owner() {
390             return AbstractField.this;
391         }
392 
getPropertyInfo()393         public final CPropertyInfo getPropertyInfo() {
394             return prop;
395         }
396     }
397 
398 
399 //
400 //
401 //     utility methods
402 //
403 //
404 
405     /**
406      * Generates the field declaration.
407      */
generateField( JType type )408     protected final JFieldVar generateField( JType type ) {
409         return outline.implClass.field( JMod.PROTECTED, type, prop.getName(false) );
410     }
411 
412     /**
413      * Case from {@link #exposedType} to {@link #implType} if necessary.
414      */
castToImplType( JExpression exp )415     protected final JExpression castToImplType( JExpression exp ) {
416         if(implType==exposedType)
417             return exp;
418         else
419             return JExpr.cast(implType,exp);
420     }
421 
422     /**
423      * Compute the type of a {@link CPropertyInfo}
424      * @param aspect
425      */
getType(final Aspect aspect)426     protected JType getType(final Aspect aspect) {
427         if(prop.getAdapter()!=null)
428             return prop.getAdapter().customType.toType(outline.parent(),aspect);
429 
430         final class TypeList extends ArrayList<JType> {
431             void add( CTypeInfo t ) {
432                 add( t.getType().toType(outline.parent(),aspect) );
433                 if(t instanceof CElementInfo) {
434                     // UGLY. element substitution is implemented in a way that
435                     // the derived elements are not assignable to base elements.
436                     // so when we compute the signature, we have to take derived types
437                     // into account
438                     add( ((CElementInfo)t).getSubstitutionMembers());
439                 }
440             }
441 
442             void add( Collection<? extends CTypeInfo> col ) {
443                 for (CTypeInfo typeInfo : col)
444                     add(typeInfo);
445             }
446         }
447         TypeList r = new TypeList();
448         r.add(prop.ref());
449 
450         JType t;
451         if(prop.baseType!=null)
452             t = prop.baseType;
453         else
454             t = TypeUtil.getCommonBaseType(codeModel,r);
455 
456         // if item type is unboxable, convert t=Integer -> t=int
457         // the in-memory data structure can't have primitives directly,
458         // but this guarantees that items cannot legal hold null,
459         // which helps us improve the boundary signature between our
460         // data structure and user code
461         if(prop.isUnboxable())
462             t = t.unboxify();
463         return t;
464     }
465 
466     /**
467      * Returns contents to be added to javadoc.
468      */
listPossibleTypes( CPropertyInfo prop )469     protected final List<Object> listPossibleTypes( CPropertyInfo prop ) {
470         List<Object> r = new ArrayList<Object>();
471         for( CTypeInfo tt : prop.ref() ) {
472             JType t = tt.getType().toType(outline.parent(),Aspect.EXPOSED);
473             if( t.isPrimitive() || t.isArray() )
474                 r.add(t.fullName());
475             else {
476                 r.add(t);
477                 r.add("\n");
478             }
479         }
480 
481         return r;
482     }
483 
484     /**
485      * return the Java type for the given type reference in the model.
486      */
resolve(CTypeRef typeRef,Aspect a)487     private JType resolve(CTypeRef typeRef,Aspect a) {
488         return outline.parent().resolve(typeRef,a);
489     }
490 
491 }
492