// $Id: XsModelWalker.java 173 2010-09-08 19:22:07Z agony $
/*
* xsAnalyzer - XML schema analyzing tool. Copyright (C) 2008 Michael Engelhardt
*
* This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program; if not, write to the Free
* Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/**
*
*/
package de.mindcrimeilab.xsanalyzer;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xerces.xs.XSAttributeDeclaration;
import org.apache.xerces.xs.XSAttributeUse;
import org.apache.xerces.xs.XSComplexTypeDefinition;
import org.apache.xerces.xs.XSConstants;
import org.apache.xerces.xs.XSElementDeclaration;
import org.apache.xerces.xs.XSModel;
import org.apache.xerces.xs.XSModelGroup;
import org.apache.xerces.xs.XSNamedMap;
import org.apache.xerces.xs.XSObject;
import org.apache.xerces.xs.XSObjectList;
import org.apache.xerces.xs.XSParticle;
import org.apache.xerces.xs.XSSimpleTypeDefinition;
import org.apache.xerces.xs.XSTerm;
import org.apache.xerces.xs.XSTypeDefinition;
import de.mindcrimeilab.xsanalyzer.util.XSModelHelper;
import de.mindcrimeilab.xsanalyzer.xsext.AnonymousTypeFactory;
/**
* The {@code XsModelWalker} iterates through a XML Schema tree representation. For each node in the tree the walker
* asks the known workers if they are applicable to this type of node and pass the node to further processing to the
* worker instances. Any user of this object might to subscribe to a property change listener to get notified about the
* iteration progress. The change listener will advise the name of the current node.
*
* References:
* <ul>
* <li>[1] http://www.w3.org/Submission/2004/SUBM-xmlschema-api-20040309/xml-schema -api.html</li>
* </ul>
*
* @author Michael Engelhardt<me@mindcrime-ilab.de>
* @author $Author: agony $
* @version $Revision: 173 $
*
*/
public class XsModelWalker {
/** name of the property change event to subscribe to the walking progress */
public static final String PC_CURRENT_COMPONENT_NAME = XsModelWalker.class.getName() + ".pc.currentComponentName";
/** logger instance */
private static final Log logger = LogFactory.getLog("xsAnalyzerApplicationLogger");
/** property change listener support */
private final PropertyChangeSupport changeSupport;
/** Stack of components which are already visited by this walker */
private final Stack<XSObject> seenComponents;
/** List of workes to hand over the nodes for further processing */
private final List<XsComponentWorker> workerList;
/** Set of already analyzed components */
;
private final Set<XSObject> analyzedComponents;
/**
* ctor() Construct a new {@code XsModelWalker}.
*/
public XsModelWalker() {
changeSupport = new PropertyChangeSupport(this);
seenComponents = new Stack<XSObject>();
analyzedComponents = new HashSet<XSObject>();
workerList = new LinkedList<XsComponentWorker>();
}
// ------------- PropertyChange Support -----------------------
/**
* Adds a property change listener to this instance.
*
* @param listener
* @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.beans.PropertyChangeListener)
*/
public void addPropertyChangeListener(PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(listener);
}
/**
* Add a property change listener for a specific property to this instance.
*
* @param propertyName
* property to listen on
* @param listener
* listener instance to add
* @see java.beans.PropertyChangeSupport#addPropertyChangeListener(java.lang.String,
* java.beans.PropertyChangeListener)
*/
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
changeSupport.addPropertyChangeListener(propertyName, listener);
}
/**
* Returns an array of registered property change listeners.
*
* @return
* @see java.beans.PropertyChangeSupport#getPropertyChangeListeners()
*/
public PropertyChangeListener[] getPropertyChangeListeners() {
return changeSupport.getPropertyChangeListeners();
}
/**
* Returns an array of registered property change listeners for a specific property.
*
* @param propertyName
* @return
* @see java.beans.PropertyChangeSupport#getPropertyChangeListeners(java.lang.String)
*/
public PropertyChangeListener[] getPropertyChangeListeners(String propertyName) {
return changeSupport.getPropertyChangeListeners(propertyName);
}
/**
* Check if there are any listeners for specific property
*
* @param propertyName
* @return
* @see java.beans.PropertyChangeSupport#hasListeners(java.lang.String)
*/
public boolean hasListeners(String propertyName) {
return changeSupport.hasListeners(propertyName);
}
/**
* Removes the property change listener from the list of bound listeners
*
* @param listener
* @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.beans.PropertyChangeListener)
*/
public void removePropertyChangeListener(PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(listener);
}
/**
* Remove the property change listener for the specific property from the list of bound listeners for certain
* property.
*
* @param propertyName
* @param listener
* @see java.beans.PropertyChangeSupport#removePropertyChangeListener(java.lang.String,
* java.beans.PropertyChangeListener)
*/
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
changeSupport.removePropertyChangeListener(propertyName, listener);
}
// ------------- Worker Handling-----------------------
/**
* Register a new {@code XsComponentWorker} to the {@code XsModelWalker}.
*
* @param worker
* worker to add
* @return true if worker was added successfully otherwise false
*/
public boolean addWorker(XsComponentWorker worker) {
return workerList.add(worker);
}
/**
* Add a list of {@code XsComponentWorker}s to the {@code XsModleWalker}
*
* @param workers
* list of workers to add
* @return true if the workers where add successfully
*/
public boolean addWorkers(Collection<XsComponentWorker> workers) {
return workerList.addAll(workers);
}
/**
* Remove a {@code XsComponentWorker} from {@code XsModelWalker}
*
* @param worker
* @return true if the worker was removed successfully, otherwise false
*/
public boolean removeWorker(XsComponentWorker worker) {
return workerList.remove(worker);
}
/**
* Removes all {@code XsComponentWorker}s from the {@code XsModelWalker}
*/
public void clearWorkers() {
workerList.clear();
}
/**
* Returns an unmodifiable list of all registered {@code XsComponentWorker}s.
*
* @return never null.
*/
public List<XsComponentWorker> getWorkers() {
return Collections.unmodifiableList(workerList);
}
/**
* Check if the certain {@code XsComponentWorker} is registered to the {@code XsModelWalker}
*
* @param worker
* @return true if the worker is in the list, otherwise false.
*/
public boolean hasWorker(XsComponentWorker worker) {
return workerList.contains(worker);
}
/**
* Walk through the given model executing all applicable, registered workers for each node.
*
* @param model
* model to walk through
*/
public void walkModel(XSModel model) {
List<? extends XSObject> globalComponents = initialize(model);
for (XSObject object : globalComponents) {
XsModelWalker.logger.debug("Global component [{" + object.getNamespace() + "}:" + object.getName() + "]");
visitComponent(object, null);
}
}
/**
* Visit a component.
*
* @param object
* @param parent
*/
private void visitComponent(XSObject object, XSObject parent) {
if (seenComponents.contains(object) || analyzedComponents.contains(object)) { return; }
seenComponents.push(object);
changeSupport.firePropertyChange(XsModelWalker.PC_CURRENT_COMPONENT_NAME, null, object.getName());
final XSTypeDefinition typeDefinition;
switch (object.getType()) {
case XSConstants.TYPE_DEFINITION:
XSTypeDefinition type = (XSTypeDefinition) object;
if (type.getAnonymous()) {
XsModelWalker.logger.debug("Handling anonymous type");
type = AnonymousTypeFactory.getProxy(type);
}
typeDefinition = onVisitTypeDefinition(type, parent);
break;
case XSConstants.ELEMENT_DECLARATION:
XSElementDeclaration elementDeclaration = (XSElementDeclaration) object;
typeDefinition = onVisitElementDeclaration(elementDeclaration, parent);
break;
case XSConstants.ATTRIBUTE_DECLARATION:
throw new RuntimeException("Not implemented yet - XSConstants.ATTRIBUTE_DECLARATION !");
case XSConstants.NOTATION_DECLARATION:
throw new RuntimeException("Not implemented yet - XSConstants.NOTATION_DECLARATION !");
case XSConstants.MODEL_GROUP_DEFINITION:
throw new RuntimeException("Not implemented yet - XSConstants.MODEL_GROUP_DEFINITION !");
default:
XsModelWalker.logger.info("Unknown component type [" + object.getType() + "]");
typeDefinition = null;
break;
}
if (null != typeDefinition) {
// iterate in depth...
XsModelWalker.logger.debug("==> of base type [{" + typeDefinition.getNamespace() + "}:" + typeDefinition.getName() + "]");
visitComponent(typeDefinition, object);
}
seenComponents.pop();
analyzedComponents.add(object);
}
private XSTypeDefinition onVisitTypeDefinition(XSTypeDefinition type, XSObject parent) {
final XSTypeDefinition baseType;
switch (type.getTypeCategory()) {
case XSTypeDefinition.COMPLEX_TYPE:
XsModelWalker.logger.debug("==> Found complex type definition");
XSComplexTypeDefinition ctypedef = (XSComplexTypeDefinition) type;
baseType = onVisitComplexTypeDefinition(ctypedef, parent);
break;
case XSTypeDefinition.SIMPLE_TYPE:
XsModelWalker.logger.debug("==> Found simple type definition");
XSSimpleTypeDefinition stypedef = (XSSimpleTypeDefinition) type;
baseType = onVisitSimpleTypeDefinition(stypedef, parent);
break;
default:
baseType = null;
break;
}
// both may contain attribute definitions
// simple types may return null - depending on type definition
// (list|atomic...)
return baseType;
}
/**
*
* @param stypedef
*
*/
private XSTypeDefinition onVisitSimpleTypeDefinition(XSSimpleTypeDefinition stypedef, XSObject parent) {
executeWorker(stypedef, parent);
// TODO handle embedded components (union, list)?
switch (stypedef.getVariety()) {
case XSSimpleTypeDefinition.VARIETY_ABSENT:
// anySimpleType
XsModelWalker.logger.debug("==> Found simple type variety absent");
break;
case XSSimpleTypeDefinition.VARIETY_ATOMIC:
// atomic (restriction)
XsModelWalker.logger.debug("==> Found simple type variety atomic");
break;
case XSSimpleTypeDefinition.VARIETY_LIST:
// list
XsModelWalker.logger.debug("==> Found simple type variety list");
XSSimpleTypeDefinition itemType = stypedef.getItemType();
visitComponent(itemType, stypedef);
break;
case XSSimpleTypeDefinition.VARIETY_UNION:
// union
XsModelWalker.logger.debug("==> Found simple type variety union");
XSObjectList unionedTypes = stypedef.getMemberTypes();
if (null != unionedTypes) {
for (int i = 0; i < unionedTypes.getLength(); ++i) {
visitComponent(unionedTypes.item(i), stypedef);
}
}
break;
default:
XsModelWalker.logger.warn("-------------- UNKNOWN SIMPLE TYPE VARIETY OF TYPE [" + stypedef.getVariety() + "]");
throw new RuntimeException("Unexpected branch selection - Not implemented");
}
return XSModelHelper.getBaseType(stypedef);
}
private XSTypeDefinition onVisitElementDeclaration(XSElementDeclaration elementDeclaration, XSObject parent) {
executeWorker(elementDeclaration, parent);
XSTypeDefinition typedef = elementDeclaration.getTypeDefinition();
return typedef;
}
private XSTypeDefinition onVisitComplexTypeDefinition(XSComplexTypeDefinition ctypedef, XSObject parent) {
executeWorker(ctypedef, parent);
// handle attributes
XSObjectList attributes = ctypedef.getAttributeUses();
onVisitAttributes(attributes, ctypedef);
XSParticle particle = ctypedef.getParticle();
if (null != particle) {
XSTerm term = particle.getTerm();
// according to [1]#Interface-XSParticle this should be either a
// model group, an element or a wildcard declaration
onVisitTerm(ctypedef, term);
}
return ctypedef.getBaseType();
}
/**
* @param ctypedef
* @param term
*/
private void onVisitTerm(XSComplexTypeDefinition ctypedef, XSTerm term) {
short termType = term.getType();
switch (termType) {
case XSConstants.MODEL_GROUP:
XsModelWalker.logger.debug("==> found model group");
XSModelGroup group = (XSModelGroup) term;
XSObjectList list = group.getParticles();
for (int i = 0; i < list.getLength(); ++i) {
XSParticle part = (XSParticle) list.item(i);
XSTerm item = part.getTerm();
XsModelWalker.logger.debug("==--> embedded [{" + item.getNamespace() + "}:" + item.getName() + "]");
onVisitTerm(ctypedef, item);
}
break;
case XSConstants.ELEMENT_DECLARATION:
XsModelWalker.logger.debug("==> found element declaration");
visitComponent(term, ctypedef);
break;
case XSConstants.WILDCARD:
XsModelWalker.logger.debug("==> found wildcard");
// ignore wildcards, no usable content - almost everything is
// useable..
break;
default:
XsModelWalker.logger.warn("-------------- UNKNOWN PARTICLE TYPE OF TYPE [" + termType + "]");
throw new RuntimeException("Unexpected branch selection - Not implemented");
}
}
private void onVisitAttributes(XSObjectList attributeUsesList, XSObject parent) {
for (int i = 0; i < attributeUsesList.getLength(); ++i) {
XSAttributeUse xsAttribute = (XSAttributeUse) attributeUsesList.item(i);
// attribute declaration contains interesting properties of the
// attribute
XSAttributeDeclaration xsAttributeType = xsAttribute.getAttrDeclaration();
XsModelWalker.logger.debug("--> Attribute [{" + xsAttributeType.getNamespace() + "}:" + xsAttributeType.getName() + "]");
executeWorker(xsAttributeType, parent);
// handle
/*
* TODO check if we really need this...
*
* A {scope} of global identifies attribute declarations available for use in complex type definitions
* throughout the schema. Locally scoped declarations are available for use only within the complex type
* definition identified by the {scope} property. This property is ·absent· in the case of declarations
* within attribute group definitions: their scope will be determined when they are used in the construction
* of complex type definitions.
*/
XSComplexTypeDefinition ctypedef = xsAttributeType.getEnclosingCTDefinition();
if (null != ctypedef) {
visitComponent(ctypedef, parent);
}
visitComponent(xsAttributeType.getTypeDefinition(), parent);
}
}
private List<? extends XSObject> initialize(XSModel model) {
List<? extends XSObject> global = new LinkedList<XSObject>();
/*
* Add globally defined schema components to list. This list will be the starting point for schema iteration.
*
* Trying to add components in an optimized way assuming that most schemas will have a number of simple types
* which are derived from basic xsd types.
*
* Complex types will be extend or restrict simple types.
*
* Elements will be use complex types as well as simple types.
*/
XSNamedMap typesMap = model.getComponents(XSConstants.TYPE_DEFINITION);
XSModelHelper.addComponents(typesMap, global);
// Global element definitions
XSNamedMap elementsMap = model.getComponents(XSConstants.ELEMENT_DECLARATION);
XSModelHelper.addComponents(elementsMap, global);
return global;
}
private void executeWorker(XSObject object, XSObject parent) {
for (XsComponentWorker worker : workerList) {
if (worker.isSupported(object)) {
worker.execute(object, parent);
}
}
}
}