1 /* 2 * Copyright (c) 1997, 2016, 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; 27 28 import java.util.Collection; 29 import java.util.HashMap; 30 import java.util.Map; 31 32 import javax.xml.bind.JAXBException; 33 import javax.xml.bind.annotation.XmlInlineBinaryData; 34 import javax.xml.namespace.QName; 35 36 import com.sun.codemodel.internal.JClass; 37 import com.sun.codemodel.internal.JCodeModel; 38 import com.sun.codemodel.internal.JDefinedClass; 39 import com.sun.codemodel.internal.JExpr; 40 import com.sun.codemodel.internal.JExpression; 41 import com.sun.codemodel.internal.JFieldVar; 42 import com.sun.codemodel.internal.JInvocation; 43 import com.sun.codemodel.internal.JMethod; 44 import com.sun.codemodel.internal.JMod; 45 import com.sun.codemodel.internal.JPackage; 46 import com.sun.codemodel.internal.JType; 47 import com.sun.codemodel.internal.JVar; 48 import com.sun.tools.internal.xjc.generator.annotation.spec.XmlElementDeclWriter; 49 import com.sun.tools.internal.xjc.generator.annotation.spec.XmlRegistryWriter; 50 import com.sun.tools.internal.xjc.model.CElementInfo; 51 import com.sun.tools.internal.xjc.model.CPropertyInfo; 52 import com.sun.tools.internal.xjc.model.Constructor; 53 import com.sun.tools.internal.xjc.model.Model; 54 import com.sun.tools.internal.xjc.outline.Aspect; 55 import com.sun.tools.internal.xjc.outline.FieldAccessor; 56 import com.sun.tools.internal.xjc.outline.FieldOutline; 57 import com.sun.xml.internal.bind.v2.TODO; 58 59 /** 60 * Generates <code>ObjectFactory</code> then wraps it and provides 61 * access to it. 62 * 63 * <p> 64 * The ObjectFactory contains 65 * factory methods for each schema derived content class 66 * 67 * @author 68 * Ryan Shoemaker 69 */ 70 abstract class ObjectFactoryGeneratorImpl extends ObjectFactoryGenerator { 71 72 private final BeanGenerator outline; 73 private final Model model; 74 private final JCodeModel codeModel; 75 /** 76 * Ref to {@link Class}. 77 */ 78 private final JClass classRef; 79 80 /** 81 * Reference to the generated ObjectFactory class. 82 */ 83 private final JDefinedClass objectFactory; 84 85 /** map of qname to the QName constant field. */ 86 private final HashMap<QName,JFieldVar> qnameMap = new HashMap<QName,JFieldVar>(); 87 88 /** 89 * Names of the element factory methods that are created. 90 * Used to detect collisions. 91 * 92 * The value is used for reporting error locations. 93 */ 94 private final Map<String,CElementInfo> elementFactoryNames = new HashMap<String,CElementInfo>(); 95 96 /** 97 * Names of the value factory methods that are created. 98 * Used to detect collisions. 99 * 100 * The value is used for reporting error locations. 101 */ 102 private final Map<String,ClassOutlineImpl> valueFactoryNames = new HashMap<String,ClassOutlineImpl>(); 103 104 /** 105 * Returns a reference to the generated (public) ObjectFactory 106 */ 107 public JDefinedClass getObjectFactory() { 108 return objectFactory; 109 } 110 111 112 113 114 public ObjectFactoryGeneratorImpl( BeanGenerator outline, Model model, JPackage targetPackage ) { 115 this.outline = outline; 116 this.model = model; 117 this.codeModel = this.model.codeModel; 118 this.classRef = codeModel.ref(Class.class); 119 120 // create the ObjectFactory class skeleton 121 objectFactory = this.outline.getClassFactory().createClass( 122 targetPackage, "ObjectFactory", null ); 123 objectFactory.annotate2(XmlRegistryWriter.class); 124 125 // generate the default constructor 126 // 127 // m1 result: 128 // public ObjectFactory() {} 129 JMethod m1 = objectFactory.constructor(JMod.PUBLIC); 130 m1.javadoc().append("Create a new ObjectFactory that can be used to " + 131 "create new instances of schema derived classes " + 132 "for package: " + targetPackage.name()); 133 134 // add some class javadoc 135 objectFactory.javadoc().append( 136 "This object contains factory methods for each \n" + 137 "Java content interface and Java element interface \n" + 138 "generated in the " + targetPackage.name() + " package. \n" + 139 "<p>An ObjectFactory allows you to programatically \n" + 140 "construct new instances of the Java representation \n" + 141 "for XML content. The Java representation of XML \n" + 142 "content can consist of schema derived interfaces \n" + 143 "and classes representing the binding of schema \n" + 144 "type definitions, element declarations and model \n" + 145 "groups. Factory methods for each of these are \n" + 146 "provided in this class." ); 147 148 } 149 150 /** 151 * Adds code for the given {@link CElementInfo} to ObjectFactory. 152 */ 153 protected final void populate( CElementInfo ei, Aspect impl, Aspect exposed ) { 154 JType exposedElementType = ei.toType(outline,exposed); 155 JType exposedType = ei.getContentInMemoryType().toType(outline,exposed); 156 JType implType = ei.getContentInMemoryType().toType(outline,impl); 157 String namespaceURI = ei.getElementName().getNamespaceURI(); 158 String localPart = ei.getElementName().getLocalPart(); 159 160 JClass scope=null; 161 if(ei.getScope()!=null) 162 scope = outline.getClazz(ei.getScope()).implClass; 163 164 165 JMethod m; 166 167 if(ei.isAbstract()) { 168 // TODO: see the "Abstract elements and mighty IXmlElement" e-mail 169 // that I sent to jaxb-tech 170 TODO.checkSpec(); 171 } 172 173 {// collision check 174 CElementInfo existing = elementFactoryNames.put(ei.getSqueezedName(),ei); 175 if( existing!=null ) { 176 outline.getErrorReceiver().error(existing.getLocator(), 177 Messages.OBJECT_FACTORY_CONFLICT.format(ei.getSqueezedName())); 178 outline.getErrorReceiver().error(ei.getLocator(), 179 Messages.OBJECT_FACTORY_CONFLICT_RELATED.format()); 180 return; 181 } 182 } 183 184 // no arg constructor 185 // [RESULT] if the element doesn't have its own class, something like: 186 // 187 // @XmlElementMapping(uri = "", name = "foo") 188 // public JAXBElement<Foo> createFoo( Foo value ) { 189 // return new JAXBElement<Foo>( 190 // new QName("","foo"),(Class)FooImpl.class,scope,(FooImpl)value); 191 // } 192 // NOTE: when we generate value classes Foo==FooImpl 193 // 194 // [RESULT] otherwise 195 // 196 // @XmlElementMapping(uri = "", name = "foo") 197 // public Foo createFoo( FooType value ) { 198 // return new Foo((FooTypeImpl)value); 199 // } 200 // NOTE: when we generate value classes FooType==FooTypeImpl 201 // 202 // to deal with 203 // new JAXBElement<List<String>>( ..., List.class, ... ); 204 // we sometimes have to produce (Class)List.class instead of just List.class 205 206 m = objectFactory.method( JMod.PUBLIC, exposedElementType, "create" + ei.getSqueezedName() ); 207 JVar $value = m.param(exposedType,"value"); 208 209 JExpression declaredType; 210 if(implType.boxify().isParameterized() || !exposedType.equals(implType)) 211 declaredType = JExpr.cast(classRef,implType.boxify().dotclass()); 212 else 213 declaredType = implType.boxify().dotclass(); 214 JExpression scopeClass = scope==null?JExpr._null():scope.dotclass(); 215 216 // build up the return extpression 217 JInvocation exp = JExpr._new(exposedElementType); 218 if(!ei.hasClass()) { 219 exp.arg(getQNameInvocation(ei)); 220 exp.arg(declaredType); 221 exp.arg(scopeClass); 222 } 223 if(implType==exposedType) 224 exp.arg($value); 225 else 226 exp.arg(JExpr.cast(implType,$value)); 227 228 m.body()._return( exp ); 229 230 m.javadoc() 231 .append("Create an instance of ") 232 .append(exposedElementType); 233 m.javadoc().addParam($value) 234 .append("Java instance representing xml element's value."); 235 m.javadoc().addReturn() 236 .append("the new instance of ") 237 .append(exposedElementType); 238 239 XmlElementDeclWriter xemw = m.annotate2(XmlElementDeclWriter.class); 240 xemw.namespace(namespaceURI).name(localPart); 241 if(scope!=null) 242 xemw.scope(scope); 243 244 if(ei.getSubstitutionHead()!=null) { 245 QName n = ei.getSubstitutionHead().getElementName(); 246 xemw.substitutionHeadNamespace(n.getNamespaceURI()); 247 xemw.substitutionHeadName(n.getLocalPart()); 248 } 249 250 if(ei.getDefaultValue()!=null) 251 xemw.defaultValue(ei.getDefaultValue()); 252 253 if(ei.getProperty().inlineBinaryData()) 254 m.annotate(XmlInlineBinaryData.class); 255 256 // if the element is adapter, put that annotation on the factory method 257 outline.generateAdapterIfNecessary(ei.getProperty(),m); 258 } 259 260 /** 261 * return a JFieldVar that represents the QName field for the given information. 262 * 263 * if it doesn't exist, create a static field in the class and store a new JFieldVar. 264 */ 265 private JExpression getQNameInvocation(CElementInfo ei) { 266 QName name = ei.getElementName(); 267 if(qnameMap.containsKey(name)) { 268 return qnameMap.get(name); 269 } 270 271 if(qnameMap.size()>1024) 272 // stop gap measure to avoid 'code too large' error in javac. 273 return createQName(name); 274 275 // [RESULT] 276 // private static final QName _XYZ_NAME = new QName("uri", "local"); 277 JFieldVar qnameField = objectFactory.field( 278 JMod.PRIVATE | JMod.STATIC | JMod.FINAL, 279 QName.class, 280 '_' + ei.getSqueezedName() + "_QNAME", createQName(name)); 281 282 qnameMap.put(name, qnameField); 283 284 return qnameField; 285 } 286 287 /** 288 * Generates an expression that evaluates to "new QName(...)" 289 */ 290 private JInvocation createQName(QName name) { 291 return JExpr._new(codeModel.ref(QName.class)).arg(name.getNamespaceURI()).arg(name.getLocalPart()); 292 } 293 294 protected final void populate( ClassOutlineImpl cc, JClass sigType ) { 295 // add static factory method for this class to JAXBContext. 296 // 297 // generate methods like: 298 // public static final SIGTYPE createFoo() { 299 // return new FooImpl(); 300 // } 301 302 if(!cc.target.isAbstract()) { 303 JMethod m = objectFactory.method( 304 JMod.PUBLIC, sigType, "create" + cc.target.getSqueezedName() ); 305 m.body()._return( JExpr._new(cc.implRef) ); 306 307 // add some jdoc to avoid javadoc warnings in jdk1.4 308 m.javadoc() 309 .append("Create an instance of ") 310 .append(cc.ref); 311 } 312 313 314 // add static factory methods for all the other constructors. 315 Collection<? extends Constructor> consl = cc.target.getConstructors(); 316 if(consl.size()!=0) { 317 // if we are going to add constructors with parameters, 318 // first we need to have a default constructor. 319 cc.implClass.constructor(JMod.PUBLIC); 320 } 321 322 {// collision check 323 String name = cc.target.getSqueezedName(); 324 ClassOutlineImpl existing = valueFactoryNames.put(name,cc); 325 if( existing!=null ) { 326 outline.getErrorReceiver().error(existing.target.getLocator(), 327 Messages.OBJECT_FACTORY_CONFLICT.format(name)); 328 outline.getErrorReceiver().error(cc.target.getLocator(), 329 Messages.OBJECT_FACTORY_CONFLICT_RELATED.format()); 330 return; 331 } 332 } 333 334 for( Constructor cons : consl ) { 335 // method on ObjectFactory 336 // [RESULT] 337 // Foo createFoo( T1 a, T2 b, T3 c, ... ) throws JAXBException { 338 // return new FooImpl(a,b,c,...); 339 // } 340 JMethod m = objectFactory.method( JMod.PUBLIC, 341 cc.ref, "create" + cc.target.getSqueezedName() ); 342 JInvocation inv = JExpr._new(cc.implRef); 343 m.body()._return(inv); 344 345 // let's not throw this exception. 346 // m._throws(codeModel.ref(JAXBException.class)); 347 348 // add some jdoc to avoid javadoc warnings in jdk1.4 349 m.javadoc() 350 .append( "Create an instance of " ) 351 .append( cc.ref ) 352 .addThrows(JAXBException.class).append("if an error occurs"); 353 354 // constructor 355 // [RESULT] 356 // FooImpl( T1 a, T2 b, T3 c, ... ) { 357 // } 358 JMethod c = cc.implClass.constructor(JMod.PUBLIC); 359 360 for( String fieldName : cons.fields ) { 361 CPropertyInfo field = cc.target.getProperty(fieldName); 362 if(field==null) { 363 outline.getErrorReceiver().error(cc.target.getLocator(), 364 Messages.ILLEGAL_CONSTRUCTOR_PARAM.format(fieldName)); 365 continue; 366 } 367 368 fieldName = camelize(fieldName); 369 370 FieldOutline fo = outline.getField(field); 371 FieldAccessor accessor = fo.create(JExpr._this()); 372 373 // declare a parameter on this factory method and set 374 // it to the field 375 inv.arg(m.param( fo.getRawType(), fieldName )); 376 377 JVar $var = c.param( fo.getRawType(), fieldName ); 378 accessor.fromRawValue(c.body(),'_'+fieldName,$var); 379 } 380 } 381 } 382 383 384 /** Change the first character to the lower case. */ 385 private static String camelize( String s ) { 386 return Character.toLowerCase(s.charAt(0)) + s.substring(1); 387 } 388 }