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 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 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 */ 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 */ 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 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 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 */ 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 */ 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 */ 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 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 */ 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 {@code <import>} 570 * is generated for it. 571 * 572 * @param p the PropertyInfo 573 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 if ((refs != null) && (!refs.isEmpty()) && (elemName != null)){ 1093 ClassInfoImpl cImpl = null; 1094 for (TypeInfo ref : refs) { 1095 if (ref == null || ref instanceof ClassInfoImpl) { 1096 if (elemName.equals(((ClassInfoImpl)ref).getElementName())) { 1097 cImpl = (ClassInfoImpl) ref; 1098 break; 1099 } 1100 } 1101 } 1102 if (cImpl != null) { 1103 if (tn.getNamespaceURI() != null && tn.getNamespaceURI().trim().length() != 0) { 1104 e.ref(new QName(tn.getNamespaceURI(), tn.getLocalPart())); 1105 } else { 1106 e.ref(new QName(cImpl.getElementName().getNamespaceURI(), tn.getLocalPart())); 1107 } 1108 } else 1109 e.ref(new QName("", tn.getLocalPart())); 1110 } else 1111 e.ref(tn); 1112 } 1113 } else { 1114 e.name(tn.getLocalPart()); 1115 writeTypeRef(e,t, "type"); 1116 elementFormDefault.writeForm(e,tn); 1117 } 1118 1119 if (t.isNillable()) { 1120 e.nillable(true); 1121 } 1122 if(t.getDefaultValue()!=null) 1123 e._default(t.getDefaultValue()); 1124 writeOccurs(e,isOptional,repeated); 1125 } 1126 }); 1127 } 1128 1129 final Tree choice = Tree.makeGroup(GroupKind.CHOICE, children) 1130 .makeOptional(!ep.isRequired()) 1131 .makeRepeated(ep.isCollection()); // see Spec table 8-13 1132 1133 1134 final QName ename = ep.getXmlName(); 1135 if (ename != null) { // wrapped collection 1136 return new Tree.Term() { 1137 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1138 LocalElement e = parent.element(); 1139 if(ename.getNamespaceURI().length()>0) { 1140 if (!ename.getNamespaceURI().equals(uri)) { 1141 // TODO: we need to generate the corresponding element declaration for this 1142 // table 8-25: Property/field element wrapper with ref attribute 1143 e.ref(new QName(ename.getNamespaceURI(), ename.getLocalPart())); 1144 return; 1145 } 1146 } 1147 e.name(ename.getLocalPart()); 1148 elementFormDefault.writeForm(e,ename); 1149 1150 if(ep.isCollectionNillable()) { 1151 e.nillable(true); 1152 } 1153 writeOccurs(e,!ep.isCollectionRequired(),repeated); 1154 1155 ComplexType p = e.complexType(); 1156 choice.write(p); 1157 } 1158 }; 1159 } else {// non-wrapped 1160 return choice; 1161 } 1162 } 1163 1164 /** 1165 * Checks if we can collapse 1166 * {@code <element name='foo' type='t' />} 1167 * to {@code <element ref='foo' />}. 1168 * 1169 * This is possible if we already have such declaration to begin with. 1170 */ 1171 private boolean canBeDirectElementRef(TypeRef<T, C> t, QName tn, TypeInfo parentInfo) { 1172 Element te = null; 1173 ClassInfo ci = null; 1174 QName targetTagName = null; 1175 1176 if(t.isNillable() || t.getDefaultValue()!=null) { 1177 // can't put those attributes on <element ref> 1178 return false; 1179 } 1180 1181 if (t.getTarget() instanceof Element) { 1182 te = (Element) t.getTarget(); 1183 targetTagName = te.getElementName(); 1184 if (te instanceof ClassInfo) { 1185 ci = (ClassInfo)te; 1186 } 1187 } 1188 1189 String nsUri = tn.getNamespaceURI(); 1190 if ((!nsUri.equals(uri) && nsUri.length()>0) && (!((parentInfo instanceof ClassInfo) && (((ClassInfo)parentInfo).getTypeName() == null)))) { 1191 return true; 1192 } 1193 1194 if ((ci != null) && ((targetTagName != null) && (te.getScope() == null) && (targetTagName.getNamespaceURI() == null))) { 1195 if (targetTagName.equals(tn)) { 1196 return true; 1197 } 1198 } 1199 1200 // we have the precise element defined already 1201 if (te != null) { // it is instanceof Element 1202 return targetTagName!=null && targetTagName.equals(tn); 1203 } 1204 1205 return false; 1206 } 1207 1208 1209 /** 1210 * Generate an attribute for the specified property on the specified complexType 1211 * 1212 * @param ap the attribute 1213 * @param attr the schema definition to which the attribute will be added 1214 */ 1215 private void handleAttributeProp(AttributePropertyInfo<T,C> ap, AttrDecls attr) { 1216 // attr is either a top-level ComplexType or a ComplexExtension 1217 // 1218 // [RESULT] 1219 // 1220 // <complexType ...> 1221 // <...>...</> 1222 // <attribute name="foo" type="xs:int"/> 1223 // </> 1224 // 1225 // or 1226 // 1227 // <complexType ...> 1228 // <complexContent> 1229 // <extension ...> 1230 // <...>...</> 1231 // </> 1232 // </> 1233 // <attribute name="foo" type="xs:int"/> 1234 // </> 1235 // 1236 // or it could also be an in-lined type (attr ref) 1237 // 1238 LocalAttribute localAttribute = attr.attribute(); 1239 1240 final String attrURI = ap.getXmlName().getNamespaceURI(); 1241 if (attrURI.equals("") /*|| attrURI.equals(uri) --- those are generated as global attributes anyway, so use them.*/) { 1242 localAttribute.name(ap.getXmlName().getLocalPart()); 1243 1244 writeAttributeTypeRef(ap, localAttribute); 1245 1246 attributeFormDefault.writeForm(localAttribute,ap.getXmlName()); 1247 } else { // generate an attr ref 1248 localAttribute.ref(ap.getXmlName()); 1249 } 1250 1251 if(ap.isRequired()) { 1252 // TODO: not type safe 1253 localAttribute.use("required"); 1254 } 1255 } 1256 1257 private void writeAttributeTypeRef(AttributePropertyInfo<T,C> ap, AttributeType a) { 1258 if( ap.isCollection() ) 1259 writeTypeRef(a.simpleType().list(), ap, "itemType"); 1260 else 1261 writeTypeRef(a, ap, "type"); 1262 } 1263 1264 /** 1265 * Generate the proper schema fragment for the given reference property into the 1266 * specified schema compositor. 1267 * 1268 * The reference property may or may not refer to a collection and it may or may 1269 * not be wrapped. 1270 */ 1271 private Tree handleReferenceProp(final ReferencePropertyInfo<T, C> rp) { 1272 // fill in content model 1273 ArrayList<Tree> children = new ArrayList<Tree>(); 1274 1275 for (final Element<T,C> e : rp.getElements()) { 1276 children.add(new Tree.Term() { 1277 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1278 LocalElement eref = parent.element(); 1279 1280 boolean local=false; 1281 1282 QName en = e.getElementName(); 1283 if(e.getScope()!=null) { 1284 // scoped. needs to be inlined 1285 boolean qualified = en.getNamespaceURI().equals(uri); 1286 boolean unqualified = en.getNamespaceURI().equals(""); 1287 if(qualified || unqualified) { 1288 // can be inlined indeed 1289 1290 // write form="..." if necessary 1291 if(unqualified) { 1292 if(elementFormDefault.isEffectivelyQualified) 1293 eref.form("unqualified"); 1294 } else { 1295 if(!elementFormDefault.isEffectivelyQualified) 1296 eref.form("qualified"); 1297 } 1298 1299 local = true; 1300 eref.name(en.getLocalPart()); 1301 1302 // write out type reference 1303 if(e instanceof ClassInfo) { 1304 writeTypeRef(eref,(ClassInfo<T,C>)e,"type"); 1305 } else { 1306 writeTypeRef(eref,((ElementInfo<T,C>)e).getContentType(),"type"); 1307 } 1308 } 1309 } 1310 if(!local) 1311 eref.ref(en); 1312 writeOccurs(eref,isOptional,repeated); 1313 } 1314 }); 1315 } 1316 1317 final WildcardMode wc = rp.getWildcard(); 1318 if( wc != null ) { 1319 children.add(new Tree.Term() { 1320 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1321 Any any = parent.any(); 1322 final String pcmode = getProcessContentsModeName(wc); 1323 if( pcmode != null ) any.processContents(pcmode); 1324 any.namespace("##other"); 1325 writeOccurs(any,isOptional,repeated); 1326 } 1327 }); 1328 } 1329 1330 1331 final Tree choice = Tree.makeGroup(GroupKind.CHOICE, children).makeRepeated(rp.isCollection()).makeOptional(!rp.isRequired()); 1332 1333 final QName ename = rp.getXmlName(); 1334 1335 if (ename != null) { // wrapped 1336 return new Tree.Term() { 1337 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1338 LocalElement e = parent.element().name(ename.getLocalPart()); 1339 elementFormDefault.writeForm(e,ename); 1340 if(rp.isCollectionNillable()) 1341 e.nillable(true); 1342 writeOccurs(e,true,repeated); 1343 1344 ComplexType p = e.complexType(); 1345 choice.write(p); 1346 } 1347 }; 1348 } else { // unwrapped 1349 return choice; 1350 } 1351 } 1352 1353 /** 1354 * Generate the proper schema fragment for the given map property into the 1355 * specified schema compositor. 1356 * 1357 * @param mp the map property 1358 */ 1359 private Tree handleMapProp(final MapPropertyInfo<T,C> mp) { 1360 return new Tree.Term() { 1361 protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { 1362 QName ename = mp.getXmlName(); 1363 1364 LocalElement e = parent.element(); 1365 elementFormDefault.writeForm(e,ename); 1366 if(mp.isCollectionNillable()) 1367 e.nillable(true); 1368 1369 e = e.name(ename.getLocalPart()); 1370 writeOccurs(e,isOptional,repeated); 1371 ComplexType p = e.complexType(); 1372 1373 // TODO: entry, key, and value are always unqualified. that needs to be fixed, too. 1374 // TODO: we need to generate the corresponding element declaration, if they are qualified 1375 e = p.sequence().element(); 1376 e.name("entry").minOccurs(0).maxOccurs("unbounded"); 1377 1378 ExplicitGroup seq = e.complexType().sequence(); 1379 writeKeyOrValue(seq, "key", mp.getKeyType()); 1380 writeKeyOrValue(seq, "value", mp.getValueType()); 1381 } 1382 }; 1383 } 1384 1385 private void writeKeyOrValue(ExplicitGroup seq, String tagName, NonElement<T, C> typeRef) { 1386 LocalElement key = seq.element().name(tagName); 1387 key.minOccurs(0); 1388 writeTypeRef(key, typeRef, "type"); 1389 } 1390 1391 public void addGlobalAttribute(AttributePropertyInfo<T,C> ap) { 1392 attributeDecls.put( ap.getXmlName().getLocalPart(), ap ); 1393 addDependencyTo(ap.getTarget().getTypeName()); 1394 } 1395 1396 public void addGlobalElement(TypeRef<T,C> tref) { 1397 elementDecls.put( tref.getTagName().getLocalPart(), new ElementWithType(false,tref.getTarget()) ); 1398 addDependencyTo(tref.getTarget().getTypeName()); 1399 } 1400 1401 @Override 1402 public String toString() { 1403 StringBuilder buf = new StringBuilder(); 1404 buf.append("[classes=").append(classes); 1405 buf.append(",elementDecls=").append(elementDecls); 1406 buf.append(",enums=").append(enums); 1407 buf.append("]"); 1408 return super.toString(); 1409 } 1410 1411 /** 1412 * Represents a global element declaration to be written. 1413 * 1414 * <p> 1415 * Because multiple properties can name the same global element even if 1416 * they have different Java type, the schema generator first needs to 1417 * walk through the model and decide what to generate for the given 1418 * element declaration. 1419 * 1420 * <p> 1421 * This class represents what will be written, and its {@link #equals(Object)} 1422 * method is implemented in such a way that two identical declarations 1423 * are considered as the same. 1424 */ 1425 abstract class ElementDeclaration { 1426 /** 1427 * Returns true if two {@link ElementDeclaration}s are representing 1428 * the same schema fragment. 1429 */ 1430 @Override 1431 public abstract boolean equals(Object o); 1432 @Override 1433 public abstract int hashCode(); 1434 1435 /** 1436 * Generates the declaration. 1437 */ 1438 public abstract void writeTo(String localName, Schema schema); 1439 } 1440 1441 /** 1442 * {@link ElementDeclaration} that refers to a {@link NonElement}. 1443 */ 1444 class ElementWithType extends ElementDeclaration { 1445 private final boolean nillable; 1446 private final NonElement<T,C> type; 1447 1448 public ElementWithType(boolean nillable,NonElement<T, C> type) { 1449 this.type = type; 1450 this.nillable = nillable; 1451 } 1452 1453 public void writeTo(String localName, Schema schema) { 1454 TopLevelElement e = schema.element().name(localName); 1455 if(nillable) 1456 e.nillable(true); 1457 if (type != null) { 1458 writeTypeRef(e,type, "type"); 1459 } else { 1460 e.complexType(); // refer to the nested empty complex type 1461 } 1462 e.commit(); 1463 } 1464 1465 public boolean equals(Object o) { 1466 if (this == o) return true; 1467 if (o == null || getClass() != o.getClass()) return false; 1468 1469 final ElementWithType that = (ElementWithType) o; 1470 return type.equals(that.type); 1471 } 1472 1473 public int hashCode() { 1474 return type.hashCode(); 1475 } 1476 } 1477 } 1478 1479 /** 1480 * Examine the specified element ref and determine if a swaRef attribute needs to be generated 1481 * @param typeRef 1482 */ 1483 private boolean generateSwaRefAdapter(NonElementRef<T,C> typeRef) { 1484 return generateSwaRefAdapter(typeRef.getSource()); 1485 } 1486 1487 /** 1488 * Examine the specified element ref and determine if a swaRef attribute needs to be generated 1489 */ 1490 private boolean generateSwaRefAdapter(PropertyInfo<T,C> prop) { 1491 final Adapter<T,C> adapter = prop.getAdapter(); 1492 if (adapter == null) return false; 1493 final Object o = navigator.asDecl(SwaRefAdapter.class); 1494 if (o == null) return false; 1495 return (o.equals(adapter.adapterType)); 1496 } 1497 1498 /** 1499 * Debug information of what's in this {@link XmlSchemaGenerator}. 1500 */ 1501 @Override 1502 public String toString() { 1503 StringBuilder buf = new StringBuilder(); 1504 for (Namespace ns : namespaces.values()) { 1505 if(buf.length()>0) buf.append(','); 1506 buf.append(ns.uri).append('=').append(ns); 1507 } 1508 return super.toString()+'['+buf+']'; 1509 } 1510 1511 /** 1512 * return the string representation of the processContents mode of the 1513 * give wildcard, or null if it is the schema default "strict" 1514 * 1515 */ 1516 private static String getProcessContentsModeName(WildcardMode wc) { 1517 switch(wc) { 1518 case LAX: 1519 case SKIP: 1520 return wc.name().toLowerCase(); 1521 case STRICT: 1522 return null; 1523 default: 1524 throw new IllegalStateException(); 1525 } 1526 } 1527 1528 1529 /** 1530 * Relativizes a URI by using another URI (base URI.) 1531 * 1532 * <p> 1533 * For example, {@code relative("http://www.sun.com/abc/def","http://www.sun.com/pqr/stu") => "../abc/def"} 1534 * 1535 * <p> 1536 * This method only works on hierarchical URI's, not opaque URI's (refer to the 1537 * <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/URI.html">java.net.URI</a> 1538 * javadoc for complete definitions of these terms. 1539 * 1540 * <p> 1541 * This method will not normalize the relative URI. 1542 * 1543 * @return the relative URI or the original URI if a relative one could not be computed 1544 */ 1545 protected static String relativize(String uri, String baseUri) { 1546 try { 1547 assert uri!=null; 1548 1549 if(baseUri==null) return uri; 1550 1551 URI theUri = new URI(escapeURI(uri)); 1552 URI theBaseUri = new URI(escapeURI(baseUri)); 1553 1554 if (theUri.isOpaque() || theBaseUri.isOpaque()) 1555 return uri; 1556 1557 if (!equalsIgnoreCase(theUri.getScheme(), theBaseUri.getScheme()) || 1558 !equal(theUri.getAuthority(), theBaseUri.getAuthority())) 1559 return uri; 1560 1561 String uriPath = theUri.getPath(); 1562 String basePath = theBaseUri.getPath(); 1563 1564 // normalize base path 1565 if (!basePath.endsWith("/")) { 1566 basePath = normalizeUriPath(basePath); 1567 } 1568 1569 if( uriPath.equals(basePath)) 1570 return "."; 1571 1572 String relPath = calculateRelativePath(uriPath, basePath, fixNull(theUri.getScheme()).equals("file")); 1573 1574 if (relPath == null) 1575 return uri; // recursion found no commonality in the two uris at all 1576 StringBuilder relUri = new StringBuilder(); 1577 relUri.append(relPath); 1578 if (theUri.getQuery() != null) 1579 relUri.append('?').append(theUri.getQuery()); 1580 if (theUri.getFragment() != null) 1581 relUri.append('#').append(theUri.getFragment()); 1582 1583 return relUri.toString(); 1584 } catch (URISyntaxException e) { 1585 throw new InternalError("Error escaping one of these uris:\n\t"+uri+"\n\t"+baseUri); 1586 } 1587 } 1588 1589 private static String fixNull(String s) { 1590 if(s==null) return ""; 1591 else return s; 1592 } 1593 1594 private static String calculateRelativePath(String uri, String base, boolean fileUrl) { 1595 // if this is a file URL (very likely), and if this is on a case-insensitive file system, 1596 // then treat it accordingly. 1597 boolean onWindows = File.pathSeparatorChar==';'; 1598 1599 if (base == null) { 1600 return null; 1601 } 1602 if ((fileUrl && onWindows && startsWithIgnoreCase(uri,base)) || uri.startsWith(base)) { 1603 return uri.substring(base.length()); 1604 } else { 1605 return "../" + calculateRelativePath(uri, getParentUriPath(base), fileUrl); 1606 } 1607 } 1608 1609 private static boolean startsWithIgnoreCase(String s, String t) { 1610 return s.toUpperCase().startsWith(t.toUpperCase()); 1611 } 1612 1613 /** 1614 * JAX-RPC wants the namespaces to be sorted in the reverse order 1615 * so that the empty namespace "" comes to the very end. Don't ask me why. 1616 */ 1617 private static final Comparator<String> NAMESPACE_COMPARATOR = new Comparator<String>() { 1618 public int compare(String lhs, String rhs) { 1619 return -lhs.compareTo(rhs); 1620 } 1621 }; 1622 1623 private static final String newline = "\n"; 1624 }