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