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 
  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 }