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.biojava.ontology;
023
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.Iterator;
028import java.util.Map;
029import java.util.NoSuchElementException;
030import java.util.Set;
031
032import org.biojava.utils.AbstractChangeable;
033import org.biojava.utils.AssertionFailure;
034import org.biojava.utils.ChangeEvent;
035import org.biojava.utils.ChangeSupport;
036import org.biojava.utils.ChangeType;
037import org.biojava.utils.ChangeVetoException;
038import org.biojava.utils.Changeable;
039
040/**
041 * An ontology.
042 *
043 * <p>This is just a set of Term objects, and a set of
044 * Triple objects describing relationships between these terms.
045 * This class does not itself contain any reasoning functionality. Ontology is
046 * a collection of facts, or axioms.</p>
047 *
048 * @author Thomas Down
049 * @author Matthew Pocock
050 *
051 * @since 1.4
052 * @see org.biojavax.ontology.ComparableOntology
053 */
054
055public interface Ontology extends Changeable {
056        public static final ChangeType TERM = new ChangeType(
057                        "A term has been added or removed",
058                        "org.biojava.ontology.Ontology",
059                        "TERM"
060        );
061
062        public static final ChangeType TRIPLE = new ChangeType(
063                        "A triple has been added or removed",
064                        "org.biojava.ontology.Ontology",
065                        "TRIPLE"
066        );
067
068        /**
069         * Return the name of this ontology
070         * @return the name of the ontology
071         */
072
073        public String getName();
074
075        /** Set the name for this ontology
076         * 
077         * @param name - the name 
078         * 
079         */
080        public void setName(String name);
081        
082        /**
083         * Return a human-readable description of this ontology, or the empty
084         * string if none is available
085         * @return the description of the term
086         */
087
088        public String getDescription();
089
090        /** set the description of this ontology
091         * 
092         * @param description
093         */
094        public void setDescription(String description);
095                
096        
097        
098        /**
099         * Return all the terms in this ontology
100         * @return a Set of all Terms of the ontology.
101         */
102
103        public Set<Term> getTerms();
104
105        /**
106         * Fetch the term with the specified name.
107         * @param name the name of the term
108         *
109         * @return The term named <code>name</code>
110         * @throws NoSuchElementException if no term exists with that name
111         */
112
113        public Term getTerm(String name) throws NoSuchElementException;
114
115        /**
116         * Return all triples from this ontology which match the supplied
117         * pattern.  If any of the parameters of this method are <code>null</code>,
118         * they are treated as wildcards.
119         *
120         * @param subject The subject to search for, or <code>null</code>
121         * @param object The object to search for, or <code>null</code>
122         * @param predicate The relationship to search for, or <code>null</code>.
123         * @return a Set of triples
124         */
125
126        public Set<Triple> getTriples(Term subject, Term object, Term predicate);
127
128        /**
129         * Return the associated OntologyOps.
130         *
131         * This method should be implemented by ontology
132         * implementors to allow OntoTools
133         * to get optimized access to some usefull ontology operations. It is not
134         * intended that users will ever invoke this. A sensible dumb implementation
135         * of this would return a per-ontology instance of DefaultOps.
136         *
137         * @return the OntologyOps instance associated with this instance.
138         */
139
140        public OntologyOps getOps();
141
142        /**
143         * Create a new term in this ontology.
144         *
145         * @param name The name of the term (must be unique))
146         * @throws IllegalArgumentException if either <code>name</code> or
147         *         <code>description</code> is <code>null</code>, or violates
148         *         some other constraint of this implementation.
149         * @throws AlreadyExistsException if a term of this name already exists
150         * @return The newly created term.
151         * @throws ChangeVetoException 
152         */
153
154        public Term createTerm(String name)
155        throws
156        AlreadyExistsException,
157        ChangeVetoException,
158        IllegalArgumentException;
159
160        /**
161         * Create a new term in this ontology.
162         *
163         * @param name The name of the term (must be unique)
164         * @param description A human-readable description (may be empty)
165         * @throws IllegalArgumentException if either <code>name</code> or
166         *         <code>description</code> is <code>null</code>, or violates
167         *         some other constraint of this implementation.
168         * @throws AlreadyExistsException if a term of this name already exists
169         * @return The newly created term.
170         * @throws ChangeVetoException 
171         */
172
173        public Term createTerm(String name, String description)
174        throws
175        AlreadyExistsException,
176        ChangeVetoException,
177        IllegalArgumentException;
178
179        /**
180         * Create a new term in this ontology.
181         *
182         * @param name The name of the term (must be unique)
183         * @param description A human-readable description (may be empty)
184         * @param synonyms Some synonyms for this term.
185         * @throws IllegalArgumentException if either <code>name</code> or
186         *         <code>description</code> is <code>null</code>, or violates
187         *         some other constraint of this implementation.
188         * @throws AlreadyExistsException if a term of this name already exists
189         * @return The newly created term.
190         * @throws ChangeVetoException 
191         */
192
193        public Term createTerm(String name, String description, Object[] synonyms)
194        throws
195        AlreadyExistsException,
196        ChangeVetoException,
197        IllegalArgumentException;
198
199        /**
200         * Create a new term in this ontology that is used as a variable.
201         *
202         * @param name The name of the term (must be unique)
203         * @param description A human-readable description (may be empty)
204         * @throws IllegalArgumentException if either <code>name</code> or
205         *         <code>description</code> is <code>null</code>, or violates
206         *         some other constraint of this implementation.
207         * @throws AlreadyExistsException if a term of this name already exists
208         * @return The newly created term.
209         * @throws ChangeVetoException 
210         */
211
212        public Variable createVariable(String name, String description)
213        throws
214        AlreadyExistsException,
215        ChangeVetoException,
216        IllegalArgumentException;
217
218        /**
219         * Create a view of a term from another ontology.  If the requested term
220         * has already been imported under that name, this method returns the existing
221         * RemoteTerm object. If the term that is being imported is itself a
222         * RemoteTerm instance then first unwrap the term back to the orriginal
223         * term it represents and then produce a RemoteTerm from that. If the term
224         * being imported orriginated from this ontology, then return that term
225         * unaltered.
226         *
227         * @param t  the Term to import
228         * @param localName  the local name to import it under, optionally null
229         * @return a Term
230         * @throws ChangeVetoException 
231         * @throws IllegalArgumentException 
232         */
233
234        public Term importTerm(Term t, String localName)
235        throws
236        ChangeVetoException,
237        IllegalArgumentException;
238
239        /**
240         * Creates a new Triple.
241         *
242         * @param subject     the subject Term
243         * @param object      the object Term
244         * @param predicate    the predicate Term
245         * @param name        the name of the triple, or null
246         * @param description the description of the triple, or null
247         * @return  a new Triple over these three terms
248         * @throws AlreadyExistsException if a triple already exists with the same
249         *      subject, object and predicate, regardless of the name and description
250         * @throws ChangeVetoException
251         * @throws NullPointerException if subject, object or predicate are null
252         * @throws IllegalArgumentException if subject, object or predicate are not all
253         *      from the same ontology
254         */
255        public Triple createTriple(Term subject, Term object, Term predicate, String name, String description)
256        throws
257        AlreadyExistsException,
258        ChangeVetoException;
259
260        /**
261         * See if a triple exists in this ontology
262         * @param subject 
263         * @param object 
264         * @param predicate 
265         * @return true if contained
266         */
267
268        public boolean containsTriple(Term subject, Term object, Term predicate);
269
270        /**
271         * Remove a term from an ontology, together with all triples which refer to it.
272         * @param t 
273         * @throws ChangeVetoException 
274         */
275
276        public void deleteTerm(Term t) throws ChangeVetoException;
277
278        /**
279         * Determines if this ontology currently contains a term named <code>name</code>
280         * @param name 
281         * @return true is contained
282         */
283
284        public boolean containsTerm(String name);
285
286        /**
287         * A basic in-memory implementation of an ontology
288         *
289         * @author Thomas Down
290         * @author Matthew Pocock
291         * @since 1.3
292         */
293
294        // AP: I am setting name and description to public changeable fields
295        // e.g during parsing of an .obo file we don't know them when the ontology is instanciated
296        public final class Impl
297        extends AbstractChangeable
298        implements Ontology, java.io.Serializable {
299                /**
300                 * 
301                 */
302                private static final long serialVersionUID = -8064461497813727957L;
303                private final Map<String,Term> terms;
304                private final Set<Triple> triples;
305                private final Map<Term, Set<Triple>> subjectTriples;
306                private final Map<Term, Set<Triple>> objectTriples;
307                private final Map<Term, Set<Triple>> relationTriples;
308                private final Map<Term,RemoteTerm> remoteTerms;
309                private final Set<Term> localRemoteTerms;
310
311                private /*final*/ String name;
312                private /*final*/ String description;
313                private final OntologyOps ops;
314
315                {
316                        terms            = new HashMap<String, Term>();
317                        triples          = new HashSet<Triple>();
318                        subjectTriples   = new HashMap<Term, Set<Triple>>();
319                        objectTriples    = new HashMap<Term, Set<Triple>>();
320                        relationTriples  = new HashMap<Term, Set<Triple>>();
321                        remoteTerms      = new HashMap<Term, RemoteTerm>();
322                        localRemoteTerms = new HashSet<Term>();
323                }
324
325                public Impl(String name, String description) {
326                        this.name = name;
327                        this.description = description;
328                        ops = new DefaultOps() {
329                                /**
330                                 * 
331                                 */
332                                private static final long serialVersionUID = -2135777733685713181L;
333
334                                public Set<Term> getRemoteTerms() {
335                                        return localRemoteTerms;
336                                }
337                        };
338                }
339
340                public String getName() {
341                        return name;
342                }
343
344                public String getDescription() {
345                        return description;
346                }
347                
348                                
349                public void setDescription(String description){
350                        this.description = description;
351                }
352
353                public Set<Term> getTerms() {
354                        return new HashSet<Term>(terms.values());
355                }
356
357                public Term getTerm(String name)
358                throws NoSuchElementException
359                {
360                        Term t = terms.get(name);
361                        if (t == null) {
362                                throw new NoSuchElementException("No term named '" + name + "'");
363                        } else {
364                                return terms.get(name);
365                        }
366                }
367
368                public Set<Triple> getTriples(Term subject, Term object, Term predicate) {
369                        if(subject != null && subject.getOntology() != this) {
370                                throw new IllegalArgumentException("Subject is not in this ontology: " + subject + " " + this);
371                        }
372
373                        if(object != null && object.getOntology() != this) {
374                                throw new IllegalArgumentException("Object is not in this ontology: " + object + " " + this);
375                        }
376
377                        if(predicate != null && predicate.getOntology() != this) {
378                                throw new IllegalArgumentException("Predicate is not in this ontology: " + predicate + " " + this);
379                        }
380
381                        if (subject != null) {
382                                return filterTriples( subjectTriples.get(subject), null, object, predicate);
383                        } else if (object != null) {
384                                return filterTriples( objectTriples.get(object), subject, null, predicate);
385                        } else if (predicate != null) {
386                                return filterTriples( relationTriples.get(predicate), subject, object, null);
387                        } else {
388                                return filterTriples(triples, subject, object, predicate);
389                        }
390                }
391
392                private Set<Triple> filterTriples(Set<Triple> base, Term subject, Term object, Term predicate) {
393                        if (base == null) {
394                                return Collections.EMPTY_SET;
395                        } else if (subject == null && object == null && predicate == null) {
396                                return Collections.unmodifiableSet(new HashSet<Triple>(base));
397                        }
398
399                        Set<Triple> retval = new HashSet<Triple>();
400                        for (Iterator<Triple> i = base.iterator(); i.hasNext(); ) {
401                                Triple t = i.next();
402                                if (subject != null && t.getSubject() != subject) {
403                                        continue;
404                                }
405                                if (object != null && t.getObject() != object) {
406                                        continue;
407                                }
408                                if (predicate != null && t.getPredicate() != predicate) {
409                                        continue;
410                                }
411                                retval.add(t);
412                        }
413                        return retval;
414                }
415
416                private void addTerm(Term t)
417                throws AlreadyExistsException, IllegalArgumentException, ChangeVetoException
418                {
419                        if (terms.containsKey(t.getName())) {
420                                throw new AlreadyExistsException("Ontology " + getName() + " already contains " + t.toString());
421                        }
422
423                        if(!hasListeners()) {
424                                terms.put(t.getName(), t);
425                        } else {
426                                ChangeEvent ce = new ChangeEvent(
427                                                this,
428                                                Ontology.TERM,
429                                                t,
430                                                null
431                                );
432                                ChangeSupport cs = getChangeSupport(Ontology.TERM);
433                                synchronized(cs) {
434                                        cs.firePreChangeEvent(ce);
435                                        terms.put(t.getName(), t);
436                                        cs.firePostChangeEvent(ce);
437                                }
438                        }
439                }
440
441                public Term createTerm(String name)
442                throws AlreadyExistsException, IllegalArgumentException, ChangeVetoException
443                {
444                        Term t = new Term.Impl(this, name);
445                        addTerm(t);
446                        return t;
447                }
448
449                public Term createTerm(String name, String description)
450                throws AlreadyExistsException, IllegalArgumentException, ChangeVetoException
451                {
452                        Term t = new Term.Impl(this, name, description);
453                        addTerm(t);
454                        return t;
455                }
456
457                public Term createTerm(String name, String description, Object[] synonyms)
458                throws AlreadyExistsException, IllegalArgumentException, ChangeVetoException
459                {
460                        Term t = new Term.Impl(this, name, description, synonyms);
461                        addTerm(t);
462                        return t;
463                }
464
465                public Variable createVariable(String name, String description)
466                throws
467                AlreadyExistsException,
468                ChangeVetoException,
469                IllegalArgumentException {
470                        Variable var = new Variable.Impl(this, name, description);
471                        addTerm(var);
472                        return var;
473                }
474
475                public OntologyTerm createOntologyTerm(Ontology o)
476                throws AlreadyExistsException, ChangeVetoException
477                {
478                        OntologyTerm ot = new OntologyTerm.Impl(this, o);
479                        addTerm(ot);
480                        return ot;
481                }
482
483
484                public Term importTerm(Term t, String name)
485                throws IllegalArgumentException, ChangeVetoException
486                {
487                        // unpack any potential indirection - belt & braces
488                        while(t instanceof RemoteTerm) {
489                                t = ((RemoteTerm) t).getRemoteTerm();
490                        }
491
492                        if(t.getOntology() == this) {
493                                return t;
494                        }
495
496                        RemoteTerm rt = remoteTerms.get(t);
497                        if (rt == null) {
498                                rt = new RemoteTerm.Impl(this, t, name);
499                                try {
500                                        addTerm(rt);
501                                } catch (AlreadyExistsException e) {
502                                        throw new AssertionFailure("This term can not exist", e);
503                                }
504                                if(name == null) {
505                                        remoteTerms.put(t, rt);
506                                }
507                                localRemoteTerms.add(rt);
508                        }
509                        return rt;
510                }
511
512                public void deleteTerm(Term t)
513                throws ChangeVetoException
514                {
515                        String name = t.getName();
516                        if (terms.get(name) != t) {
517                                return; // Should this be an exception?
518                        }
519                        if(!hasListeners()) {
520                                terms.remove(name);
521                                if(t instanceof Triple) {
522                                        removeTriple((Triple) t);
523                                }
524                        } else {
525                                ChangeEvent ce = new ChangeEvent(
526                                                this,
527                                                Ontology.TERM,
528                                                null,
529                                                t
530                                );
531                                ChangeSupport cs = getChangeSupport(Ontology.TERM);
532                                synchronized(cs) {
533                                        cs.firePreChangeEvent(ce);
534                                        terms.remove(name);
535                                        if (t instanceof Triple) {
536                                                removeTriple((Triple) t);
537                                        }
538                                        cs.firePostChangeEvent(ce);
539                                }
540                        }
541                }
542
543                public boolean containsTerm(String name) {
544                        return terms.containsKey(name);
545                }
546
547                private boolean containsTerm(Term t) {
548                        return (terms.get(t.getName()) == t);
549                }
550
551                public boolean containsTriple(Term subject, Term object, Term predicate) {
552                        if(!(subject.getOntology() == this)) return false;
553                        if(!(object.getOntology() == this)) return false;
554                        if(!(predicate.getOntology() == this)) return false;
555
556                        return triples.contains(new Triple.Impl(subject, object, predicate));
557                }
558
559                public Triple createTriple(Term subject,
560                                Term object,
561                                Term predicate,
562                                String name,
563                                String description)
564                throws
565                AlreadyExistsException,
566                IllegalArgumentException,
567                ChangeVetoException,
568                NullPointerException,
569                IllegalArgumentException
570                {
571                        Triple t = new Triple.Impl(subject, object, predicate, name, description);
572                        if (!containsTerm(subject)) {
573                                throw new IllegalArgumentException("Ontology " + getName() + " doesn't contain " + subject);
574                        }
575                        if (!containsTerm(predicate)) {
576                                throw new IllegalArgumentException("Ontology " + getName() + " doesn't contain " + predicate);
577                        }
578                        if (!containsTerm(object)) {
579                                throw new IllegalArgumentException("Ontology " + getName() + " doesn't contain " + object);
580                        }
581                        if (triples.contains(t)) {
582                                throw new AlreadyExistsException("Ontology " + getName() + " already contains " + t.toString());
583                        }
584
585                        if(!hasListeners()) {
586                                addTerm(t);
587                                addTriple(t);
588                        } else {
589                                ChangeEvent ce = new ChangeEvent(
590                                                this,
591                                                Ontology.TRIPLE,
592                                                t,
593                                                null
594                                );
595                                ChangeSupport cs = getChangeSupport(Ontology.TRIPLE);
596                                synchronized(cs) {
597                                        cs.firePreChangeEvent(ce);
598                                        addTerm(t);
599                                        addTriple(t);
600                                        cs.firePostChangeEvent(ce);
601                                }
602                        }
603                        return t;
604                }
605
606                private void addTriple(Triple t) {
607                        triples.add(t);
608                        pushTriple(subjectTriples, t.getSubject(), t);
609                        pushTriple(objectTriples, t.getObject(), t);
610                        pushTriple(relationTriples, t.getPredicate(), t);
611                }
612
613                private void pushTriple(Map<Term,Set<Triple>> m, Term key, Triple t) {
614                        Set<Triple> s = m.get(key);
615                        if (s == null) {
616                                s = new HashSet<Triple>();
617                                m.put(key, s);
618                        }
619                        s.add(t);
620                }
621
622                private void removeTriple(Triple t) {
623                        triples.remove(t);
624                        pullTriple(subjectTriples, t.getSubject(), t);
625                        pullTriple(objectTriples, t.getObject(), t);
626                        pullTriple(relationTriples, t.getPredicate(), t);
627                }
628
629                private void pullTriple(Map<Term, Set<Triple>> subjectTriples2, Term key, Triple t) {
630                        Set<Triple> s = subjectTriples2.get(key);
631                        if (s != null) {
632                                s.remove(t);
633                        }
634                }
635
636                public OntologyOps getOps() {
637                        return ops;
638                }
639
640                public String toString() {
641                        return "ontology: " + getName();
642                }
643
644                public void setName(String name) {
645                        this.name=name;
646                        
647                }
648        }
649}
650