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.xml.internal.bind.v2.schemagen; 27 28 import java.io.IOException; 29 import java.io.OutputStream; 30 import java.io.Writer; 31 import java.io.File; 32 import java.net.URI; 33 import java.net.URISyntaxException; 34 import java.util.Comparator; 35 import java.util.HashMap; 36 import java.util.LinkedHashSet; 37 import java.util.Map; 38 import java.util.Set; 39 import java.util.TreeMap; 40 import java.util.ArrayList; 41 import java.util.logging.Level; 42 import java.util.logging.Logger; 43 44 import javax.activation.MimeType; 45 import javax.xml.bind.SchemaOutputResolver; 46 import javax.xml.bind.annotation.XmlElement; 47 import javax.xml.namespace.QName; 48 import javax.xml.transform.Result; 49 import javax.xml.transform.stream.StreamResult; 50 51 import com.sun.istack.internal.Nullable; 52 import com.sun.istack.internal.NotNull; 53 import com.sun.xml.internal.bind.Util; 54 import com.sun.xml.internal.bind.api.CompositeStructure; 55 import com.sun.xml.internal.bind.api.ErrorListener; 56 import com.sun.xml.internal.bind.v2.TODO; 57 import com.sun.xml.internal.bind.v2.WellKnownNamespace; 58 import com.sun.xml.internal.bind.v2.util.CollisionCheckStack; 59 import com.sun.xml.internal.bind.v2.util.StackRecorder; 60 import static com.sun.xml.internal.bind.v2.WellKnownNamespace.XML_SCHEMA; 61 import com.sun.xml.internal.bind.v2.model.core.Adapter; 62 import com.sun.xml.internal.bind.v2.model.core.ArrayInfo; 63 import com.sun.xml.internal.bind.v2.model.core.AttributePropertyInfo; 64 import com.sun.xml.internal.bind.v2.model.core.ClassInfo; 65 import com.sun.xml.internal.bind.v2.model.core.Element; 66 import com.sun.xml.internal.bind.v2.model.core.ElementInfo; 67 import com.sun.xml.internal.bind.v2.model.core.ElementPropertyInfo; 68 import com.sun.xml.internal.bind.v2.model.core.EnumConstant; 69 import com.sun.xml.internal.bind.v2.model.core.EnumLeafInfo; 70 import com.sun.xml.internal.bind.v2.model.core.MapPropertyInfo; 71 import com.sun.xml.internal.bind.v2.model.core.MaybeElement; 72 import com.sun.xml.internal.bind.v2.model.core.NonElement; 73 import com.sun.xml.internal.bind.v2.model.core.NonElementRef; 74 import com.sun.xml.internal.bind.v2.model.core.PropertyInfo; 75 import com.sun.xml.internal.bind.v2.model.core.ReferencePropertyInfo; 76 import com.sun.xml.internal.bind.v2.model.core.TypeInfo; 77 import com.sun.xml.internal.bind.v2.model.core.TypeInfoSet; 78 import com.sun.xml.internal.bind.v2.model.core.TypeRef; 79 import com.sun.xml.internal.bind.v2.model.core.ValuePropertyInfo; 80 import com.sun.xml.internal.bind.v2.model.core.WildcardMode; 81 import com.sun.xml.internal.bind.v2.model.impl.ClassInfoImpl; 82 import com.sun.xml.internal.bind.v2.model.nav.Navigator; 83 import com.sun.xml.internal.bind.v2.runtime.SwaRefAdapter; 84 import static com.sun.xml.internal.bind.v2.schemagen.Util.*; 85 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Any; 86 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.AttrDecls; 87 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexExtension; 88 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexType; 89 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexTypeHost; 90 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ExplicitGroup; 91 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Import; 92 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.List; 93 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.LocalAttribute; 94 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.LocalElement; 95 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Schema; 96 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleExtension; 97 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleRestrictionModel; 98 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleType; 99 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleTypeHost; 100 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TopLevelAttribute; 101 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TopLevelElement; 102 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TypeHost; 103 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ContentModelContainer; 104 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TypeDefParticle; 105 import com.sun.xml.internal.bind.v2.schemagen.xmlschema.AttributeType; 106 import com.sun.xml.internal.bind.v2.schemagen.episode.Bindings; 107 import com.sun.xml.internal.txw2.TXW; 108 import com.sun.xml.internal.txw2.TxwException; 109 import com.sun.xml.internal.txw2.TypedXmlWriter; 110 import com.sun.xml.internal.txw2.output.ResultFactory; 111 import com.sun.xml.internal.txw2.output.XmlSerializer; 112 import java.util.Collection; 113 import java.util.HashSet; 114 import org.xml.sax.SAXParseException; 115 116 /** 117 * Generates a set of W3C XML Schema documents from a set of Java classes. 118 * 119 * <p> 120 * A client must invoke methods in the following order: 121 * <ol> 122 * <li>Create a new {@link XmlSchemaGenerator} 123 * <li>Invoke {@link #add} methods, multiple times if necessary. 124 * <li>Invoke {@link #write} 125 * <li>Discard the {@link XmlSchemaGenerator}. 126 * </ol> 127 * 128 * @author Ryan Shoemaker 129 * @author Kohsuke Kawaguchi (kk@kohsuke.org) 130 */ 131 public final class XmlSchemaGenerator<T,C,F,M> { 132 133 private static final Logger logger = Util.getClassLogger(); 134 135 /** 136 * Java classes to be written, organized by their namespace. 137 * 138 * <p> 139 * We use a {@link TreeMap} here so that the suggested names will 140 * be consistent across JVMs. 141 * 142 * @see SchemaOutputResolver#createOutput(String, String) 143 */ 144 private final Map<String,Namespace> namespaces = new TreeMap<String,Namespace>(NAMESPACE_COMPARATOR); 145 146 /** 147 * {@link ErrorListener} to send errors to. 148 */ 149 private ErrorListener errorListener; 150 151 /** model navigator **/ 152 private Navigator<T,C,F,M> navigator; 153 154 private final TypeInfoSet<T,C,F,M> types; 155 156 /** 157 * Representation for xs:string. 158 */ 159 private final NonElement<T,C> stringType; 160 161 /** 162 * Represents xs:anyType. 163 */ 164 private final NonElement<T,C> anyType; 165 166 /** 167 * Used to detect cycles in anonymous types. 168 */ 169 private final CollisionCheckStack<ClassInfo<T,C>> collisionChecker = new CollisionCheckStack<ClassInfo<T,C>>(); 170 XmlSchemaGenerator( Navigator<T,C,F,M> navigator, TypeInfoSet<T,C,F,M> types )171 public XmlSchemaGenerator( Navigator<T,C,F,M> navigator, TypeInfoSet<T,C,F,M> types ) { 172 this.navigator = navigator; 173 this.types = types; 174 175 this.stringType = types.getTypeInfo(navigator.ref(String.class)); 176 this.anyType = types.getAnyTypeInfo(); 177 178 // populate the object 179 for( ClassInfo<T,C> ci : types.beans().values() ) 180 add(ci); 181 for( ElementInfo<T,C> ei1 : types.getElementMappings(null).values() ) 182 add(ei1); 183 for( EnumLeafInfo<T,C> ei : types.enums().values() ) 184 add(ei); 185 for( ArrayInfo<T,C> a : types.arrays().values()) 186 add(a); 187 } 188 getNamespace(String uri)189 private Namespace getNamespace(String uri) { 190 Namespace n = namespaces.get(uri); 191 if(n==null) 192 namespaces.put(uri,n=new Namespace(uri)); 193 return n; 194 } 195 196 /** 197 * Adds a new class to the list of classes to be written. 198 * 199 * <p> 200 * A {@link ClassInfo} may have two namespaces --- one for the element name 201 * and the other for the type name. If they are different, we put the same 202 * {@link ClassInfo} to two {@link Namespace}s. 203 */ add( ClassInfo<T,C> clazz )204 public void add( ClassInfo<T,C> clazz ) { 205 assert clazz!=null; 206 207 String nsUri = null; 208 209 if(clazz.getClazz()==navigator.asDecl(CompositeStructure.class)) 210 return; // this is a special class we introduced for JAX-WS that we *don't* want in the schema 211 212 if(clazz.isElement()) { 213 // put element -> type reference 214 nsUri = clazz.getElementName().getNamespaceURI(); 215 Namespace ns = getNamespace(nsUri); 216 ns.classes.add(clazz); 217 ns.addDependencyTo(clazz.getTypeName()); 218 219 // schedule writing this global element 220 add(clazz.getElementName(),false,clazz); 221 } 222 223 QName tn = clazz.getTypeName(); 224 if(tn!=null) { 225 nsUri = tn.getNamespaceURI(); 226 } else { 227 // anonymous type 228 if(nsUri==null) 229 return; 230 } 231 232 Namespace n = getNamespace(nsUri); 233 n.classes.add(clazz); 234 235 // search properties for foreign namespace references 236 for( PropertyInfo<T,C> p : clazz.getProperties()) { 237 n.processForeignNamespaces(p, 1); 238 if (p instanceof AttributePropertyInfo) { 239 AttributePropertyInfo<T,C> ap = (AttributePropertyInfo<T,C>) p; 240 String aUri = ap.getXmlName().getNamespaceURI(); 241 if(aUri.length()>0) { 242 // global attribute 243 getNamespace(aUri).addGlobalAttribute(ap); 244 n.addDependencyTo(ap.getXmlName()); 245 } 246 } 247 if (p instanceof ElementPropertyInfo) { 248 ElementPropertyInfo<T,C> ep = (ElementPropertyInfo<T,C>) p; 249 for (TypeRef<T,C> tref : ep.getTypes()) { 250 String eUri = tref.getTagName().getNamespaceURI(); 251 if(eUri.length()>0 && !eUri.equals(n.uri)) { 252 getNamespace(eUri).addGlobalElement(tref); 253 n.addDependencyTo(tref.getTagName()); 254 } 255 } 256 } 257 258 if(generateSwaRefAdapter(p)) 259 n.useSwaRef = true; 260 261 MimeType mimeType = p.getExpectedMimeType(); 262 if( mimeType != null ) { 263 n.useMimeNs = true; 264 } 265 266 } 267 268 // recurse on baseTypes to make sure that we can refer to them in the schema 269 ClassInfo<T,C> bc = clazz.getBaseClass(); 270 if (bc != null) { 271 add(bc); 272 n.addDependencyTo(bc.getTypeName()); 273 } 274 } 275 276 /** 277 * Adds a new element to the list of elements to be written. 278 */ add( ElementInfo<T,C> elem )279 public void add( ElementInfo<T,C> elem ) { 280 assert elem!=null; 281 282 @SuppressWarnings("UnusedAssignment") 283 boolean nillable = false; // default value 284 285 QName name = elem.getElementName(); 286 Namespace n = getNamespace(name.getNamespaceURI()); 287 ElementInfo ei; 288 289 if (elem.getScope() != null) { // (probably) never happens 290 ei = this.types.getElementInfo(elem.getScope().getClazz(), name); 291 } else { 292 ei = this.types.getElementInfo(null, name); 293 } 294 295 XmlElement xmlElem = ei.getProperty().readAnnotation(XmlElement.class); 296 297 if (xmlElem == null) { 298 nillable = false; 299 } else { 300 nillable = xmlElem.nillable(); 301 } 302 303 n.elementDecls.put(name.getLocalPart(),n.new ElementWithType(nillable, elem.getContentType())); 304 305 // search for foreign namespace references 306 n.processForeignNamespaces(elem.getProperty(), 1); 307 } 308 add( EnumLeafInfo<T,C> envm )309 public void add( EnumLeafInfo<T,C> envm ) { 310 assert envm!=null; 311 312 String nsUri = null; 313 314 if(envm.isElement()) { 315 // put element -> type reference 316 nsUri = envm.getElementName().getNamespaceURI(); 317 Namespace ns = getNamespace(nsUri); 318 ns.enums.add(envm); 319 ns.addDependencyTo(envm.getTypeName()); 320 321 // schedule writing this global element 322 add(envm.getElementName(),false,envm); 323 } 324 325 final QName typeName = envm.getTypeName(); 326 if (typeName != null) { 327 nsUri = typeName.getNamespaceURI(); 328 } else { 329 if(nsUri==null) 330 return; // anonymous type 331 } 332 333 Namespace n = getNamespace(nsUri); 334 n.enums.add(envm); 335 336 // search for foreign namespace references 337 n.addDependencyTo(envm.getBaseType().getTypeName()); 338 } 339 add( ArrayInfo<T,C> a )340 public void add( ArrayInfo<T,C> a ) { 341 assert a!=null; 342 343 final String namespaceURI = a.getTypeName().getNamespaceURI(); 344 Namespace n = getNamespace(namespaceURI); 345 n.arrays.add(a); 346 347 // search for foreign namespace references 348 n.addDependencyTo(a.getItemType().getTypeName()); 349 } 350 351 /** 352 * Adds an additional element declaration. 353 * 354 * @param tagName 355 * The name of the element declaration to be added. 356 * @param type 357 * The type this element refers to. 358 * Can be null, in which case the element refers to an empty anonymous complex type. 359 */ add( QName tagName, boolean isNillable, NonElement<T,C> type )360 public void add( QName tagName, boolean isNillable, NonElement<T,C> type ) { 361 362 if(type!=null && type.getType()==navigator.ref(CompositeStructure.class)) 363 return; // this is a special class we introduced for JAX-WS that we *don't* want in the schema 364 365 366 Namespace n = getNamespace(tagName.getNamespaceURI()); 367 n.elementDecls.put(tagName.getLocalPart(), n.new ElementWithType(isNillable,type)); 368 369 // search for foreign namespace references 370 if(type!=null) 371 n.addDependencyTo(type.getTypeName()); 372 } 373 374 /** 375 * Writes out the episode file. 376 */ writeEpisodeFile(XmlSerializer out)377 public void writeEpisodeFile(XmlSerializer out) { 378 Bindings root = TXW.create(Bindings.class, out); 379 380 if(namespaces.containsKey("")) // otherwise jaxb binding NS should be the default namespace 381 root._namespace(WellKnownNamespace.JAXB,"jaxb"); 382 root.version("2.1"); 383 // TODO: don't we want to bake in versions? 384 385 // generate listing per schema 386 for (Map.Entry<String,Namespace> e : namespaces.entrySet()) { 387 Bindings group = root.bindings(); 388 389 String prefix; 390 String tns = e.getKey(); 391 if(!tns.equals("")) { 392 group._namespace(tns,"tns"); 393 prefix = "tns:"; 394 } else { 395 prefix = ""; 396 } 397 398 group.scd("x-schema::"+(tns.equals("")?"":"tns")); 399 group.schemaBindings().map(false); 400 401 for (ClassInfo<T,C> ci : e.getValue().classes) { 402 if(ci.getTypeName()==null) continue; // local type 403 404 if(ci.getTypeName().getNamespaceURI().equals(tns)) { 405 Bindings child = group.bindings(); 406 child.scd('~'+prefix+ci.getTypeName().getLocalPart()); 407 child.klass().ref(ci.getName()); 408 } 409 410 if(ci.isElement() && ci.getElementName().getNamespaceURI().equals(tns)) { 411 Bindings child = group.bindings(); 412 child.scd(prefix+ci.getElementName().getLocalPart()); 413 child.klass().ref(ci.getName()); 414 } 415 } 416 417 for (EnumLeafInfo<T,C> en : e.getValue().enums) { 418 if(en.getTypeName()==null) continue; // local type 419 420 Bindings child = group.bindings(); 421 child.scd('~'+prefix+en.getTypeName().getLocalPart()); 422 child.klass().ref(navigator.getClassName(en.getClazz())); 423 } 424 425 group.commit(true); 426 } 427 428 root.commit(); 429 } 430 431 /** 432 * Write out the schema documents. 433 */ write(SchemaOutputResolver resolver, ErrorListener errorListener)434 public void write(SchemaOutputResolver resolver, ErrorListener errorListener) throws IOException { 435 if(resolver==null) 436 throw new IllegalArgumentException(); 437 438 if(logger.isLoggable(Level.FINE)) { 439 // debug logging to see what's going on. 440 logger.log(Level.FINE,"Writing XML Schema for "+toString(),new StackRecorder()); 441 } 442 443 // make it fool-proof 444 resolver = new FoolProofResolver(resolver); 445 this.errorListener = errorListener; 446 447 Map<String, String> schemaLocations = types.getSchemaLocations(); 448 449 Map<Namespace,Result> out = new HashMap<Namespace,Result>(); 450 Map<Namespace,String> systemIds = new HashMap<Namespace,String>(); 451 452 // we create a Namespace object for the XML Schema namespace 453 // as a side-effect, but we don't want to generate it. 454 namespaces.remove(WellKnownNamespace.XML_SCHEMA); 455 456 // first create the outputs for all so that we can resolve references among 457 // schema files when we write 458 for( Namespace n : namespaces.values() ) { 459 String schemaLocation = schemaLocations.get(n.uri); 460 if(schemaLocation!=null) { 461 systemIds.put(n,schemaLocation); 462 } else { 463 Result output = resolver.createOutput(n.uri,"schema"+(out.size()+1)+".xsd"); 464 if(output!=null) { // null result means no schema for that namespace 465 out.put(n,output); 466 systemIds.put(n,output.getSystemId()); 467 } 468 } 469 //Clear the namespace specific set with already written classes 470 n.resetWritten(); 471 } 472 473 // then write'em all 474 for( Map.Entry<Namespace,Result> e : out.entrySet() ) { 475 Result result = e.getValue(); 476 e.getKey().writeTo( result, systemIds ); 477 if(result instanceof StreamResult) { 478 OutputStream outputStream = ((StreamResult)result).getOutputStream(); 479 if(outputStream != null) { 480 outputStream.close(); // fix for bugid: 6291301 481 } else { 482 final Writer writer = ((StreamResult)result).getWriter(); 483 if(writer != null) writer.close(); 484 } 485 } 486 } 487 } 488 489 490 491 /** 492 * Schema components are organized per namespace. 493 */ 494 private class Namespace { 495 final @NotNull String uri; 496 497 /** 498 * Other {@link Namespace}s that this namespace depends on. 499 */ 500 private final Set<Namespace> depends = new LinkedHashSet<Namespace>(); 501 502 /** 503 * If this schema refers to components from this schema by itself. 504 */ 505 private boolean selfReference; 506 507 /** 508 * List of classes in this namespace. 509 */ 510 private final Set<ClassInfo<T,C>> classes = new LinkedHashSet<ClassInfo<T,C>>(); 511 512 /** 513 * Set of enums in this namespace 514 */ 515 private final Set<EnumLeafInfo<T,C>> enums = new LinkedHashSet<EnumLeafInfo<T,C>>(); 516 517 /** 518 * Set of arrays in this namespace 519 */ 520 private final Set<ArrayInfo<T,C>> arrays = new LinkedHashSet<ArrayInfo<T,C>>(); 521 522 /** 523 * Global attribute declarations keyed by their local names. 524 */ 525 private final MultiMap<String,AttributePropertyInfo<T,C>> attributeDecls = new MultiMap<String,AttributePropertyInfo<T,C>>(null); 526 527 /** 528 * Global element declarations to be written, keyed by their local names. 529 */ 530 private final MultiMap<String,ElementDeclaration> elementDecls = 531 new MultiMap<String,ElementDeclaration>(new ElementWithType(true,anyType)); 532 533 private Form attributeFormDefault; 534 private Form elementFormDefault; 535 536 /** 537 * Does schema in this namespace uses swaRef? If so, we need to generate import 538 * statement. 539 */ 540 private boolean useSwaRef; 541 542 /** 543 * Import for mime namespace needs to be generated. 544 * See #856 545 */ 546 private boolean useMimeNs; 547 548 /** 549 * Container for already processed classes 550 */ 551 private final Set<ClassInfo> written = new HashSet<ClassInfo>(); 552 Namespace(String uri)553 public Namespace(String uri) { 554 this.uri = uri; 555 assert !XmlSchemaGenerator.this.namespaces.containsKey(uri); 556 XmlSchemaGenerator.this.namespaces.put(uri,this); 557 } 558 559 /** 560 * Clear out the set of already processed classes for this namespace 561 */ resetWritten()562 void resetWritten() { 563 written.clear(); 564 } 565 566 /** 567 * Process the given PropertyInfo looking for references to namespaces that 568 * are foreign to the given namespace. Any foreign namespace references 569 * found are added to the given namespaces dependency list and an <import> 570 * is generated for it. 571 * 572 * @param p the PropertyInfo 573 */ processForeignNamespaces(PropertyInfo<T, C> p, int processingDepth)574 private void processForeignNamespaces(PropertyInfo<T, C> p, int processingDepth) { 575 for (TypeInfo<T, C> t : p.ref()) { 576 if ((t instanceof ClassInfo) && (processingDepth > 0)) { 577 java.util.List<PropertyInfo> l = ((ClassInfo) t).getProperties(); 578 for (PropertyInfo subp : l) { 579 processForeignNamespaces(subp, --processingDepth); 580 } 581 } 582 if (t instanceof Element) { 583 addDependencyTo(((Element) t).getElementName()); 584 } 585 if (t instanceof NonElement) { 586 addDependencyTo(((NonElement) t).getTypeName()); 587 } 588 } 589 } 590 addDependencyTo(@ullable QName qname)591 private void addDependencyTo(@Nullable QName qname) { 592 // even though the Element interface says getElementName() returns non-null, 593 // ClassInfo always implements Element (even if an instance of ClassInfo might not be an Element). 594 // so this check is still necessary 595 if (qname==null) { 596 return; 597 } 598 599 String nsUri = qname.getNamespaceURI(); 600 601 if (nsUri.equals(XML_SCHEMA)) { 602 // no need to explicitly refer to XSD namespace 603 return; 604 } 605 606 if (nsUri.equals(uri)) { 607 selfReference = true; 608 return; 609 } 610 611 // found a type in a foreign namespace, so make sure we generate an import for it 612 depends.add(getNamespace(nsUri)); 613 } 614 615 /** 616 * Writes the schema document to the specified result. 617 * 618 * @param systemIds 619 * System IDs of the other schema documents. "" indicates 'implied'. 620 */ writeTo(Result result, Map<Namespace,String> systemIds)621 private void writeTo(Result result, Map<Namespace,String> systemIds) throws IOException { 622 try { 623 Schema schema = TXW.create(Schema.class,ResultFactory.createSerializer(result)); 624 625 // additional namespace declarations to be made. 626 Map<String, String> xmlNs = types.getXmlNs(uri); 627 628 for (Map.Entry<String, String> e : xmlNs.entrySet()) { 629 schema._namespace(e.getValue(),e.getKey()); 630 } 631 632 if(useSwaRef) 633 schema._namespace(WellKnownNamespace.SWA_URI,"swaRef"); 634 635 if(useMimeNs) 636 schema._namespace(WellKnownNamespace.XML_MIME_URI,"xmime"); 637 638 attributeFormDefault = Form.get(types.getAttributeFormDefault(uri)); 639 attributeFormDefault.declare("attributeFormDefault",schema); 640 641 elementFormDefault = Form.get(types.getElementFormDefault(uri)); 642 // TODO: if elementFormDefault is UNSET, figure out the right default value to use 643 elementFormDefault.declare("elementFormDefault",schema); 644 645 646 // declare XML Schema namespace to be xs, but allow the user to override it. 647 // if 'xs' is used for other things, we'll just let TXW assign a random prefix 648 if(!xmlNs.containsValue(WellKnownNamespace.XML_SCHEMA) 649 && !xmlNs.containsKey("xs")) 650 schema._namespace(WellKnownNamespace.XML_SCHEMA,"xs"); 651 schema.version("1.0"); 652 653 if(uri.length()!=0) 654 schema.targetNamespace(uri); 655 656 // declare prefixes for them at this level, so that we can avoid redundant 657 // namespace declarations 658 for (Namespace ns : depends) { 659 schema._namespace(ns.uri); 660 } 661 662 if(selfReference && uri.length()!=0) { 663 // use common 'tns' prefix for the own namespace 664 // if self-reference is needed 665 schema._namespace(uri,"tns"); 666 } 667 668 schema._pcdata(newline); 669 670 // refer to other schemas 671 for( Namespace n : depends ) { 672 Import imp = schema._import(); 673 if(n.uri.length()!=0) 674 imp.namespace(n.uri); 675 String refSystemId = systemIds.get(n); 676 if(refSystemId!=null && !refSystemId.equals("")) { 677 // "" means implied. null if the SchemaOutputResolver said "don't generate!" 678 imp.schemaLocation(relativize(refSystemId,result.getSystemId())); 679 } 680 schema._pcdata(newline); 681 } 682 if(useSwaRef) { 683 schema._import().namespace(WellKnownNamespace.SWA_URI).schemaLocation("http://ws-i.org/profiles/basic/1.1/swaref.xsd"); 684 } 685 if(useMimeNs) { 686 schema._import().namespace(WellKnownNamespace.XML_MIME_URI).schemaLocation("http://www.w3.org/2005/05/xmlmime"); 687 } 688 689 // then write each component 690 for (Map.Entry<String,ElementDeclaration> e : elementDecls.entrySet()) { 691 e.getValue().writeTo(e.getKey(),schema); 692 schema._pcdata(newline); 693 } 694 for (ClassInfo<T, C> c : classes) { 695 if (c.getTypeName()==null) { 696 // don't generate anything if it's an anonymous type 697 continue; 698 } 699 if(uri.equals(c.getTypeName().getNamespaceURI())) 700 writeClass(c, schema); 701 schema._pcdata(newline); 702 } 703 for (EnumLeafInfo<T, C> e : enums) { 704 if (e.getTypeName()==null) { 705 // don't generate anything if it's an anonymous type 706 continue; 707 } 708 if(uri.equals(e.getTypeName().getNamespaceURI())) 709 writeEnum(e,schema); 710 schema._pcdata(newline); 711 } 712 for (ArrayInfo<T, C> a : arrays) { 713 writeArray(a,schema); 714 schema._pcdata(newline); 715 } 716 for (Map.Entry<String,AttributePropertyInfo<T,C>> e : attributeDecls.entrySet()) { 717 TopLevelAttribute a = schema.attribute(); 718 a.name(e.getKey()); 719 if(e.getValue()==null) 720 writeTypeRef(a,stringType,"type"); 721 else 722 writeAttributeTypeRef(e.getValue(),a); 723 schema._pcdata(newline); 724 } 725 726 // close the schema 727 schema.commit(); 728 } catch( TxwException e ) { 729 logger.log(Level.INFO,e.getMessage(),e); 730 throw new IOException(e.getMessage()); 731 } 732 } 733 734 /** 735 * Writes a type attribute (if the referenced type is a global type) 736 * or writes out the definition of the anonymous type in place (if the referenced 737 * type is not a global type.) 738 * 739 * Also provides processing for ID/IDREF, MTOM @xmime, and swa:ref 740 * 741 * ComplexTypeHost and SimpleTypeHost don't share an api for creating 742 * and attribute in a type-safe way, so we will compromise for now and 743 * use _attribute(). 744 */ writeTypeRef(TypeHost th, NonElementRef<T, C> typeRef, String refAttName)745 private void writeTypeRef(TypeHost th, NonElementRef<T, C> typeRef, String refAttName) { 746 // ID / IDREF handling 747 switch(typeRef.getSource().id()) { 748 case ID: 749 th._attribute(refAttName, new QName(WellKnownNamespace.XML_SCHEMA, "ID")); 750 return; 751 case IDREF: 752 th._attribute(refAttName, new QName(WellKnownNamespace.XML_SCHEMA, "IDREF")); 753 return; 754 case NONE: 755 // no ID/IDREF, so continue on and generate the type 756 break; 757 default: 758 throw new IllegalStateException(); 759 } 760 761 // MTOM handling 762 MimeType mimeType = typeRef.getSource().getExpectedMimeType(); 763 if( mimeType != null ) { 764 th._attribute(new QName(WellKnownNamespace.XML_MIME_URI, "expectedContentTypes", "xmime"), mimeType.toString()); 765 } 766 767 // ref:swaRef handling 768 if(generateSwaRefAdapter(typeRef)) { 769 th._attribute(refAttName, new QName(WellKnownNamespace.SWA_URI, "swaRef", "ref")); 770 return; 771 } 772 773 // type name override 774 if(typeRef.getSource().getSchemaType()!=null) { 775 th._attribute(refAttName,typeRef.getSource().getSchemaType()); 776 return; 777 } 778 779 // normal type generation 780 writeTypeRef(th, typeRef.getTarget(), refAttName); 781 } 782 783 /** 784 * Writes a type attribute (if the referenced type is a global type) 785 * or writes out the definition of the anonymous type in place (if the referenced 786 * type is not a global type.) 787 * 788 * @param th 789 * the TXW interface to which the attribute will be written. 790 * @param type 791 * type to be referenced. 792 * @param refAttName 793 * The name of the attribute used when referencing a type by QName. 794 */ writeTypeRef(TypeHost th, NonElement<T,C> type, String refAttName)795 private void writeTypeRef(TypeHost th, NonElement<T,C> type, String refAttName) { 796 Element e = null; 797 if (type instanceof MaybeElement) { 798 MaybeElement me = (MaybeElement)type; 799 boolean isElement = me.isElement(); 800 if (isElement) e = me.asElement(); 801 } 802 if (type instanceof Element) { 803 e = (Element)type; 804 } 805 if (type.getTypeName()==null) { 806 if ((e != null) && (e.getElementName() != null)) { 807 th.block(); // so that the caller may write other attributes 808 if(type instanceof ClassInfo) { 809 writeClass( (ClassInfo<T,C>)type, th ); 810 } else { 811 writeEnum( (EnumLeafInfo<T,C>)type, (SimpleTypeHost)th); 812 } 813 } else { 814 // anonymous 815 th.block(); // so that the caller may write other attributes 816 if(type instanceof ClassInfo) { 817 if(collisionChecker.push((ClassInfo<T,C>)type)) { 818 errorListener.warning(new SAXParseException( 819 Messages.ANONYMOUS_TYPE_CYCLE.format(collisionChecker.getCycleString()), 820 null 821 )); 822 } else { 823 writeClass( (ClassInfo<T,C>)type, th ); 824 } 825 collisionChecker.pop(); 826 } else { 827 writeEnum( (EnumLeafInfo<T,C>)type, (SimpleTypeHost)th); 828 } 829 } 830 } else { 831 th._attribute(refAttName,type.getTypeName()); 832 } 833 } 834 835 /** 836 * writes the schema definition for the given array class 837 */ writeArray(ArrayInfo<T, C> a, Schema schema)838 private void writeArray(ArrayInfo<T, C> a, Schema schema) { 839 ComplexType ct = schema.complexType().name(a.getTypeName().getLocalPart()); 840 ct._final("#all"); 841 LocalElement le = ct.sequence().element().name("item"); 842 le.type(a.getItemType().getTypeName()); 843 le.minOccurs(0).maxOccurs("unbounded"); 844 le.nillable(true); 845 ct.commit(); 846 } 847 848 /** 849 * writes the schema definition for the specified type-safe enum in the given TypeHost 850 */ writeEnum(EnumLeafInfo<T, C> e, SimpleTypeHost th)851 private void writeEnum(EnumLeafInfo<T, C> e, SimpleTypeHost th) { 852 SimpleType st = th.simpleType(); 853 writeName(e,st); 854 855 SimpleRestrictionModel base = st.restriction(); 856 writeTypeRef(base, e.getBaseType(), "base"); 857 858 for (EnumConstant c : e.getConstants()) { 859 base.enumeration().value(c.getLexicalValue()); 860 } 861 st.commit(); 862 } 863 864 /** 865 * Writes the schema definition for the specified class to the schema writer. 866 * 867 * @param c the class info 868 * @param parent the writer of the parent element into which the type will be defined 869 */ writeClass(ClassInfo<T,C> c, TypeHost parent)870 private void writeClass(ClassInfo<T,C> c, TypeHost parent) { 871 if (written.contains(c)) { // to avoid cycles let's check if we haven't already processed the class 872 return; 873 } 874 written.add(c); 875 // special handling for value properties 876 if (containsValueProp(c)) { 877 if (c.getProperties().size() == 1) { 878 // [RESULT 2 - simpleType if the value prop is the only prop] 879 // 880 // <simpleType name="foo"> 881 // <xs:restriction base="xs:int"/> 882 // </> 883 ValuePropertyInfo<T,C> vp = (ValuePropertyInfo<T,C>)c.getProperties().get(0); 884 SimpleType st = ((SimpleTypeHost)parent).simpleType(); 885 writeName(c, st); 886 if(vp.isCollection()) { 887 writeTypeRef(st.list(),vp.getTarget(),"itemType"); 888 } else { 889 writeTypeRef(st.restriction(),vp.getTarget(),"base"); 890 } 891 return; 892 } else { 893 // [RESULT 1 - complexType with simpleContent] 894 // 895 // <complexType name="foo"> 896 // <simpleContent> 897 // <extension base="xs:int"/> 898 // <attribute name="b" type="xs:boolean"/> 899 // </> 900 // </> 901 // </> 902 // ... 903 // <element name="f" type="foo"/> 904 // ... 905 ComplexType ct = ((ComplexTypeHost)parent).complexType(); 906 writeName(c,ct); 907 if(c.isFinal()) 908 ct._final("extension restriction"); 909 910 SimpleExtension se = ct.simpleContent().extension(); 911 se.block(); // because we might have attribute before value 912 for (PropertyInfo<T,C> p : c.getProperties()) { 913 switch (p.kind()) { 914 case ATTRIBUTE: 915 handleAttributeProp((AttributePropertyInfo<T,C>)p,se); 916 break; 917 case VALUE: 918 TODO.checkSpec("what if vp.isCollection() == true?"); 919 ValuePropertyInfo vp = (ValuePropertyInfo) p; 920 se.base(vp.getTarget().getTypeName()); 921 break; 922 case ELEMENT: // error 923 case REFERENCE: // error 924 default: 925 assert false; 926 throw new IllegalStateException(); 927 } 928 } 929 se.commit(); 930 } 931 TODO.schemaGenerator("figure out what to do if bc != null"); 932 TODO.checkSpec("handle sec 8.9.5.2, bullet #4"); 933 // Java types containing value props can only contain properties of type 934 // ValuePropertyinfo and AttributePropertyInfo which have just been handled, 935 // so return. 936 return; 937 } 938 939 // we didn't fall into the special case for value props, so we 940 // need to initialize the ct. 941 // generate the complexType 942 ComplexType ct = ((ComplexTypeHost)parent).complexType(); 943 writeName(c,ct); 944 if(c.isFinal()) 945 ct._final("extension restriction"); 946 if(c.isAbstract()) 947 ct._abstract(true); 948 949 // these are where we write content model and attributes 950 AttrDecls contentModel = ct; 951 TypeDefParticle contentModelOwner = ct; 952 953 // if there is a base class, we need to generate an extension in the schema 954 final ClassInfo<T,C> bc = c.getBaseClass(); 955 if (bc != null) { 956 if(bc.hasValueProperty()) { 957 // extending complex type with simple content 958 SimpleExtension se = ct.simpleContent().extension(); 959 contentModel = se; 960 contentModelOwner = null; 961 se.base(bc.getTypeName()); 962 } else { 963 ComplexExtension ce = ct.complexContent().extension(); 964 contentModel = ce; 965 contentModelOwner = ce; 966 967 ce.base(bc.getTypeName()); 968 // TODO: what if the base type is anonymous? 969 } 970 } 971 972 if(contentModelOwner!=null) { 973 // build the tree that represents the explicit content model from iterate over the properties 974 ArrayList<Tree> children = new ArrayList<Tree>(); 975 for (PropertyInfo<T,C> p : c.getProperties()) { 976 // handling for <complexType @mixed='true' ...> 977 if(p instanceof ReferencePropertyInfo && ((ReferencePropertyInfo)p).isMixed()) { 978 ct.mixed(true); 979 } 980 Tree t = buildPropertyContentModel(p); 981 if(t!=null) 982 children.add(t); 983 } 984 985 Tree top = Tree.makeGroup( c.isOrdered() ? GroupKind.SEQUENCE : GroupKind.ALL, children); 986 987 // write the content model 988 top.write(contentModelOwner); 989 } 990 991 // then attributes 992 for (PropertyInfo<T,C> p : c.getProperties()) { 993 if (p instanceof AttributePropertyInfo) { 994 handleAttributeProp((AttributePropertyInfo<T,C>)p, contentModel); 995 } 996 } 997 if( c.hasAttributeWildcard()) { 998 contentModel.anyAttribute().namespace("##other").processContents("skip"); 999 } 1000 ct.commit(); 1001 } 1002 1003 /** 1004 * Writes the name attribute if it's named. 1005 */ writeName(NonElement<T,C> c, TypedXmlWriter xw)1006 private void writeName(NonElement<T,C> c, TypedXmlWriter xw) { 1007 QName tn = c.getTypeName(); 1008 if(tn!=null) 1009 xw._attribute("name",tn.getLocalPart()); // named 1010 } 1011 containsValueProp(ClassInfo<T, C> c)1012 private boolean containsValueProp(ClassInfo<T, C> c) { 1013 for (PropertyInfo p : c.getProperties()) { 1014 if (p instanceof ValuePropertyInfo) return true; 1015 } 1016 return false; 1017 } 1018 1019 /** 1020 * Builds content model writer for the specified property. 1021 */ buildPropertyContentModel(PropertyInfo<T,C> p)1022 private Tree buildPropertyContentModel(PropertyInfo<T,C> p) { 1023 switch(p.kind()) { 1024 case ELEMENT: 1025 return handleElementProp((ElementPropertyInfo<T,C>)p); 1026 case ATTRIBUTE: 1027 // attribuets are handled later 1028 return null; 1029 case REFERENCE: 1030 return handleReferenceProp((ReferencePropertyInfo<T,C>)p); 1031 case MAP: 1032 return handleMapProp((MapPropertyInfo<T,C>)p); 1033 case VALUE: 1034 // value props handled above in writeClass() 1035 assert false; 1036 throw new IllegalStateException(); 1037 default: 1038 assert false; 1039 throw new IllegalStateException(); 1040 } 1041 } 1042 1043 /** 1044 * Generate the proper schema fragment for the given element property into the 1045 * specified schema compositor. 1046 * 1047 * The element property may or may not represent a collection and it may or may 1048 * not be wrapped. 1049 * 1050 * @param ep the element property 1051 */ handleElementProp(final ElementPropertyInfo<T,C> ep)1052 private Tree handleElementProp(final ElementPropertyInfo<T,C> ep) { 1053 if (ep.isValueList()) { 1054 return new Tree.Term() { 1055 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1056 TypeRef<T,C> t = ep.getTypes().get(0); 1057 LocalElement e = parent.element(); 1058 e.block(); // we will write occurs later 1059 QName tn = t.getTagName(); 1060 e.name(tn.getLocalPart()); 1061 List lst = e.simpleType().list(); 1062 writeTypeRef(lst,t, "itemType"); 1063 elementFormDefault.writeForm(e,tn); 1064 writeOccurs(e,isOptional||!ep.isRequired(),repeated); 1065 } 1066 }; 1067 } 1068 1069 ArrayList<Tree> children = new ArrayList<Tree>(); 1070 for (final TypeRef<T,C> t : ep.getTypes()) { 1071 children.add(new Tree.Term() { 1072 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1073 LocalElement e = parent.element(); 1074 1075 QName tn = t.getTagName(); 1076 1077 PropertyInfo propInfo = t.getSource(); 1078 TypeInfo parentInfo = (propInfo == null) ? null : propInfo.parent(); 1079 1080 if (canBeDirectElementRef(t, tn, parentInfo)) { 1081 if ((!t.getTarget().isSimpleType()) && (t.getTarget() instanceof ClassInfo) && collisionChecker.findDuplicate((ClassInfo<T, C>) t.getTarget())) { 1082 e.ref(new QName(uri, tn.getLocalPart())); 1083 } else { 1084 1085 QName elemName = null; 1086 if (t.getTarget() instanceof Element) { 1087 Element te = (Element) t.getTarget(); 1088 elemName = te.getElementName(); 1089 } 1090 1091 Collection<TypeInfo> refs = propInfo.ref(); 1092 TypeInfo ti; 1093 if ((refs != null) && (!refs.isEmpty()) && (elemName != null) 1094 && ((ti = refs.iterator().next()) == null || ti instanceof ClassInfoImpl)) { 1095 ClassInfoImpl cImpl = (ClassInfoImpl)ti; 1096 if ((cImpl != null) && (cImpl.getElementName() != null)) { 1097 e.ref(new QName(cImpl.getElementName().getNamespaceURI(), tn.getLocalPart())); 1098 } else { 1099 e.ref(new QName("", tn.getLocalPart())); 1100 } 1101 } else { 1102 e.ref(tn); 1103 } 1104 } 1105 } else { 1106 e.name(tn.getLocalPart()); 1107 writeTypeRef(e,t, "type"); 1108 elementFormDefault.writeForm(e,tn); 1109 } 1110 1111 if (t.isNillable()) { 1112 e.nillable(true); 1113 } 1114 if(t.getDefaultValue()!=null) 1115 e._default(t.getDefaultValue()); 1116 writeOccurs(e,isOptional,repeated); 1117 } 1118 }); 1119 } 1120 1121 final Tree choice = Tree.makeGroup(GroupKind.CHOICE, children) 1122 .makeOptional(!ep.isRequired()) 1123 .makeRepeated(ep.isCollection()); // see Spec table 8-13 1124 1125 1126 final QName ename = ep.getXmlName(); 1127 if (ename != null) { // wrapped collection 1128 return new Tree.Term() { 1129 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1130 LocalElement e = parent.element(); 1131 if(ename.getNamespaceURI().length()>0) { 1132 if (!ename.getNamespaceURI().equals(uri)) { 1133 // TODO: we need to generate the corresponding element declaration for this 1134 // table 8-25: Property/field element wrapper with ref attribute 1135 e.ref(new QName(ename.getNamespaceURI(), ename.getLocalPart())); 1136 return; 1137 } 1138 } 1139 e.name(ename.getLocalPart()); 1140 elementFormDefault.writeForm(e,ename); 1141 1142 if(ep.isCollectionNillable()) { 1143 e.nillable(true); 1144 } 1145 writeOccurs(e,!ep.isCollectionRequired(),repeated); 1146 1147 ComplexType p = e.complexType(); 1148 choice.write(p); 1149 } 1150 }; 1151 } else {// non-wrapped 1152 return choice; 1153 } 1154 } 1155 1156 /** 1157 * Checks if we can collapse 1158 * <element name='foo' type='t' /> to <element ref='foo' />. 1159 * 1160 * This is possible if we already have such declaration to begin with. 1161 */ 1162 private boolean canBeDirectElementRef(TypeRef<T, C> t, QName tn, TypeInfo parentInfo) { 1163 Element te = null; 1164 ClassInfo ci = null; 1165 QName targetTagName = null; 1166 1167 if(t.isNillable() || t.getDefaultValue()!=null) { 1168 // can't put those attributes on <element ref> 1169 return false; 1170 } 1171 1172 if (t.getTarget() instanceof Element) { 1173 te = (Element) t.getTarget(); 1174 targetTagName = te.getElementName(); 1175 if (te instanceof ClassInfo) { 1176 ci = (ClassInfo)te; 1177 } 1178 } 1179 1180 String nsUri = tn.getNamespaceURI(); 1181 if ((!nsUri.equals(uri) && nsUri.length()>0) && (!((parentInfo instanceof ClassInfo) && (((ClassInfo)parentInfo).getTypeName() == null)))) { 1182 return true; 1183 } 1184 1185 if ((ci != null) && ((targetTagName != null) && (te.getScope() == null) && (targetTagName.getNamespaceURI() == null))) { 1186 if (targetTagName.equals(tn)) { 1187 return true; 1188 } 1189 } 1190 1191 // we have the precise element defined already 1192 if (te != null) { // it is instanceof Element 1193 return targetTagName!=null && targetTagName.equals(tn); 1194 } 1195 1196 return false; 1197 } 1198 1199 1200 /** 1201 * Generate an attribute for the specified property on the specified complexType 1202 * 1203 * @param ap the attribute 1204 * @param attr the schema definition to which the attribute will be added 1205 */ 1206 private void handleAttributeProp(AttributePropertyInfo<T,C> ap, AttrDecls attr) { 1207 // attr is either a top-level ComplexType or a ComplexExtension 1208 // 1209 // [RESULT] 1210 // 1211 // <complexType ...> 1212 // <...>...</> 1213 // <attribute name="foo" type="xs:int"/> 1214 // </> 1215 // 1216 // or 1217 // 1218 // <complexType ...> 1219 // <complexContent> 1220 // <extension ...> 1221 // <...>...</> 1222 // </> 1223 // </> 1224 // <attribute name="foo" type="xs:int"/> 1225 // </> 1226 // 1227 // or it could also be an in-lined type (attr ref) 1228 // 1229 LocalAttribute localAttribute = attr.attribute(); 1230 1231 final String attrURI = ap.getXmlName().getNamespaceURI(); 1232 if (attrURI.equals("") /*|| attrURI.equals(uri) --- those are generated as global attributes anyway, so use them.*/) { 1233 localAttribute.name(ap.getXmlName().getLocalPart()); 1234 1235 writeAttributeTypeRef(ap, localAttribute); 1236 1237 attributeFormDefault.writeForm(localAttribute,ap.getXmlName()); 1238 } else { // generate an attr ref 1239 localAttribute.ref(ap.getXmlName()); 1240 } 1241 1242 if(ap.isRequired()) { 1243 // TODO: not type safe 1244 localAttribute.use("required"); 1245 } 1246 } 1247 1248 private void writeAttributeTypeRef(AttributePropertyInfo<T,C> ap, AttributeType a) { 1249 if( ap.isCollection() ) 1250 writeTypeRef(a.simpleType().list(), ap, "itemType"); 1251 else 1252 writeTypeRef(a, ap, "type"); 1253 } 1254 1255 /** 1256 * Generate the proper schema fragment for the given reference property into the 1257 * specified schema compositor. 1258 * 1259 * The reference property may or may not refer to a collection and it may or may 1260 * not be wrapped. 1261 */ 1262 private Tree handleReferenceProp(final ReferencePropertyInfo<T, C> rp) { 1263 // fill in content model 1264 ArrayList<Tree> children = new ArrayList<Tree>(); 1265 1266 for (final Element<T,C> e : rp.getElements()) { 1267 children.add(new Tree.Term() { 1268 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1269 LocalElement eref = parent.element(); 1270 1271 boolean local=false; 1272 1273 QName en = e.getElementName(); 1274 if(e.getScope()!=null) { 1275 // scoped. needs to be inlined 1276 boolean qualified = en.getNamespaceURI().equals(uri); 1277 boolean unqualified = en.getNamespaceURI().equals(""); 1278 if(qualified || unqualified) { 1279 // can be inlined indeed 1280 1281 // write form="..." if necessary 1282 if(unqualified) { 1283 if(elementFormDefault.isEffectivelyQualified) 1284 eref.form("unqualified"); 1285 } else { 1286 if(!elementFormDefault.isEffectivelyQualified) 1287 eref.form("qualified"); 1288 } 1289 1290 local = true; 1291 eref.name(en.getLocalPart()); 1292 1293 // write out type reference 1294 if(e instanceof ClassInfo) { 1295 writeTypeRef(eref,(ClassInfo<T,C>)e,"type"); 1296 } else { 1297 writeTypeRef(eref,((ElementInfo<T,C>)e).getContentType(),"type"); 1298 } 1299 } 1300 } 1301 if(!local) 1302 eref.ref(en); 1303 writeOccurs(eref,isOptional,repeated); 1304 } 1305 }); 1306 } 1307 1308 final WildcardMode wc = rp.getWildcard(); 1309 if( wc != null ) { 1310 children.add(new Tree.Term() { 1311 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1312 Any any = parent.any(); 1313 final String pcmode = getProcessContentsModeName(wc); 1314 if( pcmode != null ) any.processContents(pcmode); 1315 any.namespace("##other"); 1316 writeOccurs(any,isOptional,repeated); 1317 } 1318 }); 1319 } 1320 1321 1322 final Tree choice = Tree.makeGroup(GroupKind.CHOICE, children).makeRepeated(rp.isCollection()).makeOptional(!rp.isRequired()); 1323 1324 final QName ename = rp.getXmlName(); 1325 1326 if (ename != null) { // wrapped 1327 return new Tree.Term() { 1328 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1329 LocalElement e = parent.element().name(ename.getLocalPart()); 1330 elementFormDefault.writeForm(e,ename); 1331 if(rp.isCollectionNillable()) 1332 e.nillable(true); 1333 writeOccurs(e,true,repeated); 1334 1335 ComplexType p = e.complexType(); 1336 choice.write(p); 1337 } 1338 }; 1339 } else { // unwrapped 1340 return choice; 1341 } 1342 } 1343 1344 /** 1345 * Generate the proper schema fragment for the given map property into the 1346 * specified schema compositor. 1347 * 1348 * @param mp the map property 1349 */ 1350 private Tree handleMapProp(final MapPropertyInfo<T,C> mp) { 1351 return new Tree.Term() { 1352 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1353 QName ename = mp.getXmlName(); 1354 1355 LocalElement e = parent.element(); 1356 elementFormDefault.writeForm(e,ename); 1357 if(mp.isCollectionNillable()) 1358 e.nillable(true); 1359 1360 e = e.name(ename.getLocalPart()); 1361 writeOccurs(e,isOptional,repeated); 1362 ComplexType p = e.complexType(); 1363 1364 // TODO: entry, key, and value are always unqualified. that needs to be fixed, too. 1365 // TODO: we need to generate the corresponding element declaration, if they are qualified 1366 e = p.sequence().element(); 1367 e.name("entry").minOccurs(0).maxOccurs("unbounded"); 1368 1369 ExplicitGroup seq = e.complexType().sequence(); 1370 writeKeyOrValue(seq, "key", mp.getKeyType()); 1371 writeKeyOrValue(seq, "value", mp.getValueType()); 1372 } 1373 }; 1374 } 1375 1376 private void writeKeyOrValue(ExplicitGroup seq, String tagName, NonElement<T, C> typeRef) { 1377 LocalElement key = seq.element().name(tagName); 1378 key.minOccurs(0); 1379 writeTypeRef(key, typeRef, "type"); 1380 } 1381 1382 public void addGlobalAttribute(AttributePropertyInfo<T,C> ap) { 1383 attributeDecls.put( ap.getXmlName().getLocalPart(), ap ); 1384 addDependencyTo(ap.getTarget().getTypeName()); 1385 } 1386 1387 public void addGlobalElement(TypeRef<T,C> tref) { 1388 elementDecls.put( tref.getTagName().getLocalPart(), new ElementWithType(false,tref.getTarget()) ); 1389 addDependencyTo(tref.getTarget().getTypeName()); 1390 } 1391 1392 @Override 1393 public String toString() { 1394 StringBuilder buf = new StringBuilder(); 1395 buf.append("[classes=").append(classes); 1396 buf.append(",elementDecls=").append(elementDecls); 1397 buf.append(",enums=").append(enums); 1398 buf.append("]"); 1399 return super.toString(); 1400 } 1401 1402 /** 1403 * Represents a global element declaration to be written. 1404 * 1405 * <p> 1406 * Because multiple properties can name the same global element even if 1407 * they have different Java type, the schema generator first needs to 1408 * walk through the model and decide what to generate for the given 1409 * element declaration. 1410 * 1411 * <p> 1412 * This class represents what will be written, and its {@link #equals(Object)} 1413 * method is implemented in such a way that two identical declarations 1414 * are considered as the same. 1415 */ 1416 abstract class ElementDeclaration { 1417 /** 1418 * Returns true if two {@link ElementDeclaration}s are representing 1419 * the same schema fragment. 1420 */ 1421 @Override 1422 public abstract boolean equals(Object o); 1423 @Override 1424 public abstract int hashCode(); 1425 1426 /** 1427 * Generates the declaration. 1428 */ 1429 public abstract void writeTo(String localName, Schema schema); 1430 } 1431 1432 /** 1433 * {@link ElementDeclaration} that refers to a {@link NonElement}. 1434 */ 1435 class ElementWithType extends ElementDeclaration { 1436 private final boolean nillable; 1437 private final NonElement<T,C> type; 1438 1439 public ElementWithType(boolean nillable,NonElement<T, C> type) { 1440 this.type = type; 1441 this.nillable = nillable; 1442 } 1443 1444 public void writeTo(String localName, Schema schema) { 1445 TopLevelElement e = schema.element().name(localName); 1446 if(nillable) 1447 e.nillable(true); 1448 if (type != null) { 1449 writeTypeRef(e,type, "type"); 1450 } else { 1451 e.complexType(); // refer to the nested empty complex type 1452 } 1453 e.commit(); 1454 } 1455 1456 public boolean equals(Object o) { 1457 if (this == o) return true; 1458 if (o == null || getClass() != o.getClass()) return false; 1459 1460 final ElementWithType that = (ElementWithType) o; 1461 return type.equals(that.type); 1462 } 1463 1464 public int hashCode() { 1465 return type.hashCode(); 1466 } 1467 } 1468 } 1469 1470 /** 1471 * Examine the specified element ref and determine if a swaRef attribute needs to be generated 1472 * @param typeRef 1473 */ 1474 private boolean generateSwaRefAdapter(NonElementRef<T,C> typeRef) { 1475 return generateSwaRefAdapter(typeRef.getSource()); 1476 } 1477 1478 /** 1479 * Examine the specified element ref and determine if a swaRef attribute needs to be generated 1480 */ 1481 private boolean generateSwaRefAdapter(PropertyInfo<T,C> prop) { 1482 final Adapter<T,C> adapter = prop.getAdapter(); 1483 if (adapter == null) return false; 1484 final Object o = navigator.asDecl(SwaRefAdapter.class); 1485 if (o == null) return false; 1486 return (o.equals(adapter.adapterType)); 1487 } 1488 1489 /** 1490 * Debug information of what's in this {@link XmlSchemaGenerator}. 1491 */ 1492 @Override 1493 public String toString() { 1494 StringBuilder buf = new StringBuilder(); 1495 for (Namespace ns : namespaces.values()) { 1496 if(buf.length()>0) buf.append(','); 1497 buf.append(ns.uri).append('=').append(ns); 1498 } 1499 return super.toString()+'['+buf+']'; 1500 } 1501 1502 /** 1503 * return the string representation of the processContents mode of the 1504 * give wildcard, or null if it is the schema default "strict" 1505 * 1506 */ 1507 private static String getProcessContentsModeName(WildcardMode wc) { 1508 switch(wc) { 1509 case LAX: 1510 case SKIP: 1511 return wc.name().toLowerCase(); 1512 case STRICT: 1513 return null; 1514 default: 1515 throw new IllegalStateException(); 1516 } 1517 } 1518 1519 1520 /** 1521 * Relativizes a URI by using another URI (base URI.) 1522 * 1523 * <p> 1524 * For example, {@code relative("http://www.sun.com/abc/def","http://www.sun.com/pqr/stu") => "../abc/def"} 1525 * 1526 * <p> 1527 * This method only works on hierarchical URI's, not opaque URI's (refer to the 1528 * <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/URI.html">java.net.URI</a> 1529 * javadoc for complete definitions of these terms. 1530 * 1531 * <p> 1532 * This method will not normalize the relative URI. 1533 * 1534 * @return the relative URI or the original URI if a relative one could not be computed 1535 */ 1536 protected static String relativize(String uri, String baseUri) { 1537 try { 1538 assert uri!=null; 1539 1540 if(baseUri==null) return uri; 1541 1542 URI theUri = new URI(escapeURI(uri)); 1543 URI theBaseUri = new URI(escapeURI(baseUri)); 1544 1545 if (theUri.isOpaque() || theBaseUri.isOpaque()) 1546 return uri; 1547 1548 if (!equalsIgnoreCase(theUri.getScheme(), theBaseUri.getScheme()) || 1549 !equal(theUri.getAuthority(), theBaseUri.getAuthority())) 1550 return uri; 1551 1552 String uriPath = theUri.getPath(); 1553 String basePath = theBaseUri.getPath(); 1554 1555 // normalize base path 1556 if (!basePath.endsWith("/")) { 1557 basePath = normalizeUriPath(basePath); 1558 } 1559 1560 if( uriPath.equals(basePath)) 1561 return "."; 1562 1563 String relPath = calculateRelativePath(uriPath, basePath, fixNull(theUri.getScheme()).equals("file")); 1564 1565 if (relPath == null) 1566 return uri; // recursion found no commonality in the two uris at all 1567 StringBuilder relUri = new StringBuilder(); 1568 relUri.append(relPath); 1569 if (theUri.getQuery() != null) 1570 relUri.append('?').append(theUri.getQuery()); 1571 if (theUri.getFragment() != null) 1572 relUri.append('#').append(theUri.getFragment()); 1573 1574 return relUri.toString(); 1575 } catch (URISyntaxException e) { 1576 throw new InternalError("Error escaping one of these uris:\n\t"+uri+"\n\t"+baseUri); 1577 } 1578 } 1579 1580 private static String fixNull(String s) { 1581 if(s==null) return ""; 1582 else return s; 1583 } 1584 1585 private static String calculateRelativePath(String uri, String base, boolean fileUrl) { 1586 // if this is a file URL (very likely), and if this is on a case-insensitive file system, 1587 // then treat it accordingly. 1588 boolean onWindows = File.pathSeparatorChar==';'; 1589 1590 if (base == null) { 1591 return null; 1592 } 1593 if ((fileUrl && onWindows && startsWithIgnoreCase(uri,base)) || uri.startsWith(base)) { 1594 return uri.substring(base.length()); 1595 } else { 1596 return "../" + calculateRelativePath(uri, getParentUriPath(base), fileUrl); 1597 } 1598 } 1599 1600 private static boolean startsWithIgnoreCase(String s, String t) { 1601 return s.toUpperCase().startsWith(t.toUpperCase()); 1602 } 1603 1604 /** 1605 * JAX-RPC wants the namespaces to be sorted in the reverse order 1606 * so that the empty namespace "" comes to the very end. Don't ask me why. 1607 */ 1608 private static final Comparator<String> NAMESPACE_COMPARATOR = new Comparator<String>() { 1609 public int compare(String lhs, String rhs) { 1610 return -lhs.compareTo(rhs); 1611 } 1612 }; 1613 1614 private static final String newline = "\n"; 1615 } 1616