package jalview.datamodel.ontology;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
*/
public abstract class OntologyBase implements OntologyI
{
+ protected Map<String, List<String>> rootParents = new HashMap<>();
+
@Override
public Set<String> getParentTerms(Set<String> terms)
{
* @return
*/
List<String> termsNotFound();
+
+ /**
+ * Answers the top level parent terms (normally only one) for the given term,
+ * that is, those that have no parent themselves. Answers null if {@code term}
+ * is not a sequence ontology term. Answers a list just containing
+ * {@code term} if it is a valid term with no parent.
+ *
+ * @param term
+ * @return
+ */
+ List<String> getRootParents(String term);
+
}
\ No newline at end of file
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
return termsNotFound;
}
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @throws IllegalStateException
+ * if a loop is detected in the ontology
+ */
+ @Override
+ public List<String> getRootParents(final String term)
+ {
+ /*
+ * check in cache first
+ */
+ if (rootParents.containsKey(term))
+ {
+ return rootParents.get(term);
+ }
+ Term t = getTerm(term);
+ if (t == null)
+ {
+ return null;
+ }
+
+ /*
+ * todo: check for loops using 'seen', allowing for alternate paths e.g.
+ * stop_gained isA feature_truncation isA feature_variant
+ * " isA nonsynonymous_variant ... isA geneVariant isA feature_variant
+ */
+ List<Term> seen = new ArrayList<>();
+ List<Term> top = new ArrayList<>();
+ List<Term> query = new ArrayList<>();
+ query.add(t);
+
+ while (!query.isEmpty())
+ {
+ List<Term> nextQuery = new ArrayList<>();
+ for (Term q : query)
+ {
+ Set<Triple> parents = ontology.getTriples(q, null, isA);
+ if (parents.isEmpty())
+ {
+ /*
+ * q has no parents so is a top level term
+ */
+ top.add(q);
+ }
+ else
+ {
+ /*
+ * search all parent terms
+ */
+ for (Triple triple : parents)
+ {
+ Term parent = triple.getObject();
+ nextQuery.add(parent);
+ }
+ }
+ }
+ query = nextQuery;
+ }
+
+ List<String> result = new ArrayList<>();
+ for (Term found : top)
+ {
+ String desc = found.getDescription();
+ if (!result.contains(desc))
+ {
+ result.add(desc);
+ }
+ }
+
+ /*
+ * save result in cache
+ */
+ rootParents.put(term, result);
+
+ return result;
+ }
}
import jalview.api.AlignmentViewPanel;
import jalview.api.FeatureColourI;
-import jalview.bin.Cache;
import jalview.datamodel.GraphLine;
import jalview.datamodel.features.FeatureAttributes;
import jalview.datamodel.features.FeatureAttributes.Datatype;
*/
public class FeatureTypeSettings extends JalviewDialog
{
- /*
- * 'top level' Sequence Ontology terms
- */
- private final static String SO_ROOTS = "sequence_variant,sequence_attribute,sequence_collection,sequence_feature";
-
private final static String LABEL_18N = MessageManager
.getString("label.label");
* parent of the current type
*/
SequenceOntologyI so = SequenceOntologyFactory.getInstance();
- String[] roots = Cache.getDefault("SO_ROOTS", SO_ROOTS).split(",");
- rootSOTerm = null;
- for (String root : roots)
- {
- if (so.isA(featureType, root.trim()))
- {
- rootSOTerm = root;
- break;
- }
- }
- if (rootSOTerm == null)
+ List<String> roots = so.getRootParents(featureType);
+ if (roots == null || roots.size() > 1)
{
/*
- * feature type is not an SO term
+ * feature type is not an SO term, or has ambiguous root
*/
return peers;
}
+ rootSOTerm = roots.get(0);
List<String> types = fr.getRenderOrder();
for (String type : types)
return termsNotFound;
}
}
+
+ @Override
+ public List<String> getRootParents(final String term)
+ {
+ /*
+ * check in cache first
+ */
+ if (rootParents.containsKey(term))
+ {
+ return rootParents.get(term);
+ }
+
+ List<String> top = new ArrayList<>();
+ List<String> query = new ArrayList<>();
+ query.add(term);
+
+ while (!query.isEmpty())
+ {
+ List<String> nextQuery = new ArrayList<>();
+ for (String q : query)
+ {
+ List<String> theParents = parents.get(q);
+ if (theParents != null)
+ {
+ if (theParents.size() == 1 && theParents.get(0).equals(q))
+ {
+ /*
+ * top-level term
+ */
+ if (!top.contains(q))
+ {
+ top.add(q);
+ }
+ }
+ else
+ {
+ for (String p : theParents)
+ {
+ if (!p.equals(q))
+ {
+ nextQuery.add(p);
+ }
+ }
+ }
+ }
+ }
+ query = nextQuery;
+ }
+
+ rootParents.put(term, top);
+
+ return top.isEmpty() ? null : top;
+ }
}
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import jalview.datamodel.ontology.OntologyI;
assertTrue(parents.contains("sequence_variant"));
assertTrue(parents.contains("chain"));
}
+
+ @Test(groups = "Functional")
+ public void testGetRootParents()
+ {
+ List<String> roots = so.getRootParents("xyz");
+ assertNull(roots);
+ roots = so.getRootParents(null);
+ assertNull(roots);
+
+ roots = so.getRootParents("stop_gained");
+ assertEquals(roots.size(), 1);
+ assertEquals(roots.get(0), "sequence_variant");
+
+ roots = so.getRootParents("sequence_variant");
+ assertEquals(roots.size(), 1);
+ assertEquals(roots.get(0), "sequence_variant");
+
+ roots = so.getRootParents("alanine");
+ assertEquals(roots.size(), 1);
+ assertEquals(roots.get(0), "sequence_feature");
+ }
}
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertNull;
import static org.testng.Assert.assertTrue;
import jalview.datamodel.ontology.OntologyI;
assertTrue(children.contains("synonymous_variant"));
assertTrue(children.contains("stop_lost"));
}
+
+ @Test(groups = "Functional")
+ public void testGetRootParents()
+ {
+ List<String> roots = so.getRootParents("xyz");
+ assertNull(roots);
+ roots = so.getRootParents(null);
+ assertNull(roots);
+
+ roots = so.getRootParents("stop_gained");
+ assertEquals(roots.size(), 1);
+ assertEquals(roots.get(0), "sequence_variant");
+ }
}