package org.phenoscape.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.obo.annotation.base.OBOUtil;
import org.obo.annotation.view.TermRenderer;
import org.obo.datamodel.Link;
import org.obo.datamodel.LinkedObject;
import org.obo.datamodel.OBOClass;
import org.obo.datamodel.OBOObject;
import org.obo.datamodel.OBOProperty;
import org.obo.datamodel.OBOSession;
import org.obo.datamodel.TermSubset;
import org.obo.filters.LinkFilter;
import org.obo.filters.LinkFilterImpl;
import org.obo.util.TermUtil;
import org.phenoscape.model.Character;
import org.phenoscape.model.Phenotype;
import org.phenoscape.model.State;
public class AnnotationConsistencyChecker {
private final OBOSession session;
private TermSubset relation_slim = null;
private static final String STRUCTURE = "PATO:0000141";
private static final String POSITION = "PATO:0000140";
private static final String SIZE = "PATO:0000117";
private static final String BIOLOGICAL_PROCESS = "GO:0008150";
private static final String PROCESS_QUALITY = "PATO:0001236";
public AnnotationConsistencyChecker(OBOSession session) {
this.session = session;
for (TermSubset subset : session.getSubsets()) {
if ((subset.getName() != null) && (subset.getName().equals("relational_slim"))) {
relation_slim = subset;
break;
}
}
}
public Collection<ConsistencyIssue> checkCharacter(Character character) {
final Collection<ConsistencyIssue> issues = new ArrayList<ConsistencyIssue>();
final Set<OBOClass> charactersUsed = new HashSet<OBOClass>();
for (State state : character.getStates()) {
issues.addAll(this.checkState(state, character));
for (Phenotype phenotype : state.getPhenotypes()) {
if (phenotype.getQuality() != null) {
charactersUsed.add(OBOUtil.getCharacterForValue(phenotype.getQuality()));
}
}
}
if (charactersUsed.size() > 1) {
if (character.getLabel().startsWith("Scapulocoracoid, anterior margin")) {
System.err.println(charactersUsed);
}
issues.add(new ConsistencyIssue(character, null, "Qualities used descend from multiple character qualities."));
}
return issues;
}
public Collection<ConsistencyIssue> checkState(State state, Character character) {
final Collection<ConsistencyIssue> issues = new ArrayList<ConsistencyIssue>();
if (state.getPhenotypes().isEmpty()) {
issues.add(new ConsistencyIssue(character, state, "State not annotated."));
}
for (Phenotype phenotype : state.getPhenotypes()) {
issues.addAll(this.checkPhenotype(phenotype, state, character));
}
return issues;
}
public Collection<ConsistencyIssue> checkPhenotype(Phenotype phenotype, State state, Character character) {
final Collection<ConsistencyIssue> issues = new ArrayList<ConsistencyIssue>();
if (phenotype.getEntity() == null) {
issues.add(new ConsistencyIssue(character, state, "No entity has been entered."));
} else {
if (isPostCompositionWithMultipleDifferentiae(phenotype.getEntity())) {
issues.add(new ConsistencyIssue(character, state, "Entity post-composition used with more than one differentia (may be okay)."));
}
if (usesProvisionalTerm(phenotype.getEntity())) {
issues.add(new ConsistencyIssue(character, state, "References provisional term."));
}
if (TermRenderer.isDangling(phenotype.getEntity())) {
issues.add(new ConsistencyIssue(character, state, "References dangling term."));
}
}
if (phenotype.getQuality() == null) {
issues.add(new ConsistencyIssue(character, state, "No quality has been entered."));
} else {
if (isPostCompositionWithMultipleDifferentiae(phenotype.getQuality())) {
issues.add(new ConsistencyIssue(character, state, "Quality post-composition used with more than one differentia (may be okay)."));
}
if (usesProvisionalTerm(phenotype.getQuality())) {
issues.add(new ConsistencyIssue(character, state, "References provisional term."));
}
if (TermRenderer.isDangling(phenotype.getQuality())) {
issues.add(new ConsistencyIssue(character, state, "References dangling term."));
}
}
if (phenotype.getRelatedEntity() != null) {
if (isPostCompositionWithMultipleDifferentiae(phenotype.getRelatedEntity())) {
issues.add(new ConsistencyIssue(character, state, "Related entity post-composition used with more than one differentia (may be okay)."));
}
if (usesProvisionalTerm(phenotype.getRelatedEntity())) {
issues.add(new ConsistencyIssue(character, state, "References provisional term."));
}
if (TermRenderer.isDangling(phenotype.getRelatedEntity())) {
issues.add(new ConsistencyIssue(character, state, "References dangling term."));
}
}
if (relation_slim != null) {
if ((phenotype.getQuality() != null) && (phenotype.getQuality().getSubsets() != null)) {
if (phenotype.getQuality().getSubsets().contains(relation_slim)) {
if (phenotype.getRelatedEntity() == null) {
issues.add(new ConsistencyIssue(character, state, "Relational quality has been used without a related entity."));
}
} else {
if ((phenotype.getRelatedEntity() != null) && (!this.isOptionallyRelationalQuality(phenotype.getQuality()))) {
issues.add(new ConsistencyIssue(character, state, "Related entity requires a relational quality."));
}
}
}
}
final OBOClass biologicalProcess = (OBOClass)(this.session.getObject(BIOLOGICAL_PROCESS));
final OBOClass processQuality = (OBOClass)(this.session.getObject(PROCESS_QUALITY));
if ((phenotype.getEntity() != null) && (phenotype.getQuality() != null)) {
if ((biologicalProcess != null) && (processQuality != null) && (TermUtil.hasIsAAncestor(phenotype.getEntity(), biologicalProcess) || (BIOLOGICAL_PROCESS.equals(phenotype.getEntity().getID())))) {
if (!(TermUtil.hasIsAAncestor(phenotype.getQuality(), processQuality) || (PROCESS_QUALITY.equals(phenotype.getQuality().getID())))) {
issues.add(new ConsistencyIssue(character, state, "Biological process entities require a process quality."));
}
}
if ((biologicalProcess != null) && (processQuality != null) && (TermUtil.hasIsAAncestor(phenotype.getQuality(), processQuality) || (PROCESS_QUALITY.equals(phenotype.getQuality().getID())))) {
if (!(TermUtil.hasIsAAncestor(phenotype.getEntity(), biologicalProcess) || (BIOLOGICAL_PROCESS.equals(phenotype.getEntity().getID())))) {
issues.add(new ConsistencyIssue(character, state, "Process qualities should only be used with biological process entities."));
}
}
}
return issues;
}
private boolean isOptionallyRelationalQuality(OBOClass quality) {
if ((quality.getID().equals(STRUCTURE)) || (quality.getID().equals(POSITION))) {
return true;
} else {
final OBOClass size = (OBOClass)(session.getObject(SIZE));
final LinkFilter filter = new LinkFilterImpl((OBOProperty)(session.getObject("OBO_REL:is_a")));
final Collection<LinkedObject> sizes = TermUtil.getDescendants(size, true, filter);
return sizes.contains(quality);
}
}
private boolean isPostCompositionWithMultipleDifferentiae(OBOClass term) {
if (OBOUtil.isPostCompTerm(term)) {
final List<Link> differentiae = OBOUtil.getAllDifferentia(term);
if (differentiae.size() > 1) {
return true;
} else {
return isPostCompositionWithMultipleDifferentiae((OBOClass)(differentiae.get(0).getParent()));
}
} else {
return false;
}
}
private boolean usesProvisionalTerm(OBOClass term) {
if (OBOUtil.isPostCompTerm(term)) {
if (TermRenderer.isProvisional(OBOUtil.getGenusTerm(term))) {
return true;
}
final List<Link> differentiae = OBOUtil.getAllDifferentia(term);
for (Link differentia : differentiae) {
if (differentia.getParent() instanceof OBOObject) {
return TermRenderer.isProvisional((OBOObject)(differentia.getParent()));
}
}
return false;
} else {
return TermRenderer.isProvisional(term);
}
}
}