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