Skip to content

Commit 05a9216

Browse files
authored
Allow controlling what is included in the alignment report.
* Allow controlling what is included in the alignment report. Amend the `check-align` command so that the contents of the report is tunable. For that, the report is now made in two steps: (1) We collect _all_ unaligned classes. This is unconditional. We do _not_ identify the top-level classes at this step. (2) We optionally trim the set of unaligned classes, based on the value of the `--detail` option: * ALL: no trimming; * ROOT: trim everything below the top-level classes; * BASE-ROOT: trim everything below the highest "in-base" classes.
1 parent 19d1ff5 commit 05a9216

File tree

2 files changed

+75
-24
lines changed

2 files changed

+75
-24
lines changed

src/main/java/org/incenp/obofoundry/odk/CheckAlignmentCommand.java

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -81,17 +81,22 @@ public CheckAlignmentCommand() {
8181

8282
options.addOption("r", "reasoner", true, "the reasoner to use");
8383
options.addOption("O", "report-output", true, "write report to the specified file");
84+
options.addOption(null, "detail", true, "what to report (ROOT / BASE-ROOT / ALL)");
8485
options.addOption("x", "fail", true, "if true (default), fail if the ontology is misaligned");
8586
}
8687

8788
@Override
8889
public void performOperation(CommandState state, CommandLine line) throws Exception {
8990
boolean ignoreDangling = CommandLineHelper.getBooleanValue(line, "ignore-dangling", false);
9091
boolean failOnError = CommandLineHelper.getBooleanValue(line, "fail", true);
92+
ReportMode reportMode = ReportMode.fromString(CommandLineHelper.getDefaultValue(line, "detail", "root"));
9193
if ( line.hasOption("base-iri") ) {
9294
for ( String iri : line.getOptionValues("base-iri") ) {
9395
basePrefixes.add(getIRI(iri, "base-iri").toString());
9496
}
97+
} else if ( reportMode == ReportMode.BASE_ROOT ) {
98+
// --detail BASE-ROOT makes no sense without a base IRI
99+
reportMode = ReportMode.GLOBAL_ROOT;
95100
}
96101
factory = state.getOntology().getOWLOntologyManager().getOWLDataFactory();
97102

@@ -130,14 +135,14 @@ public void performOperation(CommandState state, CommandLine line) throws Except
130135
}
131136

132137
OWLReasoner reasoner = CommandLineHelper.getReasonerFactory(line).createReasoner(ontology);
133-
Set<OWLClass> unalignedClasses = getUnalignedClasses(ontology, reasoner, upperClasses, ignoreDangling);
138+
Set<OWLClass> unalignedClasses = getAllUnalignedClasses(ontology, reasoner, upperClasses, ignoreDangling);
134139

