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