View Javadoc
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.reader.xmlschema.bindinfo;
27  
28  import javax.xml.bind.DatatypeConverter;
29  import javax.xml.bind.annotation.XmlAttribute;
30  import javax.xml.bind.annotation.XmlRootElement;
31  import javax.xml.bind.annotation.adapters.XmlAdapter;
32  import javax.xml.namespace.QName;
33  
34  import com.sun.codemodel.internal.JClass;
35  import com.sun.codemodel.internal.JClassAlreadyExistsException;
36  import com.sun.codemodel.internal.JCodeModel;
37  import com.sun.codemodel.internal.JDefinedClass;
38  import com.sun.codemodel.internal.JExpr;
39  import com.sun.codemodel.internal.JExpression;
40  import com.sun.codemodel.internal.JMethod;
41  import com.sun.codemodel.internal.JMod;
42  import com.sun.codemodel.internal.JPackage;
43  import com.sun.codemodel.internal.JType;
44  import com.sun.codemodel.internal.JVar;
45  import com.sun.codemodel.internal.JConditional;
46  import com.sun.tools.internal.xjc.ErrorReceiver;
47  import com.sun.tools.internal.xjc.model.CAdapter;
48  import com.sun.tools.internal.xjc.model.CBuiltinLeafInfo;
49  import com.sun.tools.internal.xjc.model.TypeUse;
50  import com.sun.tools.internal.xjc.model.TypeUseFactory;
51  import com.sun.tools.internal.xjc.reader.Const;
52  import com.sun.tools.internal.xjc.reader.Ring;
53  import com.sun.tools.internal.xjc.reader.TypeUtil;
54  import com.sun.tools.internal.xjc.reader.xmlschema.ClassSelector;
55  import com.sun.xml.internal.bind.v2.WellKnownNamespace;
56  import com.sun.xml.internal.xsom.XSSimpleType;
57  
58  import org.xml.sax.Locator;
59  
60  /**
61   * Conversion declaration.
62   *
63   * <p>
64   * A conversion declaration specifies how an XML type gets mapped
65   * to a Java type.
66   *
67   * @author
68   *     Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
69   */
70  public abstract class BIConversion extends AbstractDeclarationImpl {
71      @Deprecated
72      public BIConversion( Locator loc ) {
73          super(loc);
74      }
75  
76      protected BIConversion() {
77      }
78  
79      /**
80       * Gets the {@link TypeUse} object that this conversion represents.
81       * <p>
82       * The returned {@link TypeUse} object is properly adapted.
83       *
84       * @param owner
85       *      A {@link BIConversion} is always associated with one
86       *      {@link XSSimpleType}, but that's not always available
87       *      when a {@link BIConversion} is built. So we pass this
88       *      as a parameter to this method.
89       */
90      public abstract TypeUse getTypeUse( XSSimpleType owner );
91  
92      public QName getName() { return NAME; }
93  
94      /** Name of the conversion declaration. */
95      public static final QName NAME = new QName(
96          Const.JAXB_NSURI, "conversion" );
97  
98      /**
99       * Implementation that returns a statically-determined constant {@link TypeUse}.
100      */
101     public static final class Static extends BIConversion {
102         /**
103          * Always non-null.
104          */
105         private final TypeUse transducer;
106 
107         public Static(Locator loc, TypeUse transducer) {
108             super(loc);
109             this.transducer = transducer;
110         }
111 
112         public TypeUse getTypeUse(XSSimpleType owner) {
113             return transducer;
114         }
115     }
116 
117     /**
118      * User-specified &lt;javaType> customization.
119      *
120      * The parse/print methods are allowed to be null,
121      * and their default values are determined based on the
122      * owner of the token.
123      */
124     @XmlRootElement(name="javaType")
125     public static class User extends BIConversion {
126         @XmlAttribute
127         private String parseMethod;
128         @XmlAttribute
129         private String printMethod;
130         @XmlAttribute(name="name")
131         private String type = "java.lang.String";
132 
133         /**
134          * If null, computed from {@link #type}.
135          * Sometimes this can be set instead of {@link #type}.
136          */
137         private JType inMemoryType;
138 
139         public User(Locator loc, String parseMethod, String printMethod, JType inMemoryType) {
140             super(loc);
141             this.parseMethod = parseMethod;
142             this.printMethod = printMethod;
143             this.inMemoryType = inMemoryType;
144         }
145 
146         public User() {
147         }
148 
149         /**
150          * Cache used by {@link #getTypeUse(XSSimpleType)} to improve the performance.
151          */
152         private TypeUse typeUse;
153 
154         public TypeUse getTypeUse(XSSimpleType owner) {
155             if(typeUse!=null)
156                 return typeUse;
157 
158             JCodeModel cm = getCodeModel();
159 
160             if(inMemoryType==null)
161                 inMemoryType = TypeUtil.getType(cm,type,Ring.get(ErrorReceiver.class),getLocation());
162 
163             JDefinedClass adapter = generateAdapter(parseMethodFor(owner),printMethodFor(owner),owner);
164 
165             // XmlJavaType customization always converts between string and an user-defined type.
166             typeUse = TypeUseFactory.adapt(CBuiltinLeafInfo.STRING,new CAdapter(adapter));
167 
168             return typeUse;
169         }
170 
171         /**
172          * generate the adapter class.
173          */
174         private JDefinedClass generateAdapter(String parseMethod, String printMethod,XSSimpleType owner) {
175             JDefinedClass adapter = null;
176 
177             int id = 1;
178             while(adapter==null) {
179                 try {
180                     JPackage pkg = Ring.get(ClassSelector.class).getClassScope().getOwnerPackage();
181                     adapter = pkg._class("Adapter"+id);
182                 } catch (JClassAlreadyExistsException e) {
183                     // try another name in search for an unique name.
184                     // this isn't too efficient, but we expect people to usually use
185                     // a very small number of adapters.
186                     id++;
187                 }
188             }
189 
190             JClass bim = inMemoryType.boxify();
191 
192             adapter._extends(getCodeModel().ref(XmlAdapter.class).narrow(String.class).narrow(bim));
193 
194             JMethod unmarshal = adapter.method(JMod.PUBLIC, bim, "unmarshal");
195             JVar $value = unmarshal.param(String.class, "value");
196 
197             JExpression inv;
198 
199             if( parseMethod.equals("new") ) {
200                 // "new" indicates that the constructor of the target type
201                 // will do the unmarshalling.
202 
203                 // RESULT: new <type>()
204                 inv = JExpr._new(bim).arg($value);
205             } else {
206                 int idx = parseMethod.lastIndexOf('.');
207                 if(idx<0) {
208                     // parseMethod specifies the static method of the target type
209                     // which will do the unmarshalling.
210 
211                     // because of an error check at the constructor,
212                     // we can safely assume that this cast works.
213                     inv = bim.staticInvoke(parseMethod).arg($value);
214                 } else {
215                     inv = JExpr.direct(parseMethod+"(value)");
216                 }
217             }
218             unmarshal.body()._return(inv);
219 
220 
221             JMethod marshal = adapter.method(JMod.PUBLIC, String.class, "marshal");
222             $value = marshal.param(bim,"value");
223 
224             if(printMethod.startsWith("javax.xml.bind.DatatypeConverter.")) {
225                 // UGLY: if this conversion is the system-driven conversion,
226                 // check for null
227                 marshal.body()._if($value.eq(JExpr._null()))._then()._return(JExpr._null());
228             }
229 
230             int idx = printMethod.lastIndexOf('.');
231             if(idx<0) {
232                 // printMethod specifies a method in the target type
233                 // which performs the serialization.
234 
235                 // RESULT: <value>.<method>()
236                 inv = $value.invoke(printMethod);
237 
238                 // check value is not null ... if(value == null) return null;
239                 JConditional jcon = marshal.body()._if($value.eq(JExpr._null()));
240                 jcon._then()._return(JExpr._null());
241             } else {
242                 // RESULT: <className>.<method>(<value>)
243                 if(this.printMethod==null) {
244                     // HACK HACK HACK
245                     JType t = inMemoryType.unboxify();
246                     inv = JExpr.direct(printMethod+"(("+findBaseConversion(owner).toLowerCase()+")("+t.fullName()+")value)");
247                 } else
248                     inv = JExpr.direct(printMethod+"(value)");
249             }
250             marshal.body()._return(inv);
251 
252             return adapter;
253         }
254 
255         private String printMethodFor(XSSimpleType owner) {
256             if(printMethod!=null)   return printMethod;
257 
258             if(inMemoryType.unboxify().isPrimitive()) {
259                 String method = getConversionMethod("print",owner);
260                 if(method!=null)
261                     return method;
262             }
263 
264             return "toString";
265         }
266 
267         private String parseMethodFor(XSSimpleType owner) {
268             if(parseMethod!=null)   return parseMethod;
269 
270             if(inMemoryType.unboxify().isPrimitive()) {
271                 String method = getConversionMethod("parse", owner);
272                 if(method!=null) {
273                     // this cast is necessary for conversion between primitive Java types
274                     return '('+inMemoryType.unboxify().fullName()+')'+method;
275                 }
276             }
277 
278             return "new";
279         }
280 
281         private static final String[] knownBases = new String[]{
282             "Float", "Double", "Byte", "Short", "Int", "Long", "Boolean"
283         };
284 
285         private String getConversionMethod(String methodPrefix, XSSimpleType owner) {
286             String bc = findBaseConversion(owner);
287             if(bc==null)    return null;
288 
289             return DatatypeConverter.class.getName()+'.'+methodPrefix+bc;
290         }
291 
292         private String findBaseConversion(XSSimpleType owner) {
293             // find the base simple type mapping.
294             for( XSSimpleType st=owner; st!=null; st = st.getSimpleBaseType() ) {
295                 if( !WellKnownNamespace.XML_SCHEMA.equals(st.getTargetNamespace()) )
296                     continue;   // user-defined type
297 
298                 String name = st.getName().intern();
299                 for( String s : knownBases )
300                     if(name.equalsIgnoreCase(s))
301                         return s;
302             }
303 
304             return null;
305         }
306 
307         public QName getName() { return NAME; }
308 
309         /** Name of the conversion declaration. */
310         public static final QName NAME = new QName(
311             Const.JAXB_NSURI, "javaType" );
312     }
313 
314     @XmlRootElement(name="javaType",namespace=Const.XJC_EXTENSION_URI)
315     public static class UserAdapter extends BIConversion {
316         @XmlAttribute(name="name")
317         private String type = null;
318 
319         @XmlAttribute
320         private String adapter = null;
321 
322         private TypeUse typeUse;
323 
324         public TypeUse getTypeUse(XSSimpleType owner) {
325             if(typeUse!=null)
326                 return typeUse;
327 
328             JCodeModel cm = getCodeModel();
329 
330             JDefinedClass a;
331             try {
332                 a = cm._class(adapter);
333                 a.hide();   // we assume this is given by the user
334                 a._extends(cm.ref(XmlAdapter.class).narrow(String.class).narrow(
335                         cm.ref(type)));
336             } catch (JClassAlreadyExistsException e) {
337                 a = e.getExistingClass();
338             }
339 
340             // TODO: it's not correct to say that it adapts from String,
341             // but OTOH I don't think we can compute that.
342             typeUse = TypeUseFactory.adapt(
343                     CBuiltinLeafInfo.STRING,
344                     new CAdapter(a));
345 
346             return typeUse;
347         }
348     }
349 }