Sat, 07 Nov 2020 10:30:02 +0800
Added tag mips-jdk8u275-b01 for changeset fdbe50121f48
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 */
26 package com.sun.xml.internal.bind.v2.schemagen;
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;
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;
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;
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> {
133 private static final Logger logger = Util.getClassLogger();
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);
146 /**
147 * {@link ErrorListener} to send errors to.
148 */
149 private ErrorListener errorListener;
151 /** model navigator **/
152 private Navigator<T,C,F,M> navigator;
154 private final TypeInfoSet<T,C,F,M> types;
156 /**
157 * Representation for xs:string.
158 */
159 private final NonElement<T,C> stringType;
161 /**
162 * Represents xs:anyType.
163 */
164 private final NonElement<T,C> anyType;
166 /**
167 * Used to detect cycles in anonymous types.
168 */
169 private final CollisionCheckStack<ClassInfo<T,C>> collisionChecker = new CollisionCheckStack<ClassInfo<T,C>>();
171 public XmlSchemaGenerator( Navigator<T,C,F,M> navigator, TypeInfoSet<T,C,F,M> types ) {
172 this.navigator = navigator;
173 this.types = types;
175 this.stringType = types.getTypeInfo(navigator.ref(String.class));
176 this.anyType = types.getAnyTypeInfo();
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 }
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 }
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;
207 String nsUri = null;
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
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());
219 // schedule writing this global element
220 add(clazz.getElementName(),false,clazz);
221 }
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 }
232 Namespace n = getNamespace(nsUri);
233 n.classes.add(clazz);
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 }
258 if(generateSwaRefAdapter(p))
259 n.useSwaRef = true;
261 MimeType mimeType = p.getExpectedMimeType();
262 if( mimeType != null ) {
263 n.useMimeNs = true;
264 }
266 }
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 }
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;
282 @SuppressWarnings("UnusedAssignment")
283 boolean nillable = false; // default value
285 QName name = elem.getElementName();
286 Namespace n = getNamespace(name.getNamespaceURI());
287 ElementInfo ei;
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 }
295 XmlElement xmlElem = ei.getProperty().readAnnotation(XmlElement.class);
297 if (xmlElem == null) {
298 nillable = false;
299 } else {
300 nillable = xmlElem.nillable();
301 }
303 n.elementDecls.put(name.getLocalPart(),n.new ElementWithType(nillable, elem.getContentType()));
305 // search for foreign namespace references
306 n.processForeignNamespaces(elem.getProperty(), 1);
307 }
309 public void add( EnumLeafInfo<T,C> envm ) {
310 assert envm!=null;
312 String nsUri = null;
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());
321 // schedule writing this global element
322 add(envm.getElementName(),false,envm);
323 }
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 }
333 Namespace n = getNamespace(nsUri);
334 n.enums.add(envm);
336 // search for foreign namespace references
337 n.addDependencyTo(envm.getBaseType().getTypeName());
338 }
340 public void add( ArrayInfo<T,C> a ) {
341 assert a!=null;
343 final String namespaceURI = a.getTypeName().getNamespaceURI();
344 Namespace n = getNamespace(namespaceURI);
345 n.arrays.add(a);
347 // search for foreign namespace references
348 n.addDependencyTo(a.getItemType().getTypeName());
349 }
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 ) {
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
366 Namespace n = getNamespace(tagName.getNamespaceURI());
367 n.elementDecls.put(tagName.getLocalPart(), n.new ElementWithType(isNillable,type));
369 // search for foreign namespace references
370 if(type!=null)
371 n.addDependencyTo(type.getTypeName());
372 }
374 /**
375 * Writes out the episode file.
376 */
377 public void writeEpisodeFile(XmlSerializer out) {
378 Bindings root = TXW.create(Bindings.class, out);
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?
385 // generate listing per schema
386 for (Map.Entry<String,Namespace> e : namespaces.entrySet()) {
387 Bindings group = root.bindings();
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 }
398 group.scd("x-schema::"+(tns.equals("")?"":"tns"));
399 group.schemaBindings().map(false);
401 for (ClassInfo<T,C> ci : e.getValue().classes) {
402 if(ci.getTypeName()==null) continue; // local type
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 }
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 }
417 for (EnumLeafInfo<T,C> en : e.getValue().enums) {
418 if(en.getTypeName()==null) continue; // local type
420 Bindings child = group.bindings();
421 child.scd('~'+prefix+en.getTypeName().getLocalPart());
422 child.klass().ref(navigator.getClassName(en.getClazz()));
423 }
425 group.commit(true);
426 }
428 root.commit();
429 }
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();
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 }
443 // make it fool-proof
444 resolver = new FoolProofResolver(resolver);
445 this.errorListener = errorListener;
447 Map<String, String> schemaLocations = types.getSchemaLocations();
449 Map<Namespace,Result> out = new HashMap<Namespace,Result>();
450 Map<Namespace,String> systemIds = new HashMap<Namespace,String>();
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);
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 }
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 }
491 /**
492 * Schema components are organized per namespace.
493 */
494 private class Namespace {
495 final @NotNull String uri;
497 /**
498 * Other {@link Namespace}s that this namespace depends on.
499 */
500 private final Set<Namespace> depends = new LinkedHashSet<Namespace>();
502 /**
503 * If this schema refers to components from this schema by itself.
504 */
505 private boolean selfReference;
507 /**
508 * List of classes in this namespace.
509 */
510 private final Set<ClassInfo<T,C>> classes = new LinkedHashSet<ClassInfo<T,C>>();
512 /**
513 * Set of enums in this namespace
514 */
515 private final Set<EnumLeafInfo<T,C>> enums = new LinkedHashSet<EnumLeafInfo<T,C>>();
517 /**
518 * Set of arrays in this namespace
519 */
520 private final Set<ArrayInfo<T,C>> arrays = new LinkedHashSet<ArrayInfo<T,C>>();
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);
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));
533 private Form attributeFormDefault;
534 private Form elementFormDefault;
536 /**
537 * Does schema in this namespace uses swaRef? If so, we need to generate import
538 * statement.
539 */
540 private boolean useSwaRef;
542 /**
543 * Import for mime namespace needs to be generated.
544 * See #856
545 */
546 private boolean useMimeNs;
548 /**
549 * Container for already processed classes
550 */
551 private final Set<ClassInfo> written = new HashSet<ClassInfo>();
553 public Namespace(String uri) {
554 this.uri = uri;
555 assert !XmlSchemaGenerator.this.namespaces.containsKey(uri);
556 XmlSchemaGenerator.this.namespaces.put(uri,this);
557 }
559 /**
560 * Clear out the set of already processed classes for this namespace
561 */
562 void resetWritten() {
563 written.clear();
564 }
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 <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 }
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 }
599 String nsUri = qname.getNamespaceURI();
601 if (nsUri.equals(XML_SCHEMA)) {
602 // no need to explicitly refer to XSD namespace
603 return;
604 }
606 if (nsUri.equals(uri)) {
607 selfReference = true;
608 return;
609 }
611 // found a type in a foreign namespace, so make sure we generate an import for it
612 depends.add(getNamespace(nsUri));
613 }
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));
625 // additional namespace declarations to be made.
626 Map<String, String> xmlNs = types.getXmlNs(uri);
628 for (Map.Entry<String, String> e : xmlNs.entrySet()) {
629 schema._namespace(e.getValue(),e.getKey());
630 }
632 if(useSwaRef)
633 schema._namespace(WellKnownNamespace.SWA_URI,"swaRef");
635 if(useMimeNs)
636 schema._namespace(WellKnownNamespace.XML_MIME_URI,"xmime");
638 attributeFormDefault = Form.get(types.getAttributeFormDefault(uri));
639 attributeFormDefault.declare("attributeFormDefault",schema);
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);
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");
653 if(uri.length()!=0)
654 schema.targetNamespace(uri);
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 }
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 }
668 schema._pcdata(newline);
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 }
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 }
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 }
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 }
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 }
767 // ref:swaRef handling
768 if(generateSwaRefAdapter(typeRef)) {
769 th._attribute(refAttName, new QName(WellKnownNamespace.SWA_URI, "swaRef", "ref"));
770 return;
771 }
773 // type name override
774 if(typeRef.getSource().getSchemaType()!=null) {
775 th._attribute(refAttName,typeRef.getSource().getSchemaType());
776 return;
777 }
779 // normal type generation
780 writeTypeRef(th, typeRef.getTarget(), refAttName);
781 }
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 }
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 }
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);
855 SimpleRestrictionModel base = st.restriction();
856 writeTypeRef(base, e.getBaseType(), "base");
858 for (EnumConstant c : e.getConstants()) {
859 base.enumeration().value(c.getLexicalValue());
860 }
861 st.commit();
862 }
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");
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 }
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);
949 // these are where we write content model and attributes
950 AttrDecls contentModel = ct;
951 TypeDefParticle contentModelOwner = ct;
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;
967 ce.base(bc.getTypeName());
968 // TODO: what if the base type is anonymous?
969 }
970 }
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 }
985 Tree top = Tree.makeGroup( c.isOrdered() ? GroupKind.SEQUENCE : GroupKind.ALL, children);
987 // write the content model
988 top.write(contentModelOwner);
989 }
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 }
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 }
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 }
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 }
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 }
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();
1075 QName tn = t.getTagName();
1077 PropertyInfo propInfo = t.getSource();
1078 TypeInfo parentInfo = (propInfo == null) ? null : propInfo.parent();
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 {
1085 QName elemName = null;
1086 if (t.getTarget() instanceof Element) {
1087 Element te = (Element) t.getTarget();
1088 elemName = te.getElementName();
1089 }
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 }
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 }
1121 final Tree choice = Tree.makeGroup(GroupKind.CHOICE, children)
1122 .makeOptional(!ep.isRequired())
1123 .makeRepeated(ep.isCollection()); // see Spec table 8-13
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);
1142 if(ep.isCollectionNillable()) {
1143 e.nillable(true);
1144 }
1145 writeOccurs(e,!ep.isCollectionRequired(),repeated);
1147 ComplexType p = e.complexType();
1148 choice.write(p);
1149 }
1150 };
1151 } else {// non-wrapped
1152 return choice;
1153 }
1154 }
1156 /**
1157 * Checks if we can collapse
1158 * <element name='foo' type='t' /> to <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;
1167 if(t.isNillable() || t.getDefaultValue()!=null) {
1168 // can't put those attributes on <element ref>
1169 return false;
1170 }
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 }
1180 String nsUri = tn.getNamespaceURI();
1181 if ((!nsUri.equals(uri) && nsUri.length()>0) && (!((parentInfo instanceof ClassInfo) && (((ClassInfo)parentInfo).getTypeName() == null)))) {
1182 return true;
1183 }
1185 if ((ci != null) && ((targetTagName != null) && (te.getScope() == null) && (targetTagName.getNamespaceURI() == null))) {
1186 if (targetTagName.equals(tn)) {
1187 return true;
1188 }
1189 }
1191 // we have the precise element defined already
1192 if (te != null) { // it is instanceof Element
1193 return targetTagName!=null && targetTagName.equals(tn);
1194 }
1196 return false;
1197 }
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();
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());
1235 writeAttributeTypeRef(ap, localAttribute);
1237 attributeFormDefault.writeForm(localAttribute,ap.getXmlName());
1238 } else { // generate an attr ref
1239 localAttribute.ref(ap.getXmlName());
1240 }
1242 if(ap.isRequired()) {
1243 // TODO: not type safe
1244 localAttribute.use("required");
1245 }
1246 }
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 }
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>();
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();
1271 boolean local=false;
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
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 }
1290 local = true;
1291 eref.name(en.getLocalPart());
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 }
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 }
1322 final Tree choice = Tree.makeGroup(GroupKind.CHOICE, children).makeRepeated(rp.isCollection()).makeOptional(!rp.isRequired());
1324 final QName ename = rp.getXmlName();
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);
1335 ComplexType p = e.complexType();
1336 choice.write(p);
1337 }
1338 };
1339 } else { // unwrapped
1340 return choice;
1341 }
1342 }
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();
1355 LocalElement e = parent.element();
1356 elementFormDefault.writeForm(e,ename);
1357 if(mp.isCollectionNillable())
1358 e.nillable(true);
1360 e = e.name(ename.getLocalPart());
1361 writeOccurs(e,isOptional,repeated);
1362 ComplexType p = e.complexType();
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");
1369 ExplicitGroup seq = e.complexType().sequence();
1370 writeKeyOrValue(seq, "key", mp.getKeyType());
1371 writeKeyOrValue(seq, "value", mp.getValueType());
1372 }
1373 };
1374 }
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 }
1382 public void addGlobalAttribute(AttributePropertyInfo<T,C> ap) {
1383 attributeDecls.put( ap.getXmlName().getLocalPart(), ap );
1384 addDependencyTo(ap.getTarget().getTypeName());
1385 }
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 }
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 }
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();
1426 /**
1427 * Generates the declaration.
1428 */
1429 public abstract void writeTo(String localName, Schema schema);
1430 }
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;
1439 public ElementWithType(boolean nillable,NonElement<T, C> type) {
1440 this.type = type;
1441 this.nillable = nillable;
1442 }
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 }
1456 public boolean equals(Object o) {
1457 if (this == o) return true;
1458 if (o == null || getClass() != o.getClass()) return false;
1460 final ElementWithType that = (ElementWithType) o;
1461 return type.equals(that.type);
1462 }
1464 public int hashCode() {
1465 return type.hashCode();
1466 }
1467 }
1468 }
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 }
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 }
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 }
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 }
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;
1540 if(baseUri==null) return uri;
1542 URI theUri = new URI(escapeURI(uri));
1543 URI theBaseUri = new URI(escapeURI(baseUri));
1545 if (theUri.isOpaque() || theBaseUri.isOpaque())
1546 return uri;
1548 if (!equalsIgnoreCase(theUri.getScheme(), theBaseUri.getScheme()) ||
1549 !equal(theUri.getAuthority(), theBaseUri.getAuthority()))
1550 return uri;
1552 String uriPath = theUri.getPath();
1553 String basePath = theBaseUri.getPath();
1555 // normalize base path
1556 if (!basePath.endsWith("/")) {
1557 basePath = normalizeUriPath(basePath);
1558 }
1560 if( uriPath.equals(basePath))
1561 return ".";
1563 String relPath = calculateRelativePath(uriPath, basePath, fixNull(theUri.getScheme()).equals("file"));
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());
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 }
1580 private static String fixNull(String s) {
1581 if(s==null) return "";
1582 else return s;
1583 }
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==';';
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 }
1600 private static boolean startsWithIgnoreCase(String s, String t) {
1601 return s.toUpperCase().startsWith(t.toUpperCase());
1602 }
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 };
1614 private static final String newline = "\n";
1615 }