ohair@286: /* ohair@286: * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. ohair@286: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. ohair@286: * ohair@286: * This code is free software; you can redistribute it and/or modify it ohair@286: * under the terms of the GNU General Public License version 2 only, as ohair@286: * published by the Free Software Foundation. Oracle designates this ohair@286: * particular file as subject to the "Classpath" exception as provided ohair@286: * by Oracle in the LICENSE file that accompanied this code. ohair@286: * ohair@286: * This code is distributed in the hope that it will be useful, but WITHOUT ohair@286: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ohair@286: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License ohair@286: * version 2 for more details (a copy is included in the LICENSE file that ohair@286: * accompanied this code). ohair@286: * ohair@286: * You should have received a copy of the GNU General Public License version ohair@286: * 2 along with this work; if not, write to the Free Software Foundation, ohair@286: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. ohair@286: * ohair@286: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA ohair@286: * or visit www.oracle.com if you need additional information or have any ohair@286: * questions. ohair@286: */ ohair@286: ohair@286: package com.sun.xml.internal.bind.v2.schemagen; ohair@286: ohair@286: import java.io.IOException; ohair@286: import java.io.OutputStream; ohair@286: import java.io.Writer; ohair@286: import java.io.File; ohair@286: import java.net.URI; ohair@286: import java.net.URISyntaxException; ohair@286: import java.util.Comparator; ohair@286: import java.util.HashMap; ohair@286: import java.util.LinkedHashSet; ohair@286: import java.util.Map; ohair@286: import java.util.Set; ohair@286: import java.util.TreeMap; ohair@286: import java.util.ArrayList; ohair@286: import java.util.logging.Level; ohair@286: import java.util.logging.Logger; ohair@286: ohair@286: import javax.activation.MimeType; ohair@286: import javax.xml.bind.SchemaOutputResolver; ohair@286: import javax.xml.bind.annotation.XmlElement; ohair@286: import javax.xml.namespace.QName; ohair@286: import javax.xml.transform.Result; ohair@286: import javax.xml.transform.stream.StreamResult; ohair@286: ohair@286: import com.sun.istack.internal.Nullable; ohair@286: import com.sun.istack.internal.NotNull; ohair@286: import com.sun.xml.internal.bind.Util; ohair@286: import com.sun.xml.internal.bind.api.CompositeStructure; ohair@286: import com.sun.xml.internal.bind.api.ErrorListener; ohair@286: import com.sun.xml.internal.bind.v2.TODO; ohair@286: import com.sun.xml.internal.bind.v2.WellKnownNamespace; ohair@286: import com.sun.xml.internal.bind.v2.util.CollisionCheckStack; ohair@286: import com.sun.xml.internal.bind.v2.util.StackRecorder; ohair@286: import static com.sun.xml.internal.bind.v2.WellKnownNamespace.XML_SCHEMA; ohair@286: import com.sun.xml.internal.bind.v2.model.core.Adapter; ohair@286: import com.sun.xml.internal.bind.v2.model.core.ArrayInfo; ohair@286: import com.sun.xml.internal.bind.v2.model.core.AttributePropertyInfo; ohair@286: import com.sun.xml.internal.bind.v2.model.core.ClassInfo; ohair@286: import com.sun.xml.internal.bind.v2.model.core.Element; ohair@286: import com.sun.xml.internal.bind.v2.model.core.ElementInfo; ohair@286: import com.sun.xml.internal.bind.v2.model.core.ElementPropertyInfo; ohair@286: import com.sun.xml.internal.bind.v2.model.core.EnumConstant; ohair@286: import com.sun.xml.internal.bind.v2.model.core.EnumLeafInfo; ohair@286: import com.sun.xml.internal.bind.v2.model.core.MapPropertyInfo; ohair@286: import com.sun.xml.internal.bind.v2.model.core.MaybeElement; ohair@286: import com.sun.xml.internal.bind.v2.model.core.NonElement; ohair@286: import com.sun.xml.internal.bind.v2.model.core.NonElementRef; ohair@286: import com.sun.xml.internal.bind.v2.model.core.PropertyInfo; ohair@286: import com.sun.xml.internal.bind.v2.model.core.ReferencePropertyInfo; ohair@286: import com.sun.xml.internal.bind.v2.model.core.TypeInfo; ohair@286: import com.sun.xml.internal.bind.v2.model.core.TypeInfoSet; ohair@286: import com.sun.xml.internal.bind.v2.model.core.TypeRef; ohair@286: import com.sun.xml.internal.bind.v2.model.core.ValuePropertyInfo; ohair@286: import com.sun.xml.internal.bind.v2.model.core.WildcardMode; ohair@286: import com.sun.xml.internal.bind.v2.model.impl.ClassInfoImpl; ohair@286: import com.sun.xml.internal.bind.v2.model.nav.Navigator; ohair@286: import com.sun.xml.internal.bind.v2.runtime.SwaRefAdapter; ohair@286: import static com.sun.xml.internal.bind.v2.schemagen.Util.*; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Any; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.AttrDecls; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexExtension; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexType; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexTypeHost; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ExplicitGroup; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Import; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.List; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.LocalAttribute; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.LocalElement; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Schema; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleExtension; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleRestrictionModel; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleType; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleTypeHost; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TopLevelAttribute; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TopLevelElement; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TypeHost; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ContentModelContainer; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TypeDefParticle; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.AttributeType; ohair@286: import com.sun.xml.internal.bind.v2.schemagen.episode.Bindings; ohair@286: import com.sun.xml.internal.txw2.TXW; ohair@286: import com.sun.xml.internal.txw2.TxwException; ohair@286: import com.sun.xml.internal.txw2.TypedXmlWriter; ohair@286: import com.sun.xml.internal.txw2.output.ResultFactory; ohair@286: import com.sun.xml.internal.txw2.output.XmlSerializer; ohair@286: import java.util.Collection; ohair@286: import org.xml.sax.SAXParseException; ohair@286: ohair@286: /** ohair@286: * Generates a set of W3C XML Schema documents from a set of Java classes. ohair@286: * ohair@286: *

ohair@286: * A client must invoke methods in the following order: ohair@286: *

    ohair@286: *
  1. Create a new {@link XmlSchemaGenerator} ohair@286: *
  2. Invoke {@link #add} methods, multiple times if necessary. ohair@286: *
  3. Invoke {@link #write} ohair@286: *
  4. Discard the {@link XmlSchemaGenerator}. ohair@286: *
ohair@286: * ohair@286: * @author Ryan Shoemaker ohair@286: * @author Kohsuke Kawaguchi (kk@kohsuke.org) ohair@286: */ ohair@286: public final class XmlSchemaGenerator { ohair@286: ohair@286: private static final Logger logger = Util.getClassLogger(); ohair@286: ohair@286: /** ohair@286: * Java classes to be written, organized by their namespace. ohair@286: * ohair@286: *

ohair@286: * We use a {@link TreeMap} here so that the suggested names will ohair@286: * be consistent across JVMs. ohair@286: * ohair@286: * @see SchemaOutputResolver#createOutput(String, String) ohair@286: */ ohair@286: private final Map namespaces = new TreeMap(NAMESPACE_COMPARATOR); ohair@286: ohair@286: /** ohair@286: * {@link ErrorListener} to send errors to. ohair@286: */ ohair@286: private ErrorListener errorListener; ohair@286: ohair@286: /** model navigator **/ ohair@286: private Navigator navigator; ohair@286: ohair@286: private final TypeInfoSet types; ohair@286: ohair@286: /** ohair@286: * Representation for xs:string. ohair@286: */ ohair@286: private final NonElement stringType; ohair@286: ohair@286: /** ohair@286: * Represents xs:anyType. ohair@286: */ ohair@286: private final NonElement anyType; ohair@286: ohair@286: /** ohair@286: * Used to detect cycles in anonymous types. ohair@286: */ ohair@286: private final CollisionCheckStack> collisionChecker = new CollisionCheckStack>(); ohair@286: ohair@286: public XmlSchemaGenerator( Navigator navigator, TypeInfoSet types ) { ohair@286: this.navigator = navigator; ohair@286: this.types = types; ohair@286: ohair@286: this.stringType = types.getTypeInfo(navigator.ref(String.class)); ohair@286: this.anyType = types.getAnyTypeInfo(); ohair@286: ohair@286: // populate the object ohair@286: for( ClassInfo ci : types.beans().values() ) ohair@286: add(ci); ohair@286: for( ElementInfo ei1 : types.getElementMappings(null).values() ) ohair@286: add(ei1); ohair@286: for( EnumLeafInfo ei : types.enums().values() ) ohair@286: add(ei); ohair@286: for( ArrayInfo a : types.arrays().values()) ohair@286: add(a); ohair@286: } ohair@286: ohair@286: private Namespace getNamespace(String uri) { ohair@286: Namespace n = namespaces.get(uri); ohair@286: if(n==null) ohair@286: namespaces.put(uri,n=new Namespace(uri)); ohair@286: return n; ohair@286: } ohair@286: ohair@286: /** ohair@286: * Adds a new class to the list of classes to be written. ohair@286: * ohair@286: *

ohair@286: * A {@link ClassInfo} may have two namespaces --- one for the element name ohair@286: * and the other for the type name. If they are different, we put the same ohair@286: * {@link ClassInfo} to two {@link Namespace}s. ohair@286: */ ohair@286: public void add( ClassInfo clazz ) { ohair@286: assert clazz!=null; ohair@286: ohair@286: String nsUri = null; ohair@286: ohair@286: if(clazz.getClazz()==navigator.asDecl(CompositeStructure.class)) ohair@286: return; // this is a special class we introduced for JAX-WS that we *don't* want in the schema ohair@286: ohair@286: if(clazz.isElement()) { ohair@286: // put element -> type reference ohair@286: nsUri = clazz.getElementName().getNamespaceURI(); ohair@286: Namespace ns = getNamespace(nsUri); ohair@286: ns.classes.add(clazz); ohair@286: ns.addDependencyTo(clazz.getTypeName()); ohair@286: ohair@286: // schedule writing this global element ohair@286: add(clazz.getElementName(),false,clazz); ohair@286: } ohair@286: ohair@286: QName tn = clazz.getTypeName(); ohair@286: if(tn!=null) { ohair@286: nsUri = tn.getNamespaceURI(); ohair@286: } else { ohair@286: // anonymous type ohair@286: if(nsUri==null) ohair@286: return; ohair@286: } ohair@286: ohair@286: Namespace n = getNamespace(nsUri); ohair@286: n.classes.add(clazz); ohair@286: ohair@286: // search properties for foreign namespace references ohair@286: for( PropertyInfo p : clazz.getProperties()) { ohair@286: n.processForeignNamespaces(p, 1); ohair@286: if (p instanceof AttributePropertyInfo) { ohair@286: AttributePropertyInfo ap = (AttributePropertyInfo) p; ohair@286: String aUri = ap.getXmlName().getNamespaceURI(); ohair@286: if(aUri.length()>0) { ohair@286: // global attribute ohair@286: getNamespace(aUri).addGlobalAttribute(ap); ohair@286: n.addDependencyTo(ap.getXmlName()); ohair@286: } ohair@286: } ohair@286: if (p instanceof ElementPropertyInfo) { ohair@286: ElementPropertyInfo ep = (ElementPropertyInfo) p; ohair@286: for (TypeRef tref : ep.getTypes()) { ohair@286: String eUri = tref.getTagName().getNamespaceURI(); ohair@286: if(eUri.length()>0 && !eUri.equals(n.uri)) { ohair@286: getNamespace(eUri).addGlobalElement(tref); ohair@286: n.addDependencyTo(tref.getTagName()); ohair@286: } ohair@286: } ohair@286: } ohair@286: ohair@286: if(generateSwaRefAdapter(p)) ohair@286: n.useSwaRef = true; ohair@286: ohair@286: MimeType mimeType = p.getExpectedMimeType(); ohair@286: if( mimeType != null ) { ohair@286: n.useMimeNs = true; ohair@286: } ohair@286: ohair@286: } ohair@286: ohair@286: // recurse on baseTypes to make sure that we can refer to them in the schema ohair@286: ClassInfo bc = clazz.getBaseClass(); ohair@286: if (bc != null) { ohair@286: add(bc); ohair@286: n.addDependencyTo(bc.getTypeName()); ohair@286: } ohair@286: } ohair@286: ohair@286: /** ohair@286: * Adds a new element to the list of elements to be written. ohair@286: */ ohair@286: public void add( ElementInfo elem ) { ohair@286: assert elem!=null; ohair@286: ohair@286: boolean nillable = false; // default value ohair@286: ohair@286: QName name = elem.getElementName(); ohair@286: Namespace n = getNamespace(name.getNamespaceURI()); ohair@286: ElementInfo ei; ohair@286: ohair@286: if (elem.getScope() != null) { // (probably) never happens ohair@286: ei = this.types.getElementInfo(elem.getScope().getClazz(), name); ohair@286: } else { ohair@286: ei = this.types.getElementInfo(null, name); ohair@286: } ohair@286: ohair@286: XmlElement xmlElem = ei.getProperty().readAnnotation(XmlElement.class); ohair@286: ohair@286: if (xmlElem == null) { ohair@286: nillable = false; ohair@286: } else { ohair@286: nillable = xmlElem.nillable(); ohair@286: } ohair@286: ohair@286: n.elementDecls.put(name.getLocalPart(),n.new ElementWithType(nillable, elem.getContentType())); ohair@286: ohair@286: // search for foreign namespace references ohair@286: n.processForeignNamespaces(elem.getProperty(), 1); ohair@286: } ohair@286: ohair@286: public void add( EnumLeafInfo envm ) { ohair@286: assert envm!=null; ohair@286: ohair@286: String nsUri = null; ohair@286: ohair@286: if(envm.isElement()) { ohair@286: // put element -> type reference ohair@286: nsUri = envm.getElementName().getNamespaceURI(); ohair@286: Namespace ns = getNamespace(nsUri); ohair@286: ns.enums.add(envm); ohair@286: ns.addDependencyTo(envm.getTypeName()); ohair@286: ohair@286: // schedule writing this global element ohair@286: add(envm.getElementName(),false,envm); ohair@286: } ohair@286: ohair@286: final QName typeName = envm.getTypeName(); ohair@286: if (typeName != null) { ohair@286: nsUri = typeName.getNamespaceURI(); ohair@286: } else { ohair@286: if(nsUri==null) ohair@286: return; // anonymous type ohair@286: } ohair@286: ohair@286: Namespace n = getNamespace(nsUri); ohair@286: n.enums.add(envm); ohair@286: ohair@286: // search for foreign namespace references ohair@286: n.addDependencyTo(envm.getBaseType().getTypeName()); ohair@286: } ohair@286: ohair@286: public void add( ArrayInfo a ) { ohair@286: assert a!=null; ohair@286: ohair@286: final String namespaceURI = a.getTypeName().getNamespaceURI(); ohair@286: Namespace n = getNamespace(namespaceURI); ohair@286: n.arrays.add(a); ohair@286: ohair@286: // search for foreign namespace references ohair@286: n.addDependencyTo(a.getItemType().getTypeName()); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Adds an additional element declaration. ohair@286: * ohair@286: * @param tagName ohair@286: * The name of the element declaration to be added. ohair@286: * @param type ohair@286: * The type this element refers to. ohair@286: * Can be null, in which case the element refers to an empty anonymous complex type. ohair@286: */ ohair@286: public void add( QName tagName, boolean isNillable, NonElement type ) { ohair@286: ohair@286: if(type!=null && type.getType()==navigator.ref(CompositeStructure.class)) ohair@286: return; // this is a special class we introduced for JAX-WS that we *don't* want in the schema ohair@286: ohair@286: ohair@286: Namespace n = getNamespace(tagName.getNamespaceURI()); ohair@286: n.elementDecls.put(tagName.getLocalPart(), n.new ElementWithType(isNillable,type)); ohair@286: ohair@286: // search for foreign namespace references ohair@286: if(type!=null) ohair@286: n.addDependencyTo(type.getTypeName()); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Writes out the episode file. ohair@286: */ ohair@286: public void writeEpisodeFile(XmlSerializer out) { ohair@286: Bindings root = TXW.create(Bindings.class, out); ohair@286: ohair@286: if(namespaces.containsKey("")) // otherwise jaxb binding NS should be the default namespace ohair@286: root._namespace(WellKnownNamespace.JAXB,"jaxb"); ohair@286: root.version("2.1"); ohair@286: // TODO: don't we want to bake in versions? ohair@286: ohair@286: // generate listing per schema ohair@286: for (Map.Entry e : namespaces.entrySet()) { ohair@286: Bindings group = root.bindings(); ohair@286: ohair@286: String prefix; ohair@286: String tns = e.getKey(); ohair@286: if(!tns.equals("")) { ohair@286: group._namespace(tns,"tns"); ohair@286: prefix = "tns:"; ohair@286: } else { ohair@286: prefix = ""; ohair@286: } ohair@286: ohair@286: group.scd("x-schema::"+(tns.equals("")?"":"tns")); ohair@286: group.schemaBindings().map(false); ohair@286: ohair@286: for (ClassInfo ci : e.getValue().classes) { ohair@286: if(ci.getTypeName()==null) continue; // local type ohair@286: ohair@286: if(ci.getTypeName().getNamespaceURI().equals(tns)) { ohair@286: Bindings child = group.bindings(); ohair@286: child.scd('~'+prefix+ci.getTypeName().getLocalPart()); ohair@286: child.klass().ref(ci.getName()); ohair@286: } ohair@286: ohair@286: if(ci.isElement() && ci.getElementName().getNamespaceURI().equals(tns)) { ohair@286: Bindings child = group.bindings(); ohair@286: child.scd(prefix+ci.getElementName().getLocalPart()); ohair@286: child.klass().ref(ci.getName()); ohair@286: } ohair@286: } ohair@286: ohair@286: for (EnumLeafInfo en : e.getValue().enums) { ohair@286: if(en.getTypeName()==null) continue; // local type ohair@286: ohair@286: Bindings child = group.bindings(); ohair@286: child.scd('~'+prefix+en.getTypeName().getLocalPart()); ohair@286: child.klass().ref(navigator.getClassName(en.getClazz())); ohair@286: } ohair@286: ohair@286: group.commit(true); ohair@286: } ohair@286: ohair@286: root.commit(); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Write out the schema documents. ohair@286: */ ohair@286: public void write(SchemaOutputResolver resolver, ErrorListener errorListener) throws IOException { ohair@286: if(resolver==null) ohair@286: throw new IllegalArgumentException(); ohair@286: ohair@286: if(logger.isLoggable(Level.FINE)) { ohair@286: // debug logging to see what's going on. ohair@286: logger.log(Level.FINE,"Wrigin XML Schema for "+toString(),new StackRecorder()); ohair@286: } ohair@286: ohair@286: // make it fool-proof ohair@286: resolver = new FoolProofResolver(resolver); ohair@286: this.errorListener = errorListener; ohair@286: ohair@286: Map schemaLocations = types.getSchemaLocations(); ohair@286: ohair@286: Map out = new HashMap(); ohair@286: Map systemIds = new HashMap(); ohair@286: ohair@286: // we create a Namespace object for the XML Schema namespace ohair@286: // as a side-effect, but we don't want to generate it. ohair@286: namespaces.remove(WellKnownNamespace.XML_SCHEMA); ohair@286: ohair@286: // first create the outputs for all so that we can resolve references among ohair@286: // schema files when we write ohair@286: for( Namespace n : namespaces.values() ) { ohair@286: String schemaLocation = schemaLocations.get(n.uri); ohair@286: if(schemaLocation!=null) { ohair@286: systemIds.put(n,schemaLocation); ohair@286: } else { ohair@286: Result output = resolver.createOutput(n.uri,"schema"+(out.size()+1)+".xsd"); ohair@286: if(output!=null) { // null result means no schema for that namespace ohair@286: out.put(n,output); ohair@286: systemIds.put(n,output.getSystemId()); ohair@286: } ohair@286: } ohair@286: } ohair@286: ohair@286: // then write'em all ohair@286: for( Map.Entry e : out.entrySet() ) { ohair@286: Result result = e.getValue(); ohair@286: e.getKey().writeTo( result, systemIds ); ohair@286: if(result instanceof StreamResult) { ohair@286: OutputStream outputStream = ((StreamResult)result).getOutputStream(); ohair@286: if(outputStream != null) { ohair@286: outputStream.close(); // fix for bugid: 6291301 ohair@286: } else { ohair@286: final Writer writer = ((StreamResult)result).getWriter(); ohair@286: if(writer != null) writer.close(); ohair@286: } ohair@286: } ohair@286: } ohair@286: } ohair@286: ohair@286: ohair@286: ohair@286: /** ohair@286: * Schema components are organized per namespace. ohair@286: */ ohair@286: private class Namespace { ohair@286: final @NotNull String uri; ohair@286: ohair@286: /** ohair@286: * Other {@link Namespace}s that this namespace depends on. ohair@286: */ ohair@286: private final Set depends = new LinkedHashSet(); ohair@286: ohair@286: /** ohair@286: * If this schema refers to components from this schema by itself. ohair@286: */ ohair@286: private boolean selfReference; ohair@286: ohair@286: /** ohair@286: * List of classes in this namespace. ohair@286: */ ohair@286: private final Set> classes = new LinkedHashSet>(); ohair@286: ohair@286: /** ohair@286: * Set of enums in this namespace ohair@286: */ ohair@286: private final Set> enums = new LinkedHashSet>(); ohair@286: ohair@286: /** ohair@286: * Set of arrays in this namespace ohair@286: */ ohair@286: private final Set> arrays = new LinkedHashSet>(); ohair@286: ohair@286: /** ohair@286: * Global attribute declarations keyed by their local names. ohair@286: */ ohair@286: private final MultiMap> attributeDecls = new MultiMap>(null); ohair@286: ohair@286: /** ohair@286: * Global element declarations to be written, keyed by their local names. ohair@286: */ ohair@286: private final MultiMap elementDecls = ohair@286: new MultiMap(new ElementWithType(true,anyType)); ohair@286: ohair@286: private Form attributeFormDefault; ohair@286: private Form elementFormDefault; ohair@286: ohair@286: /** ohair@286: * Does schema in this namespace uses swaRef? If so, we need to generate import ohair@286: * statement. ohair@286: */ ohair@286: private boolean useSwaRef; ohair@286: ohair@286: /** ohair@286: * Import for mime namespace needs to be generated. ohair@286: * See #856 ohair@286: */ ohair@286: private boolean useMimeNs; ohair@286: ohair@286: public Namespace(String uri) { ohair@286: this.uri = uri; ohair@286: assert !XmlSchemaGenerator.this.namespaces.containsKey(uri); ohair@286: XmlSchemaGenerator.this.namespaces.put(uri,this); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Process the given PropertyInfo looking for references to namespaces that ohair@286: * are foreign to the given namespace. Any foreign namespace references ohair@286: * found are added to the given namespaces dependency list and an <import> ohair@286: * is generated for it. ohair@286: * ohair@286: * @param p the PropertyInfo ohair@286: */ ohair@286: private void processForeignNamespaces(PropertyInfo p, int processingDepth) { ohair@286: for (TypeInfo t : p.ref()) { ohair@286: if ((t instanceof ClassInfo) && (processingDepth > 0)) { ohair@286: java.util.List l = ((ClassInfo) t).getProperties(); ohair@286: for (PropertyInfo subp : l) { ohair@286: processForeignNamespaces(subp, --processingDepth); ohair@286: } ohair@286: } ohair@286: if (t instanceof Element) { ohair@286: addDependencyTo(((Element) t).getElementName()); ohair@286: } ohair@286: if (t instanceof NonElement) { ohair@286: addDependencyTo(((NonElement) t).getTypeName()); ohair@286: } ohair@286: } ohair@286: } ohair@286: ohair@286: private void addDependencyTo(@Nullable QName qname) { ohair@286: // even though the Element interface says getElementName() returns non-null, ohair@286: // ClassInfo always implements Element (even if an instance of ClassInfo might not be an Element). ohair@286: // so this check is still necessary ohair@286: if (qname==null) { ohair@286: return; ohair@286: } ohair@286: ohair@286: String nsUri = qname.getNamespaceURI(); ohair@286: ohair@286: if (nsUri.equals(XML_SCHEMA)) { ohair@286: // no need to explicitly refer to XSD namespace ohair@286: return; ohair@286: } ohair@286: ohair@286: if (nsUri.equals(uri)) { ohair@286: selfReference = true; ohair@286: return; ohair@286: } ohair@286: ohair@286: // found a type in a foreign namespace, so make sure we generate an import for it ohair@286: depends.add(getNamespace(nsUri)); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Writes the schema document to the specified result. ohair@286: * ohair@286: * @param systemIds ohair@286: * System IDs of the other schema documents. "" indicates 'implied'. ohair@286: */ ohair@286: private void writeTo(Result result, Map systemIds) throws IOException { ohair@286: try { ohair@286: Schema schema = TXW.create(Schema.class,ResultFactory.createSerializer(result)); ohair@286: ohair@286: // additional namespace declarations to be made. ohair@286: Map xmlNs = types.getXmlNs(uri); ohair@286: ohair@286: for (Map.Entry e : xmlNs.entrySet()) { ohair@286: schema._namespace(e.getValue(),e.getKey()); ohair@286: } ohair@286: ohair@286: if(useSwaRef) ohair@286: schema._namespace(WellKnownNamespace.SWA_URI,"swaRef"); ohair@286: ohair@286: if(useMimeNs) ohair@286: schema._namespace(WellKnownNamespace.XML_MIME_URI,"xmime"); ohair@286: ohair@286: attributeFormDefault = Form.get(types.getAttributeFormDefault(uri)); ohair@286: attributeFormDefault.declare("attributeFormDefault",schema); ohair@286: ohair@286: elementFormDefault = Form.get(types.getElementFormDefault(uri)); ohair@286: // TODO: if elementFormDefault is UNSET, figure out the right default value to use ohair@286: elementFormDefault.declare("elementFormDefault",schema); ohair@286: ohair@286: ohair@286: // declare XML Schema namespace to be xs, but allow the user to override it. ohair@286: // if 'xs' is used for other things, we'll just let TXW assign a random prefix ohair@286: if(!xmlNs.containsValue(WellKnownNamespace.XML_SCHEMA) ohair@286: && !xmlNs.containsKey("xs")) ohair@286: schema._namespace(WellKnownNamespace.XML_SCHEMA,"xs"); ohair@286: schema.version("1.0"); ohair@286: ohair@286: if(uri.length()!=0) ohair@286: schema.targetNamespace(uri); ohair@286: ohair@286: // declare prefixes for them at this level, so that we can avoid redundant ohair@286: // namespace declarations ohair@286: for (Namespace ns : depends) { ohair@286: schema._namespace(ns.uri); ohair@286: } ohair@286: ohair@286: if(selfReference && uri.length()!=0) { ohair@286: // use common 'tns' prefix for the own namespace ohair@286: // if self-reference is needed ohair@286: schema._namespace(uri,"tns"); ohair@286: } ohair@286: ohair@286: schema._pcdata(newline); ohair@286: ohair@286: // refer to other schemas ohair@286: for( Namespace n : depends ) { ohair@286: Import imp = schema._import(); ohair@286: if(n.uri.length()!=0) ohair@286: imp.namespace(n.uri); ohair@286: String refSystemId = systemIds.get(n); ohair@286: if(refSystemId!=null && !refSystemId.equals("")) { ohair@286: // "" means implied. null if the SchemaOutputResolver said "don't generate!" ohair@286: imp.schemaLocation(relativize(refSystemId,result.getSystemId())); ohair@286: } ohair@286: schema._pcdata(newline); ohair@286: } ohair@286: if(useSwaRef) { ohair@286: schema._import().namespace(WellKnownNamespace.SWA_URI).schemaLocation("http://ws-i.org/profiles/basic/1.1/swaref.xsd"); ohair@286: } ohair@286: if(useMimeNs) { ohair@286: schema._import().namespace(WellKnownNamespace.XML_MIME_URI).schemaLocation("http://www.w3.org/2005/05/xmlmime"); ohair@286: } ohair@286: ohair@286: // then write each component ohair@286: for (Map.Entry e : elementDecls.entrySet()) { ohair@286: e.getValue().writeTo(e.getKey(),schema); ohair@286: schema._pcdata(newline); ohair@286: } ohair@286: for (ClassInfo c : classes) { ohair@286: if (c.getTypeName()==null) { ohair@286: // don't generate anything if it's an anonymous type ohair@286: continue; ohair@286: } ohair@286: if(uri.equals(c.getTypeName().getNamespaceURI())) ohair@286: writeClass(c, schema); ohair@286: schema._pcdata(newline); ohair@286: } ohair@286: for (EnumLeafInfo e : enums) { ohair@286: if (e.getTypeName()==null) { ohair@286: // don't generate anything if it's an anonymous type ohair@286: continue; ohair@286: } ohair@286: if(uri.equals(e.getTypeName().getNamespaceURI())) ohair@286: writeEnum(e,schema); ohair@286: schema._pcdata(newline); ohair@286: } ohair@286: for (ArrayInfo a : arrays) { ohair@286: writeArray(a,schema); ohair@286: schema._pcdata(newline); ohair@286: } ohair@286: for (Map.Entry> e : attributeDecls.entrySet()) { ohair@286: TopLevelAttribute a = schema.attribute(); ohair@286: a.name(e.getKey()); ohair@286: if(e.getValue()==null) ohair@286: writeTypeRef(a,stringType,"type"); ohair@286: else ohair@286: writeAttributeTypeRef(e.getValue(),a); ohair@286: schema._pcdata(newline); ohair@286: } ohair@286: ohair@286: // close the schema ohair@286: schema.commit(); ohair@286: } catch( TxwException e ) { ohair@286: logger.log(Level.INFO,e.getMessage(),e); ohair@286: throw new IOException(e.getMessage()); ohair@286: } ohair@286: } ohair@286: ohair@286: /** ohair@286: * Writes a type attribute (if the referenced type is a global type) ohair@286: * or writes out the definition of the anonymous type in place (if the referenced ohair@286: * type is not a global type.) ohair@286: * ohair@286: * Also provides processing for ID/IDREF, MTOM @xmime, and swa:ref ohair@286: * ohair@286: * ComplexTypeHost and SimpleTypeHost don't share an api for creating ohair@286: * and attribute in a type-safe way, so we will compromise for now and ohair@286: * use _attribute(). ohair@286: */ ohair@286: private void writeTypeRef(TypeHost th, NonElementRef typeRef, String refAttName) { ohair@286: // ID / IDREF handling ohair@286: switch(typeRef.getSource().id()) { ohair@286: case ID: ohair@286: th._attribute(refAttName, new QName(WellKnownNamespace.XML_SCHEMA, "ID")); ohair@286: return; ohair@286: case IDREF: ohair@286: th._attribute(refAttName, new QName(WellKnownNamespace.XML_SCHEMA, "IDREF")); ohair@286: return; ohair@286: case NONE: ohair@286: // no ID/IDREF, so continue on and generate the type ohair@286: break; ohair@286: default: ohair@286: throw new IllegalStateException(); ohair@286: } ohair@286: ohair@286: // MTOM handling ohair@286: MimeType mimeType = typeRef.getSource().getExpectedMimeType(); ohair@286: if( mimeType != null ) { ohair@286: th._attribute(new QName(WellKnownNamespace.XML_MIME_URI, "expectedContentTypes", "xmime"), mimeType.toString()); ohair@286: } ohair@286: ohair@286: // ref:swaRef handling ohair@286: if(generateSwaRefAdapter(typeRef)) { ohair@286: th._attribute(refAttName, new QName(WellKnownNamespace.SWA_URI, "swaRef", "ref")); ohair@286: return; ohair@286: } ohair@286: ohair@286: // type name override ohair@286: if(typeRef.getSource().getSchemaType()!=null) { ohair@286: th._attribute(refAttName,typeRef.getSource().getSchemaType()); ohair@286: return; ohair@286: } ohair@286: ohair@286: // normal type generation ohair@286: writeTypeRef(th, typeRef.getTarget(), refAttName); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Writes a type attribute (if the referenced type is a global type) ohair@286: * or writes out the definition of the anonymous type in place (if the referenced ohair@286: * type is not a global type.) ohair@286: * ohair@286: * @param th ohair@286: * the TXW interface to which the attribute will be written. ohair@286: * @param type ohair@286: * type to be referenced. ohair@286: * @param refAttName ohair@286: * The name of the attribute used when referencing a type by QName. ohair@286: */ ohair@286: private void writeTypeRef(TypeHost th, NonElement type, String refAttName) { ohair@286: Element e = null; ohair@286: if (type instanceof MaybeElement) { ohair@286: MaybeElement me = (MaybeElement)type; ohair@286: boolean isElement = me.isElement(); ohair@286: if (isElement) e = me.asElement(); ohair@286: } ohair@286: if (type instanceof Element) { ohair@286: e = (Element)type; ohair@286: } ohair@286: if (type.getTypeName()==null) { ohair@286: if ((e != null) && (e.getElementName() != null)) { ohair@286: th.block(); // so that the caller may write other attributes ohair@286: if(type instanceof ClassInfo) { ohair@286: writeClass( (ClassInfo)type, th ); ohair@286: } else { ohair@286: writeEnum( (EnumLeafInfo)type, (SimpleTypeHost)th); ohair@286: } ohair@286: } else { ohair@286: // anonymous ohair@286: th.block(); // so that the caller may write other attributes ohair@286: if(type instanceof ClassInfo) { ohair@286: if(collisionChecker.push((ClassInfo)type)) { ohair@286: errorListener.warning(new SAXParseException( ohair@286: Messages.ANONYMOUS_TYPE_CYCLE.format(collisionChecker.getCycleString()), ohair@286: null ohair@286: )); ohair@286: } else { ohair@286: writeClass( (ClassInfo)type, th ); ohair@286: } ohair@286: collisionChecker.pop(); ohair@286: } else { ohair@286: writeEnum( (EnumLeafInfo)type, (SimpleTypeHost)th); ohair@286: } ohair@286: } ohair@286: } else { ohair@286: th._attribute(refAttName,type.getTypeName()); ohair@286: } ohair@286: } ohair@286: ohair@286: /** ohair@286: * writes the schema definition for the given array class ohair@286: */ ohair@286: private void writeArray(ArrayInfo a, Schema schema) { ohair@286: ComplexType ct = schema.complexType().name(a.getTypeName().getLocalPart()); ohair@286: ct._final("#all"); ohair@286: LocalElement le = ct.sequence().element().name("item"); ohair@286: le.type(a.getItemType().getTypeName()); ohair@286: le.minOccurs(0).maxOccurs("unbounded"); ohair@286: le.nillable(true); ohair@286: ct.commit(); ohair@286: } ohair@286: ohair@286: /** ohair@286: * writes the schema definition for the specified type-safe enum in the given TypeHost ohair@286: */ ohair@286: private void writeEnum(EnumLeafInfo e, SimpleTypeHost th) { ohair@286: SimpleType st = th.simpleType(); ohair@286: writeName(e,st); ohair@286: ohair@286: SimpleRestrictionModel base = st.restriction(); ohair@286: writeTypeRef(base, e.getBaseType(), "base"); ohair@286: ohair@286: for (EnumConstant c : e.getConstants()) { ohair@286: base.enumeration().value(c.getLexicalValue()); ohair@286: } ohair@286: st.commit(); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Writes the schema definition for the specified class to the schema writer. ohair@286: * ohair@286: * @param c the class info ohair@286: * @param parent the writer of the parent element into which the type will be defined ohair@286: */ ohair@286: private void writeClass(ClassInfo c, TypeHost parent) { ohair@286: // special handling for value properties ohair@286: if (containsValueProp(c)) { ohair@286: if (c.getProperties().size() == 1) { ohair@286: // [RESULT 2 - simpleType if the value prop is the only prop] ohair@286: // ohair@286: // ohair@286: // ohair@286: // ohair@286: ValuePropertyInfo vp = (ValuePropertyInfo)c.getProperties().get(0); ohair@286: SimpleType st = ((SimpleTypeHost)parent).simpleType(); ohair@286: writeName(c, st); ohair@286: if(vp.isCollection()) { ohair@286: writeTypeRef(st.list(),vp.getTarget(),"itemType"); ohair@286: } else { ohair@286: writeTypeRef(st.restriction(),vp.getTarget(),"base"); ohair@286: } ohair@286: return; ohair@286: } else { ohair@286: // [RESULT 1 - complexType with simpleContent] ohair@286: // ohair@286: // ohair@286: // ohair@286: // ohair@286: // ohair@286: // ohair@286: // ohair@286: // ohair@286: // ... ohair@286: // ohair@286: // ... ohair@286: ComplexType ct = ((ComplexTypeHost)parent).complexType(); ohair@286: writeName(c,ct); ohair@286: if(c.isFinal()) ohair@286: ct._final("extension restriction"); ohair@286: ohair@286: SimpleExtension se = ct.simpleContent().extension(); ohair@286: se.block(); // because we might have attribute before value ohair@286: for (PropertyInfo p : c.getProperties()) { ohair@286: switch (p.kind()) { ohair@286: case ATTRIBUTE: ohair@286: handleAttributeProp((AttributePropertyInfo)p,se); ohair@286: break; ohair@286: case VALUE: ohair@286: TODO.checkSpec("what if vp.isCollection() == true?"); ohair@286: ValuePropertyInfo vp = (ValuePropertyInfo) p; ohair@286: se.base(vp.getTarget().getTypeName()); ohair@286: break; ohair@286: case ELEMENT: // error ohair@286: case REFERENCE: // error ohair@286: default: ohair@286: assert false; ohair@286: throw new IllegalStateException(); ohair@286: } ohair@286: } ohair@286: se.commit(); ohair@286: } ohair@286: TODO.schemaGenerator("figure out what to do if bc != null"); ohair@286: TODO.checkSpec("handle sec 8.9.5.2, bullet #4"); ohair@286: // Java types containing value props can only contain properties of type ohair@286: // ValuePropertyinfo and AttributePropertyInfo which have just been handled, ohair@286: // so return. ohair@286: return; ohair@286: } ohair@286: ohair@286: // we didn't fall into the special case for value props, so we ohair@286: // need to initialize the ct. ohair@286: // generate the complexType ohair@286: ComplexType ct = ((ComplexTypeHost)parent).complexType(); ohair@286: writeName(c,ct); ohair@286: if(c.isFinal()) ohair@286: ct._final("extension restriction"); ohair@286: if(c.isAbstract()) ohair@286: ct._abstract(true); ohair@286: ohair@286: // these are where we write content model and attributes ohair@286: AttrDecls contentModel = ct; ohair@286: TypeDefParticle contentModelOwner = ct; ohair@286: ohair@286: // if there is a base class, we need to generate an extension in the schema ohair@286: final ClassInfo bc = c.getBaseClass(); ohair@286: if (bc != null) { ohair@286: if(bc.hasValueProperty()) { ohair@286: // extending complex type with simple content ohair@286: SimpleExtension se = ct.simpleContent().extension(); ohair@286: contentModel = se; ohair@286: contentModelOwner = null; ohair@286: se.base(bc.getTypeName()); ohair@286: } else { ohair@286: ComplexExtension ce = ct.complexContent().extension(); ohair@286: contentModel = ce; ohair@286: contentModelOwner = ce; ohair@286: ohair@286: ce.base(bc.getTypeName()); ohair@286: // TODO: what if the base type is anonymous? ohair@286: } ohair@286: } ohair@286: ohair@286: if(contentModelOwner!=null) { ohair@286: // build the tree that represents the explicit content model from iterate over the properties ohair@286: ArrayList children = new ArrayList(); ohair@286: for (PropertyInfo p : c.getProperties()) { ohair@286: // handling for ohair@286: if(p instanceof ReferencePropertyInfo && ((ReferencePropertyInfo)p).isMixed()) { ohair@286: ct.mixed(true); ohair@286: } ohair@286: Tree t = buildPropertyContentModel(p); ohair@286: if(t!=null) ohair@286: children.add(t); ohair@286: } ohair@286: ohair@286: Tree top = Tree.makeGroup( c.isOrdered() ? GroupKind.SEQUENCE : GroupKind.ALL, children); ohair@286: ohair@286: // write the content model ohair@286: top.write(contentModelOwner); ohair@286: } ohair@286: ohair@286: // then attributes ohair@286: for (PropertyInfo p : c.getProperties()) { ohair@286: if (p instanceof AttributePropertyInfo) { ohair@286: handleAttributeProp((AttributePropertyInfo)p, contentModel); ohair@286: } ohair@286: } ohair@286: if( c.hasAttributeWildcard()) { ohair@286: contentModel.anyAttribute().namespace("##other").processContents("skip"); ohair@286: } ohair@286: ct.commit(); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Writes the name attribute if it's named. ohair@286: */ ohair@286: private void writeName(NonElement c, TypedXmlWriter xw) { ohair@286: QName tn = c.getTypeName(); ohair@286: if(tn!=null) ohair@286: xw._attribute("name",tn.getLocalPart()); // named ohair@286: } ohair@286: ohair@286: private boolean containsValueProp(ClassInfo c) { ohair@286: for (PropertyInfo p : c.getProperties()) { ohair@286: if (p instanceof ValuePropertyInfo) return true; ohair@286: } ohair@286: return false; ohair@286: } ohair@286: ohair@286: /** ohair@286: * Builds content model writer for the specified property. ohair@286: */ ohair@286: private Tree buildPropertyContentModel(PropertyInfo p) { ohair@286: switch(p.kind()) { ohair@286: case ELEMENT: ohair@286: return handleElementProp((ElementPropertyInfo)p); ohair@286: case ATTRIBUTE: ohair@286: // attribuets are handled later ohair@286: return null; ohair@286: case REFERENCE: ohair@286: return handleReferenceProp((ReferencePropertyInfo)p); ohair@286: case MAP: ohair@286: return handleMapProp((MapPropertyInfo)p); ohair@286: case VALUE: ohair@286: // value props handled above in writeClass() ohair@286: assert false; ohair@286: throw new IllegalStateException(); ohair@286: default: ohair@286: assert false; ohair@286: throw new IllegalStateException(); ohair@286: } ohair@286: } ohair@286: ohair@286: /** ohair@286: * Generate the proper schema fragment for the given element property into the ohair@286: * specified schema compositor. ohair@286: * ohair@286: * The element property may or may not represent a collection and it may or may ohair@286: * not be wrapped. ohair@286: * ohair@286: * @param ep the element property ohair@286: */ ohair@286: private Tree handleElementProp(final ElementPropertyInfo ep) { ohair@286: if (ep.isValueList()) { ohair@286: return new Tree.Term() { ohair@286: protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { ohair@286: TypeRef t = ep.getTypes().get(0); ohair@286: LocalElement e = parent.element(); ohair@286: e.block(); // we will write occurs later ohair@286: QName tn = t.getTagName(); ohair@286: e.name(tn.getLocalPart()); ohair@286: List lst = e.simpleType().list(); ohair@286: writeTypeRef(lst,t, "itemType"); ohair@286: elementFormDefault.writeForm(e,tn); ohair@286: writeOccurs(e,isOptional||!ep.isRequired(),repeated); ohair@286: } ohair@286: }; ohair@286: } ohair@286: ohair@286: ArrayList children = new ArrayList(); ohair@286: for (final TypeRef t : ep.getTypes()) { ohair@286: children.add(new Tree.Term() { ohair@286: protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { ohair@286: LocalElement e = parent.element(); ohair@286: ohair@286: QName tn = t.getTagName(); ohair@286: ohair@286: PropertyInfo propInfo = t.getSource(); ohair@286: TypeInfo parentInfo = (propInfo == null) ? null : propInfo.parent(); ohair@286: ohair@286: if (canBeDirectElementRef(t, tn, parentInfo)) { ohair@286: if ((!t.getTarget().isSimpleType()) && (t.getTarget() instanceof ClassInfo) && collisionChecker.findDuplicate((ClassInfo) t.getTarget())) { ohair@286: e.ref(new QName(uri, tn.getLocalPart())); ohair@286: } else { ohair@286: ohair@286: QName elemName = null; ohair@286: if (t.getTarget() instanceof Element) { ohair@286: Element te = (Element) t.getTarget(); ohair@286: elemName = te.getElementName(); ohair@286: } ohair@286: ohair@286: Collection refs = propInfo.ref(); ohair@286: if ((refs != null) && (!refs.isEmpty()) && (elemName != null)) { ohair@286: ClassInfoImpl cImpl = (ClassInfoImpl)refs.iterator().next(); ohair@286: if ((cImpl != null) && (cImpl.getElementName() != null)) { ohair@286: e.ref(new QName(cImpl.getElementName().getNamespaceURI(), tn.getLocalPart())); ohair@286: } else { ohair@286: e.ref(new QName("", tn.getLocalPart())); ohair@286: } ohair@286: } else { ohair@286: e.ref(tn); ohair@286: } ohair@286: } ohair@286: } else { ohair@286: e.name(tn.getLocalPart()); ohair@286: writeTypeRef(e,t, "type"); ohair@286: elementFormDefault.writeForm(e,tn); ohair@286: } ohair@286: ohair@286: if (t.isNillable()) { ohair@286: e.nillable(true); ohair@286: } ohair@286: if(t.getDefaultValue()!=null) ohair@286: e._default(t.getDefaultValue()); ohair@286: writeOccurs(e,isOptional,repeated); ohair@286: } ohair@286: }); ohair@286: } ohair@286: ohair@286: final Tree choice = Tree.makeGroup(GroupKind.CHOICE, children) ohair@286: .makeOptional(!ep.isRequired()) ohair@286: .makeRepeated(ep.isCollection()); // see Spec table 8-13 ohair@286: ohair@286: ohair@286: final QName ename = ep.getXmlName(); ohair@286: if (ename != null) { // wrapped collection ohair@286: return new Tree.Term() { ohair@286: protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { ohair@286: LocalElement e = parent.element(); ohair@286: if(ename.getNamespaceURI().length()>0) { ohair@286: if (!ename.getNamespaceURI().equals(uri)) { ohair@286: // TODO: we need to generate the corresponding element declaration for this ohair@286: // table 8-25: Property/field element wrapper with ref attribute ohair@286: e.ref(new QName(ename.getNamespaceURI(), ename.getLocalPart())); ohair@286: return; ohair@286: } ohair@286: } ohair@286: e.name(ename.getLocalPart()); ohair@286: elementFormDefault.writeForm(e,ename); ohair@286: ohair@286: if(ep.isCollectionNillable()) { ohair@286: e.nillable(true); ohair@286: } ohair@286: writeOccurs(e,!ep.isCollectionRequired(),repeated); ohair@286: ohair@286: ComplexType p = e.complexType(); ohair@286: choice.write(p); ohair@286: } ohair@286: }; ohair@286: } else {// non-wrapped ohair@286: return choice; ohair@286: } ohair@286: } ohair@286: ohair@286: /** ohair@286: * Checks if we can collapse ohair@286: * <element name='foo' type='t' /> to <element ref='foo' />. ohair@286: * ohair@286: * This is possible if we already have such declaration to begin with. ohair@286: */ ohair@286: private boolean canBeDirectElementRef(TypeRef t, QName tn, TypeInfo parentInfo) { ohair@286: Element te = null; ohair@286: ClassInfo ci = null; ohair@286: QName targetTagName = null; ohair@286: ohair@286: if(t.isNillable() || t.getDefaultValue()!=null) { ohair@286: // can't put those attributes on ohair@286: return false; ohair@286: } ohair@286: ohair@286: if (t.getTarget() instanceof Element) { ohair@286: te = (Element) t.getTarget(); ohair@286: targetTagName = te.getElementName(); ohair@286: if (te instanceof ClassInfo) { ohair@286: ci = (ClassInfo)te; ohair@286: } ohair@286: } ohair@286: ohair@286: String nsUri = tn.getNamespaceURI(); ohair@286: if ((!nsUri.equals(uri) && nsUri.length()>0) && (!((parentInfo instanceof ClassInfo) && (((ClassInfo)parentInfo).getTypeName() == null)))) { ohair@286: return true; ohair@286: } ohair@286: ohair@286: // there's a circular reference from an anonymous subtype to a global element ohair@286: if ((ci != null) && ((targetTagName != null) && (te.getScope() == null))) { ohair@286: if (targetTagName.getLocalPart().equals(tn.getLocalPart())) { ohair@286: return true; ohair@286: } ohair@286: } ohair@286: ohair@286: // we have the precise element defined already ohair@286: if (te != null) { // it is instanceof Element ohair@286: return targetTagName!=null && targetTagName.equals(tn); ohair@286: } ohair@286: ohair@286: return false; ohair@286: } ohair@286: ohair@286: ohair@286: /** ohair@286: * Generate an attribute for the specified property on the specified complexType ohair@286: * ohair@286: * @param ap the attribute ohair@286: * @param attr the schema definition to which the attribute will be added ohair@286: */ ohair@286: private void handleAttributeProp(AttributePropertyInfo ap, AttrDecls attr) { ohair@286: // attr is either a top-level ComplexType or a ComplexExtension ohair@286: // ohair@286: // [RESULT] ohair@286: // ohair@286: // ohair@286: // <...>... ohair@286: // ohair@286: // ohair@286: // ohair@286: // or ohair@286: // ohair@286: // ohair@286: // ohair@286: // ohair@286: // <...>... ohair@286: // ohair@286: // ohair@286: // ohair@286: // ohair@286: // ohair@286: // or it could also be an in-lined type (attr ref) ohair@286: // ohair@286: LocalAttribute localAttribute = attr.attribute(); ohair@286: ohair@286: final String attrURI = ap.getXmlName().getNamespaceURI(); ohair@286: if (attrURI.equals("") /*|| attrURI.equals(uri) --- those are generated as global attributes anyway, so use them.*/) { ohair@286: localAttribute.name(ap.getXmlName().getLocalPart()); ohair@286: ohair@286: writeAttributeTypeRef(ap, localAttribute); ohair@286: ohair@286: attributeFormDefault.writeForm(localAttribute,ap.getXmlName()); ohair@286: } else { // generate an attr ref ohair@286: localAttribute.ref(ap.getXmlName()); ohair@286: } ohair@286: ohair@286: if(ap.isRequired()) { ohair@286: // TODO: not type safe ohair@286: localAttribute.use("required"); ohair@286: } ohair@286: } ohair@286: ohair@286: private void writeAttributeTypeRef(AttributePropertyInfo ap, AttributeType a) { ohair@286: if( ap.isCollection() ) ohair@286: writeTypeRef(a.simpleType().list(), ap, "itemType"); ohair@286: else ohair@286: writeTypeRef(a, ap, "type"); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Generate the proper schema fragment for the given reference property into the ohair@286: * specified schema compositor. ohair@286: * ohair@286: * The reference property may or may not refer to a collection and it may or may ohair@286: * not be wrapped. ohair@286: */ ohair@286: private Tree handleReferenceProp(final ReferencePropertyInfo rp) { ohair@286: // fill in content model ohair@286: ArrayList children = new ArrayList(); ohair@286: ohair@286: for (final Element e : rp.getElements()) { ohair@286: children.add(new Tree.Term() { ohair@286: protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { ohair@286: LocalElement eref = parent.element(); ohair@286: ohair@286: boolean local=false; ohair@286: ohair@286: QName en = e.getElementName(); ohair@286: if(e.getScope()!=null) { ohair@286: // scoped. needs to be inlined ohair@286: boolean qualified = en.getNamespaceURI().equals(uri); ohair@286: boolean unqualified = en.getNamespaceURI().equals(""); ohair@286: if(qualified || unqualified) { ohair@286: // can be inlined indeed ohair@286: ohair@286: // write form="..." if necessary ohair@286: if(unqualified) { ohair@286: if(elementFormDefault.isEffectivelyQualified) ohair@286: eref.form("unqualified"); ohair@286: } else { ohair@286: if(!elementFormDefault.isEffectivelyQualified) ohair@286: eref.form("qualified"); ohair@286: } ohair@286: ohair@286: local = true; ohair@286: eref.name(en.getLocalPart()); ohair@286: ohair@286: // write out type reference ohair@286: if(e instanceof ClassInfo) { ohair@286: writeTypeRef(eref,(ClassInfo)e,"type"); ohair@286: } else { ohair@286: writeTypeRef(eref,((ElementInfo)e).getContentType(),"type"); ohair@286: } ohair@286: } ohair@286: } ohair@286: if(!local) ohair@286: eref.ref(en); ohair@286: writeOccurs(eref,isOptional,repeated); ohair@286: } ohair@286: }); ohair@286: } ohair@286: ohair@286: final WildcardMode wc = rp.getWildcard(); ohair@286: if( wc != null ) { ohair@286: children.add(new Tree.Term() { ohair@286: protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { ohair@286: Any any = parent.any(); ohair@286: final String pcmode = getProcessContentsModeName(wc); ohair@286: if( pcmode != null ) any.processContents(pcmode); ohair@286: any.namespace("##other"); ohair@286: writeOccurs(any,isOptional,repeated); ohair@286: } ohair@286: }); ohair@286: } ohair@286: ohair@286: ohair@286: final Tree choice = Tree.makeGroup(GroupKind.CHOICE, children).makeRepeated(rp.isCollection()).makeOptional(!rp.isRequired()); ohair@286: ohair@286: final QName ename = rp.getXmlName(); ohair@286: ohair@286: if (ename != null) { // wrapped ohair@286: return new Tree.Term() { ohair@286: protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { ohair@286: LocalElement e = parent.element().name(ename.getLocalPart()); ohair@286: elementFormDefault.writeForm(e,ename); ohair@286: if(rp.isCollectionNillable()) ohair@286: e.nillable(true); ohair@286: writeOccurs(e,true,repeated); ohair@286: ohair@286: ComplexType p = e.complexType(); ohair@286: choice.write(p); ohair@286: } ohair@286: }; ohair@286: } else { // unwrapped ohair@286: return choice; ohair@286: } ohair@286: } ohair@286: ohair@286: /** ohair@286: * Generate the proper schema fragment for the given map property into the ohair@286: * specified schema compositor. ohair@286: * ohair@286: * @param mp the map property ohair@286: */ ohair@286: private Tree handleMapProp(final MapPropertyInfo mp) { ohair@286: return new Tree.Term() { ohair@286: protected void write(ContentModelContainer parent, boolean isOptional, boolean repeated) { ohair@286: QName ename = mp.getXmlName(); ohair@286: ohair@286: LocalElement e = parent.element(); ohair@286: elementFormDefault.writeForm(e,ename); ohair@286: if(mp.isCollectionNillable()) ohair@286: e.nillable(true); ohair@286: ohair@286: e = e.name(ename.getLocalPart()); ohair@286: writeOccurs(e,isOptional,repeated); ohair@286: ComplexType p = e.complexType(); ohair@286: ohair@286: // TODO: entry, key, and value are always unqualified. that needs to be fixed, too. ohair@286: // TODO: we need to generate the corresponding element declaration, if they are qualified ohair@286: e = p.sequence().element(); ohair@286: e.name("entry").minOccurs(0).maxOccurs("unbounded"); ohair@286: ohair@286: ExplicitGroup seq = e.complexType().sequence(); ohair@286: writeKeyOrValue(seq, "key", mp.getKeyType()); ohair@286: writeKeyOrValue(seq, "value", mp.getValueType()); ohair@286: } ohair@286: }; ohair@286: } ohair@286: ohair@286: private void writeKeyOrValue(ExplicitGroup seq, String tagName, NonElement typeRef) { ohair@286: LocalElement key = seq.element().name(tagName); ohair@286: key.minOccurs(0); ohair@286: writeTypeRef(key, typeRef, "type"); ohair@286: } ohair@286: ohair@286: public void addGlobalAttribute(AttributePropertyInfo ap) { ohair@286: attributeDecls.put( ap.getXmlName().getLocalPart(), ap ); ohair@286: addDependencyTo(ap.getTarget().getTypeName()); ohair@286: } ohair@286: ohair@286: public void addGlobalElement(TypeRef tref) { ohair@286: elementDecls.put( tref.getTagName().getLocalPart(), new ElementWithType(false,tref.getTarget()) ); ohair@286: addDependencyTo(tref.getTarget().getTypeName()); ohair@286: } ohair@286: ohair@286: @Override ohair@286: public String toString() { ohair@286: StringBuilder buf = new StringBuilder(); ohair@286: buf.append("[classes=").append(classes); ohair@286: buf.append(",elementDecls=").append(elementDecls); ohair@286: buf.append(",enums=").append(enums); ohair@286: buf.append("]"); ohair@286: return super.toString(); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Represents a global element declaration to be written. ohair@286: * ohair@286: *

ohair@286: * Because multiple properties can name the same global element even if ohair@286: * they have different Java type, the schema generator first needs to ohair@286: * walk through the model and decide what to generate for the given ohair@286: * element declaration. ohair@286: * ohair@286: *

ohair@286: * This class represents what will be written, and its {@link #equals(Object)} ohair@286: * method is implemented in such a way that two identical declarations ohair@286: * are considered as the same. ohair@286: */ ohair@286: abstract class ElementDeclaration { ohair@286: /** ohair@286: * Returns true if two {@link ElementDeclaration}s are representing ohair@286: * the same schema fragment. ohair@286: */ ohair@286: @Override ohair@286: public abstract boolean equals(Object o); ohair@286: @Override ohair@286: public abstract int hashCode(); ohair@286: ohair@286: /** ohair@286: * Generates the declaration. ohair@286: */ ohair@286: public abstract void writeTo(String localName, Schema schema); ohair@286: } ohair@286: ohair@286: /** ohair@286: * {@link ElementDeclaration} that refers to a {@link NonElement}. ohair@286: */ ohair@286: class ElementWithType extends ElementDeclaration { ohair@286: private final boolean nillable; ohair@286: private final NonElement type; ohair@286: ohair@286: public ElementWithType(boolean nillable,NonElement type) { ohair@286: this.type = type; ohair@286: this.nillable = nillable; ohair@286: } ohair@286: ohair@286: public void writeTo(String localName, Schema schema) { ohair@286: TopLevelElement e = schema.element().name(localName); ohair@286: if(nillable) ohair@286: e.nillable(true); ohair@286: if (type != null) { ohair@286: writeTypeRef(e,type, "type"); ohair@286: } else { ohair@286: e.complexType(); // refer to the nested empty complex type ohair@286: } ohair@286: e.commit(); ohair@286: } ohair@286: ohair@286: public boolean equals(Object o) { ohair@286: if (this == o) return true; ohair@286: if (o == null || getClass() != o.getClass()) return false; ohair@286: ohair@286: final ElementWithType that = (ElementWithType) o; ohair@286: return type.equals(that.type); ohair@286: } ohair@286: ohair@286: public int hashCode() { ohair@286: return type.hashCode(); ohair@286: } ohair@286: } ohair@286: } ohair@286: ohair@286: /** ohair@286: * Examine the specified element ref and determine if a swaRef attribute needs to be generated ohair@286: * @param typeRef ohair@286: */ ohair@286: private boolean generateSwaRefAdapter(NonElementRef typeRef) { ohair@286: return generateSwaRefAdapter(typeRef.getSource()); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Examine the specified element ref and determine if a swaRef attribute needs to be generated ohair@286: */ ohair@286: private boolean generateSwaRefAdapter(PropertyInfo prop) { ohair@286: final Adapter adapter = prop.getAdapter(); ohair@286: if (adapter == null) return false; ohair@286: final Object o = navigator.asDecl(SwaRefAdapter.class); ohair@286: if (o == null) return false; ohair@286: return (o.equals(adapter.adapterType)); ohair@286: } ohair@286: ohair@286: /** ohair@286: * Debug information of what's in this {@link XmlSchemaGenerator}. ohair@286: */ ohair@286: @Override ohair@286: public String toString() { ohair@286: StringBuilder buf = new StringBuilder(); ohair@286: for (Namespace ns : namespaces.values()) { ohair@286: if(buf.length()>0) buf.append(','); ohair@286: buf.append(ns.uri).append('=').append(ns); ohair@286: } ohair@286: return super.toString()+'['+buf+']'; ohair@286: } ohair@286: ohair@286: /** ohair@286: * return the string representation of the processContents mode of the ohair@286: * give wildcard, or null if it is the schema default "strict" ohair@286: * ohair@286: */ ohair@286: private static String getProcessContentsModeName(WildcardMode wc) { ohair@286: switch(wc) { ohair@286: case LAX: ohair@286: case SKIP: ohair@286: return wc.name().toLowerCase(); ohair@286: case STRICT: ohair@286: return null; ohair@286: default: ohair@286: throw new IllegalStateException(); ohair@286: } ohair@286: } ohair@286: ohair@286: ohair@286: /** ohair@286: * Relativizes a URI by using another URI (base URI.) ohair@286: * ohair@286: *

ohair@286: * For example, {@code relative("http://www.sun.com/abc/def","http://www.sun.com/pqr/stu") => "../abc/def"} ohair@286: * ohair@286: *

ohair@286: * This method only works on hierarchical URI's, not opaque URI's (refer to the ohair@286: * java.net.URI ohair@286: * javadoc for complete definitions of these terms. ohair@286: * ohair@286: *

ohair@286: * This method will not normalize the relative URI. ohair@286: * ohair@286: * @return the relative URI or the original URI if a relative one could not be computed ohair@286: */ ohair@286: protected static String relativize(String uri, String baseUri) { ohair@286: try { ohair@286: assert uri!=null; ohair@286: ohair@286: if(baseUri==null) return uri; ohair@286: ohair@286: URI theUri = new URI(escapeURI(uri)); ohair@286: URI theBaseUri = new URI(escapeURI(baseUri)); ohair@286: ohair@286: if (theUri.isOpaque() || theBaseUri.isOpaque()) ohair@286: return uri; ohair@286: ohair@286: if (!equalsIgnoreCase(theUri.getScheme(), theBaseUri.getScheme()) || ohair@286: !equal(theUri.getAuthority(), theBaseUri.getAuthority())) ohair@286: return uri; ohair@286: ohair@286: String uriPath = theUri.getPath(); ohair@286: String basePath = theBaseUri.getPath(); ohair@286: ohair@286: // normalize base path ohair@286: if (!basePath.endsWith("/")) { ohair@286: basePath = normalizeUriPath(basePath); ohair@286: } ohair@286: ohair@286: if( uriPath.equals(basePath)) ohair@286: return "."; ohair@286: ohair@286: String relPath = calculateRelativePath(uriPath, basePath, fixNull(theUri.getScheme()).equals("file")); ohair@286: ohair@286: if (relPath == null) ohair@286: return uri; // recursion found no commonality in the two uris at all ohair@286: StringBuilder relUri = new StringBuilder(); ohair@286: relUri.append(relPath); ohair@286: if (theUri.getQuery() != null) ohair@286: relUri.append('?').append(theUri.getQuery()); ohair@286: if (theUri.getFragment() != null) ohair@286: relUri.append('#').append(theUri.getFragment()); ohair@286: ohair@286: return relUri.toString(); ohair@286: } catch (URISyntaxException e) { ohair@286: throw new InternalError("Error escaping one of these uris:\n\t"+uri+"\n\t"+baseUri); ohair@286: } ohair@286: } ohair@286: ohair@286: private static String fixNull(String s) { ohair@286: if(s==null) return ""; ohair@286: else return s; ohair@286: } ohair@286: ohair@286: private static String calculateRelativePath(String uri, String base, boolean fileUrl) { ohair@286: // if this is a file URL (very likely), and if this is on a case-insensitive file system, ohair@286: // then treat it accordingly. ohair@286: boolean onWindows = File.pathSeparatorChar==';'; ohair@286: ohair@286: if (base == null) { ohair@286: return null; ohair@286: } ohair@286: if ((fileUrl && onWindows && startsWithIgnoreCase(uri,base)) || uri.startsWith(base)) { ohair@286: return uri.substring(base.length()); ohair@286: } else { ohair@286: return "../" + calculateRelativePath(uri, getParentUriPath(base), fileUrl); ohair@286: } ohair@286: } ohair@286: ohair@286: private static boolean startsWithIgnoreCase(String s, String t) { ohair@286: return s.toUpperCase().startsWith(t.toUpperCase()); ohair@286: } ohair@286: ohair@286: /** ohair@286: * JAX-RPC wants the namespaces to be sorted in the reverse order ohair@286: * so that the empty namespace "" comes to the very end. Don't ask me why. ohair@286: */ ohair@286: private static final Comparator NAMESPACE_COMPARATOR = new Comparator() { ohair@286: public int compare(String lhs, String rhs) { ohair@286: return -lhs.compareTo(rhs); ohair@286: } ohair@286: }; ohair@286: ohair@286: private static final String newline = "\n"; ohair@286: }