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 }