Understanding and Modifying OWLAPI - nononing2014/repoProtege GitHub Wiki

In this document I will explain code structure and modification needed for supporting our proposed syntax. There are many sub folders inside directory structure of owlapi0 or owlapi1 folder. For our purpose there is not much difference in owlapi-3.5.0 and owlapi-3.5.1. Since Protege is using both the version of owlapi we need to modify both the version in exactly the same manner. You have to make exact modification in both versions. Three important sub folders are:

  • api: This sub folder includes standard API provided by OWLAPI. You can use apis and data structures from other sub folders by including them.

  • impl: This sub folder includes implementation of standard APIs in api sub folder. It may also include some other java file required by implementations of standard APIs.

  • parsers: Source code in this directory deals with reading and writing of OWL files.

I will explain my modification in three steps:

Data structures & APIs

We introduced three new data structures:

  • OWLRelation: Stores information regarding new relation created by user. You can take a look at it's source code here. We included this file at api/src/main/java/org/semanticweb/owlapi/model/. Source code in this file is self explanatory.

  • OWLRelationInstance: Stores information regarding relation between two concepts created by user. You can take a look at it's source code here. We included this file at impl/src/main/java/uk/ac/manchester/cs/owl/owlapi/. Source code in this file is self explanatory.

  • OWLRelationInstanceContainer: Stores OWLRelationInstance of a OWLClass. You can take a look at it's source code here. We included this file at impl/src/main/java/uk/ac/manchester/cs/owl/owlapi/. Source code in this file is self explanatory.

Now I will discuss modifications to files already existing in java files to introduce new APIs.

OWLOntology.java

Complete source code after modification can be seen here. This class provides for working with an ontology. We introduced following new APIs in this java file.

	void addRelation(String name);

	OWLRelation getRelation(String name);
	
	void removeRelation(String name);
	
	boolean doesContainRelation(String name);

	boolean isRelated(OWLClass A, String rel, OWLClass B);
	
	void removeRelated(OWLClass A, String rel, OWLClass B);
	
	Map<OWLRelation, List<OWLClass>> getRelationToClassMap(OWLClass A);
	
	void addRelated(OWLClass A, String rel, OWLClass B);
	
	void printAllRelations();
	
	Set<OWLClass> getAllOwlClasses(OWLClass A);
	
	Map<String,String> getEdgeLabelMap(OWLClass A);
	
	List<OWLRelation> getAllRelations();
	
	void addRelationChangeListner(OWLRelationChangeListener l);
	
	void removeRelationChangeListner(OWLRelationChangeListener l);

Explanation Each of these lines represent new api that can be accessed from ontology object.

  • addRelation Add a relation to ontology where argument passed is URI of relation.
  • getRelation Return relation already stored in ontology. Argument passed is URI of relation. Return null if that relation don't exists in this ontology.
  • removeRelation Remove a relation from ontology. Argument passed is URI of relation.
  • doesContainRelation Check if this ontology contains a relation whose URI is passed.
  • isRelated Check if class A is related to class B by relation whose URI is rel.
  • removeRelated Remove relation from class A to class B whose URI is rel.
  • getRelationToClassMap Return a map of relation to related classes for class A.
  • addRelated Add relation from class A to class B by relation rel.
  • printAllRelations Print all relation contained in this ontology. Not useful for others just for debugging purpose.
  • getAllOwlClasses Get all owl classes class A is related to.
  • getEdgeLabelMap Mainly for use by OWLViz. Return edges that should be included in visualization graph.
  • getAllRelations Return all relations defined by user in this ontology.
  • addRelationChangeListner Add a relation change listener.
  • removeRelationChangeListner Remove a relation change listener.

We have also introduced a interface OWLRelationChangeListener in api/src/main/java/org/semanticweb/owlapi/model. You can see its source code here. relationChanged method of this interface is called when a relation is changed in current ontology. addRelationChangeListner and removeRelationChangeListner accepts object implementing this interface.

OWLOntologyImpl.java

