| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.apache.ws.commons.schema.walker; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import javax.xml.namespace.QName; |
| |
| import org.apache.ws.commons.schema.XmlSchema; |
| import org.apache.ws.commons.schema.XmlSchemaAll; |
| import org.apache.ws.commons.schema.XmlSchemaAllMember; |
| import org.apache.ws.commons.schema.XmlSchemaAny; |
| import org.apache.ws.commons.schema.XmlSchemaChoice; |
| import org.apache.ws.commons.schema.XmlSchemaChoiceMember; |
| import org.apache.ws.commons.schema.XmlSchemaCollection; |
| import org.apache.ws.commons.schema.XmlSchemaElement; |
| import org.apache.ws.commons.schema.XmlSchemaGroup; |
| import org.apache.ws.commons.schema.XmlSchemaGroupParticle; |
| import org.apache.ws.commons.schema.XmlSchemaGroupRef; |
| import org.apache.ws.commons.schema.XmlSchemaParticle; |
| import org.apache.ws.commons.schema.XmlSchemaSequence; |
| import org.apache.ws.commons.schema.XmlSchemaSequenceMember; |
| import org.apache.ws.commons.schema.XmlSchemaType; |
| |
| /** |
| * Walks an {@link XmlSchema} from a starting {@link XmlSchemaElement}, |
| * notifying attached visitors as it descends. |
| */ |
| public final class XmlSchemaWalker { |
| |
| private Set<QName> userRecognizedTypes; |
| |
| private final XmlSchemaCollection schemas; |
| private final List<XmlSchemaVisitor> visitors; |
| private final Map<QName, List<XmlSchemaElement>> elemsBySubstGroup; |
| private final SchemasByNamespace schemasByNamespace; |
| private final Map<QName, XmlSchemaScope> scopeCache; |
| private final Set<QName> visitedElements; |
| |
| /** |
| * Initializes the {@link XmlSchemaWalker} with the |
| * {@link XmlSchemaCollection} to reference when following an |
| * {@link XmlSchemaElement}. |
| */ |
| public XmlSchemaWalker(XmlSchemaCollection xmlSchemas) { |
| if (xmlSchemas == null) { |
| throw new IllegalArgumentException("Input XmlSchemaCollection cannot be null."); |
| } |
| |
| schemas = xmlSchemas; |
| visitors = new ArrayList<XmlSchemaVisitor>(1); |
| |
| schemasByNamespace = new SchemasByNamespace(); |
| elemsBySubstGroup = new HashMap<QName, List<XmlSchemaElement>>(); |
| |
| for (XmlSchema schema : schemas.getXmlSchemas()) { |
| schemasByNamespace.addSchema(schema.getTargetNamespace(), schema); |
| |
| for (XmlSchemaElement elem : schema.getElements().values()) { |
| if (elem.getSubstitutionGroup() != null) { |
| List<XmlSchemaElement> elems = elemsBySubstGroup.get(elem.getSubstitutionGroup()); |
| if (elems == null) { |
| elems = new ArrayList<XmlSchemaElement>(); |
| elemsBySubstGroup.put(elem.getSubstitutionGroup(), elems); |
| } |
| elems.add(elem); |
| } |
| } |
| } |
| |
| scopeCache = new HashMap<QName, XmlSchemaScope>(); |
| visitedElements = new java.util.HashSet<QName>(); |
| userRecognizedTypes = null; |
| } |
| |
| /** |
| * Initializes the <code>XmlSchemaWalker</code> with an |
| * {@link XmlSchemaVisitor} to notify as the schema is walked. |
| * <p> |
| * (Other visitors may continue to be added after this one.) |
| * </p> |
| * |
| * @param xmlSchemas The set of schemas to walk. |
| * @param visitor The visitor to visit during the walk. |
| */ |
| public XmlSchemaWalker(XmlSchemaCollection xmlSchemas, XmlSchemaVisitor visitor) { |
| |
| this(xmlSchemas); |
| if (visitor != null) { |
| visitors.add(visitor); |
| } |
| } |
| |
| /** |
| * Adds a new visitor to be notified as the XML Schemas are walked. |
| * |
| * @param visitor The visitor to be notified. |
| * @return This <code>XmlSchemaWalker</code> instance for method chaining. |
| */ |
| public XmlSchemaWalker addVisitor(XmlSchemaVisitor visitor) { |
| visitors.add(visitor); |
| return this; |
| } |
| |
| /** |
| * Removes the visitor to be notified as the XML Schemas are walked. |
| * |
| * @param visitor The visitor to remove. |
| * @return This <code>XmlSchemaWalker</code> instance for method chaining. |
| */ |
| public XmlSchemaWalker removeVisitor(XmlSchemaVisitor visitor) { |
| if (visitor != null) { |
| visitors.remove(visitor); |
| } |
| return this; |
| } |
| |
| /** |
| * Clears the internal state in preparation for another walk through the |
| * schema. |
| */ |
| public void clear() { |
| scopeCache.clear(); |
| visitedElements.clear(); |
| } |
| |
| /** |
| * Defines the set of types the calling code recognizes. If one of the types |
| * are found during the walk through the XML Schema, it is attached to the |
| * relevant {@link XmlSchemaTypeInfo} that is passed to the |
| * {@link XmlSchemaVisitor}s, with lower types in the hierarchy taking |
| * precedence over higher types. |
| * <p> |
| * This information is useful when translating from XML Schema to another |
| * schema, as this automatically associates the destination type with the |
| * source XML Schema type. |
| * </p> |
| * |
| * @param userRecognizedTypes The set of types the user recognizes and would |
| * like recognized when traversed. |
| */ |
| public void setUserRecognizedTypes(Set<QName> userRecognizedTypes) { |
| this.userRecognizedTypes = userRecognizedTypes; |
| } |
| |
| /** |
| * The user-defined types set with the call to |
| * {@link #setUserRecognizedTypes(Set)}, or <code>null</code> if none. |
| */ |
| public Set<QName> getUserRecognizedTypes() { |
| return userRecognizedTypes; |
| } |
| |
| /** |
| * Initiates a walk through the {@link XmlSchemaCollection} starting with |
| * the provided root {@link XmlSchemaElement}. Any visitors will be notified |
| * as the walk progresses. |
| * <p> |
| * Once this method completes, call {@link #clear()} before starting another |
| * walk through the XML Schemas. |
| * </p> |
| * |
| * @param element The root element to start the walk from. |
| */ |
| public void walk(XmlSchemaElement element) { |
| element = getElement(element, false); |
| |
| final XmlSchemaElement substGroupElem = element; |
| |
| /* |
| * If this element is the root of a substitution group, notify the |
| * visitors. |
| */ |
| List<XmlSchemaElement> substitutes = null; |
| if (elemsBySubstGroup.containsKey(getElementQName(element))) { |
| substitutes = elemsBySubstGroup.get(element.getQName()); |
| |
| for (XmlSchemaVisitor visitor : visitors) { |
| visitor.onEnterSubstitutionGroup(substGroupElem); |
| } |
| |
| // Force a copy to change the min & max occurs. |
| element = getElement(element, true); |
| element.setMinOccurs(XmlSchemaParticle.DEFAULT_MIN_OCCURS); |
| element.setMaxOccurs(XmlSchemaParticle.DEFAULT_MAX_OCCURS); |
| } |
| |
| XmlSchemaType schemaType = element.getSchemaType(); |
| if (schemaType == null) { |
| final QName typeQName = element.getSchemaTypeName(); |
| if (typeQName != null) { |
| schemaType = schemasByNamespace.getTypeByName(typeQName); |
| } |
| } |
| |
| if (schemaType != null) { |
| XmlSchemaScope scope = null; |
| if ((schemaType.getQName() != null) && scopeCache.containsKey(schemaType.getQName())) { |
| scope = scopeCache.get(schemaType.getQName()); |
| } else { |
| scope = new XmlSchemaScope(schemaType, schemasByNamespace, scopeCache, userRecognizedTypes); |
| if (schemaType.getQName() != null) { |
| scopeCache.put(schemaType.getQName(), scope); |
| } |
| } |
| |
| // 1. Fetch all attributes as a List<XmlSchemaAttribute>. |
| final Collection<XmlSchemaAttrInfo> attrs = scope.getAttributesInScope(); |
| final XmlSchemaTypeInfo typeInfo = scope.getTypeInfo(); |
| |
| // 2. for each visitor, call visitor.startElement(element, type); |
| final boolean previouslyVisited = (!element.isAnonymous() && visitedElements.contains(element |
| .getQName())); |
| |
| for (XmlSchemaVisitor visitor : visitors) { |
| visitor.onEnterElement(element, typeInfo, previouslyVisited); |
| } |
| |
| if (!element.isAnonymous() && !previouslyVisited) { |
| visitedElements.add(element.getQName()); |
| } |
| |
| // If we already visited this element, skip the attributes and |
| // child. |
| if (!previouslyVisited) { |
| |
| // 3. Walk the attributes in the element, retrieving type |
| // information. |
| if (attrs != null) { |
| for (XmlSchemaAttrInfo attr : attrs) { |
| XmlSchemaType attrType = attr.getAttribute().getSchemaType(); |
| if (attrType != null) { |
| XmlSchemaScope attrScope; |
| if ((attrType.getQName() != null) && scopeCache.containsKey(attrType.getQName())) { |
| attrScope = scopeCache.get(attrType.getQName()); |
| } else { |
| attrScope = new XmlSchemaScope(attrType, |
| schemasByNamespace, scopeCache, |
| userRecognizedTypes); |
| |
| if (attrType.getName() != null) { |
| scopeCache.put(attrType.getQName(), attrScope); |
| } |
| } |
| |
| final XmlSchemaTypeInfo attrTypeInfo = attrScope.getTypeInfo(); |
| attr.setType(attrTypeInfo); |
| } |
| |
| for (XmlSchemaVisitor visitor : visitors) { |
| visitor.onVisitAttribute(element, attr); |
| } |
| } |
| } |
| |
| // 4. Visit the anyAttribute, if any. |
| if (scope.getAnyAttribute() != null) { |
| for (XmlSchemaVisitor visitor : visitors) { |
| visitor.onVisitAnyAttribute(element, scope.getAnyAttribute()); |
| } |
| } |
| |
| /* |
| * 5. Notify that we visited all of the attributes (even if |
| * there weren't any). |
| */ |
| for (XmlSchemaVisitor visitor : visitors) { |
| visitor.onEndAttributes(element, typeInfo); |
| } |
| |
| // 6. Walk the child groups and elements (if any), depth-first. |
| final XmlSchemaParticle child = scope.getParticle(); |
| if (child != null) { |
| walk(child); |
| } |
| } |
| |
| /* |
| * 7. On the way back up, call visitor.endElement(element, type, |
| * attributes); |
| */ |
| for (XmlSchemaVisitor visitor : visitors) { |
| visitor.onExitElement(element, typeInfo, previouslyVisited); |
| } |
| |
| } else if (!element.isAbstract()) { |
| throw new IllegalStateException("Element " + element.getQName() |
| + " is not abstract and has no type."); |
| } |
| |
| // 8. Now handle substitute elements, if any. |
| if (substitutes != null) { |
| for (XmlSchemaElement substitute : substitutes) { |
| walk(substitute); |
| } |
| |
| for (XmlSchemaVisitor visitor : visitors) { |
| visitor.onExitSubstitutionGroup(substGroupElem); |
| } |
| } |
| } |
| |
| private void walk(XmlSchemaParticle particle) { |
| if (particle instanceof XmlSchemaGroupRef) { |
| XmlSchemaGroupRef groupRef = (XmlSchemaGroupRef)particle; |
| XmlSchemaGroupParticle group = groupRef.getParticle(); |
| if (group == null) { |
| XmlSchemaGroup g = schemasByNamespace.getGroupByName(groupRef.getRefName()); |
| if (g != null) { |
| group = g.getParticle(); |
| } |
| } |
| walk(group, groupRef.getMinOccurs(), groupRef.getMaxOccurs()); |
| |
| } else if (particle instanceof XmlSchemaGroupParticle) { |
| walk((XmlSchemaGroupParticle)particle, particle.getMinOccurs(), particle.getMaxOccurs()); |
| |
| } else if (particle instanceof XmlSchemaElement) { |
| walk((XmlSchemaElement)particle); |
| |
| } else if (particle instanceof XmlSchemaAny) { |
| for (XmlSchemaVisitor visitor : visitors) { |
| visitor.onVisitAny((XmlSchemaAny)particle); |
| } |
| |
| } else { |
| throw new IllegalArgumentException("Unknown particle type " + particle.getClass().getName()); |
| } |
| |
| } |
| |
| private void walk(XmlSchemaGroupParticle group, long minOccurs, long maxOccurs) { |
| |
| // Only make a copy of the particle if the minOccurs or maxOccurs was |
| // set. |
| final boolean forceCopy = ((minOccurs != group.getMinOccurs()) || (maxOccurs != group.getMaxOccurs())); |
| |
| // 1. Determine the group particle type. |
| XmlSchemaAll all = null; |
| XmlSchemaChoice choice = null; |
| XmlSchemaSequence seq = null; |
| |
| ArrayList<XmlSchemaParticle> children = null; |
| |
| if (group instanceof XmlSchemaAll) { |
| all = (XmlSchemaAll)group; |
| |
| } else if (group instanceof XmlSchemaChoice) { |
| choice = (XmlSchemaChoice)group; |
| |
| } else if (group instanceof XmlSchemaSequence) { |
| seq = (XmlSchemaSequence)group; |
| |
| } else { |
| throw new IllegalArgumentException("Unrecognized XmlSchemaGroupParticle of type " |
| + group.getClass().getName()); |
| } |
| |
| // 2. Make a copy if necessary. |
| if (forceCopy) { |
| if (all != null) { |
| XmlSchemaAll copy = new XmlSchemaAll(); |
| copy.setAnnotation(all.getAnnotation()); |
| copy.setId(all.getId()); |
| copy.setLineNumber(all.getLineNumber()); |
| copy.setLinePosition(all.getLinePosition()); |
| copy.setMetaInfoMap(all.getMetaInfoMap()); |
| copy.setMinOccurs(minOccurs); |
| copy.setMaxOccurs(maxOccurs); |
| copy.setSourceURI(all.getSourceURI()); |
| copy.setUnhandledAttributes(all.getUnhandledAttributes()); |
| copy.getItems().addAll(all.getItems()); |
| |
| all = copy; |
| |
| } else if (choice != null) { |
| XmlSchemaChoice copy = new XmlSchemaChoice(); |
| copy.setAnnotation(choice.getAnnotation()); |
| copy.setId(choice.getId()); |
| copy.setLineNumber(choice.getLineNumber()); |
| copy.setLinePosition(choice.getLinePosition()); |
| copy.setMinOccurs(minOccurs); |
| copy.setMaxOccurs(maxOccurs); |
| copy.setMetaInfoMap(choice.getMetaInfoMap()); |
| copy.setSourceURI(choice.getSourceURI()); |
| copy.setUnhandledAttributes(choice.getUnhandledAttributes()); |
| copy.getItems().addAll(choice.getItems()); |
| |
| choice = copy; |
| |
| } else if (seq != null) { |
| XmlSchemaSequence copy = new XmlSchemaSequence(); |
| copy.setAnnotation(seq.getAnnotation()); |
| copy.setId(seq.getId()); |
| copy.setLineNumber(seq.getLineNumber()); |
| copy.setLinePosition(seq.getLinePosition()); |
| copy.setMinOccurs(minOccurs); |
| copy.setMaxOccurs(maxOccurs); |
| copy.setMetaInfoMap(seq.getMetaInfoMap()); |
| copy.setSourceURI(seq.getSourceURI()); |
| copy.setUnhandledAttributes(seq.getUnhandledAttributes()); |
| |
| seq = copy; |
| } |
| } |
| |
| // 3. Notify the visitors. |
| for (XmlSchemaVisitor visitor : visitors) { |
| if (all != null) { |
| visitor.onEnterAllGroup(all); |
| } else if (choice != null) { |
| visitor.onEnterChoiceGroup(choice); |
| } else if (seq != null) { |
| visitor.onEnterSequenceGroup(seq); |
| } |
| } |
| |
| // 4. Walk the children. |
| if (all != null) { |
| children = new ArrayList<XmlSchemaParticle>(all.getItems().size()); |
| for (XmlSchemaAllMember item : all.getItems()) { |
| if (item instanceof XmlSchemaGroup) { |
| children.add(((XmlSchemaGroup)item).getParticle()); |
| } else if (item instanceof XmlSchemaParticle) { |
| children.add((XmlSchemaParticle)item); |
| } else { |
| throw new IllegalArgumentException( |
| "All child is not an XmlSchemaGroup or XmlSchemaParticle; " |
| + "it is a " + item.getClass().getName()); |
| } |
| } |
| } else if (choice != null) { |
| children = new ArrayList<XmlSchemaParticle>(choice.getItems().size()); |
| for (XmlSchemaChoiceMember item : choice.getItems()) { |
| if (item instanceof XmlSchemaGroup) { |
| children.add(((XmlSchemaGroup)item).getParticle()); |
| } else if (item instanceof XmlSchemaParticle) { |
| children.add((XmlSchemaParticle)item); |
| } else { |
| throw new IllegalArgumentException( |
| "Choice child is not an XmlSchemaGroup or XmlSchemaParticle; " |
| + "it is a " + item.getClass().getName()); |
| } |
| } |
| |
| } else if (seq != null) { |
| children = new ArrayList<XmlSchemaParticle>(seq.getItems().size()); |
| for (XmlSchemaSequenceMember item : seq.getItems()) { |
| if (item instanceof XmlSchemaGroup) { |
| children.add(((XmlSchemaGroup)item).getParticle()); |
| } else if (item instanceof XmlSchemaParticle) { |
| children.add((XmlSchemaParticle)item); |
| } else { |
| throw new IllegalArgumentException( |
| "Sequence child is not an XmlSchemaGroup or XmlSchemaParticle; " |
| + "it is a " + item.getClass().getName()); |
| } |
| } |
| } |
| |
| if (children == null) { |
| throw new IllegalStateException("Could not process group of type " + group.getClass().getName()); |
| } |
| |
| for (XmlSchemaParticle child : children) { |
| walk(child); |
| } |
| |
| // 5. Notify the visitors we are exiting the group. |
| for (XmlSchemaVisitor visitor : visitors) { |
| if (all != null) { |
| visitor.onExitAllGroup(all); |
| } else if (choice != null) { |
| visitor.onExitChoiceGroup(choice); |
| } else if (seq != null) { |
| visitor.onExitSequenceGroup(seq); |
| } |
| } |
| } |
| |
| /** |
| * If the provided {@link XmlSchemaElement} is a reference, track down the |
| * original and add the minimum and maximum occurrence fields. Otherwise, |
| * just return the provided <code>element</code>. |
| * |
| * @param element The element to get the definition of. |
| * @return The real {@link XmlSchemaElement}. |
| */ |
| private XmlSchemaElement getElement(XmlSchemaElement element, boolean isSubstitutionGroup) { |
| |
| if (!element.isRef() && !isSubstitutionGroup) { |
| return element; |
| } |
| |
| final QName elemQName = getElementQName(element); |
| |
| XmlSchemaElement globalElem = null; |
| if (!element.isRef()) { |
| globalElem = element; |
| } else if (element.getRef().getTarget() != null) { |
| globalElem = element.getRef().getTarget(); |
| } else { |
| globalElem = schemasByNamespace.getElementByName(elemQName); |
| } |
| |
| /* |
| * An XML Schema element reference defines the id, minOccurs, and |
| * maxOccurs attributes, while the global element definition defines id |
| * and all other attributes. This combines the two together. |
| */ |
| String id = element.getId(); |
| if (id == null) { |
| id = globalElem.getId(); |
| } |
| |
| XmlSchema schema = schemasByNamespace.getSchemaDefiningElement(elemQName); |
| if (schema == null) { |
| // TODO: is this correct? The previous code used whichever schema was stored in the schemasByNamespace HashMap |
| // for the element's namespace. |
| schema = element.getParent(); |
| } |
| |
| final XmlSchemaElement copy = new XmlSchemaElement(schema, false); |
| copy.setName(globalElem.getName()); |
| copy.setAbstract(globalElem.isAbstract()); |
| copy.setAnnotation(globalElem.getAnnotation()); |
| copy.setBlock(globalElem.getBlock()); |
| copy.setDefaultValue(globalElem.getDefaultValue()); |
| copy.setFinal(globalElem.getFinal()); |
| copy.setFixedValue(globalElem.getFixedValue()); |
| copy.setForm(globalElem.getForm()); |
| copy.setId(id); |
| copy.setLineNumber(element.getLineNumber()); |
| copy.setLinePosition(element.getLinePosition()); |
| copy.setMaxOccurs(element.getMaxOccurs()); |
| copy.setMinOccurs(element.getMinOccurs()); |
| copy.setMetaInfoMap(globalElem.getMetaInfoMap()); |
| copy.setNillable(globalElem.isNillable()); |
| copy.setType(globalElem.getSchemaType()); |
| copy.setSchemaTypeName(globalElem.getSchemaTypeName()); |
| copy.setSourceURI(globalElem.getSourceURI()); |
| copy.setSubstitutionGroup(globalElem.getSubstitutionGroup()); |
| copy.setUnhandledAttributes(globalElem.getUnhandledAttributes()); |
| |
| return copy; |
| } |
| |
| private static QName getElementQName(XmlSchemaElement element) { |
| if (element.isRef()) { |
| return element.getRefBase().getTargetQName(); |
| } else { |
| return element.getQName(); |
| } |
| } |
| } |