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