In this file we implement all APIs declared in OWLOntology.java. You can look at its source code here.

	public void addRelation(String name){
		if(name.split("#").length >= 2){
			internals.addRelation(name.split("#")[0],name.split("#")[1]);
		}
		else{
			internals.addRelation("",name);
		}
	}
	
	public void removeRelation(String name){
		if(name.split("#").length >= 2){
			internals.removeRelation(name.split("#")[0],name.split("#")[1]);
		}
		else{
			internals.removeRelation("",name);
		}
	}

	public OWLRelation getRelation(String name){
		if(name.split("#").length >= 2){
			return internals.getRelation(name.split("#")[0],name.split("#")[1]);
		}
		else{
			return internals.getRelation("",name);
		}
	}
	
	public boolean doesContainRelation(String name){
		if(name.split("#").length >= 2){
			return internals.doesContainRelation(name.split("#")[0],name.split("#")[1]);
		}
		else{
			return internals.doesContainRelation("",name);
		}
	}
	
	public boolean isRelated(OWLClass A, String rel, OWLClass B){
		if(rel.split("#").length >= 2){
			return internals.isRelated(A,rel.split("#")[0],rel.split("#")[1],B);
		}
		else{
			return internals.isRelated(A,"",rel,B);
		}
	}
	
	public void removeRelated(OWLClass A, String rel, OWLClass B){
		if(rel.split("#").length >= 2){
			internals.removeRelated(A,rel.split("#")[0],rel.split("#")[1],B);
		}
		else{
			internals.removeRelated(A,"",rel,B);
		}
	}
	
	public Map<OWLRelation, List<OWLClass>> getRelationToClassMap(OWLClass A){
		return internals.getRelationToClassMap(A);
	}

	
	public void addRelated(OWLClass A, String rel, OWLClass B){
		if(rel.split("#").length >= 2){
			internals.addRelated(A,rel.split("#")[0],rel.split("#")[1],B);
		}
		else{
			internals.addRelated(A,"",rel,B);
		}
	}
	
	public void printAllRelations(){
		internals.printRelated();
	}
	
	public Set<OWLClass> getAllOwlClasses(OWLClass A){
		return internals.getAllOwlClasses(A);
	}
	
	public Map<String,String> getEdgeLabelMap(OWLClass A){
		return internals.getEdgeLabelMap(A);
	}

	public List<OWLRelation> getAllRelations(){
		return internals.getAllRelations();
	}
	
	public void addRelationChangeListner(OWLRelationChangeListener l){
		internals.addRelationChangeListner(l);
	}
	
	public void removeRelationChangeListner(OWLRelationChangeListener l){
		internals.removeRelationChangeListner(l);
	}

All the data related to an ontology is stored using internals.class. Each instance of OWLOntology class contains one and only one internal object. As you can see all implementation declared above call internals to do all the work. At most above implementations break the URI of relation to namespace and simple name.

Internals.java

This file only provide declarations of methods supported by Internals. You can look at it's source code here. Our additions are:

	void addRelation(String ns, String name);

	OWLRelation getRelation(String ns, String name);
	
	void removeRelation(String ns, String name);
	
	boolean doesContainRelation(String ns, String name);

	boolean isRelated(OWLClass A, String ns, String name, OWLClass B);
	
	void removeRelated(OWLClass A, String ns, String name, OWLClass B);
	
	Map<OWLRelation, List<OWLClass>> getRelationToClassMap(OWLClass A);
	
	void addRelated(OWLClass A, String ns, String name, OWLClass B);
	
	void printRelated();
	
	Set<OWLClass> getAllOwlClasses(OWLClass A);
	
	Map<String,String> getEdgeLabelMap(OWLClass A);
	
	List<OWLRelation> getAllRelations();
	
	void addRelationChangeListner(OWLRelationChangeListener l);
	
	void removeRelationChangeListner(OWLRelationChangeListener l);

All these methods function in essentially the same way as methods in OWLOntology.java. They have the same name also except printRelated which is equivalent to printAllRelations.

InternalsImpl.java

This file only provide implementation of methods in Internals.java. You can look at it's source code here. We first introduced some data structure for string various information:

//Used for storing a hash map from name of relation to OWLRelation.
protected Map<String, OWLRelation> relationMap;

//Used for storing a hash map from URI of OWLClass to associated OWLRelationInstanceContainer.
protected Map<String, OWLRelationInstanceContainer> relationInstanceMap;

// List of relation change listeners currently loaded in ontology
protected List<OWLRelationChangeListener> relationChangeListners;

