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.reader.xmlschema;
  27 
  28 import java.io.StringWriter;
  29 import java.util.HashMap;
  30 import java.util.HashSet;
  31 import java.util.Map;
  32 import java.util.Set;
  33 import java.util.Stack;
  34 
  35 import com.sun.codemodel.internal.JCodeModel;
  36 import com.sun.codemodel.internal.JJavaName;
  37 import com.sun.codemodel.internal.JPackage;
  38 import com.sun.codemodel.internal.util.JavadocEscapeWriter;
  39 import com.sun.istack.internal.NotNull;
  40 import com.sun.tools.internal.xjc.model.CBuiltinLeafInfo;
  41 import com.sun.tools.internal.xjc.model.CClassInfo;
  42 import com.sun.tools.internal.xjc.model.CClassInfoParent;
  43 import com.sun.tools.internal.xjc.model.CElement;
  44 import com.sun.tools.internal.xjc.model.CElementInfo;
  45 import com.sun.tools.internal.xjc.model.CTypeInfo;
  46 import com.sun.tools.internal.xjc.model.TypeUse;
  47 import com.sun.tools.internal.xjc.model.CClass;
  48 import com.sun.tools.internal.xjc.model.CNonElement;
  49 import com.sun.tools.internal.xjc.reader.Ring;
  50 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIProperty;
  51 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISchemaBinding;
  52 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.LocalScoping;
  53 import com.sun.xml.internal.bind.v2.WellKnownNamespace;
  54 import com.sun.xml.internal.xsom.XSComplexType;
  55 import com.sun.xml.internal.xsom.XSComponent;
  56 import com.sun.xml.internal.xsom.XSDeclaration;
  57 import com.sun.xml.internal.xsom.XSElementDecl;
  58 import com.sun.xml.internal.xsom.XSSchema;
  59 import com.sun.xml.internal.xsom.XSSchemaSet;
  60 import com.sun.xml.internal.xsom.XSSimpleType;
  61 import com.sun.xml.internal.xsom.XSType;
  62 import com.sun.xml.internal.xsom.impl.util.SchemaWriter;
  63 import com.sun.xml.internal.xsom.util.ComponentNameFunction;
  64 
  65 import org.xml.sax.Locator;
  66 
  67 /**
  68  * Manages association between {@link XSComponent}s and generated
  69  * {@link CTypeInfo}s.
  70  *
  71  * <p>
  72  * This class determines which component is mapped to (or is not mapped to)
  73  * what types.
  74  *
  75  * @author
  76  *     Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
  77  */
  78 public final class ClassSelector extends BindingComponent {
  79     /** Center of owner classes. */
  80     private final BGMBuilder builder = Ring.get(BGMBuilder.class);
  81 
  82 
  83     /**
  84      * Map from XSComponents to {@link Binding}s. Keeps track of all
  85      * content interfaces that are already built or being built.
  86      */
  87     private final Map<XSComponent,Binding> bindMap = new HashMap<XSComponent,Binding>();
  88 
  89     /**
  90      * UGLY HACK.
  91      * <p>
  92      * To avoid cyclic dependency between binding elements and types,
  93      * we need additional markers that tell which elements are definitely not bound
  94      * to a class.
  95      * <p>
  96      * the cyclic dependency is as follows:
  97      * elements need to bind its types first, because otherwise it can't
  98      * determine T of JAXBElement<T>.
  99      * OTOH, types need to know whether its parent is bound to a class to decide
 100      * which class name to use.
 101      */
 102     /*package*/ final Map<XSComponent,CElementInfo> boundElements = new HashMap<XSComponent,CElementInfo>();
 103 
 104     /**
 105      * A list of {@link Binding}s object that needs to be built.
 106      */
 107     private final Stack<Binding> bindQueue = new Stack<Binding>();
 108 
 109     /**
 110      * {@link CClassInfo}s that are already {@link Binding#build() built}.
 111      */
 112     private final Set<CClassInfo> built = new HashSet<CClassInfo>();
 113 
 114     /**
 115      * Object that determines components that are mapped
 116      * to classes.
 117      */
 118     private final ClassBinder classBinder;
 119 
 120     /**
 121      * {@link CClassInfoParent}s that determines where a new class
 122      * should be created.
 123      */
 124     private final Stack<CClassInfoParent> classScopes = new Stack<CClassInfoParent>();
 125 
 126     /**
 127      * The component that is being bound to {@link #currentBean}.
 128      */
 129     private XSComponent currentRoot;
 130     /**
 131      * The bean representation we are binding right now.
 132      */
 133     private CClassInfo currentBean;
 134 
 135 
 136     private final class Binding {
 137         private final XSComponent sc;
 138         private final CTypeInfo bean;
 139 
 140         public Binding(XSComponent sc, CTypeInfo bean) {
 141             this.sc = sc;
 142             this.bean = bean;
 143         }
 144 
 145         void build() {
 146             if(!(this.bean instanceof CClassInfo))
 147                 return; // no need to "build"
 148 
 149             CClassInfo bean = (CClassInfo)this.bean;
 150 
 151             if(!built.add(bean))
 152                 return; // already built
 153 
 154             for( String reservedClassName : reservedClassNames ) {
 155                 if( bean.getName().equals(reservedClassName) ) {
 156                     getErrorReporter().error( sc.getLocator(),
 157                         Messages.ERR_RESERVED_CLASS_NAME, reservedClassName );
 158                     break;
 159                 }
 160             }
 161 
 162             // if this schema component is an element declaration
 163             // and it satisfies a set of conditions specified in the spec,
 164             // this class will receive a constructor.
 165             if(needValueConstructor(sc)) {
 166                 // TODO: fragile. There is no guarantee that the property name
 167                 // is in fact "value".
 168                 bean.addConstructor("value");
 169             }
 170 
 171             if(bean.javadoc==null)
 172                 addSchemaFragmentJavadoc(bean,sc);
 173 
 174             // build the body
 175             if(builder.getGlobalBinding().getFlattenClasses()==LocalScoping.NESTED)
 176                 pushClassScope(bean);
 177             else
 178                 pushClassScope(bean.parent());
 179             XSComponent oldRoot = currentRoot;
 180             CClassInfo oldBean = currentBean;
 181             currentRoot = sc;
 182             currentBean = bean;
 183             sc.visit(Ring.get(BindRed.class));
 184             currentBean = oldBean;
 185             currentRoot = oldRoot;
 186             popClassScope();
 187 
 188             // acknowledge property customization on this schema component,
 189             // since it is OK to have a customization at the point of declaration
 190             // even when no one is using it.
 191             BIProperty prop = builder.getBindInfo(sc).get(BIProperty.class);
 192             if(prop!=null)  prop.markAsAcknowledged();
 193         }
 194     }
 195 
 196 
 197     // should be instanciated only from BGMBuilder.
 198     public ClassSelector() {
 199         classBinder = new Abstractifier(new DefaultClassBinder());
 200         Ring.add(ClassBinder.class,classBinder);
 201 
 202         classScopes.push(null);  // so that the getClassFactory method returns null
 203 
 204         XSComplexType anyType = Ring.get(XSSchemaSet.class).getComplexType(WellKnownNamespace.XML_SCHEMA,"anyType");
 205         bindMap.put(anyType,new Binding(anyType,CBuiltinLeafInfo.ANYTYPE));
 206     }
 207 
 208     /** Gets the current class scope. */
 209     public final CClassInfoParent getClassScope() {
 210         assert !classScopes.isEmpty();
 211         return classScopes.peek();
 212     }
 213 
 214     public final void pushClassScope( CClassInfoParent clsFctry ) {
 215         assert clsFctry!=null;
 216         classScopes.push(clsFctry);
 217     }
 218 
 219     public final void popClassScope() {
 220         classScopes.pop();
 221     }
 222 
 223     public XSComponent getCurrentRoot() {
 224         return currentRoot;
 225     }
 226 
 227     public CClassInfo getCurrentBean() {
 228         return currentBean;
 229     }
 230 
 231     /**
 232      * Checks if the given component is bound to a class.
 233      */
 234     public final CElement isBound( XSElementDecl x, XSComponent referer ) {
 235         CElementInfo r = boundElements.get(x);
 236         if(r!=null)
 237             return r;
 238         return bindToType(x,referer);
 239     }
 240 
 241     /**
 242      * Checks if the given component is being mapped to a type.
 243      * If so, build that type and return that object.
 244      * If it is not being mapped to a type item, return null.
 245      */
 246     public CTypeInfo bindToType( XSComponent sc, XSComponent referer ) {
 247         return _bindToClass(sc,referer,false);
 248     }
 249 
 250     //
 251     // some schema components are guaranteed to map to a particular CTypeInfo.
 252     // the following versions capture those constraints in the signature
 253     // and making the bindToType invocation more type safe.
 254     //
 255 
 256     public CElement bindToType( XSElementDecl e, XSComponent referer ) {
 257         return (CElement)_bindToClass(e,referer,false);
 258     }
 259 
 260     public CClass bindToType( XSComplexType t, XSComponent referer, boolean cannotBeDelayed ) {
 261         // this assumption that a complex type always binds to a ClassInfo
 262         // does not hold for xs:anyType --- our current approach of handling
 263         // this idiosynchracy is to make sure that xs:anyType doesn't use
 264         // this codepath.
 265         return (CClass)_bindToClass(t,referer,cannotBeDelayed);
 266     }
 267 
 268     public TypeUse bindToType( XSType t, XSComponent referer ) {
 269         if(t instanceof XSSimpleType) {
 270             return Ring.get(SimpleTypeBuilder.class).build((XSSimpleType)t);
 271         } else
 272             return (CNonElement)_bindToClass(t,referer,false);
 273     }
 274 
 275     /**
 276      * The real meat of the "bindToType" code.
 277      *
 278      * @param cannotBeDelayed
 279      *      if the binding of the body of the class cannot be defered
 280      *      and needs to be done immediately. If the flag is false,
 281      *      the binding of the body will be done later, to avoid
 282      *      cyclic binding problem.
 283      * @param referer
 284      *      The component that refers to {@code sc}. This can be null,
 285      *      if figuring out the referer is too hard, in which case
 286      *      the error message might be less user friendly.
 287      */
 288     // TODO: consider getting rid of "cannotBeDelayed"
 289     CTypeInfo _bindToClass( @NotNull XSComponent sc, XSComponent referer, boolean cannotBeDelayed ) {
 290         // check if this class is already built.
 291         if(!bindMap.containsKey(sc)) {
 292             // craete a bind task
 293 
 294             // if this is a global declaration, make sure they will be generated
 295             // under a package.
 296             boolean isGlobal = false;
 297             if( sc instanceof XSDeclaration ) {
 298                 isGlobal = ((XSDeclaration)sc).isGlobal();
 299                 if( isGlobal )
 300                     pushClassScope( new CClassInfoParent.Package(
 301                         getPackage(((XSDeclaration)sc).getTargetNamespace())) );
 302             }
 303 
 304             // otherwise check if this component should become a class.
 305             CElement bean = sc.apply(classBinder);
 306 
 307             if( isGlobal )
 308                 popClassScope();
 309 
 310             if(bean==null)
 311                 return null;
 312 
 313             // can this namespace generate a class?
 314             if (bean instanceof CClassInfo) {
 315                 XSSchema os = sc.getOwnerSchema();
 316                 BISchemaBinding sb = builder.getBindInfo(os).get(BISchemaBinding.class);
 317                 if(sb!=null && !sb.map) {
 318                     // nope
 319                     getErrorReporter().error(sc.getLocator(),
 320                         Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS, sc.apply( new ComponentNameFunction() ) );
 321                     getErrorReporter().error(sb.getLocation(),
 322                         Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS_MAP_FALSE, os.getTargetNamespace() );
 323                     if(referer!=null)
 324                         getErrorReporter().error(referer.getLocator(),
 325                             Messages.ERR_REFERENCE_TO_NONEXPORTED_CLASS_REFERER, referer.apply( new ComponentNameFunction() ) );
 326                 }
 327             }
 328 
 329 
 330             queueBuild( sc, bean );
 331         }
 332 
 333         Binding bind = bindMap.get(sc);
 334         if( cannotBeDelayed )
 335             bind.build();
 336 
 337         return bind.bean;
 338     }
 339 
 340     /**
 341      * Runs all the pending build tasks.
 342      */
 343     public void executeTasks() {
 344         while( bindQueue.size()!=0 )
 345             bindQueue.pop().build();
 346     }
 347 
 348 
 349 
 350 
 351 
 352 
 353 
 354 
 355     /**
 356      * Determines if the given component needs to have a value
 357      * constructor (a constructor that takes a parmater.) on ObjectFactory.
 358      */
 359     private boolean needValueConstructor( XSComponent sc ) {
 360         if(!(sc instanceof XSElementDecl))  return false;
 361 
 362         XSElementDecl decl = (XSElementDecl)sc;
 363         if(!decl.getType().isSimpleType())  return false;
 364 
 365         return true;
 366     }
 367 
 368     private static final String[] reservedClassNames = new String[]{"ObjectFactory"};
 369 
 370     public void queueBuild( XSComponent sc, CElement bean ) {
 371         // it is an error if the same component is built twice,
 372         // or the association is modified.
 373         Binding b = new Binding(sc,bean);
 374         bindQueue.push(b);
 375         Binding old = bindMap.put(sc, b);
 376         assert old==null || old.bean==bean;
 377     }
 378 
 379 
 380     /**
 381      * Copies a schema fragment into the javadoc of the generated class.
 382      */
 383     private void addSchemaFragmentJavadoc( CClassInfo bean, XSComponent sc ) {
 384 
 385         // first, pick it up from <documentation> if any.
 386         String doc = builder.getBindInfo(sc).getDocumentation();
 387         if(doc!=null)
 388             append(bean, doc);
 389 
 390         // then the description of where this component came from
 391         Locator loc = sc.getLocator();
 392         String fileName = null;
 393         if(loc!=null) {
 394             fileName = loc.getPublicId();
 395             if(fileName==null)
 396                 fileName = loc.getSystemId();
 397         }
 398         if(fileName==null)  fileName="";
 399 
 400         String lineNumber=Messages.format( Messages.JAVADOC_LINE_UNKNOWN);
 401         if(loc!=null && loc.getLineNumber()!=-1)
 402             lineNumber = String.valueOf(loc.getLineNumber());
 403 
 404         String componentName = sc.apply( new ComponentNameFunction() );
 405         String jdoc = Messages.format( Messages.JAVADOC_HEADING, componentName, fileName, lineNumber );
 406         append(bean,jdoc);
 407 
 408         // then schema fragment
 409         StringWriter out = new StringWriter();
 410         out.write("<pre>\n");
 411         SchemaWriter sw = new SchemaWriter(new JavadocEscapeWriter(out));
 412         sc.visit(sw);
 413         out.write("</pre>");
 414         append(bean,out.toString());
 415     }
 416 
 417     private void append(CClassInfo bean, String doc) {
 418         if(bean.javadoc==null)
 419             bean.javadoc = doc+'\n';
 420         else
 421             bean.javadoc += '\n'+doc+'\n';
 422     }
 423 
 424 
 425     /**
 426      * Set of package names that are tested (set of {@code String}s.)
 427      *
 428      * This set is used to avoid duplicating "incorrect package name"
 429      * errors.
 430      */
 431     private static Set<String> checkedPackageNames = new HashSet<String>();
 432 
 433     /**
 434      * Gets the Java package to which classes from
 435      * this namespace should go.
 436      *
 437      * <p>
 438      * Usually, the getOuterClass method should be used
 439      * to determine where to put a class.
 440      */
 441     public JPackage getPackage(String targetNamespace) {
 442         XSSchema s = Ring.get(XSSchemaSet.class).getSchema(targetNamespace);
 443 
 444         BISchemaBinding sb =
 445             builder.getBindInfo(s).get(BISchemaBinding.class);
 446         if(sb!=null)    sb.markAsAcknowledged();
 447 
 448         String name = null;
 449 
 450         // "-p" takes precedence over everything else
 451         if( builder.defaultPackage1 != null )
 452             name = builder.defaultPackage1;
 453 
 454         // use the <jaxb:package> customization
 455         if( name == null && sb!=null && sb.getPackageName()!=null )
 456             name = sb.getPackageName();
 457 
 458         // the JAX-RPC option goes below the <jaxb:package>
 459         if( name == null && builder.defaultPackage2 != null )
 460             name = builder.defaultPackage2;
 461 
 462         // generate the package name from the targetNamespace
 463         if( name == null )
 464             name = builder.getNameConverter().toPackageName( targetNamespace );
 465 
 466         // hardcode a package name because the code doesn't compile
 467         // if it generated into the default java package
 468         if( name == null )
 469             name = "generated"; // the last resort
 470 
 471 
 472         // check if the package name is a valid name.
 473         if( checkedPackageNames.add(name) ) {
 474             // this is the first time we hear about this package name.
 475             if( !JJavaName.isJavaPackageName(name) )
 476                 // TODO: s.getLocator() is not very helpful.
 477                 // ideally, we'd like to use the locator where this package name
 478                 // comes from.
 479                 getErrorReporter().error(s.getLocator(),
 480                     Messages.ERR_INCORRECT_PACKAGE_NAME, targetNamespace, name );
 481         }
 482 
 483         return Ring.get(JCodeModel.class)._package(name);
 484     }
 485 }