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

Thu, 24 May 2018 16:44:14 +0800

author
aoqi
date
Thu, 24 May 2018 16:44:14 +0800
changeset 1288
f4ace6971570
parent 1101
bd88174c3095
parent 637
9c07ef4934dd
permissions
-rw-r--r--

Merge

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

mercurial