/* * 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.profiles; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import au.com.langdale.xmi.UML; import au.com.langdale.kena.OntModel; import au.com.langdale.kena.OntResource; import au.com.langdale.kena.Property; import au.com.langdale.kena.ResIterator; import au.com.langdale.kena.Resource; import com.hp.hpl.jena.util.OneToManyMap; import com.hp.hpl.jena.vocabulary.OWL; import com.hp.hpl.jena.vocabulary.RDF; import com.hp.hpl.jena.vocabulary.RDFS; /** * Represents a class in a profile, encapsulating its base (original) class and restrictions. */ public class ProfileClass { private final OntResource clss; private final String namespace; private final OntModel model; private OneToManyMap props; private OntResource baseClass; private Set classes; private boolean enumeratedBase; private final OntResource defaultBase; public ProfileClass(OntResource clss, String namespace, OntResource base) { this.clss = clss; this.namespace = namespace; this.model = clss.getOntModel(); this.defaultBase = base; analyse(); } public ProfileClass(OntResource clss, String namespace) { this(clss, namespace, clss.getOntModel().createClass( OWL.Thing.getURI())); } /** * Construct a map of properties to restrictions * in the context of a given class. */ public void analyse() { props = new OneToManyMap(); classes = new HashSet(); baseClass = defaultBase; ResIterator it = clss.listSuperClasses(true); while( it.hasNext()) { OntResource node = it.nextResource(); // if( ! node.isClass() && ! node.isDatatype() && ! node.equals(MESSAGE.Reference)) { // System.out.println("Superclass not typed:"); // System.out.println(node.describe()); // // } if( node.isClass() && ! node.equals(MESSAGE.Reference)) { if(node.isRestriction()) { OntResource prop = node.getOnProperty(); if( prop != null) props.put(prop, node); } else if( ! node.isAnon()) { // its a named, general base class if( node.getNameSpace().equals(namespace)) { classes.add(node); // locally defined class } else { baseClass = node; // externally defined class (expect only one) } } } } analyseBaseClass(); } private void analyseBaseClass() { // if( OWL.Thing.equals(baseClass) && ! clss.isDatatype()) { // System.out.println("Profile with no schema class:"); // System.out.println( clss.describe()); // } enumeratedBase = baseClass.hasProperty(UML.hasStereotype, UML.enumeration); } /** * remove a SomeValueFrom restriction. */ public void remove(Property prop, OntResource childClass) { Iterator it = props.getAll(prop); while(it.hasNext()) { OntResource res = (OntResource) it.next(); if( res.isSomeValuesFromRestriction()) { Resource type = res.getSomeValuesFrom(); if( type != null && type.equals(childClass)) res.remove(); } } } /** * Change the type of the node in the underlying ontology. */ public void setBaseClass(OntResource type) { if( ! OWL.Thing.equals(baseClass)) { if( baseClass.equals(type)) return; clss.removeSuperClass(baseClass); } clss.addSuperClass(type); baseClass = type; analyseBaseClass(); } /** * Remove all restrictions on the given property. */ public void remove(OntResource prop) { Iterator jt = props.getAll(prop); while( jt.hasNext()) { OntResource res = (OntResource) jt.next(); res.remove(); } props.remove(prop); } public void setMaxCardinality(int card) { if( card < Integer.MAX_VALUE) clss.setProperty(UML.hasMaxCardinality, card); else clss.removeAll(UML.hasMaxCardinality); } public void setMinCardinality(int card) { if( card > 0) clss.setProperty(UML.hasMinCardinality, card); else clss.removeAll(UML.hasMinCardinality); } public int getMaxCardinality() { Integer card = clss.getInteger(UML.hasMaxCardinality); return card != null? card.intValue(): Integer.MAX_VALUE; } public int getMinCardinality() { Integer card = clss.getInteger(UML.hasMinCardinality); return card != null? card.intValue(): 0; } private boolean removeCardinality(OntResource prop) { boolean removed = false; Iterator it = props.getAll(prop); while( it.hasNext()) { OntResource res = (OntResource) it.next(); if( res.isCardinalityRestriction()) { res.remove(); it.remove(); removed = true; } } return removed; } private void setMaxCardinality(OntResource prop, int card) { removeMaxCardinality(prop); if( card < Integer.MAX_VALUE) { OntResource res = model.createMaxCardinalityRestriction(null, prop, card); clss.addSuperClass(res); props.put(prop, res); } } private void removeMaxCardinality(OntResource prop) { Iterator it = props.getAll(prop); while( it.hasNext()) { OntResource res = (OntResource) it.next(); if( res.isMaxCardinalityRestriction()) { res.remove(); it.remove(); } } } private void setMinCardinality(OntResource prop, int card) { removeMinCardinality(prop); if( card > 0 ) { OntResource res = model.createMinCardinalityRestriction(null, prop, card); clss.addSuperClass(res); props.put(prop, res); } } private void removeMinCardinality(OntResource prop) { Iterator it = props.getAll(prop); while( it.hasNext()) { OntResource res = (OntResource) it.next(); if( res.isMinCardinalityRestriction()) { res.remove(); it.remove(); } } } public void setReference(boolean state) { setStereotype(UML.byreference, state); clss.removeSuperClass(MESSAGE.Reference); // deprecated } public void setStereotype(Resource stereo, boolean state) { if(state) clss.addProperty(UML.hasStereotype, stereo); else clss.removeProperty(UML.hasStereotype, stereo); } public boolean hasStereotype(Resource stereo) { return clss.hasProperty(UML.hasStereotype, stereo) || baseClass.hasProperty(UML.hasStereotype, stereo); } public OntResource createSomeValuesFrom(OntResource prop, OntResource type) { OntResource child = model.createClass(); child.addSuperClass(type); String label = type.getLabel(null); if( label == null) label = type.getLocalName(); child.addLabel(label, null); OntResource res = model.createSomeValuesFromRestriction(null, prop, child); clss.addSuperClass(res); props.put(prop, res); return child; } public OntResource createAllValuesFrom(OntResource prop, boolean required) { OntResource child; OntResource range = prop.getRange(); if( prop.isDatatypeProperty()) { child = model.createIndividual(RDFS.Datatype); if( range != null ) child.addProperty(OWL.equivalentClass, range); } else { child = model.createClass(); if( range != null ) child.addSuperClass(range); } String label = prop.getLabel(null); if( label == null) label = prop.getLocalName(); child.addLabel(label, null); OntResource res = model.createAllValuesFromRestriction(null, prop, child); clss.addSuperClass(res); props.put(prop, res); if(required && canBeRequired(prop)) { OntResource req = model.createMinCardinalityRestriction(null, prop, 1); clss.addSuperClass(req); props.put(prop, req); } return child; } public OntResource createSuperClass(OntResource base) { OntResource child = model.createClass(namespace + base.getLocalName()); child.addSuperClass(base); return addSuperClass(child); } public OntResource addSuperClass(OntResource child) { clss.addSuperClass(child); classes.add(child); return child; } public Iterator getIndividuals() { if( clss.isEnumeratedClass()) { return clss.getOneOf().listResourceElements(); } if( enumeratedBase && classes.size() == 0) { return baseClass.listInstances(); } return Collections.EMPTY_LIST.iterator(); } public void setRestrictedEnum( boolean state) { if( state && ! clss.isEnumeratedClass()) { clss.addProperty(OWL.oneOf, model.createList(baseClass.listInstances())); } else if( ! state && clss.isEnumeratedClass()){ OntResource extent = clss.getOneOf(); clss.removeAll(OWL.oneOf); if( ! extent.equals(RDF.nil)) extent.removeList(); } } public boolean isRestrictedEnum() { return clss.isEnumeratedClass(); } public void addIndividual(OntResource indiv) { if( ! indiv.hasRDFType(baseClass)) return; if( ! clss.isEnumeratedClass()) { clss.addProperty(OWL.oneOf, model.createList().cons(indiv)); } else { OntResource extent = clss.getOneOf(); if(! extent.contains(indiv)) clss.setOneOf(extent.cons(indiv)); } } public void removeIndividual(OntResource indiv) { setRestrictedEnum( true ); OntResource extent = clss.getOneOf(); clss.setOneOf(extent.remove(indiv)); } public boolean isUnion() { return clss.hasProperty(OWL.unionOf); } public Iterator getProperties() { return props.keySet().iterator(); } public boolean hasProperty(Property prop) { return props.containsKey(prop); } public Iterator getRestrictions(Property prop) { return props.getAll(prop); } public Iterator getSuperClasses() { if(isPropertyRange()) return Collections.EMPTY_LIST.iterator(); else return classes.iterator(); } public Iterator getSubClasses() { Set subClasses = new HashSet(); ResIterator it = clss.listSubClasses(true); while( it.hasNext()) { OntResource node = it.nextResource(); if( node.isClass() && ! node.isAnon()) subClasses.add(node); } return subClasses.iterator(); } public PropertyInfo getPropertyInfo(OntResource prop) { PropertyInfo info = new PropertyInfo(clss, prop); Iterator jt = props.getAll(prop); while(jt.hasNext()) { info.scanRestrict((OntResource)jt.next()); } assert info.range != null; return info; } public OntResource getSubject() { return clss; } public String getNamespace() { return namespace; } public OntResource getBaseClass() { return baseClass; } public boolean isEnumerated() { return enumeratedBase; } public boolean isReference() { return hasStereotype(UML.byreference) || clss.hasSuperClass(MESSAGE.Reference, false); } public class PropertyInfo { private OntResource prop; private OntResource range; private OntResource domain; private int min = 0; private int max = Integer.MAX_VALUE; private PropertyInfo(OntResource domain, OntResource prop) { this.prop = prop; this.domain = domain; if(prop.isFunctionalProperty() || prop.isDatatypeProperty()) max = 1; } public OntResource getDomain() { return domain; } public OntResource getProperty() { return prop; } public OntResource getRange() { return range; } public ProfileClass createProfileClass() { if( range == null) return null; OntResource type = prop.getRange(); if( type != null && type.isClass()) return new ProfileClass(range, namespace, type); else return new ProfileClass(range, namespace); } public ProfileClass getDomainProfile() { return ProfileClass.this; } public boolean isRequired() { return min > 0; } public boolean canBeRequired() { return ProfileClass.this.canBeRequired(prop); } public boolean isFunctional() { return max == 1; } public boolean isAlwaysFunctional() { // TODO: drop datatype term here and in ctor? return prop.isFunctionalProperty() || prop.isDatatypeProperty(); } public void setMaxCardinality(int card) { if( removeCardinality(prop)) ProfileClass.this.setMinCardinality(prop, min); ProfileClass.this.setMaxCardinality(prop, card); max = card; } public int getMaxCardinality() { return max; } public void setMinCardinality(int card) { if( removeCardinality(prop)) ProfileClass.this.setMaxCardinality(prop, max); ProfileClass.this.setMinCardinality(prop, card); min = card; } public int getMinCardinality() { return min; } /** * Scan a restriction computing net cardinality for this property. */ private void scanRestrict(OntResource res) { if(res.isAllValuesFromRestriction()) scanAllValuesFromRestriction(res); else if(res.isCardinalityRestriction()) scanCardinalityRestriction(res); else if(res.isMinCardinalityRestriction()) scanMinCardinalityRestriction(res); else if(res.isMaxCardinalityRestriction()) scanMaxCardinalityRestriction(res); } private void scanAllValuesFromRestriction( OntResource res ) { range = res.getAllValuesFrom(); } private void scanCardinalityRestriction(OntResource res) { if(res.getCardinality() > min) min = res.getCardinality(); if(res.getCardinality() < max) max = res.getCardinality(); } private void scanMinCardinalityRestriction(OntResource res) { if( res.getMinCardinality() > min) min = res.getMinCardinality(); } private void scanMaxCardinalityRestriction(OntResource res) { if(res.getMaxCardinality() < max) max = res.getMaxCardinality(); } } /** * Returns a ProfileClass for each named class. */ public static Iterator getProfileClasses(final OntModel profileModel, final OntModel fullModel) { return new Iterator() { List classes = getNamedProfiles(profileModel, fullModel); int ix; public boolean hasNext() { return ix < classes.size(); } public Object next() { OntResource clss = (OntResource)classes.get(ix++); return new ProfileClass(clss, clss.getNameSpace()); } public void remove() { } }; } /** * Return a list of named classes (excluding some named support classes). */ public static List getNamedProfiles(OntModel profileModel, OntModel fullModel) { List classes = new ArrayList(); ResIterator jt = profileModel.listNamedClasses(); while( jt.hasNext()) { Resource symbol = jt.nextResource(); if(! symbol.getNameSpace().equals(MESSAGE.NS) && ! symbol.getNameSpace().equals(OWL.NS)) { classes.add(fullModel.createResource(symbol.asNode())); } } return classes; } public boolean canBeRequired(OntResource prop) { OntResource domain = prop.getDomain(); return domain== null || baseClass.hasSuperClass(domain); } /** * Broaden this class by making it a union of its * present definition and a new anonymous profile of the given * base class. * * @return the new profile class */ public OntResource createUnionMember(OntResource base) { OntResource member = model.createClass(); member.addSuperClass(base); member.addLabel(base.getLocalName(), null); addUnionMember(member); return member; } /** * Broaden this class by making it a union of its * present definition and the given profile class. * */ public void addUnionMember(OntResource child) { OntResource union = clss.getResource(OWL.unionOf); if( union != null && union.isList()) {} else if( isPropertyRange()) union = buildUnion(); else union = model.createList(); union = union.cons(child); clss.setProperty(OWL.unionOf, union); } /** * * Create an explicit union definition for this class. * * If the class has profile superclasses these become the union members. * (This is intended for the case of a single superclass that is to be * broadened.) * * If the class has property restrictions, they are moved to a fresh * anonymous class and that becomes the single union member. * * @return an RDFList representing the members of the union. */ private OntResource buildUnion() { OntResource union = model.createList(); if(! classes.isEmpty()) { for (Iterator it = classes.iterator(); it.hasNext();) { OntResource sup = (OntResource) it.next(); clss.removeSuperClass(sup); union = union.cons(sup); } classes = new HashSet(); } if( ! props.isEmpty()) { OntResource member = model.createClass(); member.addSuperClass(baseClass); member.addLabel(baseClass.getLocalName(), null); for (Iterator it = props.keySet().iterator(); it.hasNext();) { OntResource prop = (OntResource) it.next(); for (Iterator iv = props.getAll(prop); iv.hasNext();) { OntResource res = (OntResource) iv.next(); clss.removeSuperClass(res); member.addSuperClass(res); } } union = union.cons(member); props = new OneToManyMap(); } return union; } private void removeAllProps() { for (Iterator it = props.keySet().iterator(); it.hasNext();) { OntResource prop = (OntResource) it.next(); remove(prop); } } /** * Narrow this class by removing one of the union members * of which it is composed. */ public void removeUnionMember(OntResource child) { OntResource union = clss.getResource(OWL.unionOf); if( union != null && union.isList()) { union = union.remove(child); clss.setProperty(OWL.unionOf, union); } else if(isPropertyRange()) { if( child.equals(clss)) removeAllProps(); else removeSuperClass(child); } } /** * * If this class is a union, return its members as ProfileClasses * otherwise return an empty list. * * The only classes that are may be unions are the anonymous ranges of * restricted properties and these are always regarded as unions. * * If the class has an explicit unionOf axiom, its declared members are * returned. * * If the class if a property range and has property restrictions * in turn, then it is itself regarded as the single member of the union. * * Otherwise an empty List is returned. * * * @return A List of ProfileClass */ public List getUnionMembers() { List members = new ArrayList(); OntResource union = clss.getResource(OWL.unionOf); if( union != null && union.isList()) { for (ResIterator it = union.listResourceElements(); it.hasNext();) { OntResource item = it.nextResource(); if( item.isClass()) { members.add(new ProfileClass(item, namespace)); } } } else if(isPropertyRange()) { for (Iterator it = classes.iterator(); it.hasNext();) members.add(new ProfileClass((OntResource) it.next(), namespace)); if( ! props.isEmpty()) members.add(new ProfileClass(clss, namespace, baseClass)); } return members; } public boolean isPropertyRange() { return ! defaultBase.equals(OWL.Thing); } public void removeSuperClass(OntResource child) { if(classes.remove(child)) clss.removeSuperClass(child); } }