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