1 package ubic.basecode.ontology.jena;
2
3 import com.hp.hpl.jena.ontology.*;
4 import com.hp.hpl.jena.rdf.model.Property;
5 import com.hp.hpl.jena.rdf.model.RDFNode;
6 import com.hp.hpl.jena.rdf.model.Resource;
7 import com.hp.hpl.jena.rdf.model.ResourceFactory;
8 import com.hp.hpl.jena.util.iterator.ExtendedIterator;
9 import com.hp.hpl.jena.util.iterator.UniqueExtendedIterator;
10 import org.apache.commons.lang3.time.StopWatch;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13
14 import org.jspecify.annotations.Nullable;
15 import java.util.*;
16 import java.util.stream.Collectors;
17
18 import static com.hp.hpl.jena.reasoner.ReasonerRegistry.makeDirect;
19
20 class JenaUtils {
21
22 protected static final Logger log = LoggerFactory.getLogger( JenaUtils.class );
23
24
25
26
27 public static <T extends RDFNode> Optional<T> as( RDFNode resource, Class<T> clazz ) {
28 if ( !resource.canAs( clazz ) ) {
29 return Optional.empty();
30 }
31 try {
32 return Optional.of( resource.as( clazz ) );
33 } catch ( ConversionException e ) {
34 log.warn( "Conversion of {} to {} failed.", resource, clazz.getName() );
35 return Optional.empty();
36 }
37 }
38
39 public static Collection<OntClass> getParents( OntModel model, Collection<OntClass> ontClasses, boolean direct, @Nullable Set<Restriction> additionalRestrictions ) {
40 Collection<OntClass> parents = getParentsInternal( model, ontClasses, direct, additionalRestrictions );
41 if ( shouldRevisit( parents, direct, model, additionalRestrictions ) ) {
42
43 Set<OntClass> parentsToRevisit = new HashSet<>( parents );
44 while ( !parentsToRevisit.isEmpty() ) {
45 log.debug( "Revisiting the direct parents of {} terms...", parentsToRevisit.size() );
46 parentsToRevisit = new HashSet<>( getParentsInternal( model, parentsToRevisit, true, additionalRestrictions ) );
47 parentsToRevisit.removeAll( parents );
48 log.debug( "Found {} missed parents.", parentsToRevisit.size() );
49 parents.addAll( parentsToRevisit );
50 }
51 }
52 return parents;
53 }
54
55 private static Collection<OntClass> getParentsInternal( OntModel model, Collection<OntClass> ontClasses, boolean direct, @Nullable Set<Restriction> additionalRestrictions ) {
56 ontClasses = ontClasses.stream()
57 .map( t -> t.inModel( model ) )
58 .filter( t -> t.canAs( OntClass.class ) )
59 .map( t -> as( t, OntClass.class ) )
60 .filter( Optional::isPresent )
61 .map( Optional::get )
62 .collect( Collectors.toSet() );
63 if ( ontClasses.isEmpty() ) {
64 return Collections.emptySet();
65 }
66 Iterator<OntClass> it = ontClasses.iterator();
67 ExtendedIterator<OntClass> iterator = it.next().listSuperClasses( direct );
68 while ( it.hasNext() ) {
69 iterator = iterator.andThen( it.next().listSuperClasses( direct ) );
70 }
71
72 Collection<OntClass> result = new HashSet<>();
73 while ( iterator.hasNext() ) {
74 OntClass c = iterator.next();
75
76
77 if ( additionalRestrictions != null && !additionalRestrictions.isEmpty() && c.isRestriction() ) {
78 Restriction r = c.asRestriction();
79 if ( additionalRestrictions.contains( r ) ) {
80 Resource value = getRestrictionValue( r );
81 if ( value instanceof OntClass ) {
82 c = ( OntClass ) value;
83 } else {
84 continue;
85 }
86 }
87 }
88
89
90 if ( c.getURI() == null )
91 continue;
92
93
94 if ( c.equals( model.getProfile().THING() ) )
95 continue;
96
97 result.add( c );
98 }
99
100 return result;
101 }
102
103 public static Collection<OntClass> getChildren( OntModel model, Collection<OntClass> terms, boolean direct, @Nullable Set<Restriction> additionalRestrictions ) {
104 Collection<OntClass> children = getChildrenInternal( model, terms, direct, additionalRestrictions );
105 if ( shouldRevisit( children, direct, model, additionalRestrictions ) ) {
106
107 Set<OntClass> childrenToRevisit = new HashSet<>( children );
108 while ( !childrenToRevisit.isEmpty() ) {
109 log.debug( "Revisiting the direct parents of {} terms...", childrenToRevisit.size() );
110 childrenToRevisit = new HashSet<>( JenaUtils.getChildrenInternal( model, childrenToRevisit, true, additionalRestrictions ) );
111 childrenToRevisit.removeAll( children );
112 log.debug( "Found {} missed children.", childrenToRevisit.size() );
113 children.addAll( childrenToRevisit );
114 }
115 }
116 return children;
117 }
118
119 public static Collection<OntClass> getChildrenInternal( OntModel model, Collection<OntClass> terms, boolean direct, @Nullable Set<Restriction> additionalRestrictions ) {
120 Set<OntClass> termsSet = terms.stream()
121 .map( t -> t.inModel( model ) )
122 .filter( t -> t.canAs( OntClass.class ) )
123 .map( t -> as( t, OntClass.class ) )
124 .filter( Optional::isPresent )
125 .map( Optional::get )
126 .collect( Collectors.toSet() );
127 if ( termsSet.isEmpty() ) {
128 return Collections.emptySet();
129 }
130 StopWatch timer = StopWatch.createStarted();
131 Iterator<OntClass> it = termsSet.iterator();
132 ExtendedIterator<OntClass> iterator = it.next().listSubClasses( direct );
133 while ( it.hasNext() ) {
134 iterator = iterator.andThen( it.next().listSubClasses( direct ) );
135 }
136 Set<OntClass> result = iterator
137 .filterDrop( new BnodeFilter<>() )
138 .filterDrop( new PredicateFilter<>( o -> o.equals( model.getProfile().NOTHING() ) ) )
139 .toSet();
140 if ( additionalRestrictions != null && !additionalRestrictions.isEmpty() ) {
141 timer.reset();
142 timer.start();
143 Property subClassOf = model.getProfile().SUB_CLASS_OF();
144 if ( direct ) {
145 subClassOf = ResourceFactory.createProperty( makeDirect( subClassOf.getURI() ) );
146 }
147 Set<Restriction> restrictions = UniqueExtendedIterator.create( additionalRestrictions.iterator() )
148 .filterKeep( new RestrictionWithValuesFromFilter( termsSet ) )
149 .toSet();
150 for ( Restriction r : restrictions ) {
151 result.addAll( model.listResourcesWithProperty( subClassOf, r )
152 .filterDrop( new BnodeFilter<>() )
153 .mapWith( r2 -> as( r2, OntClass.class ) )
154 .filterKeep( new PredicateFilter<Optional<OntClass>>( Optional::isPresent ) )
155 .mapWith( Optional::get )
156 .toSet() );
157 }
158 }
159 return result;
160 }
161
162
163
164
165
166
167
168
169
170
171 private static boolean shouldRevisit( Collection<OntClass> terms, boolean direct, OntModel model, @Nullable Set<Restriction> additionalRestrictions ) {
172 return !direct
173 && !terms.isEmpty()
174 && additionalRestrictions != null
175 && !additionalRestrictions.isEmpty()
176 && model.getReasoner() != null
177 && ( !supportsSubClassInference( model ) || !supportsAdditionalRestrictionsInference( model ) );
178 }
179
180
181
182
183 public static boolean supportsSubClassInference( OntModel model ) {
184 return model.getReasoner() != null
185 && model.getReasoner().supportsProperty( model.getProfile().SUB_CLASS_OF() );
186 }
187
188
189
190
191
192
193 public static boolean supportsAdditionalRestrictionsInference( OntModel model ) {
194 return model.getReasoner() != null
195 && model.getReasoner().supportsProperty( model.getProfile().SOME_VALUES_FROM() )
196 && model.getReasoner().supportsProperty( model.getProfile().ALL_VALUES_FROM() );
197 }
198
199 @Nullable
200 public static Resource getRestrictionValue( Restriction r ) {
201 if ( r.isSomeValuesFromRestriction() ) {
202 return r.asSomeValuesFromRestriction().getSomeValuesFrom();
203 } else if ( r.isAllValuesFromRestriction() ) {
204 return r.asAllValuesFromRestriction().getAllValuesFrom();
205 } else {
206 return null;
207 }
208 }
209
210
211
212
213 public static ExtendedIterator<Restriction> listRestrictionsOnProperties( OntModel model, Set<? extends Property> props, boolean includeSubProperties ) {
214 if ( includeSubProperties ) {
215 Set<Property> allProps = new HashSet<>( props );
216 for ( Property p : props ) {
217 Property property = p.inModel( model );
218
219 as( property, OntProperty.class ).ifPresent( op -> {
220 ExtendedIterator<? extends OntProperty> it = op.listSubProperties( false );
221 while ( it.hasNext() ) {
222 OntProperty sp = it.next();
223 allProps.add( sp );
224 }
225 } );
226 }
227 props = allProps;
228 }
229 return model.listRestrictions().filterKeep( new RestrictionWithOnPropertyFilter( props ) );
230 }
231 }