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 }