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:
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: }