View Javadoc
1   /*
2    * The baseCode project
3    *
4    * Copyright (c) 2013 University of British Columbia
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    * http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
12   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
13   * specific language governing permissions and limitations under the License.
14   */
15  package ubic.basecode.ontology.model;
16  
17  import com.hp.hpl.jena.ontology.*;
18  import com.hp.hpl.jena.rdf.model.*;
19  import com.hp.hpl.jena.util.iterator.ExtendedIterator;
20  import com.hp.hpl.jena.util.iterator.Filter;
21  import org.apache.commons.collections4.CollectionUtils;
22  
23  import java.util.*;
24  import java.util.stream.Collectors;
25  
26  /**
27   * Represents a class in an ontology
28   *
29   * @author Paul
30   */
31  public class OntologyTermImpl extends AbstractOntologyResource implements OntologyTerm {
32  
33      private static final String HAS_ALTERNATE_ID = "http://www.geneontology.org/formats/oboInOwl#hasAlternativeId";
34      private static final String NOTHING = "http://www.w3.org/2002/07/owl#Nothing";
35  
36      /**
37       * Properties through which propagation is allowed for {@link #getParents(boolean)}
38       */
39      private static final Set<String> PROPAGATE_PARENT_URIS = new HashSet<>();
40  
41      private static final Set<String> REJECT_PARENT_URIS = new HashSet<>();
42  
43      /**
44       *
45       */
46      private static final long serialVersionUID = 1L;
47  
48      static {
49          CollectionUtils.addAll( PROPAGATE_PARENT_URIS,
50                  "http://www.obofoundry.org/ro/ro.owl#proper_part_of",
51                  "http://purl.obolibrary.org/obo/BFO_0000050" // part of
52          );
53          CollectionUtils.addAll( REJECT_PARENT_URIS,
54                  "http://www.ifomis.org/bfo/1.1/snap#IndependentContinuant",
55                  "http://www.ifomis.org/bfo/1.1/snap#Continuant",
56                  "http://www.ifomis.org/bfo/1.1/snap#MaterialEntity",
57                  // anatomical entity
58                  "http://ontology.neuinfo.org/NIF/BiomaterialEntities/NIF-GrossAnatomy.owl#birnlex_6" );
59      }
60  
61      private String label = null;
62      private String localName = null;
63  
64      /**
65       * Ontology class underlying this term.
66       */
67      private final transient OntClass ontResource;
68  
69      /**
70       * Extra sets of properties to use when navigating parents and children of a term.
71       */
72      private final transient Set<Property> propagateParentsProperties;
73  
74      public OntologyTermImpl( OntClass resource ) {
75          this.ontResource = resource;
76          if ( ontResource != null ) {
77              this.label = ontResource.getLabel( "EN" );
78              if ( this.label == null ) this.label = ontResource.getLabel( null );
79              this.localName = ontResource.getLocalName();
80              this.propagateParentsProperties = PROPAGATE_PARENT_URIS.stream()
81                      .map( uri -> resource.getModel().getProperty( uri ) )
82                      .filter( Objects::nonNull )
83                      .collect( Collectors.toSet() );
84          } else {
85              this.propagateParentsProperties = Collections.emptySet();
86          }
87      }
88  
89      @Override
90      public boolean equals( Object obj ) {
91          if ( this == obj ) return true;
92  
93          if ( !super.equals( obj ) ) return false;
94          if ( getClass() != obj.getClass() ) return false;
95  
96          final OntologyTermImpl../ubic/basecode/ontology/model/OntologyTermImpl.html#OntologyTermImpl">OntologyTermImpl that = ( OntologyTermImpl ) obj;
97          if ( this.getUri() != null ) {
98              return Objects.equals( this.getUri(), that.getUri() );
99          }
100         return Objects.equals( this.getTerm(), that.getTerm() );
101     }
102 
103     @Override
104     public Collection<String> getAlternativeIds() {
105         Collection<String> results = new HashSet<>();
106 
107         Property alternate = ResourceFactory.createProperty( HAS_ALTERNATE_ID );
108         StmtIterator it = this.ontResource.listProperties( alternate );
109         while ( it.hasNext() ) {
110             Statement statement = it.next();
111             results.add( statement.asTriple().getMatchObject().getLiteralLexicalForm() );
112         }
113 
114         return results;
115 
116     }
117 
118     @Override
119     public Collection<AnnotationProperty> getAnnotations() {
120         Collection<AnnotationProperty> annots = new HashSet<>();
121         StmtIterator iterator = ontResource.listProperties();
122         // this is a little slow because we have to go through all statements for the term.
123         while ( iterator.hasNext() ) {
124             Statement state = iterator.next();
125             OntResource res = state.getPredicate().as( OntResource.class );
126             if ( res.isAnnotationProperty() ) {
127                 com.hp.hpl.jena.ontology.AnnotationProperty p = res.asAnnotationProperty();
128                 RDFNode n = state.getObject();
129                 annots.add( new AnnotationPropertyImpl( p, n ) );
130             }
131         }
132         return annots;
133     }
134 
135     @Override
136     public Collection<OntologyTerm> getChildren( boolean direct ) {
137         return getChildren( direct, true );
138     }
139 
140     @Override
141     public Collection<OntologyTerm> getChildren( boolean direct, boolean includePartOf ) {
142         Collection<OntClass> result = new HashSet<>();
143         ExtendedIterator<OntClass> iterator = ontResource.listSubClasses( direct )
144                 .filterDrop( new EqualityByUriFilter( NOTHING ) );
145         OntModel model = ontResource.getOntModel();
146 
147         while ( iterator.hasNext() ) {
148             OntClass c = iterator.next();
149 
150             // bnode
151             if ( c.getURI() == null )
152                 continue;
153 
154             result.add( c );
155         }
156 
157         if ( includePartOf ) {
158             Property subClassOf = model.getProfile().SUB_CLASS_OF();
159             ExtendedIterator<Restriction> restrictionsIterator = model.listRestrictions()
160                     .filterKeep( new RestrictionWithPropertyAndValueFilter( propagateParentsProperties, ontResource ) );
161             while ( restrictionsIterator.hasNext() ) {
162                 Restriction r = restrictionsIterator.next();
163                 ResIterator ss = model.listResourcesWithProperty( subClassOf, r );
164                 while ( ss.hasNext() ) {
165                     Resource s = ss.next();
166                     if ( s.getURI() != null ) {
167                         OntClass o = model.getOntClass( s.getURI() );
168                         if ( o != null ) {
169                             result.add( o );
170                         }
171                     }
172                 }
173             }
174         }
175 
176         return result.stream().map( OntologyTermImpl::new ).collect( Collectors.toSet() );
177     }
178 
179     /**
180      * Filter that retain resources with the given URI.
181      */
182     private static class EqualityByUriFilter extends Filter<OntClass> {
183         private final String uri;
184 
185         private EqualityByUriFilter( String uri ) {
186             this.uri = uri;
187         }
188 
189         @Override
190         public boolean accept( OntClass o ) {
191             return uri.equals( o.getURI() );
192         }
193     }
194 
195     /**
196      * Filter that retain only the restrictions with any of the given properties and resource as value.
197      */
198     private static class RestrictionWithPropertyAndValueFilter extends Filter<Restriction> {
199         private final Set<Property> properties;
200         private final Resource resource;
201 
202         private RestrictionWithPropertyAndValueFilter( Set<Property> properties, OntClass resource ) {
203             this.properties = properties;
204             this.resource = resource;
205         }
206 
207         @Override
208         public boolean accept( Restriction o ) {
209             return hasRestrictionValue( o, resource ) && properties.stream().anyMatch( o::onProperty );
210         }
211     }
212 
213     /*
214      * (non-Javadoc)
215      *
216      * @see ubic.gemma.ontology.OntologyTerm#getComment()
217      */
218     @Override
219     public String getComment() {
220         String comment = this.ontResource.getComment( null );
221         return comment == null ? "" : comment;
222     }
223 
224     /*
225      * (non-Javadoc)
226      *
227      * @see ubic.gemma.ontology.OntologyTerm#getIndividuals()
228      */
229     @Override
230     public Collection<OntologyIndividual> getIndividuals() {
231         return getIndividuals( true );
232     }
233 
234     @Override
235     public Collection<OntologyIndividual> getIndividuals( boolean direct ) {
236         Collection<OntologyIndividual> inds = new HashSet<>();
237         ExtendedIterator<? extends OntResource> iterator = this.ontResource.listInstances( direct );
238         while ( iterator.hasNext() ) {
239             OntResource r = iterator.next();
240             if ( r.isIndividual() ) {
241                 inds.add( new OntologyIndividualImpl( r.asIndividual() ) );
242             }
243         }
244         return inds;
245     }
246 
247     @Override
248     public String getLabel() {
249         return label;
250     }
251 
252     @Override
253     public String getLocalName() {
254         return this.localName;
255     }
256 
257     @Override
258     public Object getModel() {
259         return ontResource.getModel();
260     }
261 
262     @Override
263     public Collection<OntologyTerm> getParents( boolean direct ) {
264         return getParents( direct, true );
265     }
266 
267     @Override
268     public Collection<OntologyTerm> getParents( boolean direct, boolean includePartOf ) {
269         Collection<OntClass> result = new HashSet<>();
270         ExtendedIterator<OntClass> iterator;
271         Set<String> excludeProperties;
272         iterator = ontResource.listSuperClasses( direct );
273         excludeProperties = REJECT_PARENT_URIS;
274 
275         while ( iterator.hasNext() ) {
276             OntClass c = iterator.next();
277 
278             // handles part of some {parent container} or part of all {parent container}
279             if ( includePartOf && c.isRestriction() ) {
280                 Restriction r = c.asRestriction();
281                 if ( propagateParentsProperties.contains( r.getOnProperty() ) ) {
282                     Resource value = getRestrictionValue( c.asRestriction() );
283                     if ( value instanceof OntClass ) {
284                         c = ( OntClass ) value;
285                     } else {
286                         continue;
287                     }
288                 }
289             }
290 
291             // bnode
292             if ( c.getURI() == null )
293                 continue;
294 
295             // excluded terms
296             if ( excludeProperties.contains( c.getURI() ) )
297                 continue;
298 
299             // already visited
300             if ( result.contains( c ) )
301                 continue;
302 
303             result.add( c );
304         }
305 
306         return result.stream().map( OntologyTermImpl::new ).collect( Collectors.toSet() );
307     }
308 
309     /**
310      *
311      */
312     @Override
313     public Collection<OntologyRestriction> getRestrictions() {
314         /*
315          * Remember that restrictions are superclasses.
316          */
317         Collection<OntologyRestriction> result = new HashSet<>();
318         ExtendedIterator<OntClass> iterator = ontResource.listSuperClasses( false );
319         while ( iterator.hasNext() ) {
320             OntClass c = iterator.next();
321             if ( c.isRestriction() ) {
322                 result.add( RestrictionFactory.asRestriction( c.asRestriction() ) );
323             }
324         }
325 
326         // Check superclasses for any ADDITIONAL restrictions.
327         iterator = ontResource.listSuperClasses( false );
328         while ( iterator.hasNext() ) {
329             OntClass c = iterator.next();
330 
331             try {
332                 c.asRestriction(); // throw it away, we already processed it above.
333             } catch ( Exception e ) {
334                 // not a restriction, but a superclass that might have restrictions
335                 ExtendedIterator<OntClass> supClassesIt = c.listSuperClasses( false );
336                 loop:
337                 while ( supClassesIt.hasNext() ) {
338                     OntClass sc = supClassesIt.next();
339                     Restriction sr;
340                     try {
341                         sr = sc.asRestriction();
342 
343                         // only add it if the class doesn't already have one.
344                         OntologyRestriction candidateRestriction = RestrictionFactory.asRestriction( sr );
345                         for ( OntologyRestriction restr : result ) {
346                             if ( restr.getRestrictionOn().equals( candidateRestriction.getRestrictionOn() ) )
347                                 continue loop;
348                         }
349                         result.add( candidateRestriction );
350 
351                     } catch ( Exception ex ) {
352                         // superclass isn't a restriction.
353                     }
354                 }
355             }
356 
357         }
358 
359         return result;
360     }
361 
362     /*
363      * (non-Javadoc)
364      *
365      * @see ubic.gemma.analysis.ontology.OntologyTerm#getTerm()
366      */
367     @Override
368     public String getTerm() {
369         String res;
370         if ( this.label != null ) {
371             res = this.label;
372         } else if ( this.localName != null ) {
373             res = localName;
374         } else if ( this.getUri() != null ) {
375             res = this.getUri();
376         } else {
377             res = ontResource.toString();
378         }
379         return res;
380     }
381 
382     @Override
383     public String getUri() {
384         return this.ontResource.getURI();
385     }
386 
387     @Override
388     public int hashCode() {
389         if ( ontResource == null ) {
390             log.warn( "ontResource is null in hashCode()" );
391             return 0;
392         }
393         // assert this.getUri() != null : "No URI for " + this.getTerm();
394         if ( this.getUri() != null ) {
395             return this.getUri().hashCode();
396         }
397         return this.getTerm().hashCode();
398     }
399 
400     /*
401      * (non-Javadoc)
402      *
403      * @see ubic.gemma.analysis.ontology.OntologyTerm#isRoot()
404      */
405     @Override
406     public boolean isRoot() {
407         return !this.ontResource.listSuperClasses( true ).hasNext();
408     }
409 
410     /*
411      * (non-Javadoc)
412      *
413      * @see ubic.basecode.ontology.model.OntologyTerm#isTermObsolete()
414      */
415     @Override
416     public boolean isTermObsolete() {
417 
418         Collection<OntologyTerm> parentsOntologyTerm = getParents( false );
419 
420         // this limit of 1 was because obsolete terms are connected right to the root. But ... not guaranteed.
421         // / if ( parentsOntologyTerm.size() == 1 ) {
422 
423         for ( OntologyTerm parentOntologyTerm : parentsOntologyTerm ) {
424 
425             if ( parentOntologyTerm.getLocalName() != null
426                     && parentOntologyTerm.getLocalName().equalsIgnoreCase( "ObsoleteClass" ) ) {
427                 return true;
428             }
429 
430             // debug code.
431             if ( parentOntologyTerm.getUri() == null ) {
432                 // log.warn( "URI for " + parentOntologyTerm + " was null" ); // not a problem.
433                 continue;
434             }
435 
436             if ( parentOntologyTerm.getUri() != null
437                     && parentOntologyTerm.getUri().equalsIgnoreCase(
438                     "http://bioontology.org/projects/ontologies/birnlex#_birnlex_retired_class" )
439                     || parentOntologyTerm
440                     .getUri()
441                     .equalsIgnoreCase(
442                             "http://ontology.neuinfo.org/NIF/Backend/BIRNLex_annotation_properties.owl#_birnlex_retired_class" ) ) {
443                 return true;
444             }
445         }
446         // }
447 
448         StmtIterator iterator = ontResource.listProperties();
449         // this is a little slow because we have to go through all statements for the term.
450         while ( iterator.hasNext() ) {
451             Statement state = iterator.next();
452             if ( state.getPredicate() == null ) continue;
453             OntResource res = state.getPredicate().as( OntResource.class );
454             if ( res.isAnnotationProperty() ) {
455                 com.hp.hpl.jena.ontology.AnnotationProperty p = res.asAnnotationProperty();
456                 RDFNode n = state.getObject();
457 
458                 if ( p.getLocalName().equalsIgnoreCase( "deprecated" ) ) {
459                     if ( n.toString().contains( "true" ) ) {
460                         return true;
461                     }
462                     break;
463                 }
464             }
465         }
466         return false;
467     }
468 
469     @Override
470     public String toString() {
471         String res = null;
472         if ( this.getTerm() != null ) {
473             res = this.getTerm();
474             if ( this.localName != null && !this.getTerm().equals( this.localName ) ) {
475                 res = res + " [" + this.localName + "]";
476             }
477         } else if ( this.localName != null ) {
478             res = localName;
479         } else if ( this.getUri() != null ) {
480             res = this.getUri();
481         } else {
482             res = ontResource.toString();
483         }
484         return res;
485     }
486 
487     private static Resource getRestrictionValue( Restriction r ) {
488         if ( r.isSomeValuesFromRestriction() ) {
489             return r.asSomeValuesFromRestriction().getSomeValuesFrom();
490         } else if ( r.isAllValuesFromRestriction() ) {
491             return r.asAllValuesFromRestriction().getAllValuesFrom();
492         } else {
493             return null;
494         }
495     }
496 
497     private static boolean hasRestrictionValue( Restriction r, Resource value ) {
498         if ( r.isSomeValuesFromRestriction() ) {
499             return r.asSomeValuesFromRestriction().hasSomeValuesFrom( value );
500         } else if ( r.isAllValuesFromRestriction() ) {
501             return r.asAllValuesFromRestriction().hasAllValuesFrom( value );
502         } else {
503             return false;
504         }
505     }
506 }