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.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 .append("}"); 234 235 XmlElementDeclWriter xemw = m.annotate2(XmlElementDeclWriter.class); 236 xemw.namespace(namespaceURI).name(localPart); 237 if(scope!=null) 238 xemw.scope(scope); 239 240 if(ei.getSubstitutionHead()!=null) { 241 QName n = ei.getSubstitutionHead().getElementName(); 242 xemw.substitutionHeadNamespace(n.getNamespaceURI()); 243 xemw.substitutionHeadName(n.getLocalPart()); 244 } 245 246 if(ei.getDefaultValue()!=null) 247 xemw.defaultValue(ei.getDefaultValue()); 248 249 if(ei.getProperty().inlineBinaryData()) 250 m.annotate(XmlInlineBinaryData.class); 251 252 // if the element is adapter, put that annotation on the factory method 253 outline.generateAdapterIfNecessary(ei.getProperty(),m); 254 } 255 256 /** 257 * return a JFieldVar that represents the QName field for the given information. 258 * 259 * if it doesn't exist, create a static field in the class and store a new JFieldVar. 260 */ 261 private JExpression getQNameInvocation(CElementInfo ei) { 262 QName name = ei.getElementName(); 263 if(qnameMap.containsKey(name)) { 264 return qnameMap.get(name); 265 } 266 267 if(qnameMap.size()>1024) 268 // stop gap measure to avoid 'code too large' error in javac. 269 return createQName(name); 270 271 // [RESULT] 272 // private static final QName _XYZ_NAME = new QName("uri", "local"); 273 JFieldVar qnameField = objectFactory.field( 274 JMod.PRIVATE | JMod.STATIC | JMod.FINAL, 275 QName.class, 276 '_' + ei.getSqueezedName() + "_QNAME", createQName(name)); 277 278 qnameMap.put(name, qnameField); 279 280 return qnameField; 281 } 282 283 /** 284 * Generates an expression that evaluates to "new QName(...)" 285 */ 286 private JInvocation createQName(QName name) { 287 return JExpr._new(codeModel.ref(QName.class)).arg(name.getNamespaceURI()).arg(name.getLocalPart()); 288 } 289 290 protected final void populate( ClassOutlineImpl cc, JClass sigType ) { 291 // add static factory method for this class to JAXBContext. 292 // 293 // generate methods like: 294 // public static final SIGTYPE createFoo() { 295 // return new FooImpl(); 296 // } 297 298 if(!cc.target.isAbstract()) { 299 JMethod m = objectFactory.method( 300 JMod.PUBLIC, sigType, "create" + cc.target.getSqueezedName() ); 301 m.body()._return( JExpr._new(cc.implRef) ); 302 303 // add some jdoc to avoid javadoc warnings in jdk1.4 304 m.javadoc() 305 .append("Create an instance of ") 306 .append(cc.ref); 307 } 308 309 310 // add static factory methods for all the other constructors. 311 Collection<? extends Constructor> consl = cc.target.getConstructors(); 312 if(consl.size()!=0) { 313 // if we are going to add constructors with parameters, 314 // first we need to have a default constructor. 315 cc.implClass.constructor(JMod.PUBLIC); 316 } 317 318 {// collision check 319 String name = cc.target.getSqueezedName(); 320 ClassOutlineImpl existing = valueFactoryNames.put(name,cc); 321 if( existing!=null ) { 322 outline.getErrorReceiver().error(existing.target.getLocator(), 323 Messages.OBJECT_FACTORY_CONFLICT.format(name)); 324 outline.getErrorReceiver().error(cc.target.getLocator(), 325 Messages.OBJECT_FACTORY_CONFLICT_RELATED.format()); 326 return; 327 } 328 } 329 330 for( Constructor cons : consl ) { 331 // method on ObjectFactory 332 // [RESULT] 333 // Foo createFoo( T1 a, T2 b, T3 c, ... ) throws JAXBException { 334 // return new FooImpl(a,b,c,...); 335 // } 336 JMethod m = objectFactory.method( JMod.PUBLIC, 337 cc.ref, "create" + cc.target.getSqueezedName() ); 338 JInvocation inv = JExpr._new(cc.implRef); 339 m.body()._return(inv); 340 341 // let's not throw this exception. 342 // m._throws(codeModel.ref(JAXBException.class)); 343 344 // add some jdoc to avoid javadoc warnings in jdk1.4 345 m.javadoc() 346 .append( "Create an instance of " ) 347 .append( cc.ref ) 348 .addThrows(JAXBException.class).append("if an error occurs"); 349 350 // constructor 351 // [RESULT] 352 // FooImpl( T1 a, T2 b, T3 c, ... ) { 353 // } 354 JMethod c = cc.implClass.constructor(JMod.PUBLIC); 355 356 for( String fieldName : cons.fields ) { 357 CPropertyInfo field = cc.target.getProperty(fieldName); 358 if(field==null) { 359 outline.getErrorReceiver().error(cc.target.getLocator(), 360 Messages.ILLEGAL_CONSTRUCTOR_PARAM.format(fieldName)); 361 continue; 362 } 363 364 fieldName = camelize(fieldName); 365 366 FieldOutline fo = outline.getField(field); 367 FieldAccessor accessor = fo.create(JExpr._this()); 368 369 // declare a parameter on this factory method and set 370 // it to the field 371 inv.arg(m.param( fo.getRawType(), fieldName )); 372 373 JVar $var = c.param( fo.getRawType(), fieldName ); 374 accessor.fromRawValue(c.body(),'_'+fieldName,$var); 375 } 376 } 377 } 378 379 380 /** Change the first character to the lower case. */ 381 private static String camelize( String s ) { 382 return Character.toLowerCase(s.charAt(0)) + s.substring(1); 383 } 384 }