/* * Copyright (c) 2010 JBoss, Inc. and others * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.bpel.model.util; import java.util.ArrayList; import java.util.HashSet; import java.util.Hashtable; import java.util.LinkedList; import java.util.List; import java.util.Stack; import org.eclipse.emf.common.util.EList; import org.eclipse.xsd.XSDAttributeDeclaration; import org.eclipse.xsd.XSDAttributeGroupContent; import org.eclipse.xsd.XSDAttributeGroupDefinition; import org.eclipse.xsd.XSDAttributeUse; import org.eclipse.xsd.XSDComplexTypeDefinition; import org.eclipse.xsd.XSDConcreteComponent; import org.eclipse.xsd.XSDDiagnostic; import org.eclipse.xsd.XSDDiagnosticSeverity; import org.eclipse.xsd.XSDElementDeclaration; import org.eclipse.xsd.XSDFactory; import org.eclipse.xsd.XSDModelGroup; import org.eclipse.xsd.XSDModelGroupDefinition; import org.eclipse.xsd.XSDNamedComponent; import org.eclipse.xsd.XSDParticle; import org.eclipse.xsd.XSDParticleContent; import org.eclipse.xsd.XSDSimpleTypeDefinition; import org.eclipse.xsd.XSDTerm; import org.eclipse.xsd.XSDTypeDefinition; import org.eclipse.xsd.XSDWildcard; import org.eclipse.xsd.util.XSDConstants; /** * Compares two XSD fragments for structure and data type compatibility. * Optional components in either the left or right schema may be skipped * over during the comparison unless a strict match is requested (see * <code>setStrict()<code>). * * Components which have a "minOccurs" of 0 are considered optional. * Wildcard components (<xsd:any> elements) will match any component * in the other schema. * * After the comparison, diagnostics are available which indicate the * components in the left and right schema that do not match. Warning * diagnostics are generated whenever optional components were skipped * to force a match. * * @see https://jira.jboss.org/browse/JBIDE-7351 * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=330813 * @author Bob Brodt * @date Oct 29, 2010 */ public class XSDComparer { protected boolean strict = false; protected boolean debug = false; protected List<XSDDiagnostic> diagnostics; protected Hashtable<XSDTerm,XSDTerm> terms = new Hashtable<XSDTerm,XSDTerm>(); public void setStrict(boolean flag) { strict = flag; } public void setDebug(boolean flag) { debug = flag; } protected boolean alreadyCompared(XSDTerm term1, XSDTerm term2) { XSDTerm t = terms.get(term1); if (t==term2) return true; t = terms.get(term2); if (t==term1) return true; return false; } /** * The main comparison function that compares two XSD type definitions. * These may be the entire schema or just fragments within a schema. * * @param type1 - the "left" type definition * @param type2 - the "right" type definition * @return true if the types are compatible, false if not. * Warning diagnostics may still be generated for a successful match. */ public boolean compare(XSDTypeDefinition type1, XSDTypeDefinition type2) { boolean result = false; if (type1==null || type2==null) throw new IllegalArgumentException("XSDComparer: XSD types may not be null"); try { // always clear out diagnostics before we start if (diagnostics!=null) diagnostics.clear(); // for debugging only: run through the exercises even if the // two objects are one and the same if (!debug && type1==type2) return true; result = compare(getChildTerms(type1), getChildTerms(type2), 0); } catch (Exception e) { return false; } return result; } /** * Compares a list of the immediate children of a component; called recursively * for children that are complex elements. * * @param list1 - children of the "left" schema * @param list2 - children of the "right" schema * @param level - recursion level (for debug printing) * @return true if the left and right children lists are compatible. */ protected boolean compare(List<XSDTerm> list1, List<XSDTerm> list2, int level) { XSDTerm term1 = null; XSDTerm term2 = null; try { int index1 = 0; int index2 = 0; int size1 = list1.size(); int size2 = list2.size(); while (index1 < size1 && index2 < size2) { term1 = list1.get(index1); term2 = list2.get(index2); if (alreadyCompared(term1,term2)) { dump("Skipping ",term1,null,level); } else { terms.put(term1,term2); if ( term1 instanceof XSDElementDeclaration && term2 instanceof XSDElementDeclaration) { XSDElementDeclaration elem1 = (XSDElementDeclaration)term1; XSDElementDeclaration elem2 = (XSDElementDeclaration)term2; XSDTypeDefinition type1 = elem1.getTypeDefinition(); XSDTypeDefinition type2 = elem2.getTypeDefinition(); if (type1 instanceof XSDSimpleTypeDefinition && type2 instanceof XSDSimpleTypeDefinition) { dump("Comparing ", elem1, type1, level); dump(" to ", elem2, type2, level); if (!compareQNames(elem1, elem2)) { addError("different QNames: " + elem1.getQName() + " vs " + elem2.getQName(), term1,term2); return false; } String s1 = getTypeNameHierarchy(elem1); String s2 = getTypeNameHierarchy(elem2); if (!s1.equals(s2)) { addError("different data types: "+s1+" vs "+s2,elem1,elem2); return false; } } else if (type1 instanceof XSDComplexTypeDefinition && type2 instanceof XSDComplexTypeDefinition) { dump("Comparing ", elem1, type1, level); dump(" to ", elem2, type2, level); if (!compareQNames(elem1, elem2)) { addError("different QNames: " + elem1.getQName() + " vs " + elem2.getQName(), term1,term2); return false; } int min1 = getMinOccurs(term1); int min2 = getMinOccurs(term2); int max1 = getMaxOccurs(term1); int max2 = getMaxOccurs(term2); if (strict) { if (min1 != min2 || max1 != max2) { addError("different cardinality: " + min1 + " to " + max1 + " vs " + min2 + " to " + max2, term1,term2); return false; } } else { if ((max2>0 && min1 > max2) || (max1>0 && min2 > max1)) { addError("incompatible cardinality: " + min1 + " to " + max1 + " vs " + min2 + " to " + max2, term1,term2); return false; } } boolean result = compare(getChildTerms(elem1), getChildTerms(elem2), level + 1); if (!result) { addError("different complex element structures",elem1,elem2); return false; } } else { if (!strict) { // try shifting optional elements and continue from there dump("Elements out of sync - skipping optional elements"); if (isOptional(elem1)) { // skip over this one and compare remaining elements List<XSDTerm> newList1 = new ArrayList<XSDTerm>(); List<XSDTerm> newList2 = new ArrayList<XSDTerm>(); for (int i = index1 + 1; i < size1; ++i) newList1.add(list1.get(i)); for (int i = index2; i < size2; ++i) newList2.add(list2.get(i)); if (compare(newList1, newList2, level)) { addWarning("skipped optional element(s) in left schema",elem1,elem2); return true; } addError("different complex element structures",elem1,elem2); } if (isOptional(elem2)) { // skip over this one and compare remaining elements List<XSDTerm> newList1 = new ArrayList<XSDTerm>(); List<XSDTerm> newList2 = new ArrayList<XSDTerm>(); for (int i = index1; i < size1; ++i) newList1.add(list1.get(i)); for (int i = index2 + 1; i < size2; ++i) newList2.add(list2.get(i)); if (compare(newList1, newList2, level)) { addWarning("skipped optional element(s) in right schema",elem1,elem2); return true; } addError("different complex element structures",elem1,elem2); } } addError("different complex element structures",elem1,elem2); return false; } List<XSDAttributeDeclaration> attrs1 = getAttributeDeclarations(elem1); List<XSDAttributeDeclaration> attrs2 = getAttributeDeclarations(elem2); if (attrs1.size() != attrs2.size()) { addError("different number of attributes",elem1,elem2); return false; } for (int i = 0; i < attrs1.size(); ++i) { XSDAttributeDeclaration attr1 = attrs1.get(i); XSDAttributeDeclaration attr2 = attrs2.get(i); if (!attr1.getQName().equals(attr2.getQName())) { addError("different attribute names: "+attr1.getQName()+" vs "+attr2.getQName(),elem1,elem2); return false; } String value1 = attr1.getLexicalValue(); String value2 = attr2.getLexicalValue(); if (value1==null) value1 = ""; if (value2==null) value2 = ""; if (!value1.equals(value2)) { addError("different attribute values: "+attr1.getLexicalValue()+" vs "+attr2.getLexicalValue(),elem1,elem2); return false; } } } else if (term1 instanceof XSDWildcard && !(term2 instanceof XSDWildcard) ) { // left schema term is a wildcard, right schema is not int min = getMinOccurs(term1); int max = getMinOccurs(term1); if (max==-1) max = size1; for (int n=min; n<=max; ++n) { // skip over this one and compare remaining elements List<XSDTerm> newList1 = new ArrayList<XSDTerm>(); List<XSDTerm> newList2 = new ArrayList<XSDTerm>(); for (int i = index1 + 1 + n; i < size1; ++i) newList1.add(list1.get(i)); for (int i = index2; i < size2; ++i) newList2.add(list2.get(i)); if (compare(newList1, newList2, level)) { addWarning("skipped optional element(s) in left schema",term1,term2); return true; } } addError("different complex element structures",term1,term2); return false; } else if (term1 instanceof XSDWildcard && !(term2 instanceof XSDWildcard) ) { // right schema is a wildcard, left schema is not int min = getMinOccurs(term2); int max = getMinOccurs(term2); if (max==-1) max = size2; for (int n=min; n<=max; ++n) { // skip over this one and compare remaining elements List<XSDTerm> newList1 = new ArrayList<XSDTerm>(); List<XSDTerm> newList2 = new ArrayList<XSDTerm>(); for (int i = index1; i < size1; ++i) newList1.add(list1.get(i)); for (int i = index2 + 1 + n; i < size2; ++i) newList2.add(list2.get(i)); if (compare(newList1, newList2, level)) { addWarning("skipped optional element(s) in right schema",term1,term2); return true; } } addError("different complex element structures",term1,term2); return false; } else if (term1 instanceof XSDWildcard && term2 instanceof XSDWildcard) { // both wildcards - it's a match! int min1 = getMinOccurs(term1); int max1 = getMinOccurs(term1); int min2 = getMinOccurs(term2); int max2 = getMinOccurs(term2); if (min1!=min2 || max1!=max2) { // not possible addError("different <xsd:any> cardinality: "+min1+" to "+max1+" vs "+min2+" to "+max2,term1,term2); } } } ++index1; ++index2; } // any leftovers? they must be optional if (strict && size1 != size2) { addError("different number of elements: "+size1+" vs "+size2,term1,term2); return false; } if (index1 < size1) { dump("Additional elements in left schema"); while (index1 < size1) { term1 = list1.get(index1); if (isOptional(term1)) { dump("Ignoring optional element", term1, null, 1); addWarning("skipped optional element in left schema",term1,term2); term1 = list2.get(++index1); } else { addError("additional elements in left schema",term1,term2); return false; } } } if (index2 < size2) { dump("Additional elements in right schema"); while (index2 < size2) { term2 = list2.get(index2); if (isOptional(term2)) { dump("Ignoring optional element", term2, null, 1); addWarning("skipped optional element in right schema",term1,term2); term2 = list2.get(++index2); } else { addError("additional elements in right schema",term1,term2); return false; } } } } catch (Exception e) { addError("caught exception: "+e.toString(),term1,term2); e.printStackTrace(); return false; } if (level==0) dump("*** Schemas are compatible"); return true; } /** * Returns the data type name of the given element, including base types. * * @param elem - the element * @return data type name hierarchy as a ":" separated string */ protected String getTypeNameHierarchy(XSDElementDeclaration elem) { String s = ""; Stack<String> names = new Stack<String>(); XSDTypeDefinition type = elem.getType(); while (type != type.getBaseType()) { String name = type.getName(); if (name != null) names.push(name); type = type.getBaseType(); } while (!names.isEmpty()) { if (s.isEmpty()) s = names.pop(); else s = s + ":" + names.pop(); } return s; } private boolean compareQNames(XSDNamedComponent elem1, XSDNamedComponent elem2) { String name1 = elem1.getName(); String name2 = elem2.getName(); if (name1==null || name2==null) { if (name1==name2) return true; return false; } if (name1.equals(name2)) { if (elem1.getTargetNamespace() == null || elem1.getTargetNamespace().isEmpty() && elem2.getTargetNamespace() == null || elem2.getTargetNamespace().isEmpty()) return true; if (elem1.getTargetNamespace().equals(elem2.getTargetNamespace())) return true; if ( elem1.getSchema().getTargetNamespace().equals(elem2.getSchema().getTargetNamespace()) ) return true; } return false; } /** * Returns a list of immediate children for the specified element. * * @param element - the parent element * @return A list of XSDTerm objects */ protected final List<XSDTerm> getChildTerms(XSDElementDeclaration element) { return getChildTerms(element.getType()); } /** * Returns a list of immediate children for the specified XSD type. * * @param type - the parent type * @return A list of XSDTerm objects */ protected final List<XSDTerm> getChildTerms(XSDTypeDefinition type) { final ArrayList<XSDTerm> terms = new ArrayList<XSDTerm>(); TypeWalker.Visitor visitor = new TypeWalker.Visitor() { public boolean visit(XSDTypeDefinition type) { // simple types don't have children if (type instanceof XSDSimpleTypeDefinition) return true; XSDComplexTypeDefinition cType = (XSDComplexTypeDefinition) type; visitTerms(cType, new TermVisitor() { public void visit(XSDParticle particle) { // element declaration, or wildcard: add to list XSDParticleContent content = particle.getContent(); if (content instanceof XSDElementDeclaration) { XSDElementDeclaration decl = (XSDElementDeclaration) particle.getContent(); if (decl.isElementDeclarationReference()) decl = decl.getResolvedElementDeclaration(); // System.out.println("Adding "+decl.getName()); terms.add(decl); } else if (content instanceof XSDWildcard) { // System.out.println("Adding <xsd:any>"); terms.add((XSDWildcard) content); } } }); return true; } }; TypeWalker walker = new TypeWalker(type); walker.walk(visitor); return terms; } /** * Returns the value of the "minOccurs" attribute for the given XSD element. * * @param term - the XSD element * @return numerical value of "minOccurs" or 1 if the element does not specify */ public int getMinOccurs(XSDTerm term) { String smin = term.getElement().getAttribute("minOccurs"); int min = 1; if (smin!=null && !smin.isEmpty()) { try { min = Integer.parseInt(smin); } catch (NumberFormatException e) { } } return min; } /** * Returns the value of the "maxOccurs" attribute for the given XSD element. * * @param term - the XSD element * @return numerical value of "maxOccurs" or 1 if the element does not specify */ public int getMaxOccurs(XSDTerm term) { String smax = term.getElement().getAttribute("maxOccurs"); int max = 1; if (smax!=null && !smax.isEmpty()) { try { max = Integer.parseInt(smax); } catch (NumberFormatException e) { if (smax.equalsIgnoreCase("unbounded")) max = -1; } } return max; } /** * Returns true if the specified XSD element is optional. * * @param term * @return true if the element has "minOccurs=0" */ protected boolean isOptional(XSDTerm term) { if (getMinOccurs(term)==0) return true; return false; } /** * Examine the content of a complex type and visit each of its particles. * This will expand <xsd:sequence>, <xsd:choice>, <xsd:all>, <xsd:group> * and <xsd:any> elements. * * @param cType - the complex type to examine * @param visitor - a TermVisitor which will be called for each particle found */ private void visitTerms(XSDComplexTypeDefinition cType, TermVisitor visitor) { // simple types are leaf nodes if (cType.getContent() == null || (cType.getContent() instanceof XSDSimpleTypeDefinition)) return; // the last particle visited; used to detect <xsd:any> elements XSDParticleContent lastVisited = null; // use a queue to simulate the recursion LinkedList<XSDParticle> queue = new LinkedList<XSDParticle>(); queue.addLast((XSDParticle) cType.getContent()); while (!queue.isEmpty()) { XSDParticle particle = (XSDParticle) queue.removeFirst(); // analyze type of particle content int pType = org.eclipse.xsd.util.XSDUtil.nodeType(particle.getElement()); if (pType == XSDConstants.ELEMENT_ELEMENT) { lastVisited = particle.getContent(); visitor.visit(particle); } else { // model group XSDModelGroup modelGroup = null; switch (pType) { case XSDConstants.GROUP_ELEMENT: XSDModelGroupDefinition grpDef = (XSDModelGroupDefinition) particle.getContent(); if (grpDef.isModelGroupDefinitionReference()) grpDef = grpDef.getResolvedModelGroupDefinition(); modelGroup = grpDef.getModelGroup(); break; case XSDConstants.CHOICE_ELEMENT: case XSDConstants.ALL_ELEMENT: case XSDConstants.SEQUENCE_ELEMENT: modelGroup = (XSDModelGroup) particle.getContent(); break; case XSDConstants.ANY_ELEMENT: // see forum post at: // http://www.eclipse.org/forums/index.php?t=msg&th=20437&start=0&S=52ae90dcc09745f37034e330b91695cd // // The XSDWildcard seems to be emitted in the case where an element has no type information; // and it is also emitted in response to an actual xsd:any element appearing in the schema. // // In both cases, the XSDWildcard appears the same (surprisingly it refers to a DOM element // from Xerces of xsd:any). there is confusion here between elements of type anyType, i..e., // <xsd:element name="x" type=xsd:anyType"/> verses element wildcards <xsd:any>. When matching // element content using the DFA returned by XSDParticle, each element will match either an // element declaration or a wildcard, which you can tell apart easily by whether you've matched // an XSDElementDeclaration or an XSDWildcard. if (lastVisited!=null) { // found an <xsd:any> element - go visit it! lastVisited = particle.getContent(); visitor.visit(particle); } break; default: // TODO: do we need to handle additional XSD node types? break; } if (modelGroup != null) { // enque all particles in the group List<XSDParticle> particles = modelGroup.getParticles(); for (XSDParticle p : particles) { queue.addLast(p); } lastVisited = null; } } } } /** * Returns a list of all attribute declarations declared in the type or any * base type, of the specified element. * * @param element - the element * @return a list of XSDAttributeDeclaration objects */ public final List<XSDAttributeDeclaration> getAttributeDeclarations(XSDElementDeclaration element) { final ArrayList<XSDAttributeDeclaration> attributes = new ArrayList<XSDAttributeDeclaration>(); // walk up the type hierarchy of the element to generate a list of atts TypeWalker walker = new TypeWalker(element.getType()); TypeWalker.Visitor visitor = new TypeWalker.Visitor() { public boolean visit(XSDTypeDefinition type) { // simple types dont have attributes if (type instanceof XSDSimpleTypeDefinition) return true; XSDComplexTypeDefinition cType = (XSDComplexTypeDefinition) type; // get all the attribute content (groups,or uses) and add to list List<XSDAttributeGroupContent> contents = cType.getAttributeContents(); for (XSDAttributeGroupContent content : contents) { if (content instanceof XSDAttributeUse) { // an attribute, add it to the list XSDAttributeUse use = (XSDAttributeUse) content; attributes.add(use.getAttributeDeclaration()); } else if (content instanceof XSDAttributeGroupDefinition) { // attribute group, add all atts in group to list XSDAttributeGroupDefinition attGrp = (XSDAttributeGroupDefinition) content; if (attGrp.isAttributeGroupDefinitionReference()) { attGrp = attGrp.getResolvedAttributeGroupDefinition(); } List<XSDAttributeUse> uses = attGrp.getAttributeUses(); for (XSDAttributeUse use : uses) { attributes.add(use.getAttributeDeclaration()); } } } return true; } }; walker.walk(visitor); return attributes; } /** * Returns a list diagnostics of that may have been created * during the comparison * * @return a list of XSDDiagnostic objects */ public List<XSDDiagnostic> getDiagnostics() { if (diagnostics==null) { diagnostics = new ArrayList<XSDDiagnostic>(); } return diagnostics; } /** * Convenience method for formatting diagnostics as strings * * @param index - the index into the diagnostics array * @return a nicely formatted string */ public String getDiagnostic(int index) { String msg = ""; if (diagnostics!=null && index>=0 && index<diagnostics.size()) { XSDDiagnostic d = diagnostics.get(index); String name1 = ""; String name2 = ""; String schema1 = ""; String schema2 = ""; XSDTerm comp1 = null; XSDTerm comp2 = null; EList<XSDConcreteComponent> components = d.getComponents(); if (components.size()>0 && components.get(0) instanceof XSDTerm) comp1 = (XSDTerm)components.get(0); if (components.size()>1 && components.get(1) instanceof XSDTerm) comp2 = (XSDTerm)components.get(1); if (comp1 instanceof XSDElementDeclaration) { name1 = ((XSDElementDeclaration)comp1).getQName(); schema1 = comp1.eResource().getURI().toString(); } else if (comp1 instanceof XSDWildcard) { name1 = "<xsd:any>"; schema1 = comp1.eResource().getURI().toString(); } if (comp2 instanceof XSDElementDeclaration) { name2 = ((XSDElementDeclaration)comp2).getQName(); schema2 = comp2.eResource().getURI().toString(); } else if (comp2 instanceof XSDWildcard) { name2 = "<xsd:any>"; schema2 = comp2.eResource().getURI().toString(); } if ( comp1!=null && comp2!=null ) { msg = "Element <" + name1 + ">" + " in " + schema1 + " differs from <" + name2 + ">"; if (!schema1.equals(schema2)) msg += " in " + schema2; msg += " - " + d.getMessage(); } else { msg = "Schema " + d.getLocationURI() + " - " + d.getMessage(); } } return msg; } /** * Creates a new diagnostic and adds it to our list. * * @param severity - one of the XSDDiagnosticSeverity values (WARNING, ERROR, etc.) * @param msg - a human-readable message to associate with the diagnostic * @param comp1 - the "left" schema component * @param comp2 - the "right" schema component * @return a newly constructed XSDDiagnostic object */ protected XSDDiagnostic addDiagnostic(int severity, String msg, XSDConcreteComponent comp1, XSDConcreteComponent comp2) { XSDDiagnostic diag = XSDFactory.eINSTANCE.createXSDDiagnostic(); diag.setSeverity(XSDDiagnosticSeverity.get(severity)); diag.setMessage(msg); diag.getComponents().add(comp1); diag.getComponents().add(comp2); getDiagnostics().add(diag); if (debug) dump(getDiagnostic(getDiagnostics().size()-1)); return diag; } /** * Convenience method for creating a WARNING diagnostic * * @param msg - a human-readable message to associate with the diagnostic * @param comp1 - the "left" schema component * @param comp2 - the "right" schema component * @return a newly constructed XSDDiagnostic object */ protected XSDDiagnostic addWarning(String msg, XSDConcreteComponent comp1, XSDConcreteComponent comp2) { return addDiagnostic(XSDDiagnosticSeverity.WARNING, msg, comp1, comp2); } /** * Convenience method for creating a WARNING diagnostic * * @param msg - a human-readable message to associate with the diagnostic * @param comp1 - the "left" schema component * @param comp2 - the "right" schema component * @return a newly constructed XSDDiagnostic object */ protected XSDDiagnostic addError(String msg, XSDConcreteComponent comp1, XSDConcreteComponent comp2) { return addDiagnostic(XSDDiagnosticSeverity.ERROR, msg, comp1, comp2); } /** * Debug message printer * * @param label - a text string to print to stderr */ private void dump(String label) { if (debug) dump(label, null, null, 0); } /** * Debug message printer * * @param label - text string * @param term - an XSD element to dump * @param type - and its type * @param level - indent level */ private void dump(String label, XSDTerm term, XSDTypeDefinition type, int level) { if (debug) { if (term != null) { StringBuilder indent = new StringBuilder(); for (int i = 0; i < level; ++i) indent.append( " " ); if (term instanceof XSDElementDeclaration) { XSDElementDeclaration decl = (XSDElementDeclaration) term; if (type instanceof XSDSimpleTypeDefinition) System.err.println(label + indent + "<" + decl.getName() + "> type=\"" + this.getTypeNameHierarchy(decl)+"\""); else if (type instanceof XSDComplexTypeDefinition || type == null) System.err.println(label + indent + "<" + decl.getName() + ">"); else System.err.println("dump: unknown XSD type: " + type.getClass().toString()); } else if (term instanceof XSDWildcard) { System.err.println(label + indent + "<any/>"); } else System.err.println("dump: unknown term: " + term.getClass().toString()); } else System.err.println(label); } } /** * Simple visitor interface for complex types */ private interface TermVisitor { void visit(XSDParticle particle); } /** * XSD type hierarchy walker that visits each XSD type from the bottom-up */ private static class TypeWalker { // bottom of the type hierarchy XSDTypeDefinition base; public TypeWalker(XSDTypeDefinition base) { this.base = base; } public void walk(Visitor visitor) { XSDTypeDefinition type = base; while(type != null) { if (!visitor.visit(type)) break; // walk up the type hierarchy to the top if (type.equals(type.getBaseType())) break; type = type.getBaseType(); } } public interface Visitor { boolean visit(XSDTypeDefinition type); } } private static class XSDTermTuple { public XSDTerm term1, term2; public XSDTermTuple(XSDTerm term1, XSDTerm term2) { this.term1 = term1; this.term2 = term2; } } }