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