src/share/jaxws_classes/com/sun/xml/internal/bind/v2/schemagen/XmlSchemaGenerator.java

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

mercurial