/*
* 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.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.hp.hpl.jena.graph.FrontsNode;
import com.hp.hpl.jena.reasoner.InfGraph;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;
import au.com.langdale.inference.LOG;
import au.com.langdale.jena.JenaTreeModelBase;
import au.com.langdale.jena.UMLTreeModel.PackageNode;
import au.com.langdale.jena.UMLTreeModel.SubClassNode;
import au.com.langdale.jena.UMLTreeModel.SuperClassNode;
import au.com.langdale.profiles.ProfileClass.PropertyInfo;
import au.com.langdale.xmi.UML;
import au.com.langdale.kena.Composition;
import au.com.langdale.kena.OntModel;
import au.com.langdale.kena.OntResource;
import au.com.langdale.kena.Resource;
public class ProfileModel extends JenaTreeModelBase {
public ProfileModel() {}
private String namespace = "http://example.com/NoName#";
private OntModel profileModel, backgroundModel;
private Refactory refactory;
public Refactory getRefactory() {
return refactory;
}
public String getNamespace() {
return namespace;
}
public void setBackgroundModel(OntModel backgroundModel) {
super.setOntModel(null);
this.backgroundModel = backgroundModel;
initModels();
}
@Override
public void setOntModel(OntModel profileModel) {
super.setOntModel(null);
this.profileModel = profileModel;
if( profileModel != null) {
OntResource ont = profileModel.getValidOntology();
if( ont != null) {
namespace = ont.getURI() + "#";
setRootResource(ont);
}
}
initModels();
}
private void initModels() {
if( profileModel != null && backgroundModel != null) {
OntModel fullModel = Composition.merge(profileModel, backgroundModel);
super.setOntModel(fullModel);
refactory = new Refactory(profileModel, fullModel);
}
}
@Override
protected List findResourcePathTo(FrontsNode symbol) {
OntResource target = profileModel.createResource(symbol.asNode());
if( getRoot() == null || target == null)
return null;
OntResource start = getRoot().getSubject();
if( start == null)
return null;
ArrayList path = new ArrayList(6);
path.add(target);
while( ! target.equals(start)) {
OntResource parent;
if( target.isClass() && target.isURIResource())
parent = start;
else {
parent = findParent(target);
while( parent != null && ! parent.hasRDFType(OWL.Class))
parent = findParent(parent);
}
if(parent == null)
return null;
path.add(parent);
target = parent;
}
Collections.reverse(path);
return path;
}
private FrontsNode[] steps = new FrontsNode[] {
RDFS.subClassOf, OWL.allValuesFrom, RDF.first, RDF.rest, OWL.unionOf, OWL.oneOf
};
private OntResource findParent(OntResource target) {
for(int ix = 0; ix < steps.length; ix++) {
OntResource parent = target.getSubject(steps[ix]);
if( parent != null)
return parent;
}
// OntResource type = backgroundModel.createResource(target.asNode()).getResource(RDF.type);
// if( type != null && type.hasProperty(UML.hasStereotype, UML.enumeration))
// return profileModel.createResource(type.asNode());
return null;
}
/**
* Interface for manipulating the cardinality of a Node.
*
* Both TypeNodes (concrete classes) and ElementNodes (properties)
* have cardinality.
*
*/
public interface Cardinality {
public int getMaxCardinality();
public int getMinCardinality();
public boolean setMaxCardinality(int max);
public boolean setMinCardinality(int min);
public boolean isMinVariable();
public boolean isMaxVariable();
}
public abstract class SortedNode extends ModelNode {
public ProfileModel getProfileModel() {
return ProfileModel.this;
}
@Override
protected String collation() {
return "0" + toString();
}
public void setName(String newName) {
getSubject().setLabel(newName, null);
changed();
}
public void setComment(String text) {
getSubject().setComment(text, null);
}
protected abstract void create(Node node);
protected void createAnon(Node node) {
create(node);
}
protected void createDeep(Node node) {
create(node);
}
protected abstract void destroy();
public void profileRemove(Node node) {
if( node instanceof SortedNode ) {
SortedNode target = (SortedNode) node;
target.destroy();
InfGraph ig = (InfGraph) getSubject().getOntModel().getGraph();
ig.rebind();
structureChanged();
}
}
public void profileAddAll(Collection args) {
for (Iterator it = args.iterator(); it.hasNext();)
create((Node) it.next());
structureChanged();
}
public void profileAddAllDeep(Collection args) {
for (Iterator it = args.iterator(); it.hasNext();)
createDeep((Node) it.next());
getRoot().structureChanged();
}
public void profileAddAnon(Node node) {
createAnon(node);
structureChanged();
}
public Collection profileExpandArgs(Node node) {
Collection args = new ArrayList();
ProfileModel.buildArguments(args, node);
return args;
}
}
public class CatalogNode extends SortedNode {
private OntResource subject;
public CatalogNode(OntResource message) {
this.subject = message;
}
@Override
protected void populate() {
Iterator it = ProfileClass.getProfileClasses(profileModel, getOntModel());
while( it.hasNext()) {
ProfileClass profile = (ProfileClass) it.next();
if( profile.getBaseClass() != null) {
if( profile.getBaseClass().equals(MESSAGE.Message) )
add( new EnvelopeNode(profile));
else
add( new TypeNode(profile));
}
}
}
/**
* Create a new envelope class with the given URI.
*/
public OntResource profileAddEnvelope(String uri) {
OntResource child = getOntModel().createClass(uri);
child.addSuperClass(MESSAGE.Message);
return child;
}
@Override
protected void destroy() {
// can't destroy this node
}
/**
* Create a new named class derived from the given class.
*/
@Override
protected void create(Node node) {
OntResource base = node.getSubject();
if( base.isClass())
getRefactory().createProfileClass(base);
}
@Override
protected void createDeep(Node node) {
OntResource base = node.getSubject();
if( base.isClass()) {
getRefactory().createCompleteProfile(base, true);
}
}
@Override
public boolean getErrorIndicator() {
return false;
}
@Override
public OntResource getSubject() {
return subject;
}
public String abbrevNamespace() {
try {
String path = new URI(namespace).getPath();
while( path.endsWith("/"))
path = path.substring(0, path.length()-1);
return path.substring(path.lastIndexOf('/') + 1);
} catch (URISyntaxException e) {
return namespace;
}
}
}
/**
* Base for all nodes. Handles the creation
* of child nodes.
*
*
*/
abstract public class ProfileNode extends SortedNode {
protected ProfileNode(ProfileClass profile) {
this.profile = profile;
}
protected ProfileClass profile;
/**
* Remove all definitions associated with a profile.
*
*/
@Override
protected void destroy() {
// destroy dependent nodes
Iterator it = iterator();
while(it.hasNext()) {
SortedNode node = (SortedNode) it.next();
node.destroy();
}
profile.getSubject().remove();
}
/**
* The (usually anonymous) message element class.
*/
@Override
public OntResource getSubject() {
return profile.getSubject();
}
@Override
public boolean getErrorIndicator() {
return profile.getSubject().hasProperty(LOG.hasProblems);
}
public boolean setStereotype(Resource stereo, boolean state) {
if( hasStereotype(stereo) == state)
return false;
profile.setStereotype(stereo, state);
changed();
return true;
}
public boolean hasStereotype(Resource stereo) {
return profile.hasStereotype(stereo);
}
/**
* The CIM class on which this message element is based.
*/
public OntResource getBaseClass() {
return profile.getBaseClass();
}
/**
* The CIM property or class of which this element is profile.
*/
@Override
public OntResource getBase() {
return profile.getBaseClass();
}
@Override
public void structureChanged() {
profile.analyse();
super.structureChanged();
}
public boolean isEnumerated() {
return profile.isEnumerated();
}
public ProfileClass getProfile() {
return profile;
}
}
public abstract class NaturalNode extends ProfileNode {
/**
* A subordinate element in a message.
*/
public class ElementNode extends ProfileNode implements Cardinality {
/**
* A union member
*/
public class SubTypeNode extends GeneralTypeNode {
public SubTypeNode(ProfileClass profile) {
super(profile);
}
@Override
public boolean isPruned() {
return true;
}
@Override
protected void destroy() {
ElementNode.this.getProfile().removeUnionMember(getSubject());
if( profile.getSubject().isAnon())
super.destroy();
}
}
private OntResource prop;
private PropertyInfo info;
/**
* The element is defined by a property and the
* collection of restrictions applied to that property.
*/
public ElementNode(PropertyInfo info) {
super( info.createProfileClass());
this.info = info;
prop = info.getProperty();
}
/**
* Override in concrete nodes to control sort order.
* @return a String that will serve as the sort key.
*/
@Override
protected String collation() {
String base = toString();
String key = base.toLowerCase() + base;
if(isDatatype() || isEnumerated()) {
if( getName().equals("mRID"))
return "1" + key;
else
return "2" + key;
}
else
return "3" + key;
}
@Override
public Class getIconClass() {
if( profile.isReference())
return ReferenceNode.class;
else if(prop.isDatatypeProperty())
return AttributeNode.class;
else
return ElementNode.class;
}
@Override
public String toString() {
return getName() + " " + getCardString();
}
public String getCardString() {
int max = getMaxCardinality();
int min = getMinCardinality();
return cardString(min) + ".." + cardString(max);
}
public OntResource getBaseProperty() {
return prop;
}
/**
* The CIM property or class of which this element is profile.
*/
@Override
public OntResource getBase() {
return prop;
}
public boolean isReference() {
return profile.isReference();
}
public boolean isDatatype() {
return prop.isDatatypeProperty();
}
public boolean isMaxVariable() {
return ! info.isAlwaysFunctional();
}
public boolean isMinVariable() {
return info.canBeRequired();
}
public int getMaxCardinality() {
return info.getMaxCardinality();
}
public int getMinCardinality() {
return info.getMinCardinality();
}
@Override
public boolean getAllowsChildren() {
return ! prop.isDatatypeProperty();
}
@Override
public boolean getErrorIndicator() {
return prop.hasProperty(LOG.hasProblems) || super.getErrorIndicator();
}
public void setReference(boolean state) {
profile.setReference(state);
changed();
}
public boolean setMaxCardinality(int card) {
if(getMaxCardinality() == card || card < getMinCardinality() || ! isMaxVariable())
return false;
info.setMaxCardinality(card);
changed();
return true;
}
public boolean setMinCardinality(int card) {
if(getMinCardinality() == card || card > getMaxCardinality() || ! isMinVariable())
return false;
info.setMinCardinality(card);
changed();
return true;
}
@Override
protected void populate() {
populateUnion();
}
private void populateUnion() {
for (Iterator jt = profile.getUnionMembers().iterator(); jt.hasNext();) {
add( new SubTypeNode((ProfileClass) jt.next()));
}
}
public Collection profileExpandArgs(Node node) {
return Collections.singletonList(node);
}
@Override
protected void create(Node node) {
OntResource base = node.getSubject();
if( base.isClass() && ! isDatatype()) {
OntResource member = getRefactory().findOrCreateNamedProfile(base);
profile.addUnionMember(member);
}
}
@Override
protected void createAnon(Node node) {
OntResource base = node.getSubject();
if( base.isClass() && ! isDatatype()) {
profile.createUnionMember(base);
}
}
@Override
protected void destroy() {
super.destroy();
NaturalNode.this.getProfile().remove(getBase());
}
}
/**
* A supertype of a root element or other supertype in a message
*/
public class SuperTypeNode extends GeneralTypeNode {
public SuperTypeNode(ProfileClass profile) {
super(profile);
}
@Override
public Class getIconClass() {
return SuperTypeNode.class;
}
@Override
public boolean isPruned() {
return true;
}
@Override
protected void destroy() {
NaturalNode.this.getProfile().removeSuperClass(getSubject());
}
}
public class EnumValueNode extends SortedNode {
private OntResource subject;
public EnumValueNode(OntResource subject) {
this.subject = subject;
}
@Override
public boolean getErrorIndicator() {
return subject.hasProperty(LOG.hasProblems);
}
@Override
public OntResource getSubject() {
return subject;
}
@Override
protected void populate() {
// there are no children
}
@Override
public boolean getAllowsChildren() {
return false;
}
@Override
protected void destroy() {
NaturalNode.this.getProfile().removeIndividual(getSubject());
}
@Override
public void setName(String name) {
// can't change the name
}
@Override
public void setComment(String name) {
// can't comment
}
@Override
protected void create(Node node) {
// can't add children
}
}
protected NaturalNode(ProfileClass profile) {
super(profile);
}
/**
* Create a child element in the underlying ontology.
*/
@Override
protected void create(Node node) {
OntResource base = node.getSubject();
if( base.isProperty()) {
profile.createAllValuesFrom(base, true);
}
else if( base.hasRDFType(profile.getBaseClass())) {
profile.addIndividual(base);
}
}
@Override
protected void createDeep(Node node) {
OntResource prop = node.getSubject();
if( prop.isProperty()) {
profile.createAllValuesFrom(prop, true);
getRefactory().createDefaultRange(profile, prop);
}
}
@Override
protected void populate() {
populateProps();
populateClasses();
populateIndividuals();
}
private void populateIndividuals() {
Iterator it = profile.getIndividuals();
while(it.hasNext()) {
add( new EnumValueNode((OntResource)it.next()));
}
}
/**
* Recognise property restrictions as defining child elements.
*/
private void populateProps() {
Iterator it = profile.getProperties();
while( it.hasNext()) {
PropertyInfo info = profile.getPropertyInfo((OntResource)it.next());
// only add the child if a restriction identified its range class.
if(info.getRange() != null)
add(new ElementNode(info));
}
}
private void populateClasses() {
Iterator it = profile.getSuperClasses();
while( it.hasNext()) {
OntResource clss = (OntResource) it.next();
SuperTypeNode node = new SuperTypeNode(new ProfileClass(clss, namespace));
add(node);
}
}
}
public abstract class GeneralTypeNode extends NaturalNode implements Cardinality {
public GeneralTypeNode(ProfileClass profile) {
super(profile);
}
@Override
public Class getIconClass() {
if(hasStereotype(UML.concrete))
return RootElementNode.class;
else if(hasStereotype(UML.compound))
return CompoundElementNode.class;
else if(hasStereotype(UML.enumeration))
return EnumElementNode.class;
else if( profile.getSubject().isAnon())
return AnonTypeNode.class;
else
return TypeNode.class;
}
public int getMaxCardinality() {
return profile.getMaxCardinality();
}
public int getMinCardinality() {
return profile.getMinCardinality();
}
public boolean isMaxVariable() {
return hasStereotype(UML.concrete);
}
public boolean isMinVariable() {
return hasStereotype(UML.concrete);
}
public boolean setMaxCardinality(int card) {
if(getMaxCardinality() == card || card < getMinCardinality() || ! isMaxVariable())
return false;
profile.setMaxCardinality(card);
changed();
return true;
}
public boolean setMinCardinality(int card) {
if(getMinCardinality() == card || card > getMaxCardinality() || ! isMinVariable())
return false;
profile.setMinCardinality(card);
changed();
return true;
}
}
/**
* A root element in a message
*/
public class TypeNode extends GeneralTypeNode {
public TypeNode(ProfileClass profile) {
super(profile);
}
@Override
protected void destroy() {
getRefactory().remove(getSubject());
super.destroy();
}
}
/**
* The root node of a message.
*/
public class EnvelopeNode extends ProfileNode {
/**
* A root element in an envelope
*/
public class MessageNode extends NaturalNode {
public MessageNode(ProfileClass profile) {
super(profile);
}
@Override
public Class getIconClass() {
return RootElementNode.class;
}
@Override
protected void destroy() {
super.destroy();
EnvelopeNode.this.getProfile().remove(MESSAGE.about, this.getProfile().getSubject());
}
}
public EnvelopeNode(ProfileClass profile) {
super(profile);
}
@Override
protected void populate() {
Iterator it = profile.getRestrictions(MESSAGE.about);
while(it.hasNext()) {
OntResource res = (OntResource) it.next();
if( res.isSomeValuesFromRestriction()) {
OntResource type = res.getSomeValuesFrom();
if(type != null && type.isClass()) {
Node node = new MessageNode(new ProfileClass(type, namespace));
add(node);
}
}
}
}
@Override
protected void create(Node node) {
OntResource type = node.getSubject();
if( ! type.isClass())
return;
OntResource prop = profileModel.createOntProperty(MESSAGE.about.getURI());
profile.createSomeValuesFrom(prop, type);
}
}
/**
* A marker class returned by getIconClass() is the node is attribute-like;
*
*/
public interface AttributeNode {}
/**
* A marker class returned by getIconClass() is the node is reference-like;
*
*/
public interface ReferenceNode {}
/**
* A marker class returned for concrete type nodes;
*
*/
public interface RootElementNode {}
/**
* A marker class returned for compound type nodes;
*
*/
public interface CompoundElementNode {}
/**
* A marker class returned for enumerated property nodes;
*
*/
public interface EnumElementNode {}
/**
* A marker class returned for anonymous types.
*/
public interface AnonTypeNode {}
/**
* The root should be a subclass of the generic Message class.
*/
@Override
protected Node classify(OntResource root) {
if( root.hasRDFType(OWL.Ontology))
return new CatalogNode(root);
if( root.hasSuperClass(MESSAGE.Message))
return new EnvelopeNode(new ProfileClass(root, namespace));
return new TypeNode(new ProfileClass(root, namespace));
}
private static void buildArguments(Collection args, Node node) {
if((node instanceof SubClassNode)
|| (node instanceof SuperClassNode)
|| (node instanceof PackageNode)) {
Iterator it = node.iterator();
while (it.hasNext())
buildArguments(args, (Node) it.next());
}
else
args.add(node);
}
public static String cardString(int card) {
return cardString(card, "n");
}
public static String cardString(int card, String unbounded) {
return card == Integer.MAX_VALUE? unbounded: Integer.toString(card);
}
public static int cardInt(String symbol) {
if(symbol.equals("n"))
return Integer.MAX_VALUE;
int card = Integer.parseInt(symbol);
if( card < 0)
throw new NumberFormatException();
return card;
}
}