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