Other modification to InternalsImpl.java are:

	@Override
	public void addRelation(String ns, String name){
		//System.out.println("--> addRelated");
		//System.out.println("--> " + name);
		if(!doesContainRelation(ns,name)){
			relationMap.put(name,new OWLRelation(ns, name));
		
			for (OWLRelationChangeListener l : relationChangeListners){
				l.relationChanged("Add",relationMap.get(name));
			}
		}
		
	}
	
	@Override
	public void removeRelation(String ns, String name){
		if(doesContainRelation(ns,name)){
			OWLRelation rel = relationMap.get(name);
			relationMap.remove(name);
			for (OWLRelationChangeListener l : relationChangeListners){
					l.relationChanged("Remove",rel);
			}
		}
	}

	@Override
	public OWLRelation getRelation(String ns, String name){
		if(relationMap.get(name) == null){
			return null;
		}
		else
			if(relationMap.get(name).getNS().equals(ns))
				return relationMap.get(name);
			else
				return null;		
	}
	
	@Override
	public boolean doesContainRelation(String ns, String name){
		if(relationMap.get(name) == null){
			return false;
		}
		else
			if(relationMap.get(name).getNS().equals(ns))
				return true;
			else
				return false;
	}

	@Override
	public boolean isRelated(OWLClass A, String iri, String name, OWLClass B){
		if(relationInstanceMap.get(A.toStringID()) == null)
			return false;
		else if(relationMap.get(name) == null){
			return false;
		}
		else{
			OWLRelationInstanceContainer cont = relationInstanceMap.get(A.toStringID());
			return cont.doesContain(new OWLRelationInstance(relationMap.get(name),B));
		}
	}

	@Override
	public void removeRelated(OWLClass A, String iri, String name, OWLClass B){
		if(!isRelated(A, iri, name, B))
			return;
		else{
			System.out.println("--> HO HO HA HA");
			if(relationInstanceMap.get(A.toStringID()) == null)
				return;
			relationInstanceMap.get(A.toStringID()).remove(new OWLRelationInstance(relationMap.get(name),B));
			
			if(relationInstanceMap.get(A.toStringID()).getNoOfRelations() == 0)
				relationInstanceMap.remove(A.toStringID());
		}
	}

	@Override
	public Map<OWLRelation, List<OWLClass>> getRelationToClassMap(OWLClass A){
		Map<OWLRelation, List<OWLClass>> result = new HashMap<OWLRelation, List<OWLClass>>();
		if(relationInstanceMap.get(A.toStringID()) == null)
			return result;
		else
			return relationInstanceMap.get(A.toStringID()).getRelationToClassMap();
	}

	
	@Override
	public void addRelated(OWLClass A, String ns, String name, OWLClass B){
		if(relationInstanceMap.get(A.toStringID()) == null)
			relationInstanceMap.put(A.toStringID(), new OWLRelationInstanceContainer());
		relationInstanceMap.get(A.toStringID()).push(new OWLRelationInstance(relationMap.get(name),B));
	}
	
	@Override
	public void printRelated(){
		for(Map.Entry<String, OWLRelationInstanceContainer> entry: relationInstanceMap.entrySet()){
			System.out.println("Class object: " + entry.getKey());
			entry.getValue().printRelationInstances();
		}
	}
	
	@Override
	public Set<OWLClass> getAllOwlClasses(OWLClass A){
		if(relationInstanceMap.get(A.toStringID()) != null)
			return relationInstanceMap.get(A.toStringID()).getAllOwlClasses();
		Set<OWLClass> related = new HashSet<OWLClass>();
		return related;
	}
	
	@Override
	public Map<String,String> getEdgeLabelMap(OWLClass A){
		if(relationInstanceMap.get(A.toStringID()) != null)
			return relationInstanceMap.get(A.toStringID()).getEdgeLabelMap();
		Map<String,String> edgeMap = new HashMap<String,String>();
		return edgeMap;
	}
	
	@Override
	public List<OWLRelation> getAllRelations(){
		List<OWLRelation>  list = new ArrayList<OWLRelation>();
		for (String key: relationMap.keySet()) {
			list.add(relationMap.get(key));
		}
		return list;
	}
	
	@Override
	public void addRelationChangeListner(OWLRelationChangeListener l){
		relationChangeListners.add(l);
	}
	
	@Override
	public void removeRelationChangeListner(OWLRelationChangeListener l){
		relationChangeListners.remove(l);
	}

