1 /* 2 * Copyright (c) 1997, 2015, 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.dtd.bindinfo; 27 28 import java.io.IOException; 29 import java.io.StringReader; 30 import java.util.Map; 31 32 import javax.xml.bind.annotation.adapters.XmlAdapter; 33 import javax.xml.parsers.DocumentBuilderFactory; 34 import javax.xml.parsers.ParserConfigurationException; 35 36 import com.sun.codemodel.internal.JClass; 37 import com.sun.codemodel.internal.JClassAlreadyExistsException; 38 import com.sun.codemodel.internal.JCodeModel; 39 import com.sun.codemodel.internal.JDefinedClass; 40 import com.sun.codemodel.internal.JExpr; 41 import com.sun.codemodel.internal.JExpression; 42 import com.sun.codemodel.internal.JMethod; 43 import com.sun.codemodel.internal.JMod; 44 import com.sun.codemodel.internal.JPackage; 45 import com.sun.codemodel.internal.JPrimitiveType; 46 import com.sun.codemodel.internal.JType; 47 import com.sun.codemodel.internal.JVar; 48 import com.sun.tools.internal.xjc.model.CAdapter; 49 import com.sun.tools.internal.xjc.model.CBuiltinLeafInfo; 50 import com.sun.tools.internal.xjc.model.TypeUse; 51 import com.sun.tools.internal.xjc.model.TypeUseFactory; 52 53 import com.sun.xml.internal.bind.v2.util.XmlFactory; 54 import org.w3c.dom.Element; 55 import org.xml.sax.InputSource; 56 import org.xml.sax.Locator; 57 import org.xml.sax.SAXException; 58 59 /** 60 * {@code <conversion>} declaration in the binding file. 61 * This declaration declares a conversion by user-specified methods. 62 */ 63 public class BIUserConversion implements BIConversion 64 { 65 /** 66 * Wraps a given {@code <conversion>} element in the binding file. 67 */ 68 BIUserConversion( BindInfo bi, Element _e ) { 69 this.owner = bi; 70 this.e = _e; 71 } 72 73 private static void add( Map<String,BIConversion> m, BIConversion c ) { 74 m.put( c.name(), c ); 75 } 76 77 /** Adds all built-in conversions into the given map. */ 78 static void addBuiltinConversions( BindInfo bi, Map<String,BIConversion> m ) { 79 add( m, new BIUserConversion( bi, parse("<conversion name='boolean' type='java.lang.Boolean' parse='getBoolean' />"))); 80 add( m, new BIUserConversion( bi, parse("<conversion name='byte' type='java.lang.Byte' parse='parseByte' />"))); 81 add( m, new BIUserConversion( bi, parse("<conversion name='short' type='java.lang.Short' parse='parseShort' />"))); 82 add( m, new BIUserConversion( bi, parse("<conversion name='int' type='java.lang.Integer' parse='parseInt' />"))); 83 add( m, new BIUserConversion( bi, parse("<conversion name='long' type='java.lang.Long' parse='parseLong' />"))); 84 add( m, new BIUserConversion( bi, parse("<conversion name='float' type='java.lang.Float' parse='parseFloat' />"))); 85 add( m, new BIUserConversion( bi, parse("<conversion name='double' type='java.lang.Double' parse='parseDouble' />"))); 86 } 87 88 private static Element parse(String text) { 89 try { 90 //this is parsing well known schemas, do not configure secure processing - always true 91 DocumentBuilderFactory dbf = XmlFactory.createDocumentBuilderFactory(false); 92 InputSource is = new InputSource(new StringReader(text)); 93 return dbf.newDocumentBuilder().parse(is).getDocumentElement(); 94 } catch (SAXException x) { 95 throw new Error(x); 96 } catch (IOException x) { 97 throw new Error(x); 98 } catch (ParserConfigurationException x) { 99 throw new Error(x); 100 } 101 } 102 103 104 /** The owner {@link BindInfo} object to which this object belongs. */ 105 private final BindInfo owner; 106 107 /** {@code <conversion>} element which this object is wrapping. */ 108 private final Element e; 109 110 111 112 /** Gets the location where this declaration is declared. */ 113 public Locator getSourceLocation() { 114 return DOMLocator.getLocationInfo(e); 115 } 116 117 /** Gets the conversion name. */ 118 public String name() { return DOMUtil.getAttribute(e,"name"); } 119 120 /** Gets a transducer for this conversion. */ 121 public TypeUse getTransducer() { 122 123 String ws = DOMUtil.getAttribute(e,"whitespace"); 124 if(ws==null) ws = "collapse"; 125 126 String type = DOMUtil.getAttribute(e,"type"); 127 if(type==null) type=name(); 128 JType t=null; 129 130 int idx = type.lastIndexOf('.'); 131 if(idx<0) { 132 // no package name is specified. 133 try { 134 t = JPrimitiveType.parse(owner.codeModel,type); 135 } catch( IllegalArgumentException ex ) { 136 // otherwise treat it as a class name in the current package 137 type = owner.getTargetPackage().name()+'.'+type; 138 } 139 } 140 if(t==null) { 141 try { 142 // TODO: revisit this later 143 JDefinedClass cls = owner.codeModel._class(type); 144 cls.hide(); 145 t = cls; 146 } catch( JClassAlreadyExistsException ex ) { 147 t = ex.getExistingClass(); 148 } 149 } 150 151 String parse = DOMUtil.getAttribute(e,"parse"); 152 if(parse==null) parse="new"; 153 154 String print = DOMUtil.getAttribute(e,"print"); 155 if(print==null) print="toString"; 156 157 JDefinedClass adapter = generateAdapter(owner.codeModel, parse, print, t.boxify()); 158 159 // XmlJavaType customization always converts between string and an user-defined type. 160 return TypeUseFactory.adapt(CBuiltinLeafInfo.STRING,new CAdapter(adapter)); 161 } 162 163 // TODO: anyway to reuse this code between XML Schema compiler? 164 private JDefinedClass generateAdapter(JCodeModel cm, String parseMethod, String printMethod, JClass inMemoryType) { 165 JDefinedClass adapter = null; 166 167 int id = 1; 168 while(adapter==null) { 169 try { 170 JPackage pkg = owner.getTargetPackage(); 171 adapter = pkg._class("Adapter"+id); 172 } catch (JClassAlreadyExistsException ex) { 173 // try another name in search for an unique name. 174 // this isn't too efficient, but we expect people to usually use 175 // a very small number of adapters. 176 id++; 177 } 178 } 179 180 adapter._extends(cm.ref(XmlAdapter.class).narrow(String.class).narrow(inMemoryType)); 181 182 JMethod unmarshal = adapter.method(JMod.PUBLIC, inMemoryType, "unmarshal"); 183 JVar $value = unmarshal.param(String.class, "value"); 184 185 JExpression inv; 186 187 if( parseMethod.equals("new") ) { 188 // "new" indicates that the constructor of the target type 189 // will do the unmarshalling. 190 191 // RESULT: new <type>() 192 inv = JExpr._new(inMemoryType).arg($value); 193 } else { 194 int idx = parseMethod.lastIndexOf('.'); 195 if(idx<0) { 196 // parseMethod specifies the static method of the target type 197 // which will do the unmarshalling. 198 199 // because of an error check at the constructor, 200 // we can safely assume that this cast works. 201 inv = inMemoryType.staticInvoke(parseMethod).arg($value); 202 } else { 203 inv = JExpr.direct(parseMethod+"(value)"); 204 } 205 } 206 unmarshal.body()._return(inv); 207 208 209 JMethod marshal = adapter.method(JMod.PUBLIC, String.class, "marshal"); 210 $value = marshal.param(inMemoryType,"value"); 211 212 int idx = printMethod.lastIndexOf('.'); 213 if(idx<0) { 214 // printMethod specifies a method in the target type 215 // which performs the serialization. 216 217 // RESULT: <value>.<method>() 218 inv = $value.invoke(printMethod); 219 } else { 220 // RESULT: <className>.<method>(<value>) 221 inv = JExpr.direct(printMethod+"(value)"); 222 } 223 marshal.body()._return(inv); 224 225 return adapter; 226 } 227 }