
org.biojava.bio.AnnotationTools Maven / Gradle / Ivy
/*
* BioJava development code
*
* This code may be freely distributed and modified under the
* terms of the GNU Lesser General Public Licence. This should
* be distributed with the code. If you do not have a copy,
* see:
*
* http://www.gnu.org/copyleft/lesser.html
*
* Copyright for this code is held jointly by the individual
* authors. These should be listed in @author doc comments.
*
* For more information on the BioJava project and its aims,
* or to join the biojava-l mailing list, visit the home page
* at:
*
* http://www.biojava.org/
*
*/
package org.biojava.bio;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.biojava.bio.symbol.Location;
import org.biojava.bio.symbol.LocationTools;
import org.biojava.utils.ChangeVetoException;
/**
* AnnotationTools
is a set of static utility methods for
* manipulating Annotation
s and AnnotationType
s.
*
* The methods allIn() and allOut() let you compare an Annotation to an
* AnnotationType and produce a new Annotation with only those properties
* explicitly constrained by or not constrained by the type. This could be
* of use when using an Annotation as a template for some object. You could use
* allOut to make an Annotation that has all the properties that do not fit into
* normal constructor properties, and pass that in as the Annotation bundle.
*
* intersection(AnnotationType) and union(AnnotationType) return new
* AnnotationType instances that will accept every Annotation instance that is
* accepted by both or either respectively. It is particularly informative to
* compare the result of this to the AnnotationType.NONE to see if the two types
* are mutualy disjoint.
*
* intersection(PropertyConstraint) and union(PropertyConstraint) return new
* PropertyConstraint instances that will accept every Object that is accepted
* by both or either one respectively.
*
* FilterTools uses these methods
* when comparing filters on features by their Annotation bundles.
*
* @since 1.3
* @author Matthew Pocock
* @author Keith James (docs)
* @author Thomas Down
*
*/
public final class AnnotationTools {
/**
*
* Destructive down-cast an annotation to a type.
*
*
*
* allIn
returns a new Annotation
* containing only those values in the Annotation
* argument which are of a type specified by the
* AnnotationType
.
*
*
* @param annotation an Annotation
to scan.
* @param annType an AnnotationType
.
*
* @return an Annotation
.
*/
public static Annotation allIn(Annotation annotation, AnnotationType annType) {
Annotation res;
if (annotation instanceof SmallAnnotation) {
res = new SmallAnnotation();
} else {
res = new SimpleAnnotation();
}
for (Iterator i = annType.getProperties().iterator(); i.hasNext();) {
Object tag = i.next();
try {
res.setProperty(tag, annotation.getProperty(tag));
} catch (ChangeVetoException cve) {
throw new BioError("Assertion Failure: Can't alter an annotation", cve);
}
}
return res;
}
/**
* allOut
returns a new Annotation
* containing only those values in the Annotation
* argument which are not of a type specified by
* the AnnotationType
.
*
* @param annotation an Annotation
.
* @param annType an AnnotationType
.
*
* @return an Annotation
value.
*/
public static Annotation allOut(Annotation annotation, AnnotationType annType) {
Annotation res;
if (annotation instanceof SmallAnnotation) {
res = new SmallAnnotation();
} else {
res = new SimpleAnnotation();
}
Set props = annType.getProperties();
for (Iterator i = annotation.keys().iterator(); i.hasNext();) {
Object tag = i.next();
if (! props.contains(tag)) {
try {
res.setProperty(tag, annotation.getProperty(tag));
} catch (ChangeVetoException cve) {
throw new BioError("Assertion Failure: Can't alter an annotation", cve);
}
}
}
return res;
}
/**
*
* Scans an Annotation with an AnnotationType and returns all Annotation
* instances matching a Type.
*
*
* This differs from AnnotationType.instanceOf()
* as it will descend into properties of an Annotation if that property is
* itself an Annotation. This allows you to scan a tree of Annotations for
* nodes in the tree of a particular shape.
*
*
* @param ann the Annotation to scan
* @param query the AnnotationType to match against all nodes in the tree
* @return the set of all annotations matching the query
*/
public static Set searchAnnotation(Annotation ann, AnnotationType query) {
Set hits = new HashSet();
searchAnnotation(ann, query, hits);
return hits;
}
private static void searchAnnotation(Annotation ann, AnnotationType query, Set hits) {
if(query.instanceOf(ann)) {
hits.add(ann);
}
for(Iterator i = ann.keys().iterator(); i.hasNext(); ) {
Object prop = i.next();
Object val = ann.getProperty(prop);
if(val instanceof Annotation) {
searchAnnotation((Annotation) val, query, hits);
} else if(prop instanceof Collection) {
for(Iterator vi = ((Collection) val).iterator(); vi.hasNext(); ) {
Object v = vi.next();
if(v instanceof Annotation) {
searchAnnotation((Annotation) v, query, hits);
}
}
}
}
}
/**
* Calculate an AnnotationType that matches all Annotation instances matched
* by both types. Usually you will either use this value blind or compare it to
* AnnotationType.NONE.
*
* @param ann1 the first AnnotationType
* @param ann2 the seccond AnnotationType
* @return the intersection AnnotationType
*/
public static AnnotationType intersection(
AnnotationType ann1,
AnnotationType ann2
) {
if(ann1.subTypeOf(ann2)) {
return ann2;
} else if(ann2.subTypeOf(ann1)) {
return ann1;
} else {
Set props = new HashSet();
props.addAll(ann1.getProperties());
props.addAll(ann2.getProperties());
AnnotationType.Impl intersect = new AnnotationType.Impl();
for(Iterator i = props.iterator(); i.hasNext(); ) {
Object key = i.next();
CollectionConstraint pc1 = ann1.getConstraint(key);
CollectionConstraint pc2 = ann2.getConstraint(key);
CollectionConstraint pc = intersection(pc1, pc2);
if (pc == CollectionConstraint.NONE) {
return AnnotationType.NONE;
}
intersect.setConstraint(key, pc);
}
intersect.setDefaultConstraint(
intersection(ann1.getDefaultConstraint(), ann2.getDefaultConstraint())
);
return intersect;
}
}
/**
* Calculate the intersection of two PropertyConstraint instances. This method is realy only interesting when comparing each property in an
* AnnotationType in turn. Usually the return value is either compared to
* PropertyConstraint.NONE or is used blindly.
*
* @param pc1 the first PropertyConstraint
* @param pc2 the seccond PropertyConstraint
* @return the intersection PropertyConstraint
*
*/
public static PropertyConstraint intersection(
PropertyConstraint pc1,
PropertyConstraint pc2
) {
if(pc1.subConstraintOf(pc2)) {
return pc2;
} else if(pc2.subConstraintOf(pc1)) {
return pc1;
} else if(
pc1 instanceof PropertyConstraint.ByClass &&
pc2 instanceof PropertyConstraint.ByClass
) {
PropertyConstraint.ByClass pc1c = (PropertyConstraint.ByClass) pc1;
PropertyConstraint.ByClass pc2c = (PropertyConstraint.ByClass) pc2;
Class c1 = pc1c.getPropertyClass();
Class c2 = pc2c.getPropertyClass();
if(!c1.isInterface() && !c2.isInterface()) {
return new PropertyConstraint.And(pc1c, pc2c);
} else {
return PropertyConstraint.NONE;
}
} else if(pc2 instanceof PropertyConstraint.ByClass) {
return intersection(pc2, pc1);
} else if(pc1 instanceof PropertyConstraint.ByClass) {
PropertyConstraint.ByClass pc1c = (PropertyConstraint.ByClass) pc1;
if(pc2 instanceof PropertyConstraint.Enumeration) {
PropertyConstraint.Enumeration pc2e = (PropertyConstraint.Enumeration) pc2;
Set values = new HashSet();
for(Iterator i = pc2e.getValues().iterator(); i.hasNext(); ) {
Object val = i.next();
if(pc1c.accept(val)) {
values.add(val);
}
}
if(values.isEmpty()) {
return PropertyConstraint.NONE;
} else if(values.size() == 1) {
return new PropertyConstraint.ExactValue(values.iterator().next());
} else {
return new PropertyConstraint.Enumeration(values);
}
}
if(pc2 instanceof PropertyConstraint.ExactValue) {
// we've already checked for containment - we know this value is of
// the wrong class
return PropertyConstraint.NONE;
}
} else if(
(pc1 instanceof PropertyConstraint.Enumeration ||
pc1 instanceof PropertyConstraint.ExactValue) &&
(pc2 instanceof PropertyConstraint.Enumeration ||
pc2 instanceof PropertyConstraint.ExactValue)
) {
if (pc1 instanceof PropertyConstraint.Enumeration && pc2 instanceof PropertyConstraint.Enumeration) {
Set intersection = new HashSet(((PropertyConstraint.Enumeration) pc1).getValues());
intersection.retainAll(((PropertyConstraint.Enumeration) pc2).getValues());
if (intersection.size() == 0) {
return PropertyConstraint.NONE;
} else if (intersection.size() == 1) {
return new PropertyConstraint.ExactValue(intersection.iterator().next());
} else {
return new PropertyConstraint.Enumeration(intersection);
}
} else {
// This case already handled by subset/superset logic
return PropertyConstraint.NONE;
}
} else if(
(pc1 instanceof PropertyConstraint.ByAnnotationType &&
!(pc2 instanceof PropertyConstraint.ByAnnotationType)) ||
(pc2 instanceof PropertyConstraint.ByAnnotationType &&
!(pc1 instanceof PropertyConstraint.ByAnnotationType))
) {
return PropertyConstraint.NONE;
} else if(
pc1 instanceof PropertyConstraint.ByAnnotationType &&
pc2 instanceof PropertyConstraint.ByAnnotationType
) {
PropertyConstraint.ByAnnotationType pc1a = (PropertyConstraint.ByAnnotationType) pc1;
PropertyConstraint.ByAnnotationType pc2a = (PropertyConstraint.ByAnnotationType) pc2;
AnnotationType intersect = intersection(
pc1a.getAnnotationType(),
pc2a.getAnnotationType()
);
if(intersect == AnnotationType.NONE) {
return PropertyConstraint.NONE;
} else {
return new PropertyConstraint.ByAnnotationType(intersect);
}
}
return new PropertyConstraint.And(pc1, pc2);
}
/**
* Create an AnnotationType that matches all Anntotations that are accepted
* by two others. This method is realy not very usefull in most cases. You may wish to
* compare the result of this to AnnotationType.ANY, or use it blindly.
*
* @param ann1 the first AnnotationType
* @param ann2 the seccond AnnotationType
* @return an AnnotationType that represents their unions
*
*/
public static AnnotationType union(
AnnotationType ann1,
AnnotationType ann2
) {
if(ann1.subTypeOf(ann2)) {
return ann1;
} else if(ann2.subTypeOf(ann1)) {
return ann2;
} else {
Set props = new HashSet();
props.addAll(ann1.getProperties());
props.addAll(ann2.getProperties());
AnnotationType.Impl union = new AnnotationType.Impl();
for(Iterator i = props.iterator(); i.hasNext(); ) {
Object key = i.next();
CollectionConstraint pc1 = ann1.getConstraint(key);
CollectionConstraint pc2 = ann2.getConstraint(key);
CollectionConstraint pc = union(pc1, pc2);
union.setConstraint(key, pc);
}
return union;
}
}
/**
* Create a PropertyConstraint that matches all Objects that are accepted
* by two others. In the general case, there is no clean way to represent the union of two
* PropertyConstraint instances. You may get back a PropertyConstraint.Or
* instance, or perhaps PropertyConstraint.ANY. Alternatively, there may be
* some comparrison possible. It is a thankless task introspecting this in
* code. You have been warned.
*
* @param pc1 the first PropertyConstraint
* @param pc2 the second PropertyConstraint
* @return the union PropertyConstraint
*/
public static PropertyConstraint union(
PropertyConstraint pc1,
PropertyConstraint pc2
) {
if(pc1.subConstraintOf(pc2)) {
return pc1;
} else if(pc2.subConstraintOf(pc1)) {
return pc2;
} else if(
pc1 instanceof PropertyConstraint.ByClass &&
pc2 instanceof PropertyConstraint.ByClass
) {
return new PropertyConstraint.Or(pc1, pc2);
} else if(pc2 instanceof PropertyConstraint.ByClass) {
return intersection(pc2, pc1);
} else if(pc1 instanceof PropertyConstraint.ByClass) {
PropertyConstraint.ByClass pc1c = (PropertyConstraint.ByClass) pc1;
if(pc2 instanceof PropertyConstraint.Enumeration) {
PropertyConstraint.Enumeration pc2e = (PropertyConstraint.Enumeration) pc2;
Set values = new HashSet();
for(Iterator i = pc2e.getValues().iterator(); i.hasNext(); ) {
Object val = i.next();
if(!pc1c.accept(val)) {
values.add(val);
}
}
if(values.isEmpty()) {
return pc1;
} else if(values.size() == 1) {
return new PropertyConstraint.Or(
pc1,
new PropertyConstraint.ExactValue(values.iterator().next())
);
} else {
return new PropertyConstraint.Or(
pc1,
new PropertyConstraint.Enumeration(values)
);
}
}
if(pc2 instanceof PropertyConstraint.ExactValue) {
// we've already checked for containment - we know this value is of
// the wrong class
return new PropertyConstraint.Or(pc1, pc2);
}
} else if(
pc1 instanceof PropertyConstraint.ByAnnotationType &&
pc2 instanceof PropertyConstraint.ByAnnotationType
) {
PropertyConstraint.ByAnnotationType pc1a = (PropertyConstraint.ByAnnotationType) pc1;
PropertyConstraint.ByAnnotationType pc2a = (PropertyConstraint.ByAnnotationType) pc2;
return new PropertyConstraint.ByAnnotationType(union(
pc1a.getAnnotationType(),
pc2a.getAnnotationType()
));
}
return new PropertyConstraint.Or(pc1, pc2);
}
/**
* Return the CollectionConstraint which accept only collections accepted by
* both of those specified.
*
* @param cc1 the first CollectionConstraint
* @param cc2 the seccond CollectionConstrant
* @return a CollectionConstraint representing the intersection of the other
* two
*/
public static CollectionConstraint intersection(CollectionConstraint cc1, CollectionConstraint cc2) {
if (cc1.subConstraintOf(cc2)) {
return cc2;
} else if (cc2.subConstraintOf(cc1)) {
return cc1;
} else if (cc1 instanceof CollectionConstraint.AllValuesIn &&
cc2 instanceof CollectionConstraint.AllValuesIn)
{
PropertyConstraint pc1 = ((CollectionConstraint.AllValuesIn) cc1).getPropertyConstraint();
PropertyConstraint pc2 = ((CollectionConstraint.AllValuesIn) cc2).getPropertyConstraint();
Location card1 = ((CollectionConstraint.AllValuesIn) cc1).getCardinalityConstraint();
Location card2 = ((CollectionConstraint.AllValuesIn) cc2).getCardinalityConstraint();
Location card = LocationTools.intersection(card1, card2);
if (card == Location.empty) {
return CollectionConstraint.NONE;
}
PropertyConstraint pc = intersection(pc1, pc2);
if (pc == PropertyConstraint.NONE && !card.contains(0)) {
return CollectionConstraint.NONE;
} else {
return new CollectionConstraint.AllValuesIn(pc, card);
}
} else if (cc1 instanceof CollectionConstraint.Contains &&
cc2 instanceof CollectionConstraint.Contains)
{
PropertyConstraint pc1 = ((CollectionConstraint.Contains) cc1).getPropertyConstraint();
PropertyConstraint pc2 = ((CollectionConstraint.Contains) cc2).getPropertyConstraint();
Location card1 = ((CollectionConstraint.Contains) cc1).getCardinalityConstraint();
Location card2 = ((CollectionConstraint.Contains) cc2).getCardinalityConstraint();
Location card = LocationTools.intersection(card1, card2);
if (card == Location.empty) {
return CollectionConstraint.NONE;
}
PropertyConstraint pc = intersection(pc1, pc2);
if (pc == PropertyConstraint.NONE && !card.contains(0)) {
return CollectionConstraint.NONE;
} else {
return new CollectionConstraint.Contains(pc, card);
}
} else if (cc1 instanceof CollectionConstraint.Contains &&
cc2 instanceof CollectionConstraint.AllValuesIn)
{
PropertyConstraint pc1 = ((CollectionConstraint.Contains) cc1).getPropertyConstraint();
PropertyConstraint pc2 = ((CollectionConstraint.AllValuesIn) cc2).getPropertyConstraint();
Location card1 = ((CollectionConstraint.Contains) cc1).getCardinalityConstraint();
Location card2 = ((CollectionConstraint.AllValuesIn) cc2).getCardinalityConstraint();
if (card1.getMin() > card2.getMax()) {
// Requires too many values.
return CollectionConstraint.NONE;
}
PropertyConstraint pc = intersection(pc1, pc2);
if (pc == PropertyConstraint.NONE && !card1.contains(0)) {
return CollectionConstraint.NONE;
} else {
return new CollectionConstraint.Contains(pc, card1);
}
} else if (cc1 instanceof CollectionConstraint.AllValuesIn &&
cc2 instanceof CollectionConstraint.Contains)
{
return intersection(cc2, cc1);
} else {
return new CollectionConstraint.And(cc1, cc2);
}
}
/**
* Calculate a CollectionConstaint that will accept all items accepted by
* either constraint.
*
* @param cc1 the first CollectionConstraint
* @param cc2 the seccond collectionConstraint
* @return a CollectionConstraint representing the union of the other two
*/
public static CollectionConstraint union(CollectionConstraint cc1, CollectionConstraint cc2) {
if (cc1.subConstraintOf(cc2)) {
return cc1;
} else if (cc2.subConstraintOf(cc1)) {
return cc2;
} else {
return new CollectionConstraint.Or(cc1, cc2);
}
}
}