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 }