|
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 <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 * <element name='foo' type='t' /> to <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 } |