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

aoqi@0: * A client must invoke methods in the following order: aoqi@0: *

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

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

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

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

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

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

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

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