Skip to content

Commit

Permalink
Merge branch 'support-literal-mappings'
Browse files Browse the repository at this point in the history
  • Loading branch information
gouttegd committed Aug 9, 2024
2 parents 4d03098 + 611891a commit 5f68142
Show file tree
Hide file tree
Showing 26 changed files with 439 additions and 175 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,9 @@ private void writeSplitSet(MappingSet ms, String directory) {
pm.add(ms.getCurieMap());

for ( Mapping mapping : ms.getMappings() ) {
if ( mapping.isLiteral() ) {
continue;
}
String subjectPrefixName = pm.getPrefixName(mapping.getSubjectId());
String objectPrefixName = pm.getPrefixName(mapping.getObjectId());
if ( subjectPrefixName != null && objectPrefixName != null ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,13 @@ txField : 'comment'
| 'mapping_tool'
| 'mapping_tool_version'
| 'object_category'
| 'object_label'
| 'object_source_version'
| 'other'
| 'predicate_label'
| 'similarity_measure'
| 'subject_category'
| 'subject_label'
| 'subject_source_version'
;

Expand Down
31 changes: 31 additions & 0 deletions core/src/main/java/org/incenp/obofoundry/sssom/BaseReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
package org.incenp.obofoundry.sssom;

import java.io.IOException;
import java.util.List;

import org.incenp.obofoundry.sssom.model.Mapping;
import org.incenp.obofoundry.sssom.model.MappingSet;

/**
Expand All @@ -33,6 +35,7 @@ public abstract class BaseReader {

protected ExtraMetadataPolicy extraPolicy = ExtraMetadataPolicy.NONE;
protected PropagationPolicy propagationPolicy = PropagationPolicy.NeverReplace;
private boolean withValidation = true;

/**
* Sets the policy to deal with non-standard metadata in the input file.
Expand All @@ -55,6 +58,15 @@ public void setPropagationEnabled(boolean enabled) {
propagationPolicy = enabled ? PropagationPolicy.NeverReplace : PropagationPolicy.Disabled;
}

/**
* Enables or disables post-parsing validation of mappings.
*
* @param enabled {@code False} to disable validation; it is enabled by default.
*/
public void setValidationEnabled(boolean enabled) {
withValidation = enabled;
}

/**
* Reads a mapping set from the source file.
*
Expand All @@ -64,4 +76,23 @@ public void setPropagationEnabled(boolean enabled) {
* occurs.
*/
public abstract MappingSet read() throws SSSOMFormatException, IOException;

/**
* Validates individual mappings. This method checks that all mappings have all
* the required slots set.
*
* @param mappings The list of mappings to check.
* @throws SSSOMFormatException If any mapping is invalid.
*/
protected void validate(List<Mapping> mappings) throws SSSOMFormatException {
if ( withValidation ) {
Validator validator = new Validator();
for ( Mapping m : mappings ) {
String error = validator.validate(m);
if ( error != null ) {
throw new SSSOMFormatException(String.format("Invalid mapping: %s", error));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ public MappingSet read() throws SSSOMFormatException, IOException {
String.join(", ", converter.getPrefixManager().getUnresolvedPrefixNames())));
}

// 3. Check individual mappings for missing slots
validate(ms.getMappings());

reader.close();

return ms;
}

Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/org/incenp/obofoundry/sssom/TSVReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ private MappingSet read(Reader metaReader, boolean metadataOnly) throws SSSOMFor
String.join(", ", converter.getPrefixManager().getUnresolvedPrefixNames())));
}

validate(ms.getMappings());

metaReader.close();
if ( tsvReader != null ) {
tsvReader.close();
Expand Down
80 changes: 80 additions & 0 deletions core/src/main/java/org/incenp/obofoundry/sssom/Validator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* SSSOM-Java - SSSOM library for Java
* Copyright © 2024 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the Gnu General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.incenp.obofoundry.sssom;

import org.incenp.obofoundry.sssom.model.EntityType;
import org.incenp.obofoundry.sssom.model.Mapping;

/**
* Helper class to validate MappingSet and Mapping objects.
* <p>
* This class is primarily intended to be used internally by the parsers for
* post-parsing validation, to ensure that the parsed objects are compliant with
* the requirements from the SSSOM specification. However it may also be used
* independently of the parsers, for example to validate a set that was
* constructed <em>ex nihilo</em>.
*/
public class Validator {

public static final String MISSING_SUBJECT_LABEL = "Missing subject_label";
public static final String MISSING_SUBJECT_ID = "Missing subject_id";
public static final String MISSING_OBJECT_LABEL = "Missing object_label";
public static final String MISSING_OBJECT_ID = "Missing object_id";
public static final String MISSING_PREDICATE = "Missing predicate_id";
public static final String MISSING_JUSTIFICATION = "Missing mapping_justification";

/**
* Validates an individual mapping. This method checks that the slots that are
* required by the specification are present and non-empty.
*
* @param mapping The mapping to validate.
* @return An error message if the mapping is invalid, otherwise {@code null}.
*/
public String validate(Mapping mapping) {
if ( mapping.getSubjectType() == EntityType.RDFS_LITERAL ) {
if ( mapping.getSubjectLabel() == null || mapping.getSubjectLabel().isEmpty() ) {
return MISSING_SUBJECT_LABEL;
}
} else {
if ( mapping.getSubjectId() == null || mapping.getSubjectId().isEmpty() ) {
return MISSING_SUBJECT_ID;
}
}

if ( mapping.getObjectType() == EntityType.RDFS_LITERAL ) {
if ( mapping.getObjectLabel() == null || mapping.getObjectLabel().isEmpty() ) {
return MISSING_OBJECT_LABEL;
}
} else {
if ( mapping.getObjectId() == null || mapping.getObjectId().isEmpty() ) {
return MISSING_OBJECT_ID;
}
}

if ( mapping.getPredicateId() == null || mapping.getPredicateId().isEmpty() ) {
return MISSING_PREDICATE;
}

if ( mapping.getMappingJustification() == null || mapping.getMappingJustification().isEmpty() ) {
return MISSING_JUSTIFICATION;
}

return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.Map;

import org.incenp.obofoundry.sssom.compatibility.JsonLDConverter;
import org.incenp.obofoundry.sssom.compatibility.LiteralProfileConverter;
import org.incenp.obofoundry.sssom.compatibility.MatchTermTypeConverter;
import org.incenp.obofoundry.sssom.compatibility.MatchTypeConverter;
import org.incenp.obofoundry.sssom.compatibility.SemanticSimilarityConverter;
Expand Down Expand Up @@ -62,6 +63,7 @@ public YAMLConverter() {
preprocessors.add(new MatchTermTypeConverter());
preprocessors.add(new JsonLDConverter());
preprocessors.add(new SemanticSimilarityConverter());
preprocessors.add(new LiteralProfileConverter());

setSlotMaps = new HashMap<String, Slot<MappingSet>>();
for ( Slot<MappingSet> slot : SlotHelper.getMappingSetHelper().getSlots() ) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* SSSOM-Java - SSSOM library for Java
* Copyright © 2024 Damien Goutte-Gattat
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the Gnu General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package org.incenp.obofoundry.sssom.compatibility;

import java.util.Map;

import org.incenp.obofoundry.sssom.IYAMLPreprocessor;
import org.incenp.obofoundry.sssom.SSSOMFormatException;

/**
* A YAML preprocessor to convert <code>literal*</code> slots.
* <p>
* For a time, the SSSOM specification half-defined a "literal profile",
* intended for mappings between a literal and an entity. That profile has
* several literal-specific slots. In SSSOM 1.0, this profile has been removed
* and replaced by a convention that literal subjects are represented by storing
* the literal in the <code>subject_label</code> slot and setting the
* <code>subject_type</code> slot to <code>sssom literal</code>.
*/
public class LiteralProfileConverter implements IYAMLPreprocessor {

@Override
public void process(Map<String, Object> rawMap) throws SSSOMFormatException {
if ( rawMap.containsKey("literal") && !rawMap.containsKey("subject_label") ) {
rawMap.put("subject_label", rawMap.get("literal"));
rawMap.put("subject_type", "rdfs literal");

Object o = rawMap.get("literal_source");
if ( o != null ) {
rawMap.put("subject_source", o);
}

o = rawMap.get("literal_source_version");
if ( o != null ) {
rawMap.put("subject_source_version", o);
}

o = rawMap.get("literal_preprocessing");
if ( o != null ) {
rawMap.put("subject_preprocessing", o);
}

rawMap.remove("literal");
rawMap.remove("literal_datatype");
rawMap.remove("literal_source");
rawMap.remove("literal_source_version");
rawMap.remove("literal_preprocessing");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ public static void inferCardinality(List<Mapping> mappings) {
continue;
}

subjects.computeIfAbsent(m.getObjectId(), k -> new HashSet<String>()).add(m.getSubjectId());
objects.computeIfAbsent(m.getSubjectId(), k -> new HashSet<String>()).add(m.getObjectId());
subjects.computeIfAbsent(getObject(m), k -> new HashSet<String>()).add(getSubject(m));
objects.computeIfAbsent(getSubject(m), k -> new HashSet<String>()).add(getObject(m));
}

for ( Mapping m : mappings ) {
Expand All @@ -121,8 +121,8 @@ public static void inferCardinality(List<Mapping> mappings) {
continue;
}

int nSubjects = subjects.get(m.getObjectId()).size();
int nObjects = objects.get(m.getSubjectId()).size();
int nSubjects = subjects.get(getObject(m)).size();
int nObjects = objects.get(getSubject(m)).size();

if ( nSubjects == 1 ) {
m.setMappingCardinality(nObjects == 1 ? MappingCardinality.ONE_TO_ONE : MappingCardinality.ONE_TO_MANY);
Expand All @@ -132,4 +132,35 @@ public static void inferCardinality(List<Mapping> mappings) {
}
}
}

/**
* Gets a string representing the subject that can be used for cardinality
* computation. The returned value takes into account the <em>subject_id</em>
* (or the <em>subject_label</em> if the subject is a literal) and the
* <em>subject_type</em>.
*
* @param mapping The mapping from which to derive a subject string.
* @return A string that can be used to compare subjects across mappings.
*/
public static String getSubject(Mapping mapping) {
EntityType t = mapping.getSubjectType();
String tag = "\0" + (t == null ? "" : String.valueOf(t.ordinal())) + "\0";
return tag + (t == EntityType.RDFS_LITERAL ? mapping.getSubjectLabel() : mapping.getSubjectId());

}

/**
* Gets a string representing the object that can be used for cardinality
* computation. The returned value takes into account the <em>object_id</em> (or
* the <em>object_label</em> if the object is a literal) and the
* <em>object_type</em>.
*
* @param mapping The mapping from which to derive an object string.
* @return A String that can be used to compare objects across mappings.
*/
public static String getObject(Mapping mapping) {
EntityType t = mapping.getObjectType();
String tag = "\0" + (t == null ? "" : String.valueOf(t.ordinal())) + "\0";
return tag + (t == EntityType.RDFS_LITERAL ? mapping.getObjectLabel() : mapping.getObjectId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ public void setCheckObjectExistence(OWLOntology ontology) {
* annotation.
*/
private boolean entityAbsentOrDeprecated(OWLOntology ontology, String entity) {
if ( entity == null ) {
// The mapping does not refer to any entity at all.
return true;
}

if ( falseValue == null ) {
falseValue = ontology.getOWLOntologyManager().getOWLDataFactory().getOWLLiteral(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ public List<T> process(List<Mapping> mappings) {
List<Mapping> keptMappings = new ArrayList<Mapping>();
for ( Mapping mapping : mappings ) {
if ( rule.apply(mapping) ) {
String oldSubject = mapping.getSubjectId();
String oldObject = mapping.getObjectId();
String oldSubject = MappingCardinality.getSubject(mapping);
String oldObject = MappingCardinality.getObject(mapping);
mapping = rule.preprocess(mapping);
if ( mapping != null ) {
T product = rule.generate(mapping);
Expand All @@ -158,7 +158,8 @@ public List<T> process(List<Mapping> mappings) {
}

keptMappings.add(mapping);
if ( !mapping.getSubjectId().equals(oldSubject) || !mapping.getObjectId().equals(oldObject) ) {
if ( !MappingCardinality.getSubject(mapping).equals(oldSubject)
|| MappingCardinality.getObject(mapping).equals(oldObject) ) {
/* Subject and/or object changed, so cardinality data is no longer reliable. */
dirtyCard = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,10 @@ private IMappingFilter handleTextBasedFilter(String fieldName, String value, boo
filter = (mapping) -> testValue.apply(mapping.getObjectCategory());
break;

case "object_label":
filter = (mapping) -> testValue.apply(mapping.getObjectLabel());
break;

case "object_source":
filter = (mapping) -> testValue.apply(mapping.getObjectSource());
break;
Expand All @@ -734,6 +738,11 @@ private IMappingFilter handleTextBasedFilter(String fieldName, String value, boo
&& mapping.getPredicateModifier() != PredicateModifier.NOT;
break;

case "predicate_label":
filter = (mapping) -> testValue.apply(mapping.getPredicateLabel())
&& mapping.getPredicateModifier() != PredicateModifier.NOT;
break;

case "similarity_measure":
filter = (mapping) -> testValue.apply(mapping.getSimilarityMeasure());
break;
Expand All @@ -746,6 +755,10 @@ private IMappingFilter handleTextBasedFilter(String fieldName, String value, boo
filter = (mapping) -> testValue.apply(mapping.getSubjectCategory());
break;

case "subject_label":
filter = (mapping) -> testValue.apply(mapping.getSubjectLabel());
break;

case "subject_source":
filter = (mapping) -> testValue.apply(mapping.getSubjectSource());
break;
Expand Down
Loading

0 comments on commit 5f68142

Please sign in to comment.