1 /*
   2  * Copyright (c) 1997, 2015, 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.util.ArrayList;
  29 import java.util.HashMap;
  30 import java.util.List;
  31 import java.util.Map;
  32 import java.util.Set;
  33 
  34 import javax.xml.namespace.QName;
  35 import javax.xml.transform.Transformer;
  36 import javax.xml.transform.TransformerConfigurationException;
  37 import javax.xml.transform.TransformerFactory;
  38 
  39 import com.sun.codemodel.internal.JCodeModel;
  40 import com.sun.codemodel.internal.fmt.JTextFile;
  41 import com.sun.istack.internal.NotNull;
  42 import com.sun.istack.internal.Nullable;
  43 import com.sun.tools.internal.xjc.ErrorReceiver;
  44 import com.sun.tools.internal.xjc.Options;
  45 import com.sun.tools.internal.xjc.Plugin;
  46 import com.sun.tools.internal.xjc.generator.bean.field.FieldRendererFactory;
  47 import com.sun.tools.internal.xjc.model.CClassInfoParent;
  48 import com.sun.tools.internal.xjc.model.Model;
  49 import com.sun.tools.internal.xjc.reader.ModelChecker;
  50 import com.sun.tools.internal.xjc.reader.Ring;
  51 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIDeclaration;
  52 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIDom;
  53 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIGlobalBinding;
  54 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISchemaBinding;
  55 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BISerializable;
  56 import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BindInfo;
  57 import com.sun.tools.internal.xjc.util.CodeModelClassFactory;
  58 import com.sun.tools.internal.xjc.util.ErrorReceiverFilter;
  59 import com.sun.xml.internal.bind.api.impl.NameConverter;
  60 import com.sun.xml.internal.bind.v2.util.XmlFactory;
  61 import com.sun.xml.internal.xsom.XSAnnotation;
  62 import com.sun.xml.internal.xsom.XSAttributeUse;
  63 import com.sun.xml.internal.xsom.XSComponent;
  64 import com.sun.xml.internal.xsom.XSDeclaration;
  65 import com.sun.xml.internal.xsom.XSParticle;
  66 import com.sun.xml.internal.xsom.XSSchema;
  67 import com.sun.xml.internal.xsom.XSSchemaSet;
  68 import com.sun.xml.internal.xsom.XSSimpleType;
  69 import com.sun.xml.internal.xsom.XSTerm;
  70 import com.sun.xml.internal.xsom.XSType;
  71 import com.sun.xml.internal.xsom.XSWildcard;
  72 import com.sun.xml.internal.xsom.util.XSFinder;
  73 
  74 import org.xml.sax.Locator;
  75 
  76 /**
  77  * Root of the XML Schema binder.
  78  *
  79  * <div><img src="doc-files/binding_chart.png" alt=""></div>
  80  *
  81  * @author Kohsuke Kawaguchi
  82  */
  83 public class BGMBuilder extends BindingComponent {
  84 
  85     /**
  86      * Entry point.
  87      */
  88     public static Model build( XSSchemaSet _schemas, JCodeModel codeModel,
  89             ErrorReceiver _errorReceiver, Options opts ) {
  90         // set up a ring
  91         final Ring old = Ring.begin();
  92         try {
  93             ErrorReceiverFilter ef = new ErrorReceiverFilter(_errorReceiver);
  94 
  95             Ring.add(XSSchemaSet.class,_schemas);
  96             Ring.add(codeModel);
  97             Model model = new Model(opts, codeModel, null/*set later*/, opts.classNameAllocator, _schemas);
  98             Ring.add(model);
  99             Ring.add(ErrorReceiver.class,ef);
 100             Ring.add(CodeModelClassFactory.class,new CodeModelClassFactory(ef));
 101 
 102             BGMBuilder builder = new BGMBuilder(opts.defaultPackage,opts.defaultPackage2,
 103                 opts.isExtensionMode(),opts.getFieldRendererFactory(), opts.activePlugins);
 104             builder._build();
 105 
 106             if(ef.hadError())   return null;
 107             else                return model;
 108         } finally {
 109             Ring.end(old);
 110         }
 111     }
 112 
 113 
 114     /**
 115      * True if the compiler is running in the extension mode
 116      * (as opposed to the strict conformance mode.)
 117      */
 118     public final boolean inExtensionMode;
 119 
 120     /**
 121      * If this is non-null, this package name takes over
 122      * all the schema customizations.
 123      */
 124     public final String defaultPackage1;
 125 
 126     /**
 127      * If this is non-null, this package name will be
 128      * used when no customization is specified.
 129      */
 130     public final String defaultPackage2;
 131 
 132     private final BindGreen green = Ring.get(BindGreen.class);
 133     private final BindPurple purple = Ring.get(BindPurple.class);
 134 
 135     public final Model model = Ring.get(Model.class);
 136 
 137     public final FieldRendererFactory fieldRendererFactory;
 138 
 139     /**
 140      * Lazily computed {@link RefererFinder}.
 141      *
 142      * @see #getReferer
 143      */
 144     private RefererFinder refFinder;
 145 
 146     private List<Plugin> activePlugins;
 147 
 148     protected BGMBuilder(String defaultPackage1, String defaultPackage2,
 149             boolean _inExtensionMode, FieldRendererFactory fieldRendererFactory,
 150             List<Plugin> activePlugins) {
 151         this.inExtensionMode = _inExtensionMode;
 152         this.defaultPackage1 = defaultPackage1;
 153         this.defaultPackage2 = defaultPackage2;
 154         this.fieldRendererFactory = fieldRendererFactory;
 155         this.activePlugins = activePlugins;
 156         promoteGlobalBindings();
 157     }
 158 
 159     private void _build() {
 160         // do the binding
 161         buildContents();
 162         getClassSelector().executeTasks();
 163 
 164         // additional error check
 165         // Reports unused customizations to the user as errors.
 166         Ring.get(UnusedCustomizationChecker.class).run();
 167 
 168         Ring.get(ModelChecker.class).check();
 169 
 170         for( Plugin ma : activePlugins )
 171             ma.postProcessModel(model, Ring.get(ErrorReceiver.class));
 172 
 173     }
 174 
 175 
 176     /** List up all the global bindings. */
 177     private void promoteGlobalBindings() {
 178         // promote any global bindings in the schema
 179         XSSchemaSet schemas = Ring.get(XSSchemaSet.class);
 180 
 181         for( XSSchema s : schemas.getSchemas() ) {
 182             BindInfo bi = getBindInfo(s);
 183 
 184             // collect all global customizations
 185             model.getCustomizations().addAll(bi.toCustomizationList());
 186 
 187             BIGlobalBinding gb = bi.get(BIGlobalBinding.class);
 188             if(gb==null)
 189                 continue;
 190 
 191             gb.markAsAcknowledged();
 192 
 193             if(globalBinding==null) {
 194                 globalBinding = gb;
 195             } else {
 196                 if (!globalBinding.isEqual(gb)) { // see Issue 687 - this may happen with syntactically imported documents
 197                     // acknowledge this customization and report an error
 198                     // otherwise the user will see "customization is attached to a wrong place" error,
 199                     // which is incorrect
 200                     getErrorReporter().error( gb.getLocation(),
 201                         Messages.ERR_MULTIPLE_GLOBAL_BINDINGS);
 202                     getErrorReporter().error( globalBinding.getLocation(),
 203                         Messages.ERR_MULTIPLE_GLOBAL_BINDINGS_OTHER);
 204                 }
 205             }
 206         }
 207 
 208         if( globalBinding==null ) {
 209             // no global customization is present.
 210             // use the default one
 211             globalBinding = new BIGlobalBinding();
 212             BindInfo big = new BindInfo();
 213             big.addDecl(globalBinding);
 214             big.setOwner(this,null);
 215         }
 216 
 217         // code generation mode
 218         model.strategy = globalBinding.getCodeGenerationStrategy();
 219         model.rootClass = globalBinding.getSuperClass();
 220         model.rootInterface = globalBinding.getSuperInterface();
 221 
 222         particleBinder = globalBinding.isSimpleMode() ? new ExpressionParticleBinder() : new DefaultParticleBinder();
 223 
 224         // check XJC extensions and realize them
 225         BISerializable serial = globalBinding.getSerializable();
 226         if(serial!=null) {
 227             model.serializable = true;
 228             model.serialVersionUID = serial.uid;
 229         }
 230 
 231         // obtain the name conversion mode
 232         if (globalBinding.nameConverter!=null)
 233             model.setNameConverter(globalBinding.nameConverter);
 234 
 235         // attach global conversions to the appropriate simple types
 236         globalBinding.dispatchGlobalConversions(schemas);
 237 
 238         globalBinding.errorCheck();
 239     }
 240 
 241     /**
 242      * Global bindings.
 243      *
 244      * The empty global binding is set as the default, so that
 245      * there will be no need to test if the value is null.
 246      */
 247     private BIGlobalBinding globalBinding;
 248 
 249     /**
 250      * Gets the global bindings.
 251      */
 252     public @NotNull BIGlobalBinding getGlobalBinding() { return globalBinding; }
 253 
 254 
 255     private ParticleBinder particleBinder;
 256 
 257     /**
 258      * Gets the particle binder for this binding.
 259      */
 260     public @NotNull ParticleBinder getParticleBinder() { return particleBinder; }
 261 
 262 
 263     /**
 264      * Name converter that implements "{@code XML -> Java} name conversion"
 265      * as specified in the spec.
 266      *
 267      * This object abstracts the detail that we use different name
 268      * conversion depending on the customization.
 269      *
 270      * <p>
 271      * This object should be used to perform any name conversion
 272      * needs, instead of the JJavaName class in CodeModel.
 273      */
 274     public NameConverter getNameConverter() { return model.getNameConverter(); }
 275 
 276     /** Fill-in the contents of each classes. */
 277     private void buildContents() {
 278         ClassSelector cs = getClassSelector();
 279         SimpleTypeBuilder stb = Ring.get(SimpleTypeBuilder.class);
 280 
 281         for( XSSchema s : Ring.get(XSSchemaSet.class).getSchemas() ) {
 282             BISchemaBinding sb = getBindInfo(s).get(BISchemaBinding.class);
 283 
 284             if(sb!=null && !sb.map) {
 285                 sb.markAsAcknowledged();
 286                 continue;       // no mapping for this package
 287             }
 288 
 289             getClassSelector().pushClassScope( new CClassInfoParent.Package(
 290                 getClassSelector().getPackage(s.getTargetNamespace())) );
 291 
 292             checkMultipleSchemaBindings(s);
 293             processPackageJavadoc(s);
 294             populate(s.getAttGroupDecls(),s);
 295             populate(s.getAttributeDecls(),s);
 296             populate(s.getElementDecls(),s);
 297             populate(s.getModelGroupDecls(),s);
 298 
 299             // fill in typeUses
 300             for (XSType t : s.getTypes().values()) {
 301                 stb.refererStack.push(t);
 302                 model.typeUses().put( getName(t), cs.bindToType(t,s) );
 303                 stb.refererStack.pop();
 304             }
 305 
 306             getClassSelector().popClassScope();
 307         }
 308     }
 309 
 310     /** Reports an error if there are more than one jaxb:schemaBindings customization. */
 311     private void checkMultipleSchemaBindings( XSSchema schema ) {
 312         ArrayList<Locator> locations = new ArrayList<Locator>();
 313 
 314         BindInfo bi = getBindInfo(schema);
 315         for( BIDeclaration bid : bi ) {
 316             if( bid.getName()==BISchemaBinding.NAME )
 317                 locations.add( bid.getLocation() );
 318         }
 319         if(locations.size()<=1)    return; // OK
 320 
 321         // error
 322         getErrorReporter().error( locations.get(0),
 323             Messages.ERR_MULTIPLE_SCHEMA_BINDINGS,
 324             schema.getTargetNamespace() );
 325         for( int i=1; i<locations.size(); i++ )
 326             getErrorReporter().error( (Locator)locations.get(i),
 327                 Messages.ERR_MULTIPLE_SCHEMA_BINDINGS_LOCATION);
 328     }
 329 
 330     /**
 331      * Calls {@link ClassSelector} for each item in the iterator
 332      * to populate class items if there is any.
 333      */
 334     private void populate( Map<String,? extends XSComponent> col, XSSchema schema ) {
 335         ClassSelector cs = getClassSelector();
 336         for( XSComponent sc : col.values() )
 337             cs.bindToType(sc,schema);
 338     }
 339 
 340     /**
 341      * Generates <code>package.html</code> if the customization
 342      * says so.
 343      */
 344     private void processPackageJavadoc( XSSchema s ) {
 345         // look for the schema-wide customization
 346         BISchemaBinding cust = getBindInfo(s).get(BISchemaBinding.class);
 347         if(cust==null)      return; // not present
 348 
 349         cust.markAsAcknowledged();
 350         if( cust.getJavadoc()==null )   return;     // no javadoc customization
 351 
 352         // produce a HTML file
 353         JTextFile html = new JTextFile("package.html");
 354         html.setContents(cust.getJavadoc());
 355         getClassSelector().getPackage(s.getTargetNamespace()).addResourceFile(html);
 356     }
 357 
 358 
 359 
 360 
 361 
 362 
 363     /**
 364      * Gets or creates the BindInfo object associated to a schema component.
 365      *
 366      * @return
 367      *      Always return a non-null valid BindInfo object.
 368      *      Even if no declaration was specified, this method creates
 369      *      a new BindInfo so that new decls can be added.
 370      */
 371     public BindInfo getOrCreateBindInfo( XSComponent schemaComponent ) {
 372 
 373         BindInfo bi = _getBindInfoReadOnly(schemaComponent);
 374         if(bi!=null)    return bi;
 375 
 376         // XSOM is read-only, so we cannot add new annotations.
 377         // for components that didn't have annotations,
 378         // we maintain an external map.
 379         bi = new BindInfo();
 380         bi.setOwner(this,schemaComponent);
 381         externalBindInfos.put(schemaComponent,bi);
 382         return bi;
 383     }
 384 
 385 
 386     /**
 387      * Used as a constant instance to represent the empty {@link BindInfo}.
 388      */
 389     private final BindInfo emptyBindInfo = new BindInfo();
 390 
 391     /**
 392      * Gets the BindInfo object associated to a schema component.
 393      *
 394      * @return
 395      *      always return a valid {@link BindInfo} object. If none
 396      *      is specified for the given component, a dummy empty BindInfo
 397      *      will be returned.
 398      */
 399     public BindInfo getBindInfo( XSComponent schemaComponent ) {
 400         BindInfo bi = _getBindInfoReadOnly(schemaComponent);
 401         if(bi!=null)    return bi;
 402         else            return emptyBindInfo;
 403     }
 404 
 405     /**
 406      * Gets the BindInfo object associated to a schema component.
 407      *
 408      * @return
 409      *      null if no bind info is associated to this schema component.
 410      */
 411     private BindInfo _getBindInfoReadOnly( XSComponent schemaComponent ) {
 412 
 413         BindInfo bi = externalBindInfos.get(schemaComponent);
 414         if(bi!=null)    return bi;
 415 
 416         XSAnnotation annon = schemaComponent.getAnnotation();
 417         if(annon!=null) {
 418             bi = (BindInfo)annon.getAnnotation();
 419             if(bi!=null) {
 420                 if(bi.getOwner()==null)
 421                     bi.setOwner(this,schemaComponent);
 422                 return bi;
 423             }
 424         }
 425 
 426         return null;
 427     }
 428 
 429     /**
 430      * A map that stores binding declarations augmented by XJC.
 431      */
 432     private final Map<XSComponent,BindInfo> externalBindInfos = new HashMap<XSComponent,BindInfo>();
 433 
 434     /**
 435      * Gets the {@link BIDom} object that applies to the given particle.
 436      */
 437     protected final BIDom getLocalDomCustomization( XSParticle p ) {
 438         if (p == null) {
 439             return null;
 440         }
 441         BIDom dom = getBindInfo(p).get(BIDom.class);
 442         if(dom!=null)  return dom;
 443 
 444         // if not, the term might have one.
 445         dom = getBindInfo(p.getTerm()).get(BIDom.class);
 446         if(dom!=null)  return dom;
 447 
 448         XSTerm t = p.getTerm();
 449         // type could also have one, in case of the dom customization
 450         if(t.isElementDecl())
 451             return getBindInfo(t.asElementDecl().getType()).get(BIDom.class);
 452         // similarly the model group in a model group definition may have one.
 453         if(t.isModelGroupDecl())
 454             return getBindInfo(t.asModelGroupDecl().getModelGroup()).get(BIDom.class);
 455 
 456         return null;
 457     }
 458 
 459     /**
 460      * Returns true if the component should be processed by purple.
 461      */
 462     private final XSFinder toPurple = new XSFinder() {
 463         @Override
 464         public Boolean attributeUse(XSAttributeUse use) {
 465             // attribute use always maps to a property
 466             return true;
 467         }
 468 
 469         @Override
 470         public Boolean simpleType(XSSimpleType xsSimpleType) {
 471             // simple type always maps to a type, hence we should take purple
 472             return true;
 473         }
 474 
 475         @Override
 476         public Boolean wildcard(XSWildcard xsWildcard) {
 477             // attribute wildcards always maps to a property.
 478             // element wildcards should have been processed with particle binders
 479             return true;
 480         }
 481     };
 482     /**
 483      * If the component maps to a property, forwards to purple, otherwise to green.
 484      *
 485      * If the component is mapped to a type, this method needs to return true.
 486      * See the chart at the class javadoc.
 487      */
 488     public void ying( XSComponent sc, @Nullable XSComponent referer ) {
 489         if(sc.apply(toPurple)==true || getClassSelector().bindToType(sc,referer)!=null)
 490             sc.visit(purple);
 491         else
 492             sc.visit(green);
 493     }
 494 
 495     private Transformer identityTransformer;
 496 
 497     /**
 498      * Gets the shared instance of the identity transformer.
 499      */
 500     public Transformer getIdentityTransformer() {
 501         try {
 502             if(identityTransformer==null) {
 503                 TransformerFactory tf = XmlFactory.createTransformerFactory(model.options.disableXmlSecurity);
 504                 identityTransformer = tf.newTransformer();
 505             }
 506             return identityTransformer;
 507         } catch (TransformerConfigurationException e) {
 508             throw new Error(e); // impossible
 509         }
 510     }
 511 
 512     /**
 513      * Find all types that refer to the given complex type.
 514      */
 515     public Set<XSComponent> getReferer(XSType c) {
 516         if(refFinder==null) {
 517             refFinder = new RefererFinder();
 518             refFinder.schemaSet(Ring.get(XSSchemaSet.class));
 519         }
 520         return refFinder.getReferer(c);
 521     }
 522 
 523     /**
 524      * Returns the QName of the declaration.
 525      * @return null
 526      *      if the declaration is anonymous.
 527      */
 528     public static QName getName(XSDeclaration decl) {
 529         String local = decl.getName();
 530         if(local==null) return null;
 531         return new QName(decl.getTargetNamespace(),local);
 532     }
 533 
 534     /**
 535      * Derives a name from a schema component.
 536      *
 537      * This method handles prefix/suffix modification and
 538      * XML-to-Java name conversion.
 539      *
 540      * @param name
 541      *      The base name. This should be things like element names
 542      *      or type names.
 543      * @param comp
 544      *      The component from which the base name was taken.
 545      *      Used to determine how names are modified.
 546      */
 547     public String deriveName( String name, XSComponent comp ) {
 548         XSSchema owner = comp.getOwnerSchema();
 549 
 550         name = getNameConverter().toClassName(name);
 551 
 552         if( owner!=null ) {
 553             BISchemaBinding sb = getBindInfo(owner).get(BISchemaBinding.class);
 554 
 555             if(sb!=null)    name = sb.mangleClassName(name,comp);
 556         }
 557 
 558         return name;
 559     }
 560 
 561     public boolean isGenerateMixedExtensions() {
 562         if (globalBinding != null) {
 563             return globalBinding.isGenerateMixedExtensions();
 564         }
 565         return false;
 566     }
 567 
 568 }