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