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