Almost all of these implementation are self explanatory.

AbstractInternalsImpl.java

This file is only used for testing purposes. As this implements Internals.java we must provide implementation for methods declare by us in Internals.java. You can look at it's source code here. This file is not important it consist of only empty methods.

	@Override
	public void addRelation(String ns, String name){
	}
	
	@Override
	public void removeRelation(String ns, String name){
	}
	
	@Override
	public OWLRelation getRelation(String ns, String name){
		return null;
	}
	
	@Override
	public boolean doesContainRelation(String ns, String name){
		System.out.println("--> Called abstract Impl object this should not happen");
		return false;
	}
	
	@Override
	public boolean isRelated(OWLClass A, String iri, String name, OWLClass B){
		return false;
	}

	@Override
	public void removeRelated(OWLClass A, String iri, String name, OWLClass B){
	}

	@Override
	public Map<OWLRelation, List<OWLClass>> getRelationToClassMap(OWLClass A){
		Map<OWLRelation, List<OWLClass>> result = new HashMap<OWLRelation, List<OWLClass>>();
		return result;
	}
	
	@Override
	public void addRelated(OWLClass A, String ns, String name, OWLClass B){
	}
	
	@Override
	public void printRelated(){
	}

	@Override
	public Set<OWLClass> getAllOwlClasses(OWLClass A){
		Set<OWLClass> related = new HashSet<OWLClass>();
		return related;
	}
	
	@Override
	public Map<String,String> getEdgeLabelMap(OWLClass A){
		Map<String,String> edgeMap = new HashMap<String,String>();
		return edgeMap;
	}
	
	@Override
	public List<OWLRelation> getAllRelations(){
		List<OWLRelation>  list = new ArrayList<OWLRelation>();
		return list;
	}

	@Override
	public void addRelationChangeListner(OWLRelationChangeListener l){
	}
	
	@Override
	public void removeRelationChangeListner(OWLRelationChangeListener l){
	}

Modification required for reading a OWL File

OWLAPI has many parsers for different variations to OWL syntax. If you open a file protege tries to parse that file using all parsers one by one. If it finds a parser which don't give a error it stops. Parser of our interest is RDFXMLParser. In this section I will demonstrate how to work with RDFXMLParser. Flow of reading a owl file looks like this: Parsing flow

As RDFXMLParser is just calling RDFParser for all the work we don't need to modify it. So three file of our interest is:

RDFConstants.java

Modified source code of this file can be found here.

In this file we only added /**REL*/ String REL = "http://www.pace.edu/rel-syntax-ns";. Purpose of this line is to declare our relation namespace.

RDFParser.java

Modified source code of this file can be found here. Main purpose of this class is to go through provided file using SAXParser and make triples out of provided syntax then pass it to OWLRDFConsumer which modifies provided ontology in accordance with passed triples. RDF Parser maintains stack of states to control flow of the program. Using these states it decides on what action to take. Since most of our syntax is very similar to current OWL we don't need to modify it relating two concepts by relation or declaring a new relation but we have to do it if we are defining relation type for example transitive relation.

