package com.openMap1.mapper.structures;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Map;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
// EObjects in the EMF XSD plugin
import org.eclipse.xsd.XSDEnumerationFacet;
import org.eclipse.xsd.XSDForm;
import org.eclipse.xsd.XSDIdentityConstraintDefinition;
import org.eclipse.xsd.XSDModelGroupDefinition;
import org.eclipse.xsd.XSDNamedComponent;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDTypeDefinition;
import org.eclipse.xsd.XSDComplexTypeDefinition;
import org.eclipse.xsd.XSDSimpleTypeDefinition;
import org.eclipse.xsd.XSDAnnotation;
import org.eclipse.xsd.XSDParticle;
import org.eclipse.xsd.XSDModelGroup;
import org.eclipse.xsd.XSDElementDeclaration;
import org.eclipse.xsd.XSDAttributeUse;
import org.eclipse.xsd.XSDAttributeDeclaration;
import org.eclipse.xsd.XSDFeature;
import org.eclipse.xsd.XSDFactory;
import org.eclipse.xsd.XSDWildcard;
import org.eclipse.xsd.XSDAttributeUseCategory;
import org.eclipse.xsd.XSDAttributeGroupDefinition;
import org.eclipse.xsd.XSDDerivationMethod;
import org.eclipse.xsd.XSDConstraint;
import org.eclipse.xsd.util.XSDResourceImpl;
import com.openMap1.mapper.core.PropertyValueSupplier;
import com.openMap1.mapper.core.Xpth;
import com.openMap1.mapper.core.namespace;
import com.openMap1.mapper.core.NamespaceSet;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.util.XMLUtil;
import com.openMap1.mapper.AttributeDef;
import com.openMap1.mapper.ElementDef;
import com.openMap1.mapper.MapperFactory;
import com.openMap1.mapper.MaxMult;
import com.openMap1.mapper.MinMult;
/**
* This class has methods to convert an
* XML Schema into a tree representation of allowed XML structures.
*
* The tree representation is in some places less restrictive than the XML schema -
* eg where the schema defines choices -
* i.e an XML instance may conform to the tree but not to the schema.
*
* But any instance which conforms to the schema should conform to the tree.
* The tree is used to map nodes to a class model, not to test conformance of
* instances.
*
* @author robert
*
*/
public class XSDStructure implements StructureDefinition, PropertyValueSupplier {
/* where types can be extended, allow elements in instances to have an attribute
* xsi:type which determines the type of an element as an extension or restriction of its base type */
private static String defaultSchemaInstancePrefix = "xsi";
private String schemaInstancePrefix = defaultSchemaInstancePrefix;
private XSDSchema schema;
// set this true to send to the console a detailed trace of the schema-to-tree operations
boolean tracing = false;
private void trace(String trail)
{if (tracing) System.out.println(trail);}
/* to cut off infinite recursions before they blow the stack
* (they should not occur in any case)*/
private int MAX_REPEATS = 3;
/**
* @param trail
* @return true id any ed(name) is repeated more than MAX_REPEATS times
*/
private boolean tooDeep(String trail)
{
boolean tooDeep = (maxRepeats(trail) > MAX_REPEATS);
if (tooDeep) trace("Cut off recursion at trail " + trail);
return tooDeep;
}
/**
* @param trail
* @return The maximum number of times any element name repeats in an 'ed(name)' step in a trail
*/
private int maxRepeats(String trail)
{
int max = 0;
Hashtable<String,Integer> repeats = new Hashtable<String,Integer>();
StringTokenizer st = new StringTokenizer(trail, "/");
while (st.hasMoreTokens())
{
String step = st.nextToken();
if (step.startsWith("ed"))
{
Integer times = repeats.get(step);
if (times == null) times = new Integer(0);
int next = times.intValue() + 1;
repeats.put(step, new Integer(next));
if (next > max) max = next;
}
}
return max;
}
/**
* extend the trail of operations by adding a new step;
* but whenever the step is an 'ed' (element definition) step,
* drop the non-ed steps before it.
* If tracing, write out the trail.
* @param trail
* @param nextStep
* @return
*/
private String extendTrail(String trail, String nextStep)
{
String newTrail = trail + "/" + nextStep;
if (nextStep.startsWith("ed"))
{
newTrail = "";
StringTokenizer st = new StringTokenizer(trail,"/");
while(st.hasMoreTokens())
{
String step = st.nextToken();
if (step.startsWith("ed")) newTrail = newTrail + step + "/";
}
newTrail = newTrail + nextStep + "/";
}
trace(newTrail);
return newTrail;
}
private NamespaceSet NSSet;
public NamespaceSet NSSet() {return NSSet;}
private Hashtable<String,XSDComplexTypeDefinition> allComplexTypes;
/**
*
* @param topSchema
* @throws MapperException
*/
public XSDStructure(XSDSchema topSchema) throws MapperException
{
this.schema = topSchema;
// record whether this schema has any types derived from other types in it
recordComplexTypes();
recordDerivedTypes();
if (tracing) writeDerivedTypes();
// set up the namespaces (adding the XML Schema instance namespace only if there are derived types)
NSSet = new NamespaceSet();
makeNamespaceSet(schema);
}
private void recordComplexTypes()
{
allComplexTypes = new Hashtable<String,XSDComplexTypeDefinition>();
for (Iterator<XSDTypeDefinition> types = schema.getTypeDefinitions().iterator(); types.hasNext();)
{
XSDTypeDefinition td = types.next();
trace("type " + td.getName());
if (td instanceof XSDComplexTypeDefinition)
{
XSDComplexTypeDefinition ctd = (XSDComplexTypeDefinition)td;
allComplexTypes.put(ctd.getName(), ctd);
trace("complex type");
}
}
}
/**
* @param typeName
* @return the XSD complex type, if there is one
* This method and the previous one are a workaround for a
* strange feature of the EMF XSD package.
* When a complex type A extends another complex type B, sometimes
* (when, I cannot predict) the method A.getBaseType() returns
* an XSDTypeDefinition with the name of type B, but whose class is XSDSimpleTypeDefinition.
* Thus I have to note its name, and find a complex type of that name -
* which this method does.
*/
private XSDComplexTypeDefinition getComplexType(String typeName)
{return allComplexTypes.get(typeName);}
//------------------------------------------------------------------------------------------
// Setting up the full set of namespaces from all schema documents
//------------------------------------------------------------------------------------------
private void makeNamespaceSet(XSDSchema topSchema) throws MapperException
{
NSSet = new NamespaceSet();
/* If the top schema has no namespace, do not allow imported schemas
* to use the empty prefix for some actual namespace URI .
* remove this namespace when all imported schemas have been done */
if (topSchema.getTargetNamespace() == null)
NSSet.addNamespace(new namespace("","no target namespace"));
/* XSD recognises a top schema and those it imports or includes,
* directly or indirectly, as a single resource set.
* Iterate over it , taking namespaces from all of them. */
ResourceSet schemaSet = topSchema.eResource().getResourceSet();
for (Iterator<Resource> resources = schemaSet.getResources().iterator(); resources.hasNext();)
{
Resource resource = resources.next();
if (resource instanceof XSDResourceImpl)
{
XSDResourceImpl xsdResource = (XSDResourceImpl)resource;
XSDSchema oneSchemaDoc = xsdResource.getSchema();
addToNamespaceSet(oneSchemaDoc);
}
}
// remove this artificial namespace if it is present - reasons above.
NSSet.removeOneNamespace("no target namespace");
/* only add the W3C schema instance namespace if the schema has some derived types,
* so that the xsi:type attribute might be used. */
if (schemaHasDerivedTypes())
{
namespace instanceNamespace = NSSet.getByURI(XMLUtil.SCHEMAINSTANCEURI);
// if there is no schema instance namespace, add one with a non-clashing prefix
if (instanceNamespace == null)
{
schemaInstancePrefix = noClash(defaultSchemaInstancePrefix, NSSet);
NSSet.addNamespace(new namespace(schemaInstancePrefix, XMLUtil.SCHEMAINSTANCEURI));
}
// if there is a schema instance namespace, find its prefix, to use when adding the type attribute
else if (instanceNamespace != null)
{
schemaInstancePrefix = instanceNamespace.prefix();
}
}
}
/**
* Make additions to the Namespace set for one schema document,
* in a way that will resolve any namespace prefix clashes between different
* schema documents.
*
* (1) If a namespace URI has been encountered before, do nothing; the
* previous prefix will be used for the URI
* (2) If a new namespace URI has a prefix that has not been encountered before,
* add it
* (3) If a new namespace URI has a prefix that has been encountered before,
* make up a new prefix which does not clash
* (4) the empty String '' is a valid prefix for any namespace
* (5) '' is the only valid prefix for 'no target namespace' (which we assume
* is encountered first, in the top schema)
* If '' is used for 'no target namespace', none of the actual namespaces
* can have the same prefix ''
*
* @param oneSchema
* @throws MapperException
*/
private void addToNamespaceSet(XSDSchema oneSchema) throws MapperException
{
Map<String,String> namespaces = oneSchema.getQNamePrefixToNamespaceMap();
/* If this schema document has a target namespace, then it should declare
* that namespace with a prefix or with the empty prefix */
String targetNamespace = oneSchema.getTargetNamespace();
boolean targetNamespaceNotMatched = false;
if (targetNamespace != null) targetNamespaceNotMatched= true;
for (Iterator<String> it = namespaces.keySet().iterator();it.hasNext();)
{
String prefix = it.next();
// for a no-prefix namespace, prefix == null; but the get still works
String uriString = namespaces.get(prefix);
if (prefix == null) prefix = "";
if (targetNamespaceNotMatched)
targetNamespaceNotMatched = (!(uriString.equals(targetNamespace)));
// if this namespace URI has already been encountered, do nothing else
if (NSSet.getByURI(uriString) == null)
{
// give it a prefix which does not clash with any previous prefix, including ''
String actualPrefix = noClash(prefix,NSSet);
NSSet.addNamespace(new namespace(actualPrefix,uriString));
}
}
/* If no namespace prefix has been allocated in this document for its target namespace ,
* and no prefix has been allocated in any previous document, pick a
* non-clashing prefix out of the air and allocate it to the target namespace.*/
if ((targetNamespaceNotMatched) && (NSSet.getByURI(targetNamespace) == null))
{
String madeUpPrefix = noClash("madeup",NSSet);
/* the prefix 'xml' must be used for the XML default namespace;
* but do not add it in any case */
String XMLDefaultNamespace = "http://www.w3.org/XML/1998/namespace";
if (!targetNamespace.equals(XMLDefaultNamespace))
NSSet.addNamespace(new namespace(madeUpPrefix,targetNamespace));
}
}
/* generate a namespace prefix which does not clash with any previous prefix in the set */
private String noClash(String prefix, NamespaceSet NSSet)
{
String noClash = prefix;
int index = 1;
while (NSSet.getByPrefix(noClash) != null)
{
noClash = prefix + "_" + index;
index++;
}
return noClash;
}
/**
* @param ed an Element declaration or Attribute declaration
* @return the element or attribute name, with a namespace prefix as defined from the whole schema
* set by XSDStructure.makeNamespaceSet.
* This method is used in stead of getQName() because getQName seems to return a prefix as in
* the schema holding the declaration, whereas the single prefix I have allocated
* may be different. This ensures just one prefix for every namespace, and
* resolves prefix clashes between different schema documents.
* @throws MapperException if the namespace uri is not recognised as having a prefix
*/
private String getMappedStructureName(XSDFeature ed) throws MapperException
{
String name = ed.getName();
String namespaceURI = ed.getTargetNamespace();
/* The element name in instances will have a namespace prefix if the Element is in a
* target namespace, and either the scope of the declaration is schema-wide
* or the element form is qualified. */
if ((namespaceURI != null) &&
((ed.getScope() instanceof XSDSchema)|(ed.getForm() == XSDForm.QUALIFIED_LITERAL)))
{
namespace ns = NSSet.getByURI(namespaceURI);
if (ns == null)throw new MapperException("Cannot find namespace with URI '"
+ namespaceURI + "'");
if (!(ns.prefix().equals(""))) name = ns.prefix() + ":" + name;
}
return name;
}
//------------------------------------------------------------------------------------------
// PropertyValueSupplier interface
//------------------------------------------------------------------------------------------
/**
* @param modelClassName
* @param modelPropertyName
* @return true if this property value supplier supplies values for the
* model class and property
*/
public boolean suppliesPropertyValues(String modelClassName, String modelPropertyName)
{
if ((modelClassName.equals("MappedStructure")) &&
(modelPropertyName.equals("Top Element Type"))) return true;
if ((modelClassName.equals("MappedStructure")) &&
(modelPropertyName.equals("Top Element Name"))) return true;
return false;
}
/**
* @param modelClassName
* @param modelPropertyName
* @return the values supplied by this supplier for the model class and property
*/
public String[] propertyValues(String modelClassName, String modelPropertyName)
{
String[] vals = {};
try{
if ((modelClassName.equals("MappedStructure")) &&
(modelPropertyName.equals("Top Element Type"))) return topComplexTypes();
if ((modelClassName.equals("MappedStructure")) &&
(modelPropertyName.equals("Top Element Name"))) return topElementNames();
}
catch (MapperException ex){System.out.println(ex.getMessage());}
return vals;
}
//------------------------------------------------------------------------------------------
// StructureDefinition interface
//------------------------------------------------------------------------------------------
/**
*
* @return an array of the top-level Element names in Element Declarations defined in the schema
*/
public String[] topElementNames() throws MapperException
{
ArrayList<String> allTopNames = new ArrayList<String>();
allTopNames.add(""); // the default choice on the menu, before any choice is made, is ""
for (Iterator<XSDElementDeclaration> names = schema.getElementDeclarations().iterator();
names.hasNext();)
{
XSDElementDeclaration ed = names.next();
allTopNames.add(getMappedStructureName(ed));
}
String[] res = new String[allTopNames.size()];
return allTopNames.toArray(res);
}
/**
*
* @param type
* @return true if type is one of the top types defined in the schema
*/
public boolean isTopElementName(String name)
{
boolean found = false;
try{
String[] tn = topElementNames();
for (int i = 1; i < tn.length; i++) // tn[0] = "" does not count as a match
if (tn[i].equals(name)) found = true;
}
catch (MapperException ex){System.out.println(ex.getMessage());}
return found;
}
/**
*
* @return an array of the top-level complex types defined in the schema
*/
public String[] topComplexTypes()
{
ArrayList<String> allTypes = new ArrayList<String>();
allTypes.add(""); // the default choice on the menu, before any choice is made, is ""
for (Iterator<XSDTypeDefinition> types = schema.getTypeDefinitions().iterator();
types.hasNext();)
{
XSDTypeDefinition type = types.next();
if (type instanceof XSDComplexTypeDefinition) allTypes.add(type.getName());
}
String[] res = new String[allTypes.size()];
return allTypes.toArray(res);
}
/**
*
* @param type
* @return true if type is one of the top types defined in the schema
*/
public boolean isTopComplexType(String type)
{
String[] tt = topComplexTypes();
boolean found = false;
for (int i = 1; i < tt.length; i++) // tt[0] = "" does not count as a match
if (tt[i].equals(type)) found = true;
return found;
}
/**
* find the Element and Attribute structure of some named top element (which may have a named
* complex type, or a locally defined anonymous type), stopping at the
* next complex type definitions it refers to
* @param String name the name of the element
* @return Element the EObject subtree (Element and Attribute EObjects) defined by the name
*/
public ElementDef nameStructure(String name) throws MapperException
{
trace("Getting structure for element name '" + name + "'");
ElementDef el = MapperFactory.eINSTANCE.createElementDef();
el.setName(name);
if (isTopElementName(name))
{
XSDElementDeclaration elDec = null;
for (Iterator<XSDElementDeclaration> eds = schema.getElementDeclarations().iterator();
eds.hasNext();)
{
XSDElementDeclaration ed = eds.next();
if(getMappedStructureName(ed).equals(name)) elDec = ed;
}
extendForElementDeclaration(el,elDec,"");
}
else if (name.equals("")) {}
else {trace("Unexpected name '" + name + "'");}
return el;
}
/**
* find the Element and Attribute structure of some complex type, stopping at the
* next complex type definitions it refers to
* @param type the name of the complex type
* @return the EObject subtree (Element and Attribute EObjects) defined by the type.
* If there are other types which extend or restrict the type, then return the union of
* the trees of all those types, and allow an attribute xsi:type on the top element
*/
public ElementDef typeStructure(String typeName) throws MapperException
{
trace("\nGetting structure for element type '" + typeName + "' and derived types");
Vector<String> derivedTypes = allDerivedTypes(typeName);
if (derivedTypes.size() == 1)
{
return ownTypeStructure(typeName);
}
else if (derivedTypes.size() > 1)
{
Vector<ElementDef> derivedTypeStructures = new Vector<ElementDef>();
for (Iterator<String> it = derivedTypes.iterator();it.hasNext();)
derivedTypeStructures.add(ownTypeStructure(it.next()));
ElementDef union = unionOfTypeStructures(derivedTypeStructures);
// because the type has extensions or restrictions, let its top element have an xsi:type attribute
AttributeDef xsiType = MapperFactory.eINSTANCE.createAttributeDef();
xsiType.setName(schemaInstancePrefix + ":type");
xsiType.setType("string");
// the xsi:type attribute must be made obligatory; if allowed, it always appears on the element,
xsiType.setMinMultiplicity(MinMult.ONE);
union.getAttributeDefs().add(xsiType);
return union;
}
else throw new MapperException("Cannot find type '" + typeName + "'");
}
/**
* find the Element and Attribute structure of some complex type, stopping at the
* next complex type definitions it refers to
* @param type the name of the complex type
* @return the EObject subtree (Element and Attribute EObjects) defined by the type itself;
* if there are other types that extend or restrict the type, the result is unaffected
*/
public ElementDef ownTypeStructure(String type)
throws MapperException
{
trace("Getting structure for element type '" + type + "'");
ElementDef el = MapperFactory.eINSTANCE.createElementDef();
if (type == null) return el;
el.setType(type);
if (isTopComplexType(type))
{
XSDComplexTypeDefinition typeDef = null;
for (Iterator<XSDTypeDefinition> types = schema.getTypeDefinitions().iterator();
types.hasNext();)
{
XSDTypeDefinition td = types.next();
if(td.getName().equals(type)) typeDef = (XSDComplexTypeDefinition)td;
}
extendForComplexType(el,typeDef,"");
trace("TypeStructure " + type + " " + el.isMixed());
}
else if (type.equals("")) {}
else if (type.equals("string")) {}
else {trace("Unexpected type '" + type + "'");}
return el;
}
// -----------------------------------------------------------------------------------
// Analysing the schema
// -----------------------------------------------------------------------------------
/**
* Handle an element declaration, which may either invoke a named type
* or define the element structure with an anonymous type
* @param el the element whose declaration this is
* @param ed the XSD element declaration
* @param trail String trace of the recursion for finding out what went wrong
*/
private void extendForElementDeclaration(ElementDef el, XSDElementDeclaration ed, String trail)
throws MapperException
{
if (tooDeep(trail)) return;
/* resolve this Element declaration in case it is a ref to another declaration,
* and use the resolved declaration from now on */
XSDElementDeclaration resEd = ed.getResolvedElementDeclaration();
if (resEd == null) resEd = ed; // not sure if this is necessary
el.setName(getMappedStructureName(resEd));
trail = extendTrail(trail,"ed(" + resEd.getName() + ")");
String typeName = "";
XSDTypeDefinition td = resEd.getTypeDefinition();
if (td != null)
{
typeName = td.getName();
el.setExpanded(false);
}
el.setType(typeName); // set the type name to "" if there is no type attribute
/* the complex type will only be part of the contents immediately expanded
* if it is defined locally inside the element; otherwise it is left for expansion later */
for (Iterator<EObject> it = resEd.eContents().iterator(); it.hasNext();)
{
EObject edPart = it.next();
if (edPart instanceof XSDComplexTypeDefinition)
{
XSDComplexTypeDefinition ctd = (XSDComplexTypeDefinition)edPart;
typeName = ctd.getName();
// named complex type; do not expect this case to happen
if (typeName != null)
{System.out.println("Named complex type inside element declaration: '" + typeName + "'");}
// anonymous complex type; keep on expanding the tree
else if (typeName == null)
{
el.setExpanded(true);
String newTrail = trail;
extendForComplexType(el,ctd,newTrail);
}
}
else if (edPart instanceof XSDAnnotation) {}
// ignore identity constraints under element definitions
else if (edPart instanceof XSDIdentityConstraintDefinition) {}
// FIXME: we should really work out what to do with simple type definitions here
else if (edPart instanceof XSDSimpleTypeDefinition) {}
else unexpectedChild(resEd,edPart,1,trail);
}
}
/**
* Extend the tree of Elements and Attributes for a complex type
* @param el
* @param typeDef
* @param trail String trace of the recursion for finding out what went wrong
*/
private void extendForComplexType(ElementDef el, XSDComplexTypeDefinition typeDef, String trail)
throws MapperException
{
if (tooDeep(trail)) return;
boolean needToRestrict = false;
ElementDef newEl = el; // make extensions direct on the tree unless this is a restriction
ElementDef restrictEl = MapperFactory.eINSTANCE.createElementDef();
int method = typeDef.getDerivationMethod().getValue();
trail = extendTrail(trail,"td(" + typeDef.getName() + ":" + XSDDerivationMethod.get(method).getLiteral() + ")");
/* If this complex type extends another, extend the tree structure for that type
* first, before adding the extra stuff from this extension. */
if (method == XSDDerivationMethod.EXTENSION)
{
// WORKAROUND: getBaseType() sometimes turns complex types into simple types
XSDComplexTypeDefinition base = getComplexType(typeDef.getBaseType().getName());
if (base != null)
{
String newTrail = extendTrail(trail, "[" + base.getName() + "]");
extendForComplexType(newEl,base,newTrail);
}
else if (typeDef.getBaseType() instanceof XSDSimpleTypeDefinition)
{
/* FIXME: This treatment of complex types which are extensions of simple types is
* probably over-simplified. The Simple type which is being extended is
* replaced by an 'empty' complex type. This is OK when the simple type
* is 'string' because the mapper editor allows you to map string values to
* the text content of any Element, without checking its type. */
XSDSimpleTypeDefinition sBase = (XSDSimpleTypeDefinition)typeDef.getBaseType();
// note that sBase now gets completely ignored
XSDComplexTypeDefinition newEmpty = XSDFactory.eINSTANCE.createXSDComplexTypeDefinition();
String newTrail = extendTrail(trail, "[(ignored)" + sBase.getName() + "]");
extendForComplexType(newEl,newEmpty,newTrail);
}
}
/* if this complex type restricts another type (apart from 'anyType'),
* first construct a tree representing the type to be restricted,
* then construct a tree representing the restriction, and then combine them
* on the element being extended in the tree.*/
else if (method == XSDDerivationMethod.RESTRICTION)
{
XSDComplexTypeDefinition base = null;
if (typeDef != null)
{
if (typeDef.getBaseType() == null) base = null;
else if (typeDef.getBaseType() instanceof XSDComplexTypeDefinition)
base = (XSDComplexTypeDefinition)typeDef.getBaseType();
else base = getComplexType(typeDef.getBaseType().getName());
}
// restriction of 'anyType' means just read this type definition; don't try to restrict anything
if ((base!=null) && !(base.getName().equals("anyType")))
{
trail = extendTrail(trail,"[" + base.getName() + "]");
String newTrail = trail;
needToRestrict = true;
extendForComplexType(restrictEl,base,newTrail);
// the information about the restriction should not be put directly on the element being extended
newEl = MapperFactory.eINSTANCE.createElementDef();
}
}
/* make the extensions directly on newEl = el if this type is not a restriction of another type;
* or if it is a restriction, define in newEl what the restrictions are . */
for (Iterator<EObject> it = typeDef.eContents().iterator(); it.hasNext();)
{
EObject typePart = it.next();
String newTrail = trail;
if (typePart instanceof XSDParticle)
{
extendForParticle(newEl, (XSDParticle)typePart,newTrail);
}
else if (typePart instanceof XSDAttributeUse)
{
extendForAttributeUse(newEl,(XSDAttributeUse)typePart,newTrail);
}
else if (typePart instanceof XSDAttributeGroupDefinition)
{
extendForAttributeGroupDefinition(newEl,(XSDAttributeGroupDefinition)typePart,newTrail);
}
else if (typePart instanceof XSDWildcard)
{
trace("Wild card in type '" + typeDef.getName() + "' at trail '"
+ trail + "'");
}
else if (typePart instanceof XSDAnnotation)
{} // nothing to do
else if (typePart instanceof XSDTypeDefinition)
{} // the extension cases dealt with above; nothing to do
else unexpectedChild(typeDef,typePart,2,trail);
}
/* if this type is a restriction of another type, add the type to be restricted,
* making the appropriate restrictions */
if (needToRestrict) makeRestriction(el,restrictEl,newEl,trail);
/* record the 'mixed' property of the complex type, so that for V3 data types
* (where the Ecore class model is derived from the schema structure)
* the class derived from a mixed element can be given an extra 'textContent' attribute */
el.setIsMixed(typeDef.isMixed());
trace("type " + typeDef.getName() + "; mixed = " + typeDef.isMixed());
}
/**
* Extend the tree from element el, using the structure of subtree
* restrictEl, except where it is restricted by newEl.
*
* This uses a simple override by name, at the level of the immediate
* children of the element, and reports if it was unable to make any overrides at all.
*
* @param el the element in the tree being constructed
* @param restrictEl the tree structure which needs to be added with restrictions
* @param newEl the tree structure which defines the restrictions
* @param trail String trace of the recursion for finding out what went wrong
*/
private void makeRestriction(ElementDef el, ElementDef restrictEl, ElementDef newEl, String trail)
{
if (tooDeep(trail)) return;
/* extend the element by the restricted child attributes, using an intermediate
* Vector to avoid corrupting the source EList */
Vector<AttributeDef> ats = new Vector<AttributeDef>();
for (Iterator<AttributeDef> it = restrictEl.getAttributeDefs().iterator();it.hasNext();)
{
AttributeDef mightBeRestricted = it.next();
AttributeDef mightRestrict = newEl.getNamedAttribute(mightBeRestricted.getName());
/* If the restricting structure has an attribute of the right name, use
* it in stead of the attribute from the structure being restricted. */
if (mightRestrict == null) ats.add(mightBeRestricted);
else if (mightRestrict != null)
{
// if the restricting type prohibits this attribute, do not add it
if (!mightRestrict.useIsProhibited()) ats.add(mightRestrict);
}
}
for (Iterator<AttributeDef> it = ats.iterator();it.hasNext();)
{el.getAttributeDefs().add(it.next());}
/* extend the element by the restricted child elements, using an intermediate
* Vector to avoid corrupting the source EList */
Vector<ElementDef> els = new Vector<ElementDef>();
for (Iterator<ElementDef> it = restrictEl.getChildElements().iterator();it.hasNext();)
{
ElementDef mightBeRestricted = it.next();
ElementDef mightRestrict = newEl.getNamedChildElement(mightBeRestricted.getName());
/* If the restricting structure has an element of the right name, use
* it in stead of the element from the structure being restricted. */
if (mightRestrict == null) els.add(mightBeRestricted);
else if (mightRestrict != null)
{
// if the restricting type prohibits this element (maxOccurs = 0), do not add it
if (!mightRestrict.useIsProhibited()) els.add(mightRestrict);
}
}
for (Iterator<ElementDef> it = els.iterator();it.hasNext();)
{el.getChildElements().add(it.next());}
/* System.out.println("Made " + restrictions + " restrictions on "
+ features + " features in element at trail '" + trail + "'"); */
// pass XML schema 'mixed type' information through a restriction
el.setIsMixed(restrictEl.isMixed());
}
/**
*
* @param el
* @param attGroup
* @param trail String trace of the recursion for finding out what went wrong
*/
private void extendForAttributeGroupDefinition(ElementDef el, XSDAttributeGroupDefinition attGroup, String trail)
throws MapperException
{
if (tooDeep(trail)) return;
// attribute group references need to be resolved
XSDAttributeGroupDefinition expandable = attGroup;
if (attGroup.isAttributeGroupDefinitionReference())
{
trail = extendTrail(trail , "/resolveAG");
trace(trail);
expandable = attGroup.getResolvedAttributeGroupDefinition();
}
trail = extendTrail(trail,"agr(" + expandable.getName() + ")");
for (Iterator<EObject> it = expandable.eContents().iterator(); it.hasNext();)
{
EObject groupPart = it.next();
if (groupPart instanceof XSDAttributeUse)
{
String newTrail = trail;
extendForAttributeUse(el, (XSDAttributeUse)groupPart,newTrail);
}
else if (groupPart instanceof XSDAnnotation) {} // silently ignore annotations
else unexpectedChild(attGroup,groupPart,3,trail);
}
}
/**
*
* @param el
* @param attUse
* @param trail String trace of the recursion for finding out what went wrong
*/
private void extendForAttributeUse(ElementDef el, XSDAttributeUse attUse, String trail)
throws MapperException
{
if (tooDeep(trail)) return;
// no further recursion so the next statement is not very useful, unless we need traces in this method
trail = extendTrail(trail,"au(" + attUse.getUse().getName() + ")");
boolean isRequired = attUse.isRequired();
XSDAttributeDeclaration attDecl = attUse.getAttributeDeclaration();
AttributeDef att = MapperFactory.eINSTANCE.createAttributeDef();
if (attUse.getConstraint()!= null)
{
//Convert the default or fixed value to a String
String stringValue = "";
Object value = attUse.getValue();
if (value == null) {stringValue = "null";}
else
{
if (value instanceof String) {stringValue = (String)value;}
else if (value instanceof Boolean) {stringValue = value.toString();}
else if (value instanceof Integer) {stringValue = value.toString();}
else if (value instanceof java.math.BigDecimal)
{stringValue = ((java.math.BigDecimal)value).toString();}
else System.out.println("Cannot yet convert attribute value of class " + value.getClass().getName() + " to a String");
}
if (attUse.getConstraint().getValue() == XSDConstraint.DEFAULT)
att.setDefaultValue(stringValue);
if (attUse.getConstraint().getValue() == XSDConstraint.FIXED)
att.setFixedValue(stringValue);
}
att.setName(getMappedStructureName(attDecl));
att.setType(getAttributeType(attDecl));
att.setMinMultiplicity(MinMult.get(isRequired));
// record if the attribute is to be prohibited
if (attUse.getUse().getValue() == XSDAttributeUseCategory.PROHIBITED) att.setUseIsProhibited(true);
el.getAttributeDefs().add(att);
}
private String getAttributeType(XSDAttributeDeclaration attDecl)
{
return attDecl.getTypeDefinition().getName();
}
/**
* Handle XSD particles encountered in schemas. From W3C schema recommendation:
*
* A particle is a term in the grammar for element content, consisting of either
* an element declaration, a wildcard or a model group,
* together with occurrence constraints (= minOccurs and maxOccurs)
* @param el the Element whose structure is being determined
* @param typePart the XSD particle
* @param refCount the depth of cross-references that have been followed in model groups; limit to 3
* @param trail String trace of the recursion for finding out what went wrong
*/
private void extendForParticle(ElementDef el, XSDParticle typePart, String trail)
throws MapperException
{
if (tooDeep(trail)) return;
Integer min = new Integer(typePart.getMinOccurs()); // 0 or 1
Integer max = new Integer(typePart.getMaxOccurs()); // 1 or -1 (for unbounded) or 0 (prohibited)
// nesting of prohibited nodes is not useful; cut off recursion at any particle inside a maxOccurs = 0 particle
if ((trail.contains("pt(0:0"))|((trail.contains("pt(1:0")))) return;
trail = extendTrail(trail,"pt(" + min + ":" + max + ")");
int contained = 0;
for (Iterator<EObject> iu = typePart.eContents().iterator(); iu.hasNext();)
{
String newTrail = trail;
EObject pChild = iu.next();
contained++;
if ((pChild instanceof XSDModelGroup)|(pChild instanceof XSDModelGroupDefinition))
{
XSDModelGroup modelGroup = null;
if (pChild instanceof XSDModelGroup)
modelGroup = (XSDModelGroup)pChild;
else if (pChild instanceof XSDModelGroupDefinition)
{
XSDModelGroupDefinition mgd = (XSDModelGroupDefinition)pChild;
modelGroup = mgd.getModelGroup();
if (modelGroup == null)
modelGroup = mgd.getResolvedModelGroupDefinition().getModelGroup();
}
if (modelGroup == null)
{
System.out.println("Null model group at trail " + trail);
return;
}
extendForModelGroup(el, modelGroup,newTrail);
// apply the multiplicities to the added elements
for (Iterator<ElementDef> it = el.getChildElements().iterator(); it.hasNext();)
{
ElementDef child = it.next();
if (min == 0) child.setMinMultiplicity(MinMult.ZERO);
if (max == -1) child.setMaxMultiplicity(MaxMult.UNBOUNDED);
}
// apply the min multiplicities to the added attributes
for (Iterator<AttributeDef> it = el.getAttributeDefs().iterator(); it.hasNext();)
{
AttributeDef at = it.next();
if (min == 0) at.setMinMultiplicity(MinMult.ZERO);
}
}
else if (pChild instanceof XSDElementDeclaration)
{
XSDElementDeclaration edd = (XSDElementDeclaration)pChild;
ElementDef newEl = MapperFactory.eINSTANCE.createElementDef();
newEl.setMinMultiplicity(MinMult.get(min.toString()));
// maxOccurs = 0 means use is prohibited
if (max == 0) newEl.setUseIsProhibited(true);
else newEl.setMaxMultiplicity(MaxMult.get(max.toString()));
extendForElementDeclaration(newEl,edd,newTrail);
el.getChildElements().add(newEl);
}
else if (pChild instanceof XSDWildcard)
{
trace("Wild card in particle at trail '"
+ trail + "'");
}
else unexpectedChild(typePart,pChild,4,trail);
}
if (!(contained == 1)) trace("XSD Particle with " + contained
+ " contents at trail " + trail + "; unexpected");
}
/**
* Handle model groups encountered in schemas. From the W3C recommendation:
*
* A model group is a constraint in the form of a grammar fragment that
* applies to lists of element information items.
* It consists of a list of particles, i.e. element declarations, wildcards and model groups.
* There are three varieties of model group:
* Sequence: <xs:sequence>
* Conjunction: <xs:all>
* Disjunction: <xs:choice>
* @param el the Element whose structure is being determined
* @param modelGroup the XSD model group
* @param refCount the depth of cross-references that have been followed in model groups; limit to 3
* @param trail String trace of the recursion for finding out what went wrong
*/
private void extendForModelGroup(ElementDef el, XSDModelGroup modelGroup, String trail)
throws MapperException
{
if (tooDeep(trail)) {trace("trail '" + trail + "' too deep");return;}
if (modelGroup.getCompositor() == null)
{
System.out.println("Null compositor for model group at trail " + trail);
return;
}
String compositor = modelGroup.getCompositor().getName();
trail = extendTrail(trail,"mg(" + compositor + ")");
boolean isChoice = (compositor.equals("choice"));
/* for 'sequence' and 'all' model groups, simply add the different subtrees
* occurring in the group */
if (!isChoice) for (Iterator<EObject> iv = modelGroup.eContents().iterator(); iv.hasNext();)
{
String newTrail = trail;
EObject part2 = iv.next();
if (part2 instanceof XSDParticle)
{
// count2++;
extendForParticle(el,(XSDParticle)part2,newTrail);
}
else unexpectedChild(modelGroup,part2,5,trail);
}
/* this warning sometimes appeared in the V3 data types schema */
// if (count2 == 0) System.out.println(compositor + " with no contained particles in Element at trail " + trail);
/* 'choice' model groups; cannot simply add nodes to the subtree when the same
* element name and type appears in more than one choice. Must add nodes without repetition. */
else if (isChoice)
{
// to store subtrees without duplication
Hashtable<String,ElementDef> choiceTrees = new Hashtable<String,ElementDef>();
Hashtable<String,AttributeDef> choiceAtts = new Hashtable<String,AttributeDef>();
for (Iterator<EObject> iv = modelGroup.eContents().iterator(); iv.hasNext();)
{
ElementDef newEl = MapperFactory.eINSTANCE.createElementDef();
String newTrail = trail;
EObject part = iv.next();
if (part instanceof XSDParticle)
{
extendForParticle(newEl,(XSDParticle)part,newTrail);
saveSubTreeStructures(newEl, choiceTrees);
saveAttributes(newEl, choiceAtts);
}
else unexpectedChild(modelGroup,part,6,trail);
}
// add the non-duplicated subtrees to the tree
for (Enumeration<ElementDef> en = choiceTrees.elements(); en.hasMoreElements();)
el.getChildElements().add(en.nextElement());
for (Enumeration<AttributeDef> en = choiceAtts.elements();en.hasMoreElements();)
el.getAttributeDefs().add(en.nextElement());
/* if there is more than one choice with different subtrees, they must have MinMult = 0.
* This implementation is a quick fudge.
* The proper implementation should look at each top element
* and ask if it appears in every choice; only then might it have MinMult = 1.
* This just sets MinMult zero anyway ,assuming the choice makes everything optional. */
for (Iterator<ElementDef> it = el.getChildElements().iterator();it.hasNext();)
it.next().setMinMultiplicity(MinMult.ZERO);
for (Iterator<AttributeDef> it = el.getAttributeDefs().iterator();it.hasNext();)
it.next().setMinMultiplicity(MinMult.ZERO);
}
}
//----------------------------------------------------------------------------------------
// other stuff
//----------------------------------------------------------------------------------------
/**
* save all the subtrees, merging duplicates with the same name and type
* @param newEl element which has all the subtrees for one choice attached
* @param choiceTrees Hashtable of subtrees to be built up
*/
private void saveSubTreeStructures(ElementDef el, Hashtable<String,ElementDef> choiceTrees)
{
for (Iterator<ElementDef> it = el.getChildElements().iterator();it.hasNext();)
{
ElementDef child = it.next();
String key = child.getName() + "|" + child.getType();
ElementDef previous = choiceTrees.get(key);
child = mergeSubtrees(child, previous); // OK if previous == null
choiceTrees.put(key, child);
}
}
/**
* merge two subtrees that have been found under one choice node -
* assuming that they belong to different branches of the choice.
*
* Fixed and default values are only carried through if the two choices agree.
*
* @param el a subtree with given name and type found under the current choice
* @param previous the merge of subtrees with this name and type found under previous
* choices, or null if none have been found yet.
* @return the merge
*/
private ElementDef mergeSubtrees(ElementDef el, ElementDef previous)
{
if (previous == null) return el;
/* merge the property values of this and the previous element for the choice */
ElementDef newEl = MapperFactory.eINSTANCE.createElementDef();
newEl.setName(el.getName()); // must be equal to previous
newEl.setType(el.getType()); // must be equal to previous
newEl.setExpanded(el.isExpanded()|previous.isExpanded());
newEl.setFixedValue(mergeFixedValues(el.getFixedValue(),previous.getFixedValue()));
newEl.setDefaultValue(mergeFixedValues(el.getDefaultValue(),previous.getDefaultValue()));
// if either el or previous departs from (min,max) = (1,1), the result must depart
newEl.setMinMultiplicity(el.getMinMultiplicity());
if (previous.getMinMultiplicity() == MinMult.ZERO) el.setMinMultiplicity(MinMult.ZERO);
newEl.setMaxMultiplicity(el.getMaxMultiplicity());
if (previous.getMaxMultiplicity() == MaxMult.UNBOUNDED) el.setMaxMultiplicity(MaxMult.UNBOUNDED);
// merge the child elements; first those from el, which may or may not be matched
for (Iterator<ElementDef> it = el.getChildElements().iterator(); it.hasNext();)
{
ElementDef child = it.next();
ElementDef previousChild = childWithNameAndType(previous, child.getName(),child.getType());
if (previousChild == null)
{
// as the child does not occur in one of the choices, it is optional in the tree
child.setMinMultiplicity(MinMult.ZERO);
newEl.getChildElements().add(child);
}
else newEl.getChildElements().add(mergeSubtrees(child,previousChild));
}
// then child elements from previous which are not children of el
for (Iterator<ElementDef> it = previous.getChildElements().iterator(); it.hasNext();)
{
ElementDef prev = it.next();
if (childWithNameAndType(el,prev.getName(),prev.getType()) == null)
{
// as the child does not occur in one of the choices, it is optional in the tree
prev.setMinMultiplicity(MinMult.ZERO);
newEl.getChildElements().add(prev);
}
}
// merge the attributes. First those in el which may or may not be matched in previous
for (Iterator<AttributeDef> it = el.getAttributeDefs().iterator(); it.hasNext();)
{
AttributeDef at = it.next();
AttributeDef atp = previous.getNamedAttribute(at.getName());
/* if the attribute does not occur in one of the choices,
* it is optional and has no fixed or default value */
if (atp == null)
{
at.setFixedValue(null); // when the attribute does not occur, it cannot have a fixed or default value
at.setDefaultValue(null);
at.setMinMultiplicity(MinMult.ZERO);
}
else
{
// store fixed and default values only if the two choices are equal
at.setFixedValue(mergeFixedValues(at.getFixedValue(),atp.getFixedValue()));
at.setDefaultValue(mergeFixedValues(at.getDefaultValue(),atp.getDefaultValue()));
if (atp.getMinMultiplicity() == MinMult.ZERO) at.setMinMultiplicity(MinMult.ZERO);
}
newEl.getAttributeDefs().add(at);
}
// next the attributes in previous which are not matched in el
for (Iterator<AttributeDef> it = previous.getAttributeDefs().iterator(); it.hasNext();)
{
AttributeDef atp = it.next();
AttributeDef at = el.getNamedAttribute(atp.getName());
if (at == null)
{
atp.setFixedValue(null); // when the attribute does not occur, it cannot have a fixed or default value
atp.setDefaultValue(null);
// if the attribute does not occur in one of the choices, it is optional
atp.setMinMultiplicity(MinMult.ZERO);
newEl.getAttributeDefs().add(atp);
}
}
return el;
}
/**
* Merge fixed or default values arising from two different choices, in the same attribute
* in different choices.
* If either choice has no fixed value, the structure tree has no fixed value (null).
* If the choices provide different fixed values, there is no fixed value.
* Only if both choices define the same value should the tree record a fixed value.
* @param value1
* @param value2
* @return
*/
private String mergeFixedValues(String value1, String value2)
{
String res = null;
if (value1 == null) return res;
if (value2 == null) return res;
if (value1.equals(value2)) res = value1;
return res;
}
private ElementDef childWithNameAndType(ElementDef el,String name, String type)
{
ElementDef child = el.getNamedChildElement(type);
if ((child != null) && (child.getType().equals(type))) return child;
return null;
}
/**
* save all the attributes by name alone - more than one attribute of the same
* name but different types is not allowed (?)
* @param newEl
* @param choiceTrees
*/
private void saveAttributes(ElementDef el, Hashtable<String,AttributeDef> choiceAtts)
{
for (Iterator<AttributeDef> it = el.getAttributeDefs().iterator();it.hasNext();)
{
AttributeDef at = it.next();
AttributeDef atp = choiceAtts.get(at.getName());
// if the attribute is new, add it
if (atp == null) choiceAtts.put(at.getName(), at);
// if it exists already with MinMult zero, keep MinMult zero
else if (atp != null)
{
if (atp.getMinMultiplicity() == MinMult.ZERO)
at.setMinMultiplicity(MinMult.ZERO);
choiceAtts.put(at.getName(), at);
}
}
}
/**
* message if some child class is unexpected in the XSD package;
* write out the classes (and names if possible) of the whole subtree
* @param parent
* @param child
*/
private void unexpectedChild(EObject parent, EObject child, int type, String trail)
{
System.out.println("New subtree under " + description(parent)
+ " at call " + type + " with trail '" + trail + "'");
showDescendants(child,0);
}
// basic description of an EObject - class name, and 'getName()' if defined
private String description(EObject eo)
{
String cName = null;
StringTokenizer cst = new StringTokenizer(eo.getClass().getName(),".");
while (cst.hasMoreTokens()) {cName = cst.nextToken();}
if (eo instanceof XSDNamedComponent)
{cName = cName + " '" + ((XSDNamedComponent)eo).getName() + "'";}
return cName;
}
/**
* write out a basic description of the subtree of an EObject
* @param eo the EObject
* @param level keeps track of the depth in the tree; truncate
*/
private void showDescendants(EObject eo, int level)
{
if (level > 1) return;
System.out.println(level + ": " + description(eo));
for (Iterator<EObject> it = eo.eContents().iterator(); it.hasNext();)
{showDescendants(it.next(),level + 1);}
}
//-------------------------------------------------------------------------------
// Static method to return a top XSDSchema object
//-------------------------------------------------------------------------------
/**
* Open a file as a XSD model
* @param uri the URI of the XSD model file
* @return the XSDSchema (EObject subclass) root of the model; or null if failed to open
*/
public static XSDSchema getXSDRoot(URI uri)
{
XSDSchema theSchema = null;
ResourceSet resourceSet = new ResourceSetImpl();
// load the main schema file into the resourceSet.
//XSDResourceImpl xsdRes =
resourceSet.getResource(uri, true);
// getResources() returns an iterator over all the resources, therefore, the main resource
// and those that have been included, imported, or redefined.
for (Iterator<Resource> resources = resourceSet.getResources().iterator();
resources.hasNext(); /* no-op */)
{
// Return the first schema object found, which is the main schema
// loaded from the provided schemaURL
Resource resource = resources.next();
if ((resource instanceof XSDResourceImpl) && (theSchema == null))
{
XSDResourceImpl xsdResource = (XSDResourceImpl)resource;
theSchema = xsdResource.getSchema();
}
}
return theSchema;
}
/** key = string form of a non-definite path;
* value = the shortest compatible definite path */
Hashtable<String, Xpth> shortestPaths = new Hashtable<String, Xpth>();
//------------------------------------------------------------------------------------------------------
// Dealing with xsi:type; extended types
//------------------------------------------------------------------------------------------------------
/* If the schema has complex types that are extensions or restrictions on other types,
* then instances may use xsi:type on elements to say what types the elements are.
* In this case the tree of allowed structure is the union of the structure trees
* of the base type and all its extensions */
/**
* key = a type name;
* value = names of all the types derived directly from it, by extension or restriction
*/
private Hashtable<String,Vector<String>> directDerivedTypes = new Hashtable<String,Vector<String>>();
/**
* @return true if this schema has any complex types derived from others by extension or restriction
*/
public boolean schemaHasDerivedTypes() {return (directDerivedTypes.size() > 0);}
/**
* set up the Hashtable directDerivedTypes, recording which types are derived directly
* from others.
* Note the workaround below for a strange feature of the XSD package
*/
private void recordDerivedTypes()
{
for (Iterator<XSDTypeDefinition> types = schema.getTypeDefinitions().iterator();
types.hasNext();)
{
XSDTypeDefinition td = types.next();
if (td instanceof XSDComplexTypeDefinition)
{
XSDComplexTypeDefinition ctd = (XSDComplexTypeDefinition)td;
String derivedName = ctd.getName();
int method = ctd.getDerivationMethod().getValue();
if ((method == XSDDerivationMethod.EXTENSION)|(method == XSDDerivationMethod.RESTRICTION))
{
String baseName = ctd.getBaseType().getName();
// WORKAROUND; getBaseType() sometimes wrongly returns an XSDSimpleType with the name of the XSDComplexType
XSDComplexTypeDefinition base = getComplexType(baseName);
if (base != null)
{
Vector<String> derivedNames = directDerivedTypes.get(baseName);
if (derivedNames == null) derivedNames = new Vector<String>();
derivedNames.add(derivedName);
directDerivedTypes.put(baseName,derivedNames);
}
}
}
}
}
/**
* basic diagnostic write of derived types
*/
private void writeDerivedTypes()
{
for (Enumeration<String> en = directDerivedTypes.keys(); en.hasMoreElements();)
{
String typeLine = en.nextElement();
Vector<String> derived = directDerivedTypes.get(typeLine);
typeLine = typeLine + "\t";
for (Iterator<String> it = derived.iterator();it.hasNext();)
{
typeLine = typeLine + it.next() + ",";
}
System.out.println(typeLine);
}
}
/**
* @param typeName the name of a complex type
* @return a Vector starting with that type, and containing the names of all types
* derived from it, directly or indirectly, such that any type always precedes
* the types derived from it.
*/
private Vector<String> allDerivedTypes(String typeName)
{
Vector<String> types = new Vector<String>();
types.add(typeName);
addDerivedTypes(types, typeName,0);
return types;
}
/**
* @param types a Vector of type names
* @param typeName a type name
* Add to the Vector all types derived directly from the type,
* and recursively add those derived indirectly
*/
private void addDerivedTypes(Vector<String> types,String typeName, int depth)
{
int MAXDEPTH = 20; // some silly schemas self-recurse
if (typeName != null)
{
Vector<String> directDerived = directDerivedTypes.get(typeName);
if (directDerived != null)
for (Iterator<String> it = directDerived.iterator();it.hasNext();)
{
String dType = it.next();
types.add(dType); // add a type derived directly from the type
if (depth < MAXDEPTH) // has been known to blow up
addDerivedTypes(types,dType,depth + 1); // recursively add those derived from the derived type
}
}
}
/**
* @param derivedTypeStructures Vector of ElementDefs for a type, and all types
* derived from it by extension or restriction. Any type comes before the types
* derived from it.
* @return the union of the ElementDef trees, preserving order of descendant elements,
* and putting elements from extended types after elements from their base types
*/
private ElementDef unionOfTypeStructures(Vector<ElementDef>derivedTypeStructures)
{
Hashtable<String,String> usedChildElementNames = new Hashtable<String,String>();
Hashtable<String,String> usedAttributeNames = new Hashtable<String,String>();
// copy properties of any element from the base type that introduces it
ElementDef el = MapperFactory.eINSTANCE.createElementDef();
ElementDef first = derivedTypeStructures.get(0);
el.setType(first.getType());
el.setName(first.getName());
el.setMinMultiplicity(first.getMinMultiplicity());
el.setMaxMultiplicity(first.getMaxMultiplicity());
el.setDefaultValue(first.getDefaultValue());
el.setFixedValue(first.getFixedValue());
/* iterate over all derived types and all their child Elements,
* addressing each child element name only once */
boolean isBaseType = true; // remains true only for the first (base) structure
for (Iterator<ElementDef> ip = derivedTypeStructures.iterator();ip.hasNext();)
{
ElementDef parent = ip.next();
// if any extended type allows unbounded max multiplicity,allow it for the union
if (parent.getMaxMultiplicity() == MaxMult.UNBOUNDED) el.setMaxMultiplicity(MaxMult.UNBOUNDED);
for (Iterator <ElementDef> ic = parent.getChildElements().iterator();ic.hasNext();)
{
String cName = ic.next().getName();
if (usedChildElementNames.get(cName) == null) // first encounter of this element name
{
usedChildElementNames.put(cName, "1");
// pick up child elements of this name from all parents, and put them in a Vector
Vector<ElementDef> namedChildren = new Vector<ElementDef>();
for (Iterator<ElementDef> ipp = derivedTypeStructures.iterator();ipp.hasNext();)
{
ElementDef par = ipp.next();
for (Iterator <ElementDef> icc = par.getChildElements().iterator();icc.hasNext();)
{
ElementDef ch = icc.next();
if (ch.getName().equals(cName)) namedChildren.add(ch);
}
}
// make the union of type structures for all child elements with a given name
ElementDef childStruct = unionOfTypeStructures(namedChildren);
// if the name does not come from the base type, make the ElementDef optional
if (!isBaseType) childStruct.setMinMultiplicity(MinMult.ZERO);
el.getChildElements().add(childStruct);
} // end of first encounter with a child element name
} // end of loop over child elements of derived type structures
for (Iterator<AttributeDef> ia = parent.getAttributeDefs().iterator();ia.hasNext();)
{
AttributeDef ad = ia.next();
String attName = ad.getName();
/* if this is the first encounter with this attribute name, don't bother
* to find all later occurrences of the attribute name in all derived types;
* take the properties from the first occurrence. Incorrect? */
if (usedAttributeNames.get(attName) == null)
{
usedAttributeNames.put(attName,"1");
AttributeDef at = MapperFactory.eINSTANCE.createAttributeDef();
at.setName(attName);
at.setType(ad.getType());
at.setMinMultiplicity(ad.getMinMultiplicity());
/* if an attribute does not appear in the base type, make it optional
* (restricted types cannot make an obligatory attribute optional) */
if (!isBaseType) at.setMinMultiplicity(MinMult.ZERO);
at.setDefaultValue(ad.getDefaultValue());
at.setFixedValue(ad.getFixedValue());
el.getAttributeDefs().add(at);
} // end of first encounter with an attribute of given name
} // end of loop over attributes of derived type ElementDefs
// so that elements and attributes first encountered in non-base types can min multiplicity = 0
isBaseType = false;
} // end of loop over derived type structures
return el;
}
//------------------------------------------------------------------------------------------------------
// Getting Enumeration values of simple types
//------------------------------------------------------------------------------------------------------
/**
* @param simpleTypeName the name of a simple type
* @return a list of its enumerated values, if it has any; otherwise an empty list
* @throws MapperException if there is no such simple type
*/
public Vector<String> getSimpleTypeEnumeratedValues(String simpleTypeName) throws MapperException
{
Vector<String> values = new Vector<String>();
XSDSimpleTypeDefinition theType = null;
EList<XSDTypeDefinition> types = schema.getTypeDefinitions();
for (Iterator<XSDTypeDefinition> it = types.iterator();it.hasNext();)
{
XSDTypeDefinition next = it.next();
if (theType == null)
{
if ((next instanceof XSDSimpleTypeDefinition) && (next.getName().equals(simpleTypeName)))
theType = (XSDSimpleTypeDefinition)next;
if ((theType != null) && (theType.getEnumerationFacets() != null))
{
int size = theType.getEnumerationFacets().size();
if (size > 0)
{
for (Iterator<XSDEnumerationFacet> iu = theType.getEnumerationFacets().iterator();iu.hasNext();)
{
XSDEnumerationFacet facet = iu.next();
EList<Object> valueSet = facet.getValue();
if ((valueSet != null) && (valueSet.size() == 1))
{
Object value = valueSet.get(0);
if (value instanceof String) values.add((String)value);
}
}
}
}
}
}
if (theType == null) throw new MapperException("Simple type '" + simpleTypeName + "' not found.");
return values;
}
//------------------------------------------------------------------------------------------------------
// Bits and bobs
//------------------------------------------------------------------------------------------------------
protected void message(String s) {System.out.println(s);}
}