135140
if ( line.hasOption("report-output") ) {
136141
// If a report has been requested, we always produce it, even if no unaligned
137142
// classes were found
138143
BufferedWriter writer = new BufferedWriter(new FileWriter(line.getOptionValue("report-output")));
139144
List<String> unalignedIRIs = new ArrayList<>();
140-
for ( OWLClass unalignedClass : unalignedClasses ) {
145+
for ( OWLClass unalignedClass : trimReport(reasoner, unalignedClasses, reportMode) ) {
141146
unalignedIRIs.add(unalignedClass.getIRI().toString());
142147
}
143148
unalignedIRIs.sort((a, b) -> a.compareTo(b));
@@ -149,7 +154,7 @@ public void performOperation(CommandState state, CommandLine line) throws Except
149154
}
150155

151156
if ( !unalignedClasses.isEmpty() ) {
152-
logger.error("Ontology contains {} top-level unaligned class(es)", unalignedClasses.size());
157+
logger.error("Ontology contains {} unaligned class(es)", unalignedClasses.size());
153158
if ( failOnError ) {
154159
System.exit(1);
155160
}
@@ -191,10 +196,7 @@ private Set<OWLClass> getPreferredRoots(OWLOntology ontology) {
191196
return roots;
192197
}
193198

194-
/*
195-
* Gets all top-level unaligned classes in the ontology.
196-
*/
197-
private Set<OWLClass> getUnalignedClasses(OWLOntology ontology, OWLReasoner reasoner, Set<OWLClass> upperClasses,
199+
private Set<OWLClass> getAllUnalignedClasses(OWLOntology ontology, OWLReasoner reasoner, Set<OWLClass> upperClasses,
198200
boolean ignoreDangling) {
199201
Set<OWLClass> unalignedClasses = new HashSet<>();
200202
for ( OWLClass klass : ontology.getClassesInSignature(Imports.INCLUDED) ) {
@@ -205,6 +207,10 @@ private Set<OWLClass> getUnalignedClasses(OWLOntology ontology, OWLReasoner reas
205207
if ( Util.isObsolete(ontology, klass) ) {
206208
continue;
207209
}
210+
if ( unalignedClasses.contains(klass) ) {
211+
// Already found, skip
212+
continue;
213+
}
208214

209215
Set<OWLClass> ancestors = reasoner.getSuperClasses(klass, false).getFlattened();
210216
boolean aligned = false;
@@ -214,28 +220,49 @@ private Set<OWLClass> getUnalignedClasses(OWLOntology ontology, OWLReasoner reas
214220
break;
215221
}
216222
}
223+
217224
if ( !aligned ) {
218-
if ( ancestors.size() == 1 ) {
219-
// This is already a top-level class
220-
logger.debug("Unaligned class: {}", klass.getIRI().toQuotedString());
221-
unalignedClasses.add(klass);
222-
} else {
223-
// Find the top-level ancestor(s)
224-
for ( OWLClass ancestor : ancestors ) {
225-
if ( reasoner.getSuperClasses(ancestor, false).isTopSingleton() ) {
226-
logger.debug("Unaligned class: {} (from {})", ancestor.getIRI().toQuotedString(),
227-
klass.getIRI().toQuotedString());
228-
unalignedClasses.add(ancestor);
229-
}
230-
}
231-
}
225+
logger.debug("Unaligned class: {}", klass.getIRI().toQuotedString());
226+
unalignedClasses.add(klass);
227+
// If this class is unaligned, then all its ancestors necessarily are too
228+
unalignedClasses.addAll(ancestors);
232229
}
233230
}
234231
}
235232

233+
unalignedClasses.remove(factory.getOWLThing());
236234
return unalignedClasses;
237235
}
238236

237+
private Set<OWLClass> trimReport(OWLReasoner reasoner, Set<OWLClass> unalignedClasses, ReportMode mode) {
238+
Set<OWLClass> trimmedOut = new HashSet<>();
239+
Set<OWLClass> reportSet = new HashSet<>();
240+
241+
if ( mode == ReportMode.GLOBAL_ROOT ) {
242+
// Exclude any class that is not a top-level class
243+
for ( OWLClass unaligned : unalignedClasses ) {
244+
if ( !reasoner.getSuperClasses(unaligned, false).isTopSingleton() ) {
245+
trimmedOut.add(unaligned);
246+
}
247+
}
248+
} else if ( mode == ReportMode.BASE_ROOT ) {
249+
// Exclude any class that is a descendant of an in-base class
250+
for ( OWLClass unaligned : unalignedClasses ) {
251+
if ( isInBase(unaligned.getIRI().toString()) ) {
252+
trimmedOut.addAll(reasoner.getSubClasses(unaligned, false).getFlattened());
253+
}
254+
}
255+
}
256+
257+
for ( OWLClass unaligned : unalignedClasses ) {
258+
if ( !trimmedOut.contains(unaligned) ) {
259+
reportSet.add(unaligned);
260+
}
261+
}
262+
263+
return reportSet;
264+
}
265+
239266
private boolean isInBase(String iri) {
240267
for ( String base : basePrefixes ) {
241268
if ( iri.startsWith(base) ) {
@@ -244,4 +271,20 @@ private boolean isInBase(String iri) {
244271
}
245272
return basePrefixes.isEmpty();
246273
}
274+
275+
private enum ReportMode {
276+
GLOBAL_ROOT,
277+
BASE_ROOT,
278+
ALL;
279+
280+
static ReportMode fromString(String name) {
281+
if ( name.equalsIgnoreCase("all") ) {
282+
return ALL;
283+
} else if ( name.equalsIgnoreCase("base-root") ) {
284+
return BASE_ROOT;
285+
} else {
286+
return GLOBAL_ROOT;
287+
}
288+
}
289+
}
247290
}

src/site/markdown/alignment.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,17 @@ because `--fail false` was used), the next command in the pipeline
9898
receives the original, unmodified ontology.
9999

100100
To produce a report from the validation results, use the
101-
`--report-output <FILE>` option. The file will contain a list of all the
102-
top-level classes (if any) of the input ontology that are not aligned
103-
against the upper ontology.
101+
`--report-output <FILE>` option. What will be included in this report is
102+
controlled by the `--detail <MODE>` option:
103+
104+
* `--detail ROOT` will report all the top-level classes (if any) of the
105+
input ontology that are not aligned against the upper ontology; this is
106+
the default behaviour;
107+
* `--detail BASE-ROOT` will report the highest “in-base” classes that
108+
are not aligned and all their ancestors up to the top-level classes;
109+
this is only meaningful if `--base-iri` is also used, otherwise this
110+
is the same as `--detail ROOT`;
111+
* `--detail ALL` will report all unaligned classes.
104112

105113
Restricting the scope of the validation
106114
---------------------------------------

0 commit comments

Comments
 (0)