Our modification:

   protected class NodeElement extends State {
             ::
             ::
        @Override
        public void startElement(String namespaceIRI, String localName,
            if (!isRDFNS || !ELT_DESCRIPTION.equals(localName)) {
             ::
             ::
		if(namespaceIRI.equals("http://www.pace.edu/rel-syntax-ns#")){
			//Calling for symmetric and other cases
			pushState( new RelationElementList(atts.getValue(0)));
			return;
		}
             ::
             ::
        }
        ::
        ::
   }

   protected class RelationElementList extends State {

		String relationName;
	
		public RelationElementList(String name) {
            relationName = name;
        }
	
        @Override
        public void startElement(String namespaceIRI, String localName,
                String qName, Attributes atts) throws SAXException {
			if(qName.equals("rdf:type")){
				pushState( new RelationRestrictionElement(relationName));
				m_state.startElement(namespaceIRI, localName, qName, atts);
			}
        }

        @Override
        public void endElement(String namespaceIRI, String localName,
                String qName) throws SAXException {
			popState();
        }

        @Override
        public void characters(char[] data, int start, int length)
                throws SAXException {
            if (!isWhitespaceOnly(data, start, length)) {
                throw new RDFParserException(
                        "Expecting an object element instead of character hoho content.",
                        m_documentLocator);
            }
        }
    }

    protected class RelationRestrictionElement extends State {

		String relationName;
	
	public RelationRestrictionElement(String name) {
            relationName = name;
        }
	
        @Override
        public void startElement(String namespaceIRI, String localName,
                String qName, Attributes atts) throws SAXException {
			if(atts.getValue(0).split("#").length == 2)
				m_consumer.statementWithResourceValue("relationConstraint",atts.getValue(0).split("#")[1], relationName);
        }

        @Override
        public void endElement(String namespaceIRI, String localName,
                String qName) throws SAXException {
			popState();
        }

        @Override
        public void characters(char[] data, int start, int length)
                throws SAXException {
            if (!isWhitespaceOnly(data, start, length)) {
                throw new RDFParserException(
                        "Expecting an object element instead of character hoho content.",
                        m_documentLocator);
            }
        }
    }

Basically what we are doing is we are pushing RelationElementList when start element of a new relation is encountered. This indicates that one or more relation restrictions may follow. In this state if we encounter a restriction like transitive restriction we will push RelationRestrictionElement to process that restriction.

OWLRDFConsumer.java

This class provides two kind of method to external owl files. They are statementWithLiteralValue and statementWithResourceValue. First one is called when we want to define a value to something for example data property. Second one statementWithResourceValue is responsible for remaining kind of triples. Our modification is:

@Override
    public void statementWithResourceValue(String subject, String predicate,
            String object) throws SAXException {
        try {
			
			if(subject.equals("relationConstraint")){
				addRelationConstraint(predicate, object);
				return;
			}
		
            IRI subjectIRI = getIRI(subject);
            IRI predicateIRI = getIRI(predicate);
            predicateIRI = getSynonym(predicateIRI);
            IRI objectIRI = getSynonym(getIRI(object));
			
			if(object.split("#").length == 2){
				if(predicate.split("#")[0].equals("http://www.pace.edu/rel-syntax-ns")){
					handleRelatedDeclaration(getOWLClass(subjectIRI),predicate,getOWLClass(objectIRI));
					this.ontology.printAllRelations();
					return;
				}	
				if(object.split("#")[1].equals("NewRelation")){
					handleRelationDeclaration(subject);
					return;
				}
			}
         ::
         ::
    }

	public void handleRelatedDeclaration(OWLClass A, String rel, OWLClass B){
		this.ontology.addRelated(A, rel, B);
	}
	
	public void handleRelationDeclaration(String relationName){
		this.ontology.addRelation(relationName);
	}

	public void addRelationConstraint(String predicate, String object){
		OWLRelation rel = ontology.getRelation(object);
		System.out.println("--> Called with");
		System.out.println(predicate);
		if(predicate.equals("AsymmetricRelation")){
            rel.setAsymmetric(true);
			return;}
        else if(predicate.equals("FunctionalRelation")){
			rel.setFunctional(true);
			return;}
        else if(predicate.equals("InverseFunctionalRelation")){
			rel.setInverseFunctional(true);
			return;}
        else if(predicate.equals("IrreflexiveRelation")){
            rel.setIrreflexive(true);
			return;}
        else if(predicate.equals("ReflexiveRelation")){
            rel.setReflexive(true);
			return;}
        else if(predicate.equals("SymmetricRelation")){
			rel.setSymmetric(true);
			return;}
        else if(predicate.equals("TransitiveRelation")){
            rel.setTransitive(true);
			return;}
        else{
            System.out.println("----> INFO: No match found in addRelationConstraint");}

	}
 

If you followed through this wiki above code should make sense.

Modification for writing a ontology in a OWL file

RDFXMLOntologyStorer is called by protege to store an ontology to a specified file. It calls RDFXMLRenderer to do all rendering. RDFXMLRenderer use XMLWriterImpl to perform actual writing to file inside a file system. It also uses OWLOntologyXMLNamespaceManager to manage namespaces of current ontology. RDFXMLRenderer implements RDFXMLRendererBase. RDFXMLRendererBase implements some of the functionality of RDFXMLRenderer. Dependency graph for writer can be represented by following diagram.

Graph for writer

RDFXMLRendererBase.java

You can look at source code here.

Our modification are:

public boolean isClassRendering = false;
public OWLClass currClass;

private void renderInOntologySignatureEntities() throws IOException {
  ::
  renderRelations();
  ::
}

private void renderRelations() throws IOException{
		try{
			List<OWLRelation> lisRelation = ontology.getAllRelations();
			if(lisRelation.size() > 0)
				writeBanner("Relations");
			for( OWLRelation rel: lisRelation)
				render(rel);
		}
		catch(Exception e){
			throw new IOException();
		}	
	}	

public void render(OWLRelation rel) throws IOException{
	}

Basically we are rendering all relation included in current ontology also we are storing currewnt class that is being rendered in currClass. We also set isClassRendering to true if currently a class is being rendered.

OWLOntologyXMLNamespaceManager

Source code can be found here. In this we only included setPrefix("rel","http://www.pace.edu/rel-syntax-ns#");line to store our created namespace as default namespace manager.

RDFXMLRenderer

This is where real rendering take place. You can checkout source code here.

Our modification:

	@Override
	public void render(OWLRelation rel) throws IOException{
		StringBuilder RelString = new StringBuilder();
		RelString.append("	");
		RelString.append("<rel:NewRelation rdf:about=\"");
		RelString.append(rel.toString());
		boolean writeEnd = false;
		if(rel.isAsymmetric()){
			if(!writeEnd){
				writeEnd = true;
				RelString.append("\">\n");
			}
			RelString.append("		<rdf:type rdf:resource=\"&owl;AsymmetricRelation\"/>\n");
		}
		if(rel.isFunctional()){
			if(!writeEnd){
				writeEnd = true;
				RelString.append("\">\n");
			}
			RelString.append("		<rdf:type rdf:resource=\"&owl;FunctionalRelation\"/>\n");
		}
		if(rel.isInverseFunctional()){
			if(!writeEnd){
				writeEnd = true;
				RelString.append("\">\n");
			}
			RelString.append("		<rdf:type rdf:resource=\"&owl;InverseFunctionalRelation\"/>\n");
		}
		if(rel.isIrreflexive()){
			if(!writeEnd){
				writeEnd = true;
				RelString.append("\">\n");
			}
			RelString.append("		<rdf:type rdf:resource=\"&owl;IrreflexiveRelation\"/>\n");
		}
		if(rel.isReflexive()){
			if(!writeEnd){
				writeEnd = true;
				RelString.append("\">\n");
			}
			RelString.append("		<rdf:type rdf:resource=\"&owl;ReflexiveRelation\"/>\n");
		}
		if(rel.isSymmetric()){
			if(!writeEnd){
				writeEnd = true;
				RelString.append("\">\n");
			}
			RelString.append("		<rdf:type rdf:resource=\"&owl;SymmetricRelation\"/>\n");
		}
		if(rel.isTransitive()){
			if(!writeEnd){
				writeEnd = true;
				RelString.append("\">\n");
			}
			RelString.append("		<rdf:type rdf:resource=\"&owl;TransitiveRelation\"/>\n");
		}
		if(writeEnd)
			RelString.append("	</rel:NewRelation>\n");
		else
			RelString.append("\"/>\n");
		relWriter.write(RelString.toString());
	}
	
	public String render(OWLClass cls) {
		Map<OWLRelation, List<OWLClass>> maRelToCls =  ont.getRelationToClassMap(cls);
		StringBuilder RelatedString = new StringBuilder();
		RelatedString.append("\n");
		for(OWLRelation rel : maRelToCls.keySet()){
			for(OWLClass c : maRelToCls.get(rel)){
				RelatedString.append("        <rel:");
				RelatedString.append(rel.getName());
				RelatedString.append(" rdf:resource=\"");
				RelatedString.append(c.getIRI().toString());
				RelatedString.append("\"/>\n");
			}
		}
		return RelatedString.toString();
	}
	
    @Override
    public void render(RDFResourceNode node) throws IOException {
        ::
		if(isClassRendering && pending.size() == 1)
				writer.writeTextContent(render(currClass));
        ::
    }

Most of the code here is self explanatory.

⚠️ **GitHub.com Fallback** ⚠️