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
26package com.sun.xml.internal.bind.v2.schemagen;
27
28import java.io.IOException;
29import java.io.OutputStream;
30import java.io.Writer;
31import java.io.File;
32import java.net.URI;
33import java.net.URISyntaxException;
34import java.util.Comparator;
35import java.util.HashMap;
36import java.util.LinkedHashSet;
37import java.util.Map;
38import java.util.Set;
39import java.util.TreeMap;
40import java.util.ArrayList;
41import java.util.logging.Level;
42import java.util.logging.Logger;
43
44import javax.activation.MimeType;
45import javax.xml.bind.SchemaOutputResolver;
46import javax.xml.bind.annotation.XmlElement;
47import javax.xml.namespace.QName;
48import javax.xml.transform.Result;
49import javax.xml.transform.stream.StreamResult;
50
51import com.sun.istack.internal.Nullable;
52import com.sun.istack.internal.NotNull;
53import com.sun.xml.internal.bind.Util;
54import com.sun.xml.internal.bind.api.CompositeStructure;
55import com.sun.xml.internal.bind.api.ErrorListener;
56import com.sun.xml.internal.bind.v2.TODO;
57import com.sun.xml.internal.bind.v2.WellKnownNamespace;
58import com.sun.xml.internal.bind.v2.util.CollisionCheckStack;
59import com.sun.xml.internal.bind.v2.util.StackRecorder;
60import static com.sun.xml.internal.bind.v2.WellKnownNamespace.XML_SCHEMA;
61import com.sun.xml.internal.bind.v2.model.core.Adapter;
62import com.sun.xml.internal.bind.v2.model.core.ArrayInfo;
63import com.sun.xml.internal.bind.v2.model.core.AttributePropertyInfo;
64import com.sun.xml.internal.bind.v2.model.core.ClassInfo;
65import com.sun.xml.internal.bind.v2.model.core.Element;
66import com.sun.xml.internal.bind.v2.model.core.ElementInfo;
67import com.sun.xml.internal.bind.v2.model.core.ElementPropertyInfo;
68import com.sun.xml.internal.bind.v2.model.core.EnumConstant;
69import com.sun.xml.internal.bind.v2.model.core.EnumLeafInfo;
70import com.sun.xml.internal.bind.v2.model.core.MapPropertyInfo;
71import com.sun.xml.internal.bind.v2.model.core.MaybeElement;
72import com.sun.xml.internal.bind.v2.model.core.NonElement;
73import com.sun.xml.internal.bind.v2.model.core.NonElementRef;
74import com.sun.xml.internal.bind.v2.model.core.PropertyInfo;
75import com.sun.xml.internal.bind.v2.model.core.ReferencePropertyInfo;
76import com.sun.xml.internal.bind.v2.model.core.TypeInfo;
77import com.sun.xml.internal.bind.v2.model.core.TypeInfoSet;
78import com.sun.xml.internal.bind.v2.model.core.TypeRef;
79import com.sun.xml.internal.bind.v2.model.core.ValuePropertyInfo;
80import com.sun.xml.internal.bind.v2.model.core.WildcardMode;
81import com.sun.xml.internal.bind.v2.model.impl.ClassInfoImpl;
82import com.sun.xml.internal.bind.v2.model.nav.Navigator;
83import com.sun.xml.internal.bind.v2.runtime.SwaRefAdapter;
84import static com.sun.xml.internal.bind.v2.schemagen.Util.*;
85import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Any;
86import com.sun.xml.internal.bind.v2.schemagen.xmlschema.AttrDecls;
87import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexExtension;
88import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexType;
89import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexTypeHost;
90import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ExplicitGroup;
91import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Import;
92import com.sun.xml.internal.bind.v2.schemagen.xmlschema.List;
93import com.sun.xml.internal.bind.v2.schemagen.xmlschema.LocalAttribute;
94import com.sun.xml.internal.bind.v2.schemagen.xmlschema.LocalElement;
95import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Schema;
96import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleExtension;
97import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleRestrictionModel;
98import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleType;
99import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleTypeHost;
100import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TopLevelAttribute;
101import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TopLevelElement;
102import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TypeHost;
103import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ContentModelContainer;
104import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TypeDefParticle;
105import com.sun.xml.internal.bind.v2.schemagen.xmlschema.AttributeType;
106import com.sun.xml.internal.bind.v2.schemagen.episode.Bindings;
107import com.sun.xml.internal.txw2.TXW;
108import com.sun.xml.internal.txw2.TxwException;
109import com.sun.xml.internal.txw2.TypedXmlWriter;
110import com.sun.xml.internal.txw2.output.ResultFactory;
111import com.sun.xml.internal.txw2.output.XmlSerializer;
112import java.util.Collection;
113import java.util.HashSet;
114import 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 */
131public 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}
1625