1   /*
2    * TaxonomyImpl.java
3    * Copyright:    Copyright (c) 2001, OntoText Lab.
4    * Company:      OntoText Lab.
5    * borislav popov 02/2002 */
6   package com.ontotext.gate.ontology;
7   
8   import java.util.*;
9   import java.net.*;
10  import gate.creole.*;
11  import gate.creole.ontology.*;
12  import gate.creole.ontology.jena.JenaOntologyImpl;
13  import gate.*;
14  import gate.event.*;
15  import gate.util.*;
16  
17  /**
18   * A Taxonomy Implementation Class
19   * 
20   * @author borislav popov
21   * @author Valentin Tablan
22   */
23  public class TaxonomyImpl extends gate.creole.AbstractLanguageResource
24                                                                        implements
25                                                                        Taxonomy {
26    /** denotes a direct closure(no transitivity) */
27    public static final byte DIRECT_CLOSURE = 0;
28    /** denotes atransitive closure */
29    public static final byte TRANSITIVE_CLOSURE = 1;
30    /** Object Modification Listeners */
31    private Set listeners = new HashSet();
32    private String label;
33    private URL url;
34    private String defaultNameSpace;
35    private String version;
36    private String id;
37    private String comment;
38    private Map classesByName = new HashMap();
39    private Set classes = new HashSet();
40    private Set tops;
41    private String name;
42    protected long lastGeneratedId = 0;
43  
44    /**
45     * Adds an object modification listener.
46     * 
47     * @param listener
48     *          listener to be added.
49     */
50    public void addObjectModificationListener(ObjectModificationListener listener) {
51      if(null == listener)
52        throw new IllegalArgumentException(
53                "The object modification listener should not be [null].");
54      listeners.add(listener);
55    }
56  
57    /**
58     * Fires an object modification event.
59     * 
60     * @param event
61     *          the event to be fired
62     */
63    protected void fireObjectModificationEvent(Object source) {
64      ObjectModificationEvent event = new ObjectModificationEvent(source,
65              ObjectModificationEvent.OBJECT_MODIFIED,
66              ObjectModificationEvent.OBJECT_MODIFIED);
67      ArrayList ll = new ArrayList(listeners);
68      for(int i = 0; i < ll.size(); i++){
69        ((ObjectModificationListener)ll.get(i)).objectModified(event);
70      }
71    }
72  
73    /**
74     * Gets a taxonomy by URL. The taxonomy will be searched first among the 
75     * existing LRs and afterwards loaded by the URL if not found
76     * 
77     * @param someUrl the url of the taxonomy
78     * @return the retrieved or loaded taxonomy
79     * @throws ResourceInstantiationException
80     *           if something gets wrong with the loading
81     */
82    public static Taxonomy getOntology(URL someUrl) 
83        throws ResourceInstantiationException {
84      //This is the type of taxonomy that gets created when a previously loaded 
85      //one was not found.
86      //This would normally be a constant, but as this method is a horrible hack 
87      //and will be removed at the first opportunity, it was preferred to store
88      //this value here to insulate the hacky code from the rest of this class.
89      String DEFAULT_ONTOLOGY_TYPE = "gate.creole.ontology.jena.JenaOntologyImpl";
90      
91      //first try and find an appropriate already loaded taxonomy
92      List loadedTaxonomies = null;
93      try{
94         loadedTaxonomies = Gate.getCreoleRegister().getAllInstances(
95                 Taxonomy.class.getName());
96      }catch(GateException ge){
97        throw new ResourceInstantiationException("Cannot list loaded taxonomies", 
98                ge);
99      }
100     
101     Taxonomy result = null;
102     Iterator taxIter = loadedTaxonomies.iterator();
103     while(result == null && taxIter.hasNext()){
104       Taxonomy aTaxonomy = (Taxonomy)taxIter.next();
105       if(aTaxonomy.getURL().equals(someUrl)) result = aTaxonomy;
106     }
107     
108     //if not found, load it
109     if(result == null){
110       //hardcoded to use OWL as the ontology type
111       FeatureMap params = Factory.newFeatureMap();
112       params.put("owlLiteFileURL", someUrl);
113       
114       result = (Taxonomy)Factory.createResource(DEFAULT_ONTOLOGY_TYPE, params);
115     }
116     
117     return result;
118   }
119   
120   /**
121    * Whether the ontology has been modified switches to true when null-ing and
122    * reinfering the subclasses and super classes and tops
123    */
124   protected boolean nullBuffers = false;
125   /**
126    * Whether the ontology has been modified after loading. once it became true
127    * it stays true till a save takes place
128    */
129   protected boolean modified = false;
130 
131   /** Initialises this resource, and returns it. */
132   public Resource init() throws ResourceInstantiationException {
133     if(null == url)
134       throw new ResourceInstantiationException("URL not set (null).");
135     load();
136     return this;
137   } // init()
138 
139   public URL getURL() {
140     return url;
141   }
142 
143   public void setURL(URL aUrl) {
144     url = aUrl;
145     //null URL is now OK - used to create an empty ontology
146 //    if(null == url){
147 //      throw new GateRuntimeException("Ontology URL set to null."); 
148 //    }
149     if(url != null){
150       /* unpack the gate:path urls to absolute form */
151       if(-1 != url.getProtocol().indexOf("gate")){
152         url = gate.util.protocols.gate.Handler.class.getResource(Files
153                 .getResourcePath()
154                 + url.getPath());
155       }// if
156     }
157   }// void setURL(URL)
158 
159   /**
160    * Sets the label of the ontology
161    * 
162    * @param theLabel
163    *          the label to be set
164    */
165   public void setLabel(String theLabel) {
166     label = theLabel;
167   }
168 
169   /**
170    * Retrieves the label of the ontology
171    * 
172    * @return the label of the ontology
173    */
174   public String getLabel() {
175     return label;
176   }
177 
178   public void load() throws ResourceInstantiationException {
179     throw new UnsupportedOperationException(
180             "OntologyImpl does not support load().\nRefer to DAMLOntology.");
181   }
182 
183   public void store() throws ResourceInstantiationException {
184     throw new UnsupportedOperationException(
185             "OntologyImpl does not support store().\nRefer to DAMLOntology.");
186   }
187 
188   public void setDefaultNameSpace(String theURI) {
189     this.modified = true;
190     defaultNameSpace = theURI;
191     if(defaultNameSpace != null &&
192        -1 == defaultNameSpace.indexOf('#')){
193       defaultNameSpace = defaultNameSpace + '#';
194     }
195     fireObjectModificationEvent(this);
196   }
197 
198   public String getDefaultNameSpace() {
199     return defaultNameSpace;
200   }
201 
202   public void setVersion(String theVersion) {
203     this.modified = true;
204     version = theVersion;
205     fireObjectModificationEvent(this);
206   }
207 
208   public String getVersion() {
209     return version;
210   }
211 
212   public String getId() {
213     return id;
214   }
215 
216   public void setId(String theID) {
217     this.modified = true;
218     id = theID;
219     fireObjectModificationEvent(this);
220   }
221 
222   public String getComment() {
223     return comment;
224   }
225 
226   public void setComment(String theComment) {
227     this.modified = true;
228     comment = theComment;
229     fireObjectModificationEvent(this);
230   }
231 
232   public TClass createClass(String aName, String aComment) {
233     this.modified = true;
234     TClass theClass = new TClassImpl(Long.toString(++lastGeneratedId), aName,
235             aComment, this);
236     theClass.setURI(getDefaultNameSpace() + aName);
237     addClass(theClass);
238     nullBuffers = true;
239     fireObjectModificationEvent(this);
240     return theClass;
241   }
242 
243   /**
244    * note: if a class is deleted and there aresome subclasses of this class
245    * which lack any other super classes : then they become top classes. this
246    * could be changed on request or made optional.
247    * 
248    * @param theClass
249    *          the class to be removed
250    */
251   public void removeClass(TClass theClass) {
252     this.modified = true;
253     Iterator superi = theClass.getSuperClasses(TClass.DIRECT_CLOSURE)
254             .iterator();
255     while(superi.hasNext()){
256       TClass sc = (TClass)superi.next();
257       sc.removeSubClass(theClass);
258     } // while supers
259     Iterator subi = theClass.getSubClasses(TClass.DIRECT_CLOSURE).iterator();
260     while(subi.hasNext()){
261       TClass sc = (TClass)subi.next();
262       sc.removeSuperClass(theClass);
263     } // while subs
264     classes.remove(theClass);
265     classesByName.remove(theClass.getName());
266     nullBuffers = true;
267     fireObjectModificationEvent(this);
268   }
269 
270   public void addClass(TClass theClass) {
271     this.modified = true;
272     classes.add(theClass);
273     classesByName.put(theClass.getName(), theClass);
274     nullBuffers = true;
275     fireObjectModificationEvent(this);
276   }
277 
278   public TClass getClassByName(String theName) {
279     return (TClass)classesByName.get(theName);
280   }
281 
282   public boolean containsClassByName(String theName) {
283     return classesByName.containsKey(theName);
284   }
285 
286   public Set getClasses() {
287     return classes;
288   }
289 
290   public Iterator getClasses(Comparator comp) {
291     /** @todo: to be implemented */
292     return null;
293   }
294 
295   private void determineTops() {
296     tops = new HashSet();
297     TClass currentClass;
298     Iterator citer = classes.iterator();
299     while(citer.hasNext()){
300       currentClass = (TClass)citer.next();
301       if(currentClass.isTopClass()){
302         tops.add(currentClass);
303       }
304     } // while citer
305   } // determineTops();
306 
307   public Set getTopClasses() {
308     if(nullBuffers){
309       reinfer();
310     } // if nullBuffers
311     if(null == tops){
312       determineTops();
313     }
314     return new HashSet(tops);
315   }
316 
317   /**
318    * calculates the taxonomic distance between two classes. note that the method
319    * is relatively big, but in case similar methods are developed for graph
320    * traversal, some parts of this method would naturally become separate
321    * methods/members.
322    * 
323    * @param class1
324    *          the first class
325    * @param class2
326    *          the second class
327    */
328   public int getTaxonomicDistance(TClass class1, TClass class2) {
329     int result = 0;
330     ArrayList root = new ArrayList();
331     TClass c;
332     /* */
333     ArrayList supers1 = class1.getSuperClassesVSDistance();
334     ArrayList supers2 = class2.getSuperClassesVSDistance();
335     /* test if class1-2 are sub/super of each other */
336     for(int i1 = 0; i1 < supers1.size(); i1++){
337       if(((Set)supers1.get(i1)).contains(class2)){
338         result = i1 + 1;
339         break;
340       }
341     } // for i1
342     for(int i2 = 0; i2 < supers2.size(); i2++){
343       if(((Set)supers2.get(i2)).contains(class1)){
344         result = i2 + 1;
345         break;
346       }
347     } // for i2
348     /* find common classes/nodes */
349     if(0 == result){
350       for(int i1 = 0; i1 < supers1.size(); i1++){
351         for(int i2 = 0; i2 < supers2.size(); i2++){
352           Set s1 = (Set)supers1.get(i1);
353           Set s2 = (Set)supers2.get(i2);
354           Iterator i3 = s1.iterator();
355           while(i3.hasNext()){
356             c = (TClass)i3.next();
357             if(s2.contains(c)){
358               result = i1 + i2 + 2;
359               i1 = supers1.size();
360               i2 = supers2.size();
361               break;
362             }
363           } // while i3
364         } // for i2
365       } // for i1
366     } // if result is zero
367     return result;
368   }
369 
370   /**
371    * Compares the id,uri and url of the ontology.
372    * 
373    * @param o
374    *          another ontology to compare with
375    * @return true if id,uri and url match
376    */
377   public boolean equals(Object o) {
378     boolean result = false;
379     if(o instanceof Taxonomy){
380       Taxonomy onto = (Taxonomy)o;
381       result = true;
382       if(null != this.getId() & null != onto.getId())
383         result &= this.getId().equals(onto.getId());
384       else{
385         /*
386          * check if both ids are null; if so, consider the ontologies partially
387          * equal
388          */
389         result = this.getId() == onto.getId();
390       }
391       if(null != this.getURL() & null != onto.getURL())
392         result &= this.getURL().equals(onto.getURL());
393       else result = this.getURL() == onto.getURL();
394       if(null != this.getDefaultNameSpace() & null != onto.getDefaultNameSpace())
395         result &= this.getDefaultNameSpace().equals(onto.getDefaultNameSpace());
396       else result = this.getDefaultNameSpace() == onto.getDefaultNameSpace();
397     }
398     return result;
399   } // equals
400 
401   public String toString() {
402     return getName();
403   }
404 
405   /**
406    * Called when the ontology has been modified to re-infer all sub/super
407    * classes, tops, etc. currently could be implemented simpler but this
408    * implementation could be useful in the future
409    */
410   protected void reinfer() {
411     tops = null;
412   } // reinfer
413 
414   public void setModified(boolean isModified) {
415     modified = isModified;
416     if(modified) fireObjectModificationEvent(this);
417   }
418 
419   public boolean isModified() {
420     return modified;
421   }
422 
423   /**
424    * Check for subclass relation with transitive closure
425    * 
426    * @param cls1
427    *          the first class
428    * @param cls2
429    *          the second class
430    */
431   public boolean isSubClassOf(String cls1, String cls2) {
432     boolean result = false;
433     TClass c1 = getClassByName(cls1);
434     TClass c2 = getClassByName(cls2);
435     if(null != c1 && null != c2){
436       if(c1.equals(c2)){
437         result = true;
438       }else{
439         Set subs1;
440         subs1 = c1.getSubClasses(TClass.TRANSITIVE_CLOSURE);
441         if(subs1.contains(c2)) result = true;
442       } // else
443     } // if not null classes
444     return result;
445   }
446 
447   /**
448    * Check for subclass relation with direct closure
449    * 
450    * @param cls1
451    *          the first class
452    * @param cls2
453    *          the second class
454    */
455   public boolean isDirectSubClassOf(String cls1, String cls2) {
456     boolean result = false;
457     TClass c1 = getClassByName(cls1);
458     TClass c2 = getClassByName(cls2);
459     if(null != c1 && null != c2){
460       if(c1.equals(c2)){
461         result = true;
462       }else{
463         Set subs1;
464         subs1 = c1.getSubClasses(TClass.DIRECT_CLOSURE);
465         if(subs1.contains(c2)) result = true;
466       } // else
467     } // if not null classes
468     return result;
469   }
470 } // Taxonomy
471