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