/* * This software is Copyright 2005,2006,2007,2008 Langdale Consultants. * Langdale Consultants can be contacted at: http://www.langdale.com.au */ package au.com.langdale.xmi; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import au.com.langdale.kena.OntResource; import com.hp.hpl.jena.graph.FrontsNode; import au.com.langdale.sax.XMLElement; import au.com.langdale.sax.XMLInterpreter; import au.com.langdale.sax.XMLMode; public class XMIParser extends XMIModel { /** * Parse the given file as XMI producing an OWL model. */ public void parse(String fileName) throws IOException, SAXException, ParserConfigurationException, FactoryConfigurationError { parse(new InputSource(fileName)); } public void parse(InputStream stream) throws IOException, SAXException, ParserConfigurationException, FactoryConfigurationError { parse(new InputSource(stream)); } public void parse(InputSource source) throws ParserConfigurationException, SAXException, IOException { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); // trying to stop parser read DTD's // factory.setValidating(false); // factory.setFeature("http://xml.org/sax/features/external-general-entities", false); // factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); SAXParser parser = factory.newSAXParser(); XMLReader reader = parser.getXMLReader(); // still trying to kill attempts to read DTD reader.setEntityResolver(new EntityResolver() { public InputSource resolveEntity(String arg0, String arg1) throws SAXException, IOException { return new InputSource(new StringReader("")); }}); // kick off in global package mode - can vacumn up elements that // slip outside the Model element in some xmi dialects reader.setContentHandler( new XMLInterpreter( new PackageMode() )); reader.parse(source); } /** * Base class for all parser modes. */ private abstract class BaseMode implements XMLMode { /** * Common part of visit() for most model elements * collects annotations and stereotypes. */ protected XMLMode visit(XMLElement element, OntResource resource) { if ( element.matches("TaggedValue")) { return new TaggedValueMode(element, resource); } else if( element.matches("Stereotype")) { return new StereotypeMode(element, resource); } else if( element.matches("Comment")) { String comment = element.getAttributes().getValue("body"); if( comment != null) resource.addComment(comment, null); return null; } else if( element.matches("Operation")) return null; else return this; } public void visit(XMLElement element, String text) { // ignore text nodes } public void leave() { // no action } } /** * Interpret a UML tag instance as an owl annotation. */ private class TaggedValueMode extends BaseMode { OntResource subject; String tagValue; FrontsNode property; /** * Construct for an unknown subject (expect a modelElement reference) */ TaggedValueMode(XMLElement element) { this(element, null); } /** * Construct for a known subject. */ TaggedValueMode(XMLElement element, OntResource resource) { subject = resource; tagValue = element.getAttributes().getValue("value"); // in UML 1.3 documentation tags carry the comments String type = element.getAttributes().getValue("tag"); // in UML 1.4 the type is in the name attribute if(type == null) type = element.getAttributes().getValue("name"); if( type != null ) property = Translator.annotationResource(type); // we override the default subject here and in visit() OntResource ref = createUnknown(element.getAttributes().getValue("modelElement")); if( ref != null) subject = ref; } public XMLMode visit(XMLElement element) { // handle a tag definition or a reference to one if ( element.matches("TagDefinition")) { property = createAnnotationProperty(element); return null; } else if ( element.matches("TaggedValue.dataValue") || element.matches("TaggedValue.value")) { return new TaggedValueDataValueMode(); } else if( element.matches("ModelElement")) { subject = createUnknown(element.getAttributes().getValue("xmi.idref")); return null; } else return this; } private class TaggedValueDataValueMode extends BaseMode { @Override public void visit(XMLElement element, String text) { if( tagValue == null ) tagValue = text; else tagValue += text; } public XMLMode visit(XMLElement element) { return this; } } @Override public void leave() { if ( tagValue != null && subject != null && property != null) { subject.addProperty(property, tagValue.trim()); } } } /** * Interpret an UML stereotype */ private class StereotypeMode extends BaseMode { private OntResource subject; private OntResource stereo; private StereotypeMode(XMLElement element) { this(element, null); } private StereotypeMode(XMLElement element, OntResource subject) { this.subject = subject; stereo = createStereotype(element); extend(element); } /** * Multiple stereotyped elements */ protected void extend(XMLElement element) { String list = element.getAttributes().getValue("extendedElement"); if( stereo != null && list != null) { subject = null; // forget subject // parse list of subjects String[] xuids = list.split(" +"); for(int ix = 0; ix < xuids.length; ix++) { OntResource given = createUnknown(xuids[ix]); if( given != null) given.addProperty(UML.hasStereotype, stereo); } } } public XMLMode visit(XMLElement element) { if( element.matches("ModelElement")) { subject = null; // forget the given subject OntResource given = createUnknown(element.getAttributes().getValue("xmi.idref")); if(stereo != null && given != null) { given.addProperty(UML.hasStereotype, stereo); } } return this; } /** * Apply the stereotype to the default subject, if possible. */ @Override public void leave() { if(stereo != null && subject != null) { subject.addProperty(UML.hasStereotype, stereo); } } } /** * Parse a UML package and the classes and associations it contains. */ private class PackageMode extends BaseMode { OntResource packResource; /** * Construct for the top-level package. */ PackageMode() { packResource = createGlobalPackage(); } /** * Construct for a subordinate package. */ PackageMode(XMLElement element, PackageMode parent) { packResource = createPackage(element); parent.packageDefines(packResource); } void packageDefines(OntResource res) { if( ! packResource.equals(UML.global_package)) res.addIsDefinedBy(packResource); } public XMLMode visit(XMLElement element) { if ( element.matches("Association")) { return new AssociationMode(element); } else if ( matchDef( element, "Class")) { return new ClassMode(element); } else if ( matchDef( element, "Enumeration")) { return new EnumerationMode(element); } else if ( matchDef( element, "Package")) { return new PackageMode(element, this); } else if ( matchDef( element, "Component")) { createUnknown(element); return null; } else if( matchDef( element, "AssociationClass")) { // TODO: implement association classes return null; } else if ( matchDef(element, "DataType")) { if(packResource.equals(UML.global_package)) { createUnknown( element ); // top level datatypes are generally garbage return null; } else return new DatatypeMode( element ); } else if ( element.matches("Generalization")) { return new GeneralizationMode(element); } else if ( matchDef( element, "TagDefinition")) { createAnnotationProperty(element); return null; } else if( element.matches("Stereotype")) { return new StereotypeMode(element); // pick up stereotype but don't apply to package } else if( element.matches("Diagram")) { return null; //chop off any diagrams } else if( element.matches("Subsystem")) { return null; // chop off subsystem definitions } else return visit(element, packResource); } /** * Interpret an XMI generalisation as an OWL subClass. */ private class GeneralizationMode extends BaseMode { OntResource ontChild; OntResource ontParent; GeneralizationMode(XMLElement element) { ontChild = findClass(element, "child"); ontParent = findClass(element, "parent"); } public XMLMode visit(XMLElement element) { if ( element.matches("Generalization.child")) return new GeneralizationChildMode(); else if ( element.matches("Generalization.parent")) return new GeneralizationParentMode(); else return this; } private class GeneralizationChildMode extends BaseMode { public XMLMode visit(XMLElement element) { if ( element.matches("Class")) { ontChild = findClass(element); return null; } return this; } } private class GeneralizationParentMode extends BaseMode { public XMLMode visit(XMLElement element) { if ( element.matches("Class")) { ontParent = findClass(element); return null; } return this; } } @Override public void leave() {; if ( ontChild != null && ontParent != null) { ontParent.addSubClass(ontChild); } } } /** * Interpret a UML association as a pair of OWL ObjectProperties. */ private class AssociationMode extends BaseMode { AssociationEndMode endA, endB; String associd; OntResource assoc; public AssociationMode(XMLElement element) { associd = element.getAttributes().getValue("xmi.id"); assoc = createAssocation(associd); } public XMLMode visit(XMLElement element) { if ( element.matches("AssociationEnd")) { AssociationEndMode mode = new AssociationEndMode(element, endA == null); if( endA == null ) endA = mode; else endB = mode; return mode; } return visit(element, assoc); } /** * Once recognised, mate the OWL properties. */ @Override public void leave() { if( endA != null && endB != null) { endA.role.mate(endB.role); endB.role.mate(endA.role); } } /** * Interpret a UML AssociationEnd as an OWL ObjectProperty. */ private class AssociationEndMode extends BaseMode { Role role = new Role(); AssociationEndMode( XMLElement element, boolean sideA ) { role.property = createObjectProperty(element); if( role.property == null) role.property = createObjectProperty(element, associd, sideA); if( role.property != null ) { packageDefines(role.property); if( assoc != null ) role.property.addProperty(sideA? UML.roleAOf: UML.roleBOf, assoc); } role.range = findClass( element, "type"); if( role.range == null ) role.range = findClass( element, "participant"); String agg = element.getAttributes().getValue("aggregation"); if( agg != null) { role.composite = agg.equals("composite"); role.aggregate = agg.equals("aggregate"); } } public XMLMode visit(XMLElement element) { if ( element.matches("Multiplicity")) return new MultiplicityMode(); else if ( element.matches("Class")) { role.range = findClass(element); return null; } else if( role.property != null) return visit(element, role.property); else return null; } /** * Collect multiplicity information for one association end. */ private class MultiplicityMode extends BaseMode { public XMLMode visit(XMLElement element) { if ( element.matches("MultiplicityRange")) { Attributes attrs = element.getAttributes(); role.lower = number(attrs, "lower"); role.upper = number(attrs, "upper"); return null; } return this; } /** * Interpret a multiplicity attribute as a decimal. */ int number(Attributes atts, String name) { String value = atts.getValue(name); if( value != null ) { try { return Integer.parseInt(value); } catch( NumberFormatException e) { return -1; } } else return -1; } } } } /** * Interpret a UML enumeration as an OWL Class plus individuals. */ private class EnumerationMode extends BaseMode { OntResource classResource; public EnumerationMode(XMLElement element) { classResource = createClass(element); classResource.addProperty(UML.hasStereotype, UML.enumeration); packageDefines(classResource); } public XMLMode visit(XMLElement element) { if ( matchDef( element, "EnumerationLiteral")) return new IndividualMode(element); else return visit(element, classResource); } private class IndividualMode extends BaseMode { OntResource indivResource; public IndividualMode(XMLElement element) { indivResource = createIndividual(element, classResource); } public XMLMode visit(XMLElement element) { return visit(element, indivResource); } } } /** * Interpret a UML class as an OWL class. */ private class ClassMode extends BaseMode { OntResource classResource; ClassMode (XMLElement element) { classResource = createClass(element); Attributes atts = element.getAttributes(); String sxuid = atts.getValue("stereotype"); if( sxuid != null ) { classResource.addProperty(UML.hasStereotype, createStereotype(sxuid)); } packageDefines(classResource); } public XMLMode visit(XMLElement element) { if ( matchDef( element, "Attribute")) return new AttributeMode(element); else if ( element.matches("Generalization")) return new GeneralizationMode(element); else return visit(element, classResource); } /** * Interpret a UML attribute as an OWL property. * * Make this a datatype property if the * object is marked as a UML data type. * Otherwise, leave the propertype open * for later assignment in stereotype processing. */ private class AttributeMode extends BaseMode { OntResource attrResource; AttributeMode(XMLElement element) { attrResource =createAttributeProperty(element); attrResource.addDomain(classResource); packageDefines(attrResource); OntResource type = findResource(element, "type"); if( type != null) attrResource.addRange(type); } public XMLMode visit(XMLElement element) { if ( element.matches("Class") || element.matches("Classifier") || element.matches("DataType")) { OntResource type = findResource(element); if( type != null) attrResource.addRange(type); if ( element.matches("DataType")) attrResource.convertToDatatypeProperty(); return null; } else if( element.matches("Expression")) { String value = element.getAttributes().getValue("body"); if( value != null ) { value = value.trim(); while( value.startsWith("\"") && value.endsWith("\"") || value.endsWith("'") && value.endsWith("'")) { value = value.substring(1, value.length()-1).trim(); } if(value.length() > 0) attrResource.addProperty(UML.hasInitialValue, value); } return null; } else return visit(element, attrResource); } } } private class DatatypeMode extends ClassMode { DatatypeMode(XMLElement element) { super(element); classResource.addProperty(UML.hasStereotype, UML.datatype); } } } }