| /** |
| * XBeans implementation. |
| * Author: David Bau |
| * Date: Oct 1, 2003 |
| */ |
| package org.apache.xmlbeans.impl.binding.bts; |
| |
| import org.apache.xmlbeans.impl.common.XMLChar; |
| import org.apache.xmlbeans.SchemaType; |
| import org.apache.xmlbeans.SchemaTypeLoader; |
| import org.apache.xmlbeans.XmlBeans; |
| import org.apache.xmlbeans.soap.SOAPArrayType; |
| |
| import javax.xml.namespace.QName; |
| |
| /** |
| * An XmlName is a way of uniquely identifying any |
| * logical component in a schema. |
| * |
| * For a diagram of the kinds of components that can be referenced, |
| * see http://www.w3.org/TR/xmlschema-1/components.gif |
| * |
| * A signature looks like this: |
| * |
| * a-name1|t|e=name2|p.3|s|p.5|c|p.0|t|e=name3@my-namespace |
| * |
| * This reads as:g |
| * The attribute declaration called "name1" (unqualified) inside |
| * the anonymous type of |
| * the element declaration called "name2" (qualified) which is |
| * the third particle of |
| * the sequence which is |
| * the fifth particle of |
| * the choice which is |
| * the zeroth particle of |
| * the anonymous type of |
| * the element named name3 (qualified) in |
| * the namespace my-namespace. |
| * |
| * Hyphens (-) introduce unqualified names |
| * Equals (=) introduce qualified names |
| * Dot (.) introduce numbers |
| * Anonymous components are just single-letters. |
| * |
| * There are these types of components |
| * |
| * n = Notations (named, global only) |
| * e = Element decls (named, global or inside t or p) |
| * Identity constraint defs |
| * k = key/unique/keyref (named, in elts only) |
| * g = Model group defs (named, global only) |
| * Model group compositors: |
| * l = All compositors (anon, inside p or g) |
| * s = Sequence compositors (anon, inside p or g) |
| * c = Choice compositors (anon, inside p or g) |
| * p = Particles (numbered, inside t or l or s or c) |
| * w = Wildcards (anon, inside p only) |
| * v = Attribute Uses (named, in type only, inside t) |
| * a = Attribute Declarations (named, global or inside t or v) |
| * r = Attribute group definitions (named, global only) |
| * t = Type definitions (name when global, or anon inside i, m, e, a, t) |
| * |
| * Not part of the spec, but we treat as components: |
| * d = document type definition (named, global only) |
| * b = attribute-container type definition (named, global only) |
| * m = union member definition (numbered, inside t only) |
| * y = soap array of dimension n (numbered, referencing global t or another y only) |
| * |
| * A canonical signature will shortcut the following: |
| * |
| * - global elements do not need to be explicitly nested inside their |
| * containing document types (e=name|d=name@namespace -> e=name@namespace) |
| * - global attributes do not need to be explicitly nested inside their |
| * containing attribute types (e=name|d=name@namespace -> e=name@namespace) |
| * - an element of a type, if its name is unique, may be referenced without the |
| * intervening particle path. (e.g., e=name2|p.3|s|p.5|c|p.0|t can be |
| * reduced to e=name2|t) |
| * - an attribute of a type may be referenced without explicitly putting it |
| * inside an attribute use (e.g., a-name1|v-name1|t -> a-name1|t) |
| * |
| * Notice SOAP arrays are included in the naming schema, as follows: |
| * |
| * A soap array type written like this: |
| * x:drg[,,,][,][,,] |
| * |
| * Has the following signature: |
| * y.3|y.2|y.4|t=drg@foobar |
| */ |
| public class XmlName |
| { |
| private String namespace; |
| private String path; |
| |
| public static final char NOTATION = 'n'; |
| public static final char ELEMENT = 'e'; |
| public static final char ID_CONSTRAINT = 'k'; |
| public static final char MODEL_GROUP = 'g'; |
| public static final char ALL = 'l'; |
| public static final char SEQUENCE = 's'; |
| public static final char CHOICE = 'c'; |
| public static final char PARTICLE = 'p'; |
| public static final char WILDCARD = 'w'; |
| public static final char ATTRIBUTE_USE = 'v'; |
| public static final char ATTRIBUTE = 'a'; |
| public static final char ATTRIBUTE_GROUP = 'r'; |
| public static final char TYPE = 't'; |
| public static final char DOCUMENT_TYPE = 'd'; |
| public static final char ATTRIBUTE_TYPE = 'b'; |
| public static final char MEMBER = 'm'; |
| public static final char SOAP_ARRAY = 'y'; |
| public static final char NO_TYPE = 'z'; |
| |
| /** |
| * This function is used to see if a path is valid or not. |
| */ |
| public boolean valid() |
| { |
| XmlName outerComponent = null; |
| int outerType = 0; |
| String localName = internalGetStringName(); |
| |
| boolean hasNumber = internalGetNumber() >= 0; |
| boolean hasName = (localName != null); |
| boolean isAnonymous = internalIsAnonymous(); |
| boolean isQualified = internalIsQualified(); |
| boolean isGlobal = isGlobal(); |
| |
| if (!isGlobal) |
| { |
| outerComponent = getOuterComponent(); |
| outerType = outerComponent.getComponentType(); |
| } |
| |
| boolean result; |
| |
| if (localName != null && !XMLChar.isValidNCName(localName)) |
| return false; |
| |
| switch (getComponentType()) |
| { |
| case NOTATION: |
| result = (isGlobal && hasName && isQualified); |
| break; |
| |
| case ELEMENT: |
| result = (hasName && (isGlobal && isQualified || outerType == TYPE || outerType == PARTICLE)); |
| break; |
| |
| case ID_CONSTRAINT: |
| result = (hasName && outerType == ELEMENT); |
| break; |
| |
| case MODEL_GROUP: |
| result = (hasName && isGlobal); |
| break; |
| |
| case ALL: |
| case SEQUENCE: |
| case CHOICE: |
| result = (isAnonymous && (outerType == PARTICLE || outerType == MODEL_GROUP)); |
| break; |
| |
| case PARTICLE: |
| result = (hasNumber && (outerType == SEQUENCE || outerType == CHOICE || outerType == ALL || outerType == TYPE)); |
| break; |
| |
| case WILDCARD: |
| result = (isAnonymous && (outerType == PARTICLE || outerType == TYPE || outerType == ATTRIBUTE_GROUP)); |
| break; |
| |
| case ATTRIBUTE_USE: |
| result = (hasName && (outerType == TYPE || outerType == ATTRIBUTE_GROUP)); |
| break; |
| |
| case ATTRIBUTE: |
| result = (hasName && (isGlobal && isQualified || outerType == TYPE || outerType == ATTRIBUTE_USE)); |
| break; |
| |
| case ATTRIBUTE_GROUP: |
| result = (hasName && isQualified && isGlobal); |
| break; |
| |
| case TYPE: |
| result = ((hasName && isQualified && isGlobal) || (isAnonymous && outerType == TYPE || outerType == ELEMENT || outerType == ATTRIBUTE || outerType == MEMBER)); |
| break; |
| |
| case DOCUMENT_TYPE: |
| result = (hasName && isQualified && isGlobal); |
| break; |
| |
| case ATTRIBUTE_TYPE: |
| result = (hasName && isQualified && isGlobal); |
| break; |
| |
| case MEMBER: |
| result = (isAnonymous && outerType == TYPE); |
| break; |
| |
| case SOAP_ARRAY: |
| result = (hasNumber && (outerType == SOAP_ARRAY || outerType == TYPE && outerComponent.isGlobal())); |
| break; |
| |
| case NO_TYPE: |
| result = (isAnonymous && isGlobal && namespace.length() == 0); |
| break; |
| |
| default: |
| result = false; |
| } |
| if (!result) |
| return false; |
| |
| if (isGlobal) |
| return true; |
| |
| return outerComponent.valid(); |
| } |
| |
| /** |
| * Creates an XMLName based on the given String signature. |
| * |
| * This signature is described in the javadoc for this class. |
| */ |
| public static XmlName forString(String signature) |
| { |
| String path; |
| String namespace; |
| int atSign = signature.indexOf('@'); |
| if (atSign < 0) |
| { |
| namespace = ""; |
| path = signature; |
| } |
| else |
| { |
| namespace = signature.substring(atSign + 1); |
| path = signature.substring(0, atSign); |
| } |
| return forPathAndNamespace(path, namespace); |
| } |
| |
| /** |
| * Creates an XMLName for a schema type with the given fully-qualified QName. |
| */ |
| public static XmlName forTypeNamed(QName name) |
| { |
| return forPathAndNamespace(TYPE + "=" + name.getLocalPart(), name.getNamespaceURI()); |
| } |
| |
| /** |
| * Creates an XMLName for a global schema element with the given fully-qualified QName. |
| */ |
| public static XmlName forGlobalName(char kind, QName name) |
| { |
| return forPathAndNamespace(kind + "=" + name.getLocalPart(), name.getNamespaceURI()); |
| } |
| |
| /** |
| * Creates an XMLName for a nested component |
| */ |
| public static XmlName forNestedName(char kind, String localName, boolean qualified, XmlName outer) |
| { |
| return forPathAndNamespace(kind + (qualified ? "=" : "-") + localName + "|" + outer.path, outer.namespace); |
| } |
| |
| /** |
| * Creates an XMLName for a nested component |
| */ |
| public static XmlName forNestedNumber(char kind, int n, XmlName outer) |
| { |
| return forPathAndNamespace(kind + "." + n + "|" + outer.path, outer.namespace); |
| } |
| |
| /** |
| * Creates an XMLName for a nested component |
| */ |
| public static XmlName forNestedAnonymous(char kind, XmlName outer) |
| { |
| return forPathAndNamespace(kind + "|" + outer.path, outer.namespace); |
| } |
| |
| /** |
| * Creates an XMLName for a particular schema type |
| */ |
| public static XmlName forSchemaType(SchemaType sType) |
| { |
| if (sType.getName() != null) |
| return forTypeNamed(sType.getName()); |
| |
| if (sType.isDocumentType()) |
| return forGlobalName(DOCUMENT_TYPE, sType.getDocumentElementName()); |
| |
| if (sType.isAttributeType()) |
| return forGlobalName(ATTRIBUTE_TYPE, sType.getAttributeTypeAttributeName()); |
| |
| if (sType.isNoType() || sType.getOuterType() == null) // latter is an error |
| return forPathAndNamespace("" + NO_TYPE, ""); |
| |
| SchemaType outerType = sType.getOuterType(); |
| XmlName outerName = forSchemaType(outerType); |
| |
| if (sType.getContainerField() != null) |
| { |
| boolean qualified = sType.getContainerField().getName().getNamespaceURI().length() > 0; |
| String localName = sType.getContainerField().getName().getLocalPart(); |
| char kind = (sType.getContainerField().isAttribute() ? ATTRIBUTE : ELEMENT); |
| return forNestedAnonymous(TYPE, forNestedName(kind, localName, qualified, outerName)); |
| } |
| |
| if (outerType.getSimpleVariety() == SchemaType.UNION) |
| return forNestedAnonymous(TYPE, forNestedNumber(MEMBER, sType.getAnonymousUnionMemberOrdinal(), outerName)); |
| |
| return forNestedAnonymous(TYPE, outerName); |
| } |
| |
| /** |
| * Creates one for a SOAPArrayType |
| */ |
| public static XmlName forSoapArrayType(SOAPArrayType sType) |
| { |
| StringBuffer sb = new StringBuffer(); |
| sb.append(SOAP_ARRAY + "." + sType.getDimensions().length); |
| int[] ranks = sType.getRanks(); |
| for (int i = ranks.length - 1; i >= 0; i-= 1) |
| { |
| sb.append("|" + SOAP_ARRAY + "." + ranks[i]); |
| } |
| QName name = sType.getQName(); |
| sb.append("|" + TYPE + "=" + name.getLocalPart()); |
| return forPathAndNamespace(sb.toString(), name.getNamespaceURI()); |
| } |
| |
| /** |
| * True if it is a schema type |
| */ |
| public boolean isSchemaType() |
| { |
| switch (getComponentType()) |
| { |
| case TYPE: |
| case DOCUMENT_TYPE: |
| case ATTRIBUTE_TYPE: |
| case NO_TYPE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Finds a type with the given name. |
| */ |
| public SchemaType findTypeIn(SchemaTypeLoader loader) |
| { |
| switch (getComponentType()) |
| { |
| case NO_TYPE: |
| return XmlBeans.NO_TYPE; |
| case DOCUMENT_TYPE: |
| return loader.findDocumentType(getQName()); |
| case ATTRIBUTE_TYPE: |
| return loader.findAttributeType(getQName()); |
| default: |
| return null; |
| case TYPE: |
| break; |
| } |
| |
| if (isGlobal()) |
| return loader.findType(getQName()); |
| |
| XmlName outerName = getOuterComponent(); |
| |
| // if the component is contained within a type, get it |
| for (XmlName outerTypeName = outerName; ; outerTypeName = outerTypeName.getOuterComponent()) |
| { |
| if (outerTypeName.isSchemaType()) |
| { |
| SchemaType outerType = outerTypeName.findTypeIn(loader); |
| switch (outerName.getComponentType()) |
| { |
| default: |
| throw new IllegalStateException("Illegal type name " + this); |
| |
| case TYPE: |
| return outerType.getAnonymousTypes()[0]; |
| |
| case ELEMENT: |
| return outerType.getElementType(outerName.getQName(), null, loader); |
| |
| case ATTRIBUTE: |
| return outerType.getAttributeType(outerName.getQName(), loader); |
| |
| case MEMBER: |
| return outerType.getAnonymousTypes()[outerName.getNumber()]; |
| } |
| } |
| if (outerTypeName.isGlobal()) |
| { |
| switch (outerName.getComponentType()) |
| { |
| default: |
| throw new IllegalStateException("Illegal type name " + this); |
| |
| case ELEMENT: |
| return loader.findDocumentType(outerTypeName.getQName()).getElementType(outerTypeName.getQName(), null, loader); |
| |
| case ATTRIBUTE: |
| return loader.findAttributeType(outerTypeName.getQName()).getAttributeType(outerTypeName.getQName(), loader); |
| } |
| } |
| } |
| } |
| |
| private static XmlName forPathAndNamespace(String path, String namespace) |
| { |
| return new XmlName(path, namespace); |
| } |
| |
| private XmlName(String path, String namespace) |
| { |
| if (path == null || namespace == null) |
| throw new IllegalArgumentException(); |
| |
| this.path = path; |
| this.namespace = namespace; |
| } |
| |
| public boolean isGlobal() |
| { |
| int index = path.indexOf('|'); |
| return index < 0; |
| } |
| |
| public XmlName getOuterComponent() |
| { |
| int index = path.indexOf('|'); |
| if (index < 0) |
| return null; |
| return forPathAndNamespace(path.substring(index + 1), namespace); |
| } |
| |
| public int getComponentType() |
| { |
| if (path.length() > 0) |
| return path.charAt(0); |
| return 0; // unknown type |
| } |
| |
| /** |
| * Returns negative if there is no number |
| */ |
| private int internalGetNumber() |
| { |
| if (path.length() <= 1 || path.charAt(1) != '.') |
| return -1; |
| |
| int index = path.indexOf('|'); |
| if (index < 0) |
| index = path.length(); |
| |
| try |
| { |
| return Integer.parseInt(path.substring(2, index)); |
| } |
| catch (Exception e) |
| { |
| return -1; |
| } |
| } |
| |
| /** |
| * Returns the number locating this component within its parent, |
| * or throws an exception if there is none. Only union members (m) |
| * and particles (p) have numbers. |
| */ |
| public int getNumber() |
| { |
| int result = internalGetNumber(); |
| if (result < 0) |
| throw new IllegalStateException("Path has no number"); |
| return result; |
| } |
| |
| private String internalGetStringName() |
| { |
| if (path.length() <= 1 || path.charAt(1) != '=' && path.charAt(1) != '-') |
| return null; |
| |
| int index = path.indexOf('|'); |
| if (index < 0) |
| index = path.length(); |
| |
| return path.substring(2, index); |
| } |
| |
| private boolean internalIsQualified() |
| { |
| return (path.length() > 1 && path.charAt(1) == '='); |
| } |
| |
| private boolean internalIsAnonymous() |
| { |
| return (path.length() <= 1 || path.charAt(1) == '|'); |
| } |
| |
| /** |
| * Returns the name locating this component within its parent, or |
| * returns null if there is none. Notice that if looking up elements |
| * by name in a type, you get the first element defintion when there |
| * are multiple ones. A full particle path can disambiguate. |
| * |
| * This name will be qualified in a namespace if appropriate, but, |
| * for example, local unqualified attributes will have a QName that |
| * is unqualified. |
| * |
| * Element, attributes, identity constraints, model/attribute groups, |
| * and notations have names. |
| */ |
| public QName getQName() |
| { |
| String localName = internalGetStringName(); |
| if (localName == null) |
| return null; |
| |
| if (internalIsQualified()) |
| return new QName(namespace, localName); |
| else |
| return new QName(localName); |
| } |
| |
| /** |
| * Returns the signature string. |
| */ |
| public String toString() |
| { |
| if (namespace.length() == 0) |
| return path; |
| |
| return path + '@' + namespace; |
| } |
| |
| public boolean equals(Object o) |
| { |
| if (this == o) return true; |
| if (!(o instanceof XmlName)) return false; |
| |
| final XmlName xmlName = (XmlName) o; |
| |
| if (!namespace.equals(xmlName.namespace)) return false; |
| if (!path.equals(xmlName.path)) return false; |
| |
| return true; |
| } |
| |
| public int hashCode() |
| { |
| int result; |
| result = namespace.hashCode(); |
| result = 29 * result + path.hashCode(); |
| return result; |
| } |
| } |