001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojavax.ontology;
023
024import java.util.Collections;
025import java.util.Iterator;
026import java.util.Map;
027import java.util.NoSuchElementException;
028import java.util.Set;
029import java.util.TreeMap;
030import java.util.TreeSet;
031
032import org.biojava.ontology.AlreadyExistsException;
033import org.biojava.ontology.DefaultOps;
034import org.biojava.ontology.Ontology;
035import org.biojava.ontology.OntologyOps;
036import org.biojava.ontology.Term;
037import org.biojava.ontology.Triple;
038import org.biojava.ontology.Variable;
039import org.biojava.utils.AbstractChangeable;
040import org.biojava.utils.ChangeEvent;
041import org.biojava.utils.ChangeSupport;
042import org.biojava.utils.ChangeVetoException;
043
044/**
045 * Represents an ontology that can be compared to other ontologies.
046 * @author Richard Holland
047 * @author Mark Schreiber
048 * @since 1.5
049 */
050public class SimpleComparableOntology extends AbstractChangeable implements ComparableOntology {
051    
052    private String name;
053    private String description;
054    private Set terms = new TreeSet();
055    private Map termsMap = new TreeMap();
056    private Set triples = new TreeSet();
057    private OntologyOps ops;
058    
059    /**
060     * Creates a new instance of SimpleComparableOntology with the given,
061     * immutable, non-nullable name.
062     * @param name the name of the ontology.
063     */
064    public SimpleComparableOntology(String name) {
065        if (name==null) throw new IllegalArgumentException("Name cannot be null");
066        this.name = name;
067        this.description = null;
068        this.ops = new DefaultOps() {
069            public Set getRemoteTerms() {
070                return Collections.EMPTY_SET;
071            }
072        };
073    }
074    
075    // Hibernate requirement - not for public use.
076    protected SimpleComparableOntology() {}
077    
078    /**
079     * {@inheritDoc}
080     * Ontologies are compared only by name.
081     */
082    public int compareTo(Object o) {
083        if (o==this) return 0;
084        // Hibernate comparison - we haven't been populated yet
085        if (this.name==null) return -1;
086        // Normal comparison
087        Ontology them = (Ontology)o;
088        return this.name.compareTo(them.getName());
089    }
090    
091    /**
092     * {@inheritDoc}
093     * Ontologies are equal if their names are equal.
094     */
095    public boolean equals(Object obj) {
096        if(this == obj) return true;
097        if (obj==null || !(obj instanceof Ontology)) return false;
098        // Hibernate comparison - we haven't been populated yet
099        if (this.name==null) return false;
100        // Normal comparison
101        Ontology them = (Ontology)obj;
102        return this.name.equals(them.getName());
103    }
104    
105    /**
106     * {@inheritDoc}
107     */
108    public int hashCode() {
109        int hash = 17;
110        // Hibernate comparison - we haven't been populated yet
111        if (this.name==null) return hash;
112        // Normal comparison
113        return 31*hash + this.name.hashCode();
114    }
115    
116    /**
117     * {@inheritDoc}
118     * Form: "name"
119     */
120    public String toString() {
121        return this.getName();
122    }
123    
124    /**
125     * {@inheritDoc}
126     */
127    public boolean containsTerm(String name) { return this.termsMap.containsKey(name); }
128    
129    /**
130     * {@inheritDoc}
131     */
132    public Term getTerm(String s) throws NoSuchElementException {
133        if (!this.termsMap.containsKey(s)) throw new NoSuchElementException("Ontology does not have term with name "+s);
134        return (ComparableTerm)this.termsMap.get(s);
135    }
136    
137    /**
138     * {@inheritDoc}
139     * If the term has to be created, it is added with the description "auto-generated by biojavax".
140     */
141    public ComparableTerm getOrCreateTerm(String name) {
142        try {
143            if (!this.termsMap.containsKey(name)) return (ComparableTerm)this.createTerm(name,"auto-generated by biojavax",null);
144            else return (ComparableTerm)this.getTerm(name);
145        } catch (ChangeVetoException e) {
146            return null;
147        } catch (AlreadyExistsException e) {
148            return (ComparableTerm)this.getTerm(name);
149        }
150    }
151    
152    
153    /**
154     *{@inheritDoc}
155     */
156    public ComparableTriple getOrCreateTriple(Term subject, Term object, Term predicate){
157        
158        try {
159            if (this.getTriples(subject, object, predicate).size() == 0) {
160                return (ComparableTriple)this.createTriple(subject, object, predicate, 
161                    predicate.getName()+"("+subject.getName()+", "+object.getName()+")",
162                        null);
163            }
164            else return (ComparableTriple)this.getTriples(subject, object, predicate).iterator().next();
165        } catch (ChangeVetoException e) {
166            return null;
167        } catch (AlreadyExistsException e) {
168            return (ComparableTriple)this.getTriples(subject, object, predicate).iterator().next();
169        }
170    }
171    /**
172     * {@inheritDoc}
173     */
174    public ComparableTerm getOrImportTerm(Term term) {
175        //if (term instanceof ComparableTerm) return (ComparableTerm)term;
176        try {
177            if (!this.termsMap.containsKey(term.getName())) return (ComparableTerm)this.importTerm(term,term.getName());
178            else return (ComparableTerm)this.getTerm(term.getName());
179        } catch (ChangeVetoException e) {
180            return null;
181        }
182    }
183    
184    /**
185     * {@inheritDoc}
186     */
187    public Term createTerm(String name, String description, Object[] synonyms) throws AlreadyExistsException, ChangeVetoException, IllegalArgumentException {
188        if (name==null) throw new IllegalArgumentException("Name cannot be null");
189        if (this.termsMap.containsKey(name)) throw new AlreadyExistsException("Ontology already has term with this name");
190        ComparableTerm ct = new SimpleComparableTerm(this,name,synonyms);
191        ct.setDescription(description);
192        if(!this.hasListeners(ComparableOntology.TERM)) {
193            this.termsMap.put(name,ct);
194            this.terms.add(ct);
195        } else {
196            ChangeEvent ce = new ChangeEvent(
197                    this,
198                    ComparableOntology.TERM,
199                    ct,
200                    this.termsMap.get(name)
201                    );
202            ChangeSupport cs = this.getChangeSupport(ComparableOntology.TERM);
203            synchronized(cs) {
204                cs.firePreChangeEvent(ce);
205                this.termsMap.put(name,ct);
206                this.terms.add(ct);
207                cs.firePostChangeEvent(ce);
208            }
209        }
210        return ct;
211    }
212    
213    /**
214     * {@inheritDoc}
215     * This particular implementation merely copies the term into this ontology, 
216     * and returns a pointer to the copied term. Thus the term
217     * becomes a part of this ontology instead of a pointer to another ontology.
218     * @see ComparableTerm
219     */
220    public Term importTerm(Term t, String localName) throws ChangeVetoException, IllegalArgumentException {
221        if (localName==null) localName=t.getName();
222        if (localName==null) throw new IllegalArgumentException("Name cannot be null");
223        if (this.termsMap.containsKey(localName)) return (ComparableTerm)this.termsMap.get(localName);
224        ComparableTerm ct = new SimpleComparableTerm(this,localName,t.getSynonyms());
225        ct.setDescription(t.getDescription());
226        if(!this.hasListeners(ComparableOntology.TERM)) {
227            this.termsMap.put(localName,ct);
228            this.terms.add(ct);
229        } else {
230            ChangeEvent ce = new ChangeEvent(
231                    this,
232                    ComparableOntology.TERM,
233                    ct,
234                    this.termsMap.get(localName)
235                    );
236            ChangeSupport cs = this.getChangeSupport(ComparableOntology.TERM);
237            synchronized(cs) {
238                cs.firePreChangeEvent(ce);
239                this.termsMap.put(localName,ct);
240                this.terms.add(ct);
241                cs.firePostChangeEvent(ce);
242            }
243        }
244        return ct;
245    }
246    
247    /**
248     * {@inheritDoc}
249     * If you call this method with plain Terms instead of ComparableTerms, it will 
250     * import them into the local ontology first. This is done BEFORE the check to
251     * see if the triple already exists, as obviously it can't check until it has
252     * the right terms to check with. So you may find the terms get imported but the
253     * triple does not. Moral of the story: use ComparableTerm objects!    
254     * @see ComparableTerm
255     */
256    public Triple createTriple(Term subject, Term object, Term predicate, String name, String description) throws AlreadyExistsException, ChangeVetoException {
257        if (!(subject instanceof ComparableTerm)) subject = this.getOrImportTerm(subject);
258        if (!(object instanceof ComparableTerm)) object = this.getOrImportTerm(object);
259        if (!(predicate instanceof ComparableTerm)) predicate = this.getOrImportTerm(predicate);
260        if (this.containsTriple(subject,object,predicate)) 
261            throw new AlreadyExistsException("Ontology already has triple with this subject/object/predicate combination");
262        ComparableTriple ct = new SimpleComparableTriple(this,(ComparableTerm)subject,(ComparableTerm)object,(ComparableTerm)predicate);
263        if (!this.triples.contains(ct)) {
264            if(!this.hasListeners(ComparableOntology.TRIPLE)) {
265                this.triples.add(ct);
266            } else {
267                ChangeEvent ce = new ChangeEvent(
268                        this,
269                        ComparableOntology.TRIPLE,
270                        ct,
271                        null
272                        );
273                ChangeSupport cs = this.getChangeSupport(ComparableOntology.TRIPLE);
274                synchronized(cs) {
275                    cs.firePreChangeEvent(ce);
276                    this.triples.add(ct);
277                    cs.firePostChangeEvent(ce);
278                }
279            }
280        }
281        return ct;
282    }
283    
284    /**
285     * {@inheritDoc}
286     * If you call this method with a plain Term instead of a ComparableTerm, it may
287     * not match any of the terms in the ontology as they are all stored as 
288     * ComparableTerms. So, use ComparableTerm objects!
289     * This method also removes all triples that the term is involved in.
290     * @see ComparableTerm
291     */
292    public void deleteTerm(Term t) throws ChangeVetoException {
293        // Remove all Triples involving this term.
294        for (Iterator i = this.triples.iterator(); i.hasNext();) {
295            ComparableTriple ct = (ComparableTriple)i.next();
296            if (ct.equals(t) || ct.getSubject().equals(t) || ct.getObject().equals(t) || ct.getPredicate().equals(t)) {
297                if(!this.hasListeners(ComparableOntology.TRIPLE)) {
298                    i.remove();
299                } else {
300                    ChangeEvent ce = new ChangeEvent(
301                            this,
302                            ComparableOntology.TRIPLE,
303                            null,
304                            ct
305                            );
306                    ChangeSupport cs = this.getChangeSupport(ComparableOntology.TRIPLE);
307                    synchronized(cs) {
308                        cs.firePreChangeEvent(ce);
309                        i.remove();
310                        cs.firePostChangeEvent(ce);
311                    }
312                }
313            }
314        }
315        // Remove the term.
316        if(!this.hasListeners(ComparableOntology.TERM)) {
317            if (t instanceof Triple) this.triples.remove(t);
318            else {
319                this.termsMap.remove(t.getName());
320                this.terms.remove(t);
321            }
322        } else {
323            ChangeEvent ce = new ChangeEvent(
324                    this,
325                    ComparableOntology.TERM,
326                    null,
327                    t
328                    );
329            ChangeSupport cs = this.getChangeSupport(ComparableOntology.TERM);
330            synchronized(cs) {
331                cs.firePreChangeEvent(ce);
332                if (t instanceof Triple) this.triples.remove(t);
333                else {
334                    this.termsMap.remove(t.getName());
335                    this.terms.remove(t);
336                }
337                cs.firePostChangeEvent(ce);
338            }
339        }
340    }
341    
342    /**
343     * {@inheritDoc}
344     * If you call this method with plain Terms instead of ComparableTerms, it may
345     * not match any of the triples in the ontology as they are all stored as 
346     * ComparableTerms. So, use ComparableTerm objects! The set returned is a set
347     * of ComparableTriple objects.
348     * @see ComparableTerm
349     * @see ComparableTriple
350     */
351    public Set getTriples(Term subject, Term object, Term predicate) {
352        Set results = new TreeSet();
353        for (Iterator i = this.triples.iterator(); i.hasNext();) {
354            ComparableTriple ct = (ComparableTriple)i.next();
355            if ((subject==null || ct.getSubject().equals(subject)) &&
356                    (object==null || ct.getObject().equals(object)) &&
357                    (predicate==null || ct.getPredicate().equals(predicate))) results.add(ct);
358        }
359        return results;
360    }
361    
362    /**
363     * {@inheritDoc}
364     * <b>Warning</b> this method gives access to the original 
365     * Collection not a copy. This is required by Hibernate. If you
366     * modify the object directly the behaviour may be unpredictable.
367     */
368    public void setTripleSet(Set triples) throws ChangeVetoException { this.triples = triples; }  // must be original for Hibernate
369    
370    /**
371     * {@inheritDoc}
372     * <b>Warning</b> this method gives access to the original 
373     * Collection not a copy. This is required by Hibernate. If you
374     * modify the object directly the behaviour may be unpredictable.
375     */
376    public Set getTripleSet() { return this.triples; } // must be original for Hibernate
377    
378    /**
379     * {@inheritDoc}
380     * This will always return a set of ComparableTerm objects. It is not the original
381     * set so you are safe to modify it.
382     * @see ComparableTerm
383     */
384    public Set getTerms() { return new TreeSet(this.termsMap.values()); }
385    
386    /**
387     * {@inheritDoc}
388     * <b>Warning</b> this method gives access to the original 
389     * Collection not a copy. This is required by Hibernate. If you
390     * modify the object directly the behaviour may be unpredictable.
391     * @see ComparableTerm
392     */
393    public void setTermSet(Set terms) throws ChangeVetoException {
394        this.terms = terms; // must be original for Hibernate
395        this.termsMap.clear();
396        for (Iterator i = this.terms.iterator(); i.hasNext(); ) {
397            ComparableTerm t = (ComparableTerm)i.next();
398            this.termsMap.put(t.getName(),t);
399        }
400    }
401    
402    /**
403     * {@inheritDoc}
404     * <b>Warning</b> this method gives access to the original 
405     * Collection not a copy. This is required by Hibernate. If you
406     * modify the object directly the behaviour may be unpredictable.
407     */
408    public Set getTermSet() { return this.terms; } // must be original for Hibernate
409    
410    /**
411     * {@inheritDoc}
412     * If you call this method with plain Terms instead of ComparableTerms, it may
413     * not match any of the triples in the ontology as they are all stored as 
414     * ComparableTerms. So, use ComparableTerm objects! The set returned is a set
415     * of ComparableTriple objects.
416     * @see ComparableTerm
417     * @see ComparableTriple
418     */
419    public boolean containsTriple(Term subject, Term object, Term predicate) {
420        for (Iterator i = this.triples.iterator(); i.hasNext();) {
421            ComparableTriple ct = (ComparableTriple)i.next();
422            if (ct.getSubject().equals(subject) &&
423                    ct.getObject().equals(object) &&
424                    ct.getPredicate().equals(predicate)) return true;
425        }
426        return false;
427    }
428    
429    /**
430     * {@inheritDoc}
431     */
432    public Term createTerm(String name) throws AlreadyExistsException, ChangeVetoException, IllegalArgumentException {
433        return this.createTerm(name,null,null);
434    }
435    
436    /**
437     * {@inheritDoc}
438     */
439    public Term createTerm(String name, String description) throws AlreadyExistsException, ChangeVetoException, IllegalArgumentException {
440        return this.createTerm(name,description,null);
441    }
442    
443    /**
444     * {@inheritDoc}
445     * NOT IMPLEMENTED
446     */
447    public Variable createVariable(String name, String description) throws AlreadyExistsException, ChangeVetoException, IllegalArgumentException {
448        throw new ChangeVetoException("BioSQL doesn't know what these are so we cowardly refuse to know too.");
449    }
450    
451    /**
452     * {@inheritDoc}
453     */
454    public String getDescription() {  return this.description; }
455    
456    /**
457     * {@inheritDoc}
458     */
459    public void setDescription(String description) throws ChangeVetoException {
460        if(!this.hasListeners(ComparableOntology.DESCRIPTION)) {
461            this.description = description;
462        } else {
463            ChangeEvent ce = new ChangeEvent(
464                    this,
465                    ComparableOntology.DESCRIPTION,
466                    description,
467                    this.description
468                    );
469            ChangeSupport cs = this.getChangeSupport(ComparableOntology.DESCRIPTION);
470            synchronized(cs) {
471                cs.firePreChangeEvent(ce);
472                this.description = description;
473                cs.firePostChangeEvent(ce);
474            }
475        }
476    }
477    
478    /**
479     * {@inheritDoc}
480     */
481    public String getName() { return this.name; }
482    
483    // Hibernate requirement - not for public use.
484    public void setName(String name) { this.name = name; }
485    
486    /**
487     * {@inheritDoc}
488     */
489    public OntologyOps getOps() { return this.ops; }
490    
491    // Hibernate requirement - not for public use.
492    private Integer id;
493    
494    /**
495     * Gets the Hibernate ID. Should be used with caution.
496     * @return the Hibernate ID, if using Hibernate.
497     */
498    public Integer getId() { return this.id; }
499    
500    /**
501     * Sets the Hibernate ID. Should be used with caution.
502     * @param id the Hibernate ID, if using Hibernate.
503     */
504    public void setId(Integer id) { this.id = id;}
505    
506}
507