View Javadoc
1   package ubic.basecode.ontology.jena;
2   
3   import com.hp.hpl.jena.ontology.ConversionException;
4   import com.hp.hpl.jena.ontology.OntClass;
5   import com.hp.hpl.jena.ontology.OntModel;
6   import com.hp.hpl.jena.ontology.Restriction;
7   import com.hp.hpl.jena.rdf.model.*;
8   import com.hp.hpl.jena.util.iterator.ExtendedIterator;
9   import com.hp.hpl.jena.util.iterator.Filter;
10  import com.hp.hpl.jena.util.iterator.UniqueExtendedIterator;
11  import org.apache.commons.lang3.time.StopWatch;
12  import org.slf4j.Logger;
13  import org.slf4j.LoggerFactory;
14  
15  import javax.annotation.Nullable;
16  import java.util.*;
17  import java.util.function.Predicate;
18  import java.util.stream.Collectors;
19  
20  import static com.hp.hpl.jena.reasoner.ReasonerRegistry.makeDirect;
21  
22  class JenaUtils {
23  
24      protected static Logger log = LoggerFactory.getLogger( JenaUtils.class );
25  
26      public static Collection<OntClass> getParents( OntModel model, Collection<OntClass> ontClasses, boolean direct, @Nullable Set<Restriction> additionalRestrictions ) {
27          Collection<OntClass> parents = getParentsInternal( model, ontClasses, direct, additionalRestrictions );
28          if ( shouldRevisit( parents, direct, model, additionalRestrictions ) ) {
29              // if there are some missing direct parents, revisit the hierarchy
30              Set<OntClass> parentsToRevisit = new HashSet<>( parents );
31              while ( !parentsToRevisit.isEmpty() ) {
32                  log.debug( "Revisiting the direct parents of {} terms...", parentsToRevisit.size() );
33                  parentsToRevisit = new HashSet<>( getParentsInternal( model, parentsToRevisit, true, additionalRestrictions ) );
34                  parentsToRevisit.removeAll( parents );
35                  log.debug( "Found {} missed parents.", parentsToRevisit.size() );
36                  parents.addAll( parentsToRevisit );
37              }
38          }
39          return parents;
40      }
41  
42      private static Collection<OntClass> getParentsInternal( OntModel model, Collection<OntClass> ontClasses, boolean direct, @Nullable Set<Restriction> additionalRestrictions ) {
43          ontClasses = ontClasses.stream()
44                  .map( t -> t.inModel( model ) )
45                  .filter( t -> t.canAs( OntClass.class ) )
46                  .map( t -> {
47                      try {
48                          return t.as( OntClass.class );
49                      } catch ( ConversionException e ) {
50                          log.error( "Conversion failed for " + t, e );
51                          return null;
52                      }
53                  } )
54                  .filter( Objects::nonNull )
55                  .collect( Collectors.toList() );
56          if ( ontClasses.isEmpty() ) {
57              return Collections.emptySet();
58          }
59          Iterator<OntClass> it = ontClasses.iterator();
60          ExtendedIterator<OntClass> iterator = it.next().listSuperClasses( direct );
61          while ( it.hasNext() ) {
62              iterator = iterator.andThen( it.next().listSuperClasses( direct ) );
63          }
64  
65          Collection<OntClass> result = new HashSet<>();
66          while ( iterator.hasNext() ) {
67              OntClass c = iterator.next();
68  
69              // handles part of some {parent container} or part of all {parent container}
70              if ( additionalRestrictions != null && !additionalRestrictions.isEmpty() && c.isRestriction() ) {
71                  Restriction r = c.asRestriction();
72                  if ( additionalRestrictions.contains( r ) ) {
73                      Resource value = getRestrictionValue( r );
74                      if ( value instanceof OntClass ) {
75                          c = ( OntClass ) value;
76                      } else {
77                          continue;
78                      }
79                  }
80              }
81  
82              // bnode
83              if ( c.getURI() == null )
84                  continue;
85  
86              // owl:Thing
87              if ( c.equals( model.getProfile().THING() ) )
88                  continue;
89  
90              result.add( c );
91          }
92  
93          return result;
94      }
95  
96      public static Collection<OntClass> getChildren( OntModel model, Collection<OntClass> terms, boolean direct, @Nullable Set<Restriction> additionalRestrictions ) {
97          Collection<OntClass> children = getChildrenInternal( model, terms, direct, additionalRestrictions );
98          if ( shouldRevisit( children, direct, model, additionalRestrictions ) ) {
99              // if there are some missing direct children, revisit the hierarchy
100             Set<OntClass> childrenToRevisit = new HashSet<>( children );
101             while ( !childrenToRevisit.isEmpty() ) {
102                 log.debug( "Revisiting the direct parents of {} terms...", childrenToRevisit.size() );
103                 childrenToRevisit = new HashSet<>( JenaUtils.getChildrenInternal( model, childrenToRevisit, true, additionalRestrictions ) );
104                 childrenToRevisit.removeAll( children );
105                 log.debug( "Found {} missed children.", childrenToRevisit.size() );
106                 children.addAll( childrenToRevisit );
107             }
108         }
109         return children;
110     }
111 
112     public static Collection<OntClass> getChildrenInternal( OntModel model, Collection<OntClass> terms, boolean direct, @Nullable Set<Restriction> additionalRestrictions ) {
113         terms = terms.stream()
114                 .map( t -> t.inModel( model ) )
115                 .filter( t -> t.canAs( OntClass.class ) )
116                 .map( t -> {
117                     try {
118                         return t.as( OntClass.class );
119                     } catch ( ConversionException e ) {
120                         log.error( "Conversion failed for " + t, e );
121                         return null;
122                     }
123                 } )
124                 .filter( Objects::nonNull )
125                 .collect( Collectors.toList() );
126         if ( terms.isEmpty() ) {
127             return Collections.emptySet();
128         }
129         StopWatch timer = StopWatch.createStarted();
130         Iterator<OntClass> it = terms.iterator();
131         ExtendedIterator<OntClass> iterator = it.next().listSubClasses( direct );
132         while ( it.hasNext() ) {
133             iterator = iterator.andThen( it.next().listSubClasses( direct ) );
134         }
135         Set<OntClass> result = iterator
136                 .filterDrop( new BnodeFilter<>() )
137                 .filterDrop( new PredicateFilter<>( o -> o.equals( model.getProfile().NOTHING() ) ) )
138                 .toSet();
139         if ( additionalRestrictions != null && !additionalRestrictions.isEmpty() ) {
140             timer.reset();
141             timer.start();
142             Property subClassOf = model.getProfile().SUB_CLASS_OF();
143             if ( direct ) {
144                 subClassOf = ResourceFactory.createProperty( makeDirect( subClassOf.getURI() ) );
145             }
146             Set<Restriction> restrictions = UniqueExtendedIterator.create( additionalRestrictions.iterator() )
147                     .filterKeep( new RestrictionWithValuesFromFilter( terms ) )
148                     .toSet();
149             for ( Restriction r : restrictions ) {
150                 result.addAll( model.listResourcesWithProperty( subClassOf, r )
151                         .filterDrop( new BnodeFilter<>() )
152                         .mapWith( r2 -> r2.as( OntClass.class ) )
153                         .toSet() );
154             }
155         }
156         return result;
157     }
158 
159     /**
160      * Check if a set of terms should be revisited to find missing parents or children.
161      * <p>
162      * To be considered, the model must have a reasoner that lacks one of {@code rdfs:subClassOf}, {@code owl:subValuesFrom}
163      * or {@code owl:allValuesFrom} inference capabilities. If a model has no reasoner, revisiting is not desirable and
164      * thus this will return false.
165      * <p>
166      * If direct is false or terms is empty, it's not worth revisiting.
167      */
168     private static boolean shouldRevisit( Collection<OntClass> terms, boolean direct, OntModel model, @Nullable Set<Restriction> additionalRestrictions ) {
169         return !direct
170                 && !terms.isEmpty()
171                 && additionalRestrictions != null
172                 && !additionalRestrictions.isEmpty()
173                 && model.getReasoner() != null
174                 && ( !supportsSubClassInference( model ) || !supportsAdditionalRestrictionsInference( model ) );
175     }
176 
177     /**
178      * Check if an ontology model supports inference of {@code rdfs:subClassOf}.
179      */
180     public static boolean supportsSubClassInference( OntModel model ) {
181         return model.getReasoner() != null
182                 && model.getReasoner().supportsProperty( model.getProfile().SUB_CLASS_OF() );
183     }
184 
185     /**
186      * Checks if an ontology model supports inference of with additional restrictions.
187      * <p>
188      * This covers {@code owl:subValuesFrom} and {@code owl:allValuesFrom} restrictions.
189      */
190     public static boolean supportsAdditionalRestrictionsInference( OntModel model ) {
191         return model.getReasoner() != null
192                 && model.getReasoner().supportsProperty( model.getProfile().SOME_VALUES_FROM() )
193                 && model.getReasoner().supportsProperty( model.getProfile().ALL_VALUES_FROM() );
194     }
195 
196     public static Resource getRestrictionValue( Restriction r ) {
197         if ( r.isSomeValuesFromRestriction() ) {
198             return r.asSomeValuesFromRestriction().getSomeValuesFrom();
199         } else if ( r.isAllValuesFromRestriction() ) {
200             return r.asAllValuesFromRestriction().getAllValuesFrom();
201         } else {
202             return null;
203         }
204     }
205 
206 
207     /**
208      * Use to pretty-print a RDFNode
209      */
210     public static String asString( RDFNode object ) {
211         return ( String ) object.visitWith( new RDFVisitor() {
212 
213             @Override
214             public Object visitBlank( Resource r, AnonId id ) {
215                 return r.getLocalName();
216             }
217 
218             @Override
219             public Object visitLiteral( Literal l ) {
220                 return l.toString().replaceAll( "\\^\\^.+", "" );
221             }
222 
223             @Override
224             public Object visitURI( Resource r, String uri ) {
225                 return r.getLocalName();
226             }
227         } );
228     }
229 
230     public static <T> Filter<T> where( Predicate<T> predicate ) {
231         return new PredicateFilter<>( predicate );
232     }
233 }