0001: /*
0002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package com.sun.xml.internal.bind.v2.schemagen;
0027:
0028: import java.io.IOException;
0029: import java.io.OutputStream;
0030: import java.io.Writer;
0031: import java.net.URI;
0032: import java.net.URISyntaxException;
0033: import java.util.Comparator;
0034: import java.util.HashMap;
0035: import java.util.LinkedHashSet;
0036: import java.util.Map;
0037: import java.util.Set;
0038: import java.util.TreeMap;
0039: import java.util.logging.Level;
0040: import java.util.logging.Logger;
0041:
0042: import javax.activation.MimeType;
0043: import javax.xml.bind.SchemaOutputResolver;
0044: import javax.xml.namespace.QName;
0045: import javax.xml.transform.Result;
0046: import javax.xml.transform.stream.StreamResult;
0047:
0048: import com.sun.istack.internal.Nullable;
0049: import com.sun.xml.internal.bind.Util;
0050: import com.sun.xml.internal.bind.api.CompositeStructure;
0051: import com.sun.xml.internal.bind.v2.TODO;
0052: import com.sun.xml.internal.bind.v2.WellKnownNamespace;
0053: import static com.sun.xml.internal.bind.v2.WellKnownNamespace.XML_SCHEMA;
0054: import com.sun.xml.internal.bind.v2.model.core.Adapter;
0055: import com.sun.xml.internal.bind.v2.model.core.ArrayInfo;
0056: import com.sun.xml.internal.bind.v2.model.core.AttributePropertyInfo;
0057: import com.sun.xml.internal.bind.v2.model.core.ClassInfo;
0058: import com.sun.xml.internal.bind.v2.model.core.Element;
0059: import com.sun.xml.internal.bind.v2.model.core.ElementInfo;
0060: import com.sun.xml.internal.bind.v2.model.core.ElementPropertyInfo;
0061: import com.sun.xml.internal.bind.v2.model.core.EnumConstant;
0062: import com.sun.xml.internal.bind.v2.model.core.EnumLeafInfo;
0063: import com.sun.xml.internal.bind.v2.model.core.MapPropertyInfo;
0064: import com.sun.xml.internal.bind.v2.model.core.NonElement;
0065: import com.sun.xml.internal.bind.v2.model.core.NonElementRef;
0066: import com.sun.xml.internal.bind.v2.model.core.PropertyInfo;
0067: import com.sun.xml.internal.bind.v2.model.core.ReferencePropertyInfo;
0068: import com.sun.xml.internal.bind.v2.model.core.TypeInfo;
0069: import com.sun.xml.internal.bind.v2.model.core.TypeInfoSet;
0070: import com.sun.xml.internal.bind.v2.model.core.TypeRef;
0071: import com.sun.xml.internal.bind.v2.model.core.ValuePropertyInfo;
0072: import com.sun.xml.internal.bind.v2.model.core.WildcardMode;
0073: import com.sun.xml.internal.bind.v2.model.nav.Navigator;
0074: import com.sun.xml.internal.bind.v2.runtime.SwaRefAdapter;
0075: import static com.sun.xml.internal.bind.v2.schemagen.Util.*;
0076: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Any;
0077: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.AttrDecls;
0078: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexExtension;
0079: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexType;
0080: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ComplexTypeHost;
0081: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.ExplicitGroup;
0082: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Import;
0083: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.List;
0084: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.LocalAttribute;
0085: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.LocalElement;
0086: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Occurs;
0087: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.Schema;
0088: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleExtension;
0089: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleRestrictionModel;
0090: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleType;
0091: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.SimpleTypeHost;
0092: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TopLevelAttribute;
0093: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TopLevelElement;
0094: import com.sun.xml.internal.bind.v2.schemagen.xmlschema.TypeHost;
0095: import com.sun.xml.internal.txw2.TXW;
0096: import com.sun.xml.internal.txw2.TxwException;
0097: import com.sun.xml.internal.txw2.TypedXmlWriter;
0098: import com.sun.xml.internal.txw2.output.ResultFactory;
0099:
0100: /**
0101: * Generates a set of W3C XML Schema documents from a set of Java classes.
0102: *
0103: * <p>
0104: * A client must invoke methods in the following order:
0105: * <ol>
0106: * <li>Create a new {@link XmlSchemaGenerator}
0107: * <li>Invoke {@link #add} methods, multiple times if necessary.
0108: * <li>Invoke {@link #write}
0109: * <li>Discard the {@link XmlSchemaGenerator}.
0110: * </ol>
0111: *
0112: * @author Ryan Shoemaker
0113: * @author Kohsuke Kawaguchi (kk@kohsuke.org)
0114: */
0115: public final class XmlSchemaGenerator<T, C, F, M> {
0116:
0117: private static final Logger logger = Util.getClassLogger();
0118:
0119: /**
0120: * Java classes to be written, organized by their namespace.
0121: *
0122: * <p>
0123: * We use a {@link TreeMap} here so that the suggested names will
0124: * be consistent across JVMs.
0125: *
0126: * @see SchemaOutputResolver#createOutput(String, String)
0127: */
0128: private final Map<String, Namespace> namespaces = new TreeMap<String, Namespace>(
0129: NAMESPACE_COMPARATOR);
0130:
0131: /** model navigator **/
0132: private Navigator<T, C, F, M> navigator;
0133:
0134: private final TypeInfoSet<T, C, F, M> types;
0135:
0136: /**
0137: * Representation for xs:string.
0138: */
0139: private final NonElement<T, C> stringType;
0140:
0141: /**
0142: * Represents xs:anyType.
0143: */
0144: private final NonElement<T, C> anyType;
0145:
0146: public XmlSchemaGenerator(Navigator<T, C, F, M> navigator,
0147: TypeInfoSet<T, C, F, M> types) {
0148: this .navigator = navigator;
0149: this .types = types;
0150:
0151: this .stringType = types
0152: .getTypeInfo(navigator.ref(String.class));
0153: this .anyType = types.getAnyTypeInfo();
0154:
0155: // populate the object
0156: for (ClassInfo<T, C> ci : types.beans().values())
0157: add(ci);
0158: for (ElementInfo<T, C> ei1 : types.getElementMappings(null)
0159: .values())
0160: add(ei1);
0161: for (EnumLeafInfo<T, C> ei : types.enums().values())
0162: add(ei);
0163: for (ArrayInfo<T, C> a : types.arrays().values())
0164: add(a);
0165: }
0166:
0167: private Namespace getNamespace(String uri) {
0168: Namespace n = namespaces.get(uri);
0169: if (n == null)
0170: namespaces.put(uri, n = new Namespace(uri));
0171: return n;
0172: }
0173:
0174: /**
0175: * Adds a new class to the list of classes to be written.
0176: *
0177: * <p>
0178: * A {@link ClassInfo} may have two namespaces --- one for the element name
0179: * and the other for the type name. If they are different, we put the same
0180: * {@link ClassInfo} to two {@link Namespace}s.
0181: */
0182: public void add(ClassInfo<T, C> clazz) {
0183: assert clazz != null;
0184:
0185: String nsUri = null;
0186:
0187: if (clazz.getClazz() == navigator
0188: .asDecl(CompositeStructure.class))
0189: return; // this is a special class we introduced for JAX-WS that we *don't* want in the schema
0190:
0191: if (clazz.isElement()) {
0192: // put element -> type reference
0193: nsUri = clazz.getElementName().getNamespaceURI();
0194: Namespace ns = getNamespace(nsUri);
0195: ns.classes.add(clazz);
0196: ns.addDependencyTo(clazz.getTypeName());
0197:
0198: // schedule writing this global element
0199: add(clazz.getElementName(), false, clazz);
0200: }
0201:
0202: QName tn = clazz.getTypeName();
0203: if (tn != null) {
0204: nsUri = tn.getNamespaceURI();
0205: } else {
0206: // anonymous type
0207: if (nsUri == null)
0208: return;
0209: }
0210:
0211: Namespace n = getNamespace(nsUri);
0212: n.classes.add(clazz);
0213:
0214: // search properties for foreign namespace references
0215: for (PropertyInfo<T, C> p : clazz.getProperties()) {
0216: n.processForeignNamespaces(p);
0217: if (p instanceof AttributePropertyInfo) {
0218: AttributePropertyInfo<T, C> ap = (AttributePropertyInfo<T, C>) p;
0219: String aUri = ap.getXmlName().getNamespaceURI();
0220: if (aUri.length() > 0) {
0221: // global attribute
0222: getNamespace(aUri).addGlobalAttribute(ap);
0223: n.addDependencyTo(ap.getXmlName());
0224: }
0225: }
0226: if (p instanceof ElementPropertyInfo) {
0227: ElementPropertyInfo<T, C> ep = (ElementPropertyInfo<T, C>) p;
0228: for (TypeRef<T, C> tref : ep.getTypes()) {
0229: String eUri = tref.getTagName().getNamespaceURI();
0230: if (eUri.length() > 0 && !eUri.equals(n.uri)) {
0231: getNamespace(eUri).addGlobalElement(tref);
0232: n.addDependencyTo(tref.getTagName());
0233: }
0234: }
0235: }
0236: }
0237:
0238: // recurse on baseTypes to make sure that we can refer to them in the schema
0239: ClassInfo<T, C> bc = clazz.getBaseClass();
0240: if (bc != null)
0241: add(bc);
0242: }
0243:
0244: /**
0245: * Adds a new element to the list of elements to be written.
0246: */
0247: public void add(ElementInfo<T, C> elem) {
0248: assert elem != null;
0249:
0250: QName name = elem.getElementName();
0251: Namespace n = getNamespace(name.getNamespaceURI());
0252: n.elementDecls.put(name.getLocalPart(), n.new ElementWithType(
0253: true, elem.getContentType()));
0254:
0255: // search for foreign namespace references
0256: n.processForeignNamespaces(elem.getProperty());
0257: }
0258:
0259: public void add(EnumLeafInfo<T, C> envm) {
0260: assert envm != null;
0261:
0262: String nsUri = null;
0263:
0264: if (envm.isElement()) {
0265: // put element -> type reference
0266: nsUri = envm.getElementName().getNamespaceURI();
0267: Namespace ns = getNamespace(nsUri);
0268: ns.enums.add(envm);
0269: ns.addDependencyTo(envm.getTypeName());
0270:
0271: // schedule writing this global element
0272: add(envm.getElementName(), false, envm);
0273: }
0274:
0275: final QName typeName = envm.getTypeName();
0276: if (typeName != null) {
0277: nsUri = typeName.getNamespaceURI();
0278: } else {
0279: if (nsUri == null)
0280: return; // anonymous type
0281: }
0282:
0283: Namespace n = getNamespace(nsUri);
0284: n.enums.add(envm);
0285:
0286: // search for foreign namespace references
0287: n.addDependencyTo(envm.getBaseType().getTypeName());
0288: }
0289:
0290: public void add(ArrayInfo<T, C> a) {
0291: assert a != null;
0292:
0293: final String namespaceURI = a.getTypeName().getNamespaceURI();
0294: Namespace n = getNamespace(namespaceURI);
0295: n.arrays.add(a);
0296:
0297: // search for foreign namespace references
0298: n.addDependencyTo(a.getItemType().getTypeName());
0299: }
0300:
0301: /**
0302: * Adds an additional element declaration.
0303: *
0304: * @param tagName
0305: * The name of the element declaration to be added.
0306: * @param type
0307: * The type this element refers to.
0308: * Can be null, in which case the element refers to an empty anonymous complex type.
0309: */
0310: public void add(QName tagName, boolean isNillable,
0311: NonElement<T, C> type) {
0312:
0313: if (type != null
0314: && type.getType() == navigator
0315: .ref(CompositeStructure.class))
0316: return; // this is a special class we introduced for JAX-WS that we *don't* want in the schema
0317:
0318: Namespace n = getNamespace(tagName.getNamespaceURI());
0319: n.elementDecls.put(tagName.getLocalPart(),
0320: n.new ElementWithType(isNillable, type));
0321:
0322: // search for foreign namespace references
0323: if (type != null)
0324: n.addDependencyTo(type.getTypeName());
0325: }
0326:
0327: /**
0328: * Write out the schema documents.
0329: */
0330: public void write(SchemaOutputResolver resolver) throws IOException {
0331: if (resolver == null)
0332: throw new IllegalArgumentException();
0333:
0334: // make it fool-proof
0335: resolver = new FoolProofResolver(resolver);
0336:
0337: Map<Namespace, Result> out = new HashMap<Namespace, Result>();
0338:
0339: // we create a Namespace object for the XML Schema namespace
0340: // as a side-effect, but we don't want to generate it.
0341: namespaces.remove(WellKnownNamespace.XML_SCHEMA);
0342:
0343: // first create the outputs for all so that we can resolve references among
0344: // schema files when we write
0345: for (Namespace n : namespaces.values()) {
0346: final Result output = resolver.createOutput(n.uri, "schema"
0347: + (out.size() + 1) + ".xsd");
0348: if (output != null) { // null result means no schema for that namespace
0349: out.put(n, output);
0350: }
0351: }
0352:
0353: // then write'em all
0354: for (Namespace n : namespaces.values()) {
0355: Result result = out.get(n);
0356: if (result != null) {
0357: n.writeTo(result, out);
0358: if (result instanceof StreamResult) {
0359: OutputStream outputStream = ((StreamResult) result)
0360: .getOutputStream();
0361: if (outputStream != null) {
0362: outputStream.close(); // fix for bugid: 6291301
0363: } else {
0364: final Writer writer = ((StreamResult) result)
0365: .getWriter();
0366: if (writer != null)
0367: writer.close();
0368: }
0369: }
0370: }
0371: }
0372: }
0373:
0374: /**
0375: * Schema components are organized per namespace.
0376: */
0377: private class Namespace {
0378: final String uri;
0379:
0380: /**
0381: * Other {@link Namespace}s that this namespace depends on.
0382: */
0383: private final Set<Namespace> depends = new LinkedHashSet<Namespace>();
0384:
0385: /**
0386: * If this schema refers to components from this schema by itself.
0387: */
0388: private boolean selfReference;
0389:
0390: /**
0391: * List of classes in this namespace.
0392: */
0393: private final Set<ClassInfo<T, C>> classes = new LinkedHashSet<ClassInfo<T, C>>();
0394:
0395: /**
0396: * Set of enums in this namespace
0397: */
0398: private final Set<EnumLeafInfo<T, C>> enums = new LinkedHashSet<EnumLeafInfo<T, C>>();
0399:
0400: /**
0401: * Set of arrays in this namespace
0402: */
0403: private final Set<ArrayInfo<T, C>> arrays = new LinkedHashSet<ArrayInfo<T, C>>();
0404:
0405: /**
0406: * Global attribute declarations keyed by their local names.
0407: */
0408: private final MultiMap<String, NonElement<T, C>> attributeDecls = new MultiMap<String, NonElement<T, C>>(
0409: stringType);
0410:
0411: /**
0412: * Global element declarations to be written, keyed by their local names.
0413: */
0414: private final MultiMap<String, ElementDeclaration> elementDecls = new MultiMap<String, ElementDeclaration>(
0415: new ElementWithType(true, anyType));
0416:
0417: private Form attributeFormDefault;
0418: private Form elementFormDefault;
0419:
0420: public Namespace(String uri) {
0421: this .uri = uri;
0422: assert !XmlSchemaGenerator.this .namespaces.containsKey(uri);
0423: XmlSchemaGenerator.this .namespaces.put(uri, this );
0424: }
0425:
0426: /**
0427: * Process the given PropertyInfo looking for references to namespaces that
0428: * are foreign to the given namespace. Any foreign namespace references
0429: * found are added to the given namespaces dependency list and an <import>
0430: * is generated for it.
0431: *
0432: * @param p the PropertyInfo
0433: */
0434: private void processForeignNamespaces(PropertyInfo<T, C> p) {
0435: // TODO: missing the correct handling of anonymous type,
0436: // which requires recursive checks
0437: for (TypeInfo<T, C> t : p.ref()) {
0438: if (t instanceof Element) {
0439: addDependencyTo(((Element) t).getElementName());
0440: }
0441: if (t instanceof NonElement) {
0442: addDependencyTo(((NonElement) t).getTypeName());
0443: }
0444: }
0445: }
0446:
0447: private void addDependencyTo(@Nullable
0448: QName qname) {
0449: // even though the Element interface says getElementName() returns non-null,
0450: // ClassInfo always implements Element (even if an instance of ClassInfo might not be an Element).
0451: // so this check is still necessary
0452: if (qname == null)
0453: return;
0454:
0455: String nsUri = qname.getNamespaceURI();
0456:
0457: if (nsUri.equals(XML_SCHEMA))
0458: // no need to explicitly refer to XSD namespace
0459: return;
0460:
0461: if (nsUri.equals(uri)) {
0462: selfReference = true;
0463: return;
0464: }
0465:
0466: // found a type in a foreign namespace, so make sure we generate an import for it
0467: depends.add(getNamespace(nsUri));
0468: }
0469:
0470: /**
0471: * Writes the schema document to the specified result.
0472: */
0473: private void writeTo(Result result, Map<Namespace, Result> out)
0474: throws IOException {
0475: try {
0476: Schema schema = TXW.create(Schema.class, ResultFactory
0477: .createSerializer(result));
0478:
0479: // additional namespace declarations to be made.
0480: Map<String, String> xmlNs = types.getXmlNs(uri);
0481:
0482: for (Map.Entry<String, String> e : xmlNs.entrySet()) {
0483: schema._namespace(e.getValue(), e.getKey());
0484: }
0485:
0486: attributeFormDefault = Form.get(types
0487: .getAttributeFormDefault(uri));
0488: attributeFormDefault.declare("attributeFormDefault",
0489: schema);
0490:
0491: elementFormDefault = Form.get(types
0492: .getElementFormDefault(uri));
0493: // TODO: if elementFormDefault is UNSET, figure out the right default value to use
0494: elementFormDefault
0495: .declare("elementFormDefault", schema);
0496:
0497: // declare XML Schema namespace to be xs, but allow the user to override it.
0498: // if 'xs' is used for other things, we'll just let TXW assign a random prefix
0499: if (!xmlNs.containsValue(WellKnownNamespace.XML_SCHEMA)
0500: && !xmlNs.containsKey("xs"))
0501: schema._namespace(WellKnownNamespace.XML_SCHEMA,
0502: "xs");
0503: schema.version("1.0");
0504:
0505: if (uri.length() != 0)
0506: schema.targetNamespace(uri);
0507:
0508: // declare prefixes for them at this level, so that we can avoid redundant
0509: // namespace declarations
0510: for (Namespace ns : depends) {
0511: schema._namespace(ns.uri);
0512: }
0513:
0514: if (selfReference && uri.length() != 0) {
0515: // use common 'tns' prefix for the own namespace
0516: // if self-reference is needed
0517: schema._namespace(uri, "tns");
0518: }
0519:
0520: schema._pcdata(newline);
0521:
0522: // refer to other schemas
0523: for (Namespace n : depends) {
0524: Import imp = schema._import();
0525: if (n.uri.length() != 0)
0526: imp.namespace(n.uri);
0527: imp.schemaLocation(relativize(out.get(n)
0528: .getSystemId(), result.getSystemId()));
0529: schema._pcdata(newline);
0530: }
0531:
0532: // then write each component
0533: for (Map.Entry<String, ElementDeclaration> e : elementDecls
0534: .entrySet()) {
0535: e.getValue().writeTo(e.getKey(), schema);
0536: schema._pcdata(newline);
0537: }
0538: for (ClassInfo<T, C> c : classes) {
0539: if (c.getTypeName() == null) {
0540: // don't generate anything if it's an anonymous type
0541: continue;
0542: }
0543: if (uri.equals(c.getTypeName().getNamespaceURI()))
0544: writeClass(c, schema);
0545: schema._pcdata(newline);
0546: }
0547: for (EnumLeafInfo<T, C> e : enums) {
0548: if (e.getTypeName() == null) {
0549: // don't generate anything if it's an anonymous type
0550: continue;
0551: }
0552: if (uri.equals(e.getTypeName().getNamespaceURI()))
0553: writeEnum(e, schema);
0554: schema._pcdata(newline);
0555: }
0556: for (ArrayInfo<T, C> a : arrays) {
0557: writeArray(a, schema);
0558: schema._pcdata(newline);
0559: }
0560: for (Map.Entry<String, NonElement<T, C>> e : attributeDecls
0561: .entrySet()) {
0562: TopLevelAttribute a = schema.attribute();
0563: a.name(e.getKey());
0564: writeTypeRef(a, e.getValue(), "type");
0565: schema._pcdata(newline);
0566: }
0567:
0568: // close the schema
0569: schema.commit();
0570: } catch (TxwException e) {
0571: logger.log(Level.INFO, e.getMessage(), e);
0572: throw new IOException(e.getMessage());
0573: }
0574: }
0575:
0576: /**
0577: * Writes a type attribute (if the referenced type is a global type)
0578: * or writes out the definition of the anonymous type in place (if the referenced
0579: * type is not a global type.)
0580: *
0581: * Also provides processing for ID/IDREF, MTOM @xmime, and swa:ref
0582: *
0583: * ComplexTypeHost and SimpleTypeHost don't share an api for creating
0584: * and attribute in a type-safe way, so we will compromise for now and
0585: * use _attribute().
0586: */
0587: private void writeTypeRef(TypeHost th,
0588: NonElementRef<T, C> typeRef, String refAttName) {
0589: // ID / IDREF handling
0590: switch (typeRef.getSource().id()) {
0591: case ID:
0592: th._attribute(refAttName, new QName(
0593: WellKnownNamespace.XML_SCHEMA, "ID"));
0594: return;
0595: case IDREF:
0596: th._attribute(refAttName, new QName(
0597: WellKnownNamespace.XML_SCHEMA, "IDREF"));
0598: return;
0599: case NONE:
0600: // no ID/IDREF, so continue on and generate the type
0601: break;
0602: default:
0603: throw new IllegalStateException();
0604: }
0605:
0606: // MTOM handling
0607: MimeType mimeType = typeRef.getSource()
0608: .getExpectedMimeType();
0609: if (mimeType != null) {
0610: th._attribute(new QName(
0611: WellKnownNamespace.XML_MIME_URI,
0612: "expectedContentTypes", "xmime"), mimeType
0613: .toString());
0614: }
0615:
0616: // ref:swaRef handling
0617: if (generateSwaRefAdapter(typeRef)) {
0618: th._attribute(refAttName, new QName(
0619: WellKnownNamespace.SWA_URI, "swaRef", "ref"));
0620: return;
0621: }
0622:
0623: // type name override
0624: if (typeRef.getSource().getSchemaType() != null) {
0625: th._attribute(refAttName, typeRef.getSource()
0626: .getSchemaType());
0627: return;
0628: }
0629:
0630: // normal type generation
0631: writeTypeRef(th, typeRef.getTarget(), refAttName);
0632: }
0633:
0634: /**
0635: * Examine the specified element ref and determine if a swaRef attribute needs to be generated
0636: * @param typeRef
0637: */
0638: private boolean generateSwaRefAdapter(
0639: NonElementRef<T, C> typeRef) {
0640: final Adapter<T, C> adapter = typeRef.getSource()
0641: .getAdapter();
0642: if (adapter == null)
0643: return false;
0644: final Object o = navigator.asDecl(SwaRefAdapter.class);
0645: if (o == null)
0646: return false;
0647: return (o.equals(adapter.adapterType));
0648: }
0649:
0650: /**
0651: * Writes a type attribute (if the referenced type is a global type)
0652: * or writes out the definition of the anonymous type in place (if the referenced
0653: * type is not a global type.)
0654: *
0655: * @param th
0656: * the TXW interface to which the attribute will be written.
0657: * @param type
0658: * type to be referenced.
0659: * @param refAttName
0660: * The name of the attribute used when referencing a type by QName.
0661: */
0662: private void writeTypeRef(TypeHost th, NonElement<T, C> type,
0663: String refAttName) {
0664: if (type.getTypeName() == null) {
0665: if (type instanceof ClassInfo) {
0666: writeClass((ClassInfo<T, C>) type, th);
0667: } else {
0668: writeEnum((EnumLeafInfo<T, C>) type,
0669: (SimpleTypeHost) th);
0670: }
0671: } else {
0672: th._attribute(refAttName, type.getTypeName());
0673: }
0674: }
0675:
0676: /**
0677: * writes the schema definition for the given array class
0678: */
0679: private void writeArray(ArrayInfo<T, C> a, Schema schema) {
0680: ComplexType ct = schema.complexType().name(
0681: a.getTypeName().getLocalPart());
0682: ct._final("#all");
0683: LocalElement le = ct.sequence().element().name("item");
0684: le.type(a.getItemType().getTypeName());
0685: le.minOccurs(0).maxOccurs("unbounded");
0686: le.nillable(true);
0687: ct.commit();
0688: }
0689:
0690: /**
0691: * writes the schema definition for the specified type-safe enum in the given TypeHost
0692: */
0693: private void writeEnum(EnumLeafInfo<T, C> e, SimpleTypeHost th) {
0694: SimpleType st = th.simpleType();
0695: writeName(e, st);
0696:
0697: SimpleRestrictionModel base = st.restriction();
0698: writeTypeRef(base, e.getBaseType(), "base");
0699:
0700: for (EnumConstant c : e.getConstants()) {
0701: base.enumeration().value(c.getLexicalValue());
0702: }
0703: st.commit();
0704: }
0705:
0706: /**
0707: * Writes the schema definition for the specified class to the schema writer.
0708: *
0709: * @param c the class info
0710: * @param parent the writer of the parent element into which the type will be defined
0711: */
0712: private void writeClass(ClassInfo<T, C> c, TypeHost parent) {
0713: // special handling for value properties
0714: if (containsValueProp(c)) {
0715: if (c.getProperties().size() == 1) {
0716: // [RESULT 2 - simpleType if the value prop is the only prop]
0717: //
0718: // <simpleType name="foo">
0719: // <xs:restriction base="xs:int"/>
0720: // </>
0721: ValuePropertyInfo<T, C> vp = (ValuePropertyInfo<T, C>) c
0722: .getProperties().get(0);
0723: SimpleType st = ((SimpleTypeHost) parent)
0724: .simpleType();
0725: writeName(c, st);
0726: if (vp.isCollection()) {
0727: writeTypeRef(st.list(), vp.getTarget(),
0728: "itemType");
0729: } else {
0730: writeTypeRef(st.restriction(), vp.getTarget(),
0731: "base");
0732: }
0733: return;
0734: } else {
0735: // [RESULT 1 - complexType with simpleContent]
0736: //
0737: // <complexType name="foo">
0738: // <simpleContent>
0739: // <extension base="xs:int"/>
0740: // <attribute name="b" type="xs:boolean"/>
0741: // </>
0742: // </>
0743: // </>
0744: // ...
0745: // <element name="f" type="foo"/>
0746: // ...
0747: ComplexType ct = ((ComplexTypeHost) parent)
0748: .complexType();
0749: writeName(c, ct);
0750: if (c.isFinal())
0751: ct._final("extension restriction");
0752:
0753: SimpleExtension se = ct.simpleContent().extension();
0754: se.block(); // because we might have attribute before value
0755: for (PropertyInfo<T, C> p : c.getProperties()) {
0756: switch (p.kind()) {
0757: case ATTRIBUTE:
0758: handleAttributeProp(
0759: (AttributePropertyInfo<T, C>) p, se);
0760: break;
0761: case VALUE:
0762: TODO
0763: .checkSpec("what if vp.isCollection() == true?");
0764: ValuePropertyInfo vp = (ValuePropertyInfo) p;
0765: se.base(vp.getTarget().getTypeName());
0766: break;
0767: case ELEMENT: // error
0768: case REFERENCE: // error
0769: default:
0770: assert false;
0771: throw new IllegalStateException();
0772: }
0773: }
0774: se.commit();
0775: }
0776: TODO
0777: .schemaGenerator("figure out what to do if bc != null");
0778: TODO.checkSpec("handle sec 8.9.5.2, bullet #4");
0779: // Java types containing value props can only contain properties of type
0780: // ValuePropertyinfo and AttributePropertyInfo which have just been handled,
0781: // so return.
0782: return;
0783: }
0784:
0785: // we didn't fall into the special case for value props, so we
0786: // need to initialize the ct.
0787: // generate the complexType
0788: ComplexType ct = ((ComplexTypeHost) parent).complexType();
0789: writeName(c, ct);
0790: if (c.isFinal())
0791: ct._final("extension restriction");
0792: if (c.isAbstract())
0793: ct._abstract(true);
0794:
0795: // hold the ct open in case we need to generate @mixed below...
0796: ct.block();
0797:
0798: // either <sequence> or <all>
0799: ExplicitGroup compositor = null;
0800:
0801: // only necessary if this type has a base class we need to extend from
0802: AttrDecls contentModel = ct;
0803:
0804: // if there is a base class, we need to generate an extension in the schema
0805: final ClassInfo<T, C> bc = c.getBaseClass();
0806: if (bc != null) {
0807: ComplexExtension ce = ct.complexContent().extension();
0808: contentModel = ce;
0809:
0810: ce.base(bc.getTypeName());
0811: // TODO: what if the base type is anonymous?
0812: // ordered props go in a sequence, unordered go in an all
0813: if (c.isOrdered()) {
0814: compositor = ce.sequence();
0815: } else {
0816: compositor = ce.all();
0817: }
0818: }
0819:
0820: // iterate over the properties
0821: if (c.hasProperties()) {
0822: if (compositor == null) { // if there is no extension base, create a top level seq
0823: // ordered props go in a sequence, unordered go in an all
0824: if (c.isOrdered()) {
0825: compositor = ct.sequence();
0826: } else {
0827: compositor = ct.all();
0828: }
0829: }
0830:
0831: // block writing the compositor because we might need to
0832: // write some out of order attributes to handle min/maxOccurs
0833: compositor.block();
0834:
0835: for (PropertyInfo<T, C> p : c.getProperties()) {
0836: // handling for <complexType @mixed='true' ...>
0837: if (p instanceof ReferencePropertyInfo
0838: && ((ReferencePropertyInfo) p).isMixed()) {
0839: ct.mixed(true);
0840: }
0841: writeProperty(p, contentModel, compositor);
0842: }
0843:
0844: compositor.commit();
0845: }
0846:
0847: // look for wildcard attributes
0848: if (c.hasAttributeWildcard()) {
0849: // TODO: not type safe
0850: contentModel.anyAttribute().namespace("##other")
0851: .processContents("skip");
0852: }
0853:
0854: // finally commit the ct
0855: ct.commit();
0856: }
0857:
0858: /**
0859: * Writes the name attribute if it's named.
0860: */
0861: private void writeName(NonElement<T, C> c, TypedXmlWriter xw) {
0862: QName tn = c.getTypeName();
0863: if (tn != null)
0864: xw._attribute("name", tn.getLocalPart()); // named
0865: }
0866:
0867: private boolean containsValueProp(ClassInfo<T, C> c) {
0868: for (PropertyInfo p : c.getProperties()) {
0869: if (p instanceof ValuePropertyInfo)
0870: return true;
0871: }
0872: return false;
0873: }
0874:
0875: /**
0876: * write the schema definition(s) for the specified property
0877: */
0878: private void writeProperty(PropertyInfo<T, C> p,
0879: AttrDecls attr, ExplicitGroup compositor) {
0880: switch (p.kind()) {
0881: case ELEMENT:
0882: handleElementProp((ElementPropertyInfo<T, C>) p,
0883: compositor);
0884: break;
0885: case ATTRIBUTE:
0886: handleAttributeProp((AttributePropertyInfo<T, C>) p,
0887: attr);
0888: break;
0889: case REFERENCE:
0890: handleReferenceProp((ReferencePropertyInfo<T, C>) p,
0891: compositor);
0892: break;
0893: case MAP:
0894: handleMapProp((MapPropertyInfo<T, C>) p, compositor);
0895: break;
0896: case VALUE:
0897: // value props handled above in writeClass()
0898: assert false;
0899: throw new IllegalStateException();
0900: // break();
0901: default:
0902: assert false;
0903: throw new IllegalStateException();
0904: }
0905: }
0906:
0907: /**
0908: * Generate the proper schema fragment for the given element property into the
0909: * specified schema compositor.
0910: *
0911: * The element property may or may not represent a collection and it may or may
0912: * not be wrapped.
0913: *
0914: * @param ep the element property
0915: * @param compositor the schema compositor (sequence or all)
0916: */
0917: private void handleElementProp(ElementPropertyInfo<T, C> ep,
0918: ExplicitGroup compositor) {
0919: QName ename = ep.getXmlName();
0920: Occurs occurs = null;
0921:
0922: if (ep.isValueList()) {
0923: TypeRef<T, C> t = ep.getTypes().get(0);
0924: LocalElement e = compositor.element();
0925:
0926: QName tn = t.getTagName();
0927: e.name(tn.getLocalPart());
0928: List lst = e.simpleType().list();
0929: writeTypeRef(lst, t, "itemType");
0930: elementFormDefault.writeForm(e, tn);
0931: return;
0932: }
0933:
0934: if (ep.isCollection()) {
0935: if (ename != null) { // wrapped collection
0936: LocalElement e = compositor.element();
0937: if (ename.getNamespaceURI().length() > 0) {
0938: if (!ename.getNamespaceURI().equals(this .uri)) {
0939: // TODO: we need to generate the corresponding element declaration for this
0940: // table 8-25: Property/field element wrapper with ref attribute
0941: e.ref(new QName(ename.getNamespaceURI(),
0942: ename.getLocalPart()));
0943: return;
0944: }
0945: }
0946: elementFormDefault.writeForm(e, ename);
0947:
0948: ComplexType p = e.name(ename.getLocalPart())
0949: .complexType();
0950: if (ep.isCollectionNillable()) {
0951: e.nillable(true);
0952: } else {
0953: e.minOccurs(0);
0954: }
0955: if (ep.getTypes().size() == 1) {
0956: compositor = p.sequence();
0957: } else {
0958: compositor = p.choice();
0959: occurs = compositor;
0960: }
0961: } else { // unwrapped collection
0962: if (ep.getTypes().size() > 1) {
0963: compositor = compositor.choice();
0964: occurs = compositor;
0965: }
0966: }
0967: } else {
0968: if (ep.getTypes().size() > 1) {
0969: compositor = compositor.choice();
0970: occurs = compositor;
0971: }
0972: }
0973:
0974: // fill in the content model
0975: for (TypeRef<T, C> t : ep.getTypes()) {
0976: LocalElement e = compositor.element();
0977: if (occurs == null)
0978: occurs = e;
0979: QName tn = t.getTagName();
0980:
0981: if (canBeDirectElementRef(t, tn)
0982: || (!tn.getNamespaceURI().equals(uri) && tn
0983: .getNamespaceURI().length() > 0)) {
0984: e.ref(tn);
0985: } else {
0986: e.name(tn.getLocalPart());
0987: writeTypeRef(e, t, "type");
0988: elementFormDefault.writeForm(e, tn);
0989: }
0990:
0991: if (t.isNillable()) {
0992: e.nillable(true);
0993: }
0994: if (t.getDefaultValue() != null)
0995: e._default(t.getDefaultValue());
0996: }
0997:
0998: if (ep.isCollection())
0999: occurs.maxOccurs("unbounded");
1000:
1001: if (!ep.isRequired())
1002: // see Spec table 8-13
1003: occurs.minOccurs(0);
1004: // else minOccurs defaults to 1
1005: }
1006:
1007: /**
1008: * Checks if we can collapse
1009: * <element name='foo' type='t' /> to <element ref='foo' />.
1010: *
1011: * This is possible if we already have such declaration to begin with.
1012: */
1013: private boolean canBeDirectElementRef(TypeRef<T, C> t, QName tn) {
1014: if (t.isNillable() || t.getDefaultValue() != null)
1015: // can't put those attributes on <element ref>
1016: return false;
1017:
1018: if (t.getTarget() instanceof Element) {
1019: Element te = (Element) t.getTarget();
1020: QName targetTagName = te.getElementName();
1021: return targetTagName != null
1022: && targetTagName.equals(tn);
1023: }
1024:
1025: return false;
1026: }
1027:
1028: /**
1029: * Generate an attribute for the specified property on the specified complexType
1030: *
1031: * @param ap the attribute
1032: * @param attr the schema definition to which the attribute will be added
1033: */
1034: private void handleAttributeProp(
1035: AttributePropertyInfo<T, C> ap, AttrDecls attr) {
1036: // attr is either a top-level ComplexType or a ComplexExtension
1037: //
1038: // [RESULT]
1039: //
1040: // <complexType ...>
1041: // <...>...</>
1042: // <attribute name="foo" type="xs:int"/>
1043: // </>
1044: //
1045: // or
1046: //
1047: // <complexType ...>
1048: // <complexContent>
1049: // <extension ...>
1050: // <...>...</>
1051: // </>
1052: // </>
1053: // <attribute name="foo" type="xs:int"/>
1054: // </>
1055: //
1056: // or it could also be an in-lined type (attr ref)
1057: //
1058: LocalAttribute localAttribute = attr.attribute();
1059:
1060: final String attrURI = ap.getXmlName().getNamespaceURI();
1061: if (attrURI.equals("") || attrURI.equals(uri)) {
1062: localAttribute.name(ap.getXmlName().getLocalPart());
1063:
1064: TypeHost th;
1065: String refAtt;
1066: if (ap.isCollection()) {
1067: th = localAttribute.simpleType().list();
1068: refAtt = "itemType";
1069: } else {
1070: th = localAttribute;
1071: refAtt = "type";
1072: }
1073: writeTypeRef(th, ap, refAtt);
1074:
1075: attributeFormDefault.writeForm(localAttribute, ap
1076: .getXmlName());
1077: } else { // generate an attr ref
1078: localAttribute.ref(ap.getXmlName());
1079: }
1080:
1081: if (ap.isRequired()) {
1082: // TODO: not type safe
1083: localAttribute.use("required");
1084: }
1085: }
1086:
1087: /**
1088: * Generate the proper schema fragment for the given reference property into the
1089: * specified schema compositor.
1090: *
1091: * The reference property may or may not refer to a collection and it may or may
1092: * not be wrapped.
1093: *
1094: * @param rp
1095: * @param compositor
1096: */
1097: private void handleReferenceProp(
1098: ReferencePropertyInfo<T, C> rp, ExplicitGroup compositor) {
1099: QName ename = rp.getXmlName();
1100: Occurs occurs = null;
1101:
1102: if (rp.isCollection()) {
1103: if (ename != null) { // wrapped collection
1104: LocalElement e = compositor.element();
1105: ComplexType p = e.name(ename.getLocalPart())
1106: .complexType();
1107: elementFormDefault.writeForm(e, ename);
1108: if (rp.isCollectionNillable())
1109: e.nillable(true);
1110: if (rp.getElements().size() == 1) {
1111: compositor = p.sequence();
1112: } else {
1113: compositor = p.choice();
1114: occurs = compositor;
1115: }
1116: } else { // unwrapped collection
1117: if (rp.getElements().size() > 1) {
1118: compositor = compositor.choice();
1119: occurs = compositor;
1120: }
1121: }
1122: }
1123:
1124: // fill in content model
1125: TODO
1126: .checkSpec("should we loop in the case of a non-collection ep?");
1127: for (Element<T, C> e : rp.getElements()) {
1128: LocalElement eref = compositor.element();
1129: if (occurs == null)
1130: occurs = eref;
1131:
1132: QName en = e.getElementName();
1133: if (e.getScope() != null) {
1134: // scoped. needs to be inlined
1135: boolean qualified = en.getNamespaceURI()
1136: .equals(uri);
1137: boolean unqualified = en.getNamespaceURI().equals(
1138: "");
1139: if (qualified || unqualified) {
1140: // can be inlined indeed
1141:
1142: // write form="..." if necessary
1143: if (unqualified) {
1144: if (elementFormDefault.isEffectivelyQualified)
1145: eref.form("unqualified");
1146: } else {
1147: if (!elementFormDefault.isEffectivelyQualified)
1148: eref.form("qualified");
1149: }
1150:
1151: eref.name(en.getLocalPart());
1152:
1153: // write out type reference
1154: if (e instanceof ClassInfo) {
1155: writeTypeRef(eref, (ClassInfo<T, C>) e,
1156: "type");
1157: } else {
1158: writeTypeRef(eref, ((ElementInfo<T, C>) e)
1159: .getContentType(), "type");
1160: }
1161: continue;
1162: }
1163: }
1164: eref.ref(en);
1165: }
1166:
1167: WildcardMode wc = rp.getWildcard();
1168: if (wc != null) {
1169: Any any = compositor.any();
1170: final String pcmode = getProcessContentsModeName(wc);
1171: if (pcmode != null)
1172: any.processContents(pcmode);
1173: TODO.schemaGenerator("generate @namespace ???");
1174: if (occurs == null)
1175: occurs = any;
1176: }
1177:
1178: if (rp.isCollection())
1179: occurs.maxOccurs("unbounded");
1180:
1181: }
1182:
1183: /**
1184: * Generate the proper schema fragment for the given map property into the
1185: * specified schema compositor.
1186: *
1187: * @param mp the map property
1188: * @param compositor the schema compositor (sequence or all)
1189: */
1190: private void handleMapProp(MapPropertyInfo<T, C> mp,
1191: ExplicitGroup compositor) {
1192: QName ename = mp.getXmlName();
1193:
1194: LocalElement e = compositor.element();
1195: elementFormDefault.writeForm(e, ename);
1196: if (mp.isCollectionNillable())
1197: e.nillable(true);
1198: ComplexType p = e.name(ename.getLocalPart()).complexType();
1199:
1200: // TODO: entry, key, and value are always unqualified. that needs to be fixed, too.
1201: // TODO: we need to generate the corresponding element declaration, if they are qualified
1202: e = p.sequence().element();
1203: e.name("entry").minOccurs(0).maxOccurs("unbounded");
1204:
1205: ExplicitGroup seq = e.complexType().sequence();
1206: writeKeyOrValue(seq, "key", mp.getKeyType());
1207: writeKeyOrValue(seq, "value", mp.getValueType());
1208: }
1209:
1210: private void writeKeyOrValue(ExplicitGroup seq, String tagName,
1211: NonElement<T, C> typeRef) {
1212: LocalElement key = seq.element().name(tagName);
1213: key.minOccurs(0);
1214: writeTypeRef(key, typeRef, "type");
1215: }
1216:
1217: public void addGlobalAttribute(AttributePropertyInfo<T, C> ap) {
1218: attributeDecls.put(ap.getXmlName().getLocalPart(), ap
1219: .getTarget());
1220: addDependencyTo(ap.getTarget().getTypeName());
1221: }
1222:
1223: public void addGlobalElement(TypeRef<T, C> tref) {
1224: elementDecls.put(tref.getTagName().getLocalPart(),
1225: new ElementWithType(false, tref.getTarget()));
1226: addDependencyTo(tref.getTarget().getTypeName());
1227: }
1228:
1229: /**
1230: * Represents a global element declaration to be written.
1231: *
1232: * <p>
1233: * Because multiple properties can name the same global element even if
1234: * they have different Java type, the schema generator first needs to
1235: * walk through the model and decide what to generate for the given
1236: * element declaration.
1237: *
1238: * <p>
1239: * This class represents what will be written, and its {@link #equals(Object)}
1240: * method is implemented in such a way that two identical declarations
1241: * are considered as the same.
1242: */
1243: abstract class ElementDeclaration {
1244: /**
1245: * Returns true if two {@link ElementDeclaration}s are representing
1246: * the same schema fragment.
1247: */
1248: public abstract boolean equals(Object o);
1249:
1250: public abstract int hashCode();
1251:
1252: /**
1253: * Generates the declaration.
1254: */
1255: public abstract void writeTo(String localName, Schema schema);
1256: }
1257:
1258: /**
1259: * {@link ElementDeclaration} that refers to a {@link NonElement}.
1260: */
1261: class ElementWithType extends ElementDeclaration {
1262: private final boolean nillable;
1263: private final NonElement<T, C> type;
1264:
1265: public ElementWithType(boolean nillable,
1266: NonElement<T, C> type) {
1267: this .type = type;
1268: this .nillable = nillable;
1269: }
1270:
1271: public void writeTo(String localName, Schema schema) {
1272: TopLevelElement e = schema.element().name(localName);
1273: if (nillable)
1274: e.nillable(true);
1275: if (type != null) {
1276: writeTypeRef(e, type, "type");
1277: } else {
1278: e.complexType(); // refer to the nested empty complex type
1279: }
1280: e.commit();
1281: }
1282:
1283: public boolean equals(Object o) {
1284: if (this == o)
1285: return true;
1286: if (o == null || getClass() != o.getClass())
1287: return false;
1288:
1289: final ElementWithType that = (ElementWithType) o;
1290: return type.equals(that.type);
1291: }
1292:
1293: public int hashCode() {
1294: return type.hashCode();
1295: }
1296: }
1297: }
1298:
1299: /**
1300: * return the string representation of the processContents mode of the
1301: * give wildcard, or null if it is the schema default "strict"
1302: *
1303: */
1304: private static String getProcessContentsModeName(WildcardMode wc) {
1305: switch (wc) {
1306: case LAX:
1307: case SKIP:
1308: return wc.name().toLowerCase();
1309: case STRICT:
1310: return null;
1311: default:
1312: throw new IllegalStateException();
1313: }
1314: }
1315:
1316: /**
1317: * TODO: JAX-WS dependency on this method - consider moving this method into com.sun.tools.internal.jxc.util.Util
1318: *
1319: * Relativizes a URI by using another URI (base URI.)
1320: *
1321: * <p>
1322: * For example, {@code relative("http://www.sun.com/abc/def","http://www.sun.com/pqr/stu") => "../abc/def"}
1323: *
1324: * <p>
1325: * This method only works on hierarchical URI's, not opaque URI's (refer to the
1326: * <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/net/URI.html">java.net.URI</a>
1327: * javadoc for complete definitions of these terms.
1328: *
1329: * <p>
1330: * This method will not normalize the relative URI.
1331: *
1332: * @return the relative URI or the original URI if a relative one could not be computed
1333: */
1334: protected static String relativize(String uri, String baseUri) {
1335: try {
1336: assert uri != null;
1337:
1338: if (baseUri == null)
1339: return uri;
1340:
1341: URI theUri = new URI(escapeURI(uri));
1342: URI theBaseUri = new URI(escapeURI(baseUri));
1343:
1344: if (theUri.isOpaque() || theBaseUri.isOpaque())
1345: return uri;
1346:
1347: if (!equalsIgnoreCase(theUri.getScheme(), theBaseUri
1348: .getScheme())
1349: || !equal(theUri.getAuthority(), theBaseUri
1350: .getAuthority()))
1351: return uri;
1352:
1353: String uriPath = theUri.getPath();
1354: String basePath = theBaseUri.getPath();
1355:
1356: // normalize base path
1357: if (!basePath.endsWith("/")) {
1358: basePath = normalizeUriPath(basePath);
1359: }
1360:
1361: if (uriPath.equals(basePath))
1362: return ".";
1363:
1364: String relPath = calculateRelativePath(uriPath, basePath);
1365:
1366: if (relPath == null)
1367: return uri; // recursion found no commonality in the two uris at all
1368: StringBuffer relUri = new StringBuffer();
1369: relUri.append(relPath);
1370: if (theUri.getQuery() != null)
1371: relUri.append('?').append(theUri.getQuery());
1372: if (theUri.getFragment() != null)
1373: relUri.append('#').append(theUri.getFragment());
1374:
1375: return relUri.toString();
1376: } catch (URISyntaxException e) {
1377: throw new InternalError(
1378: "Error escaping one of these uris:\n\t" + uri
1379: + "\n\t" + baseUri);
1380: }
1381: }
1382:
1383: private static String calculateRelativePath(String uri, String base) {
1384: if (base == null) {
1385: return null;
1386: }
1387: if (uri.startsWith(base)) {
1388: return uri.substring(base.length());
1389: } else {
1390: return "../"
1391: + calculateRelativePath(uri, getParentUriPath(base));
1392: }
1393: }
1394:
1395: /**
1396: * JAX-RPC wants the namespaces to be sorted in the reverse order
1397: * so that the empty namespace "" comes to the very end. Don't ask me why.
1398: */
1399: private static final Comparator<String> NAMESPACE_COMPARATOR = new Comparator<String>() {
1400: public int compare(String lhs, String rhs) {
1401: return -lhs.compareTo(rhs);
1402: }
1403: };
1404:
1405: private static final String newline = "\n";
1406: }
|