/*
* 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.biojavax.bio.seq;
import java.util.Collection;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import org.biojava.bio.Annotation;
import org.biojava.bio.BioException;
import org.biojava.bio.seq.Feature;
import org.biojava.bio.seq.FeatureFilter;
import org.biojava.bio.seq.FeatureHolder;
import org.biojava.bio.seq.FilterUtils;
import org.biojava.bio.seq.Sequence;
import org.biojava.bio.seq.SimpleFeatureHolder;
import org.biojava.bio.seq.StrandedFeature;
import org.biojava.bio.symbol.Location;
import org.biojava.bio.symbol.SymbolList;
import org.biojava.ontology.InvalidTermException;
import org.biojava.ontology.Term;
import org.biojava.utils.AbstractChangeable;
import org.biojava.utils.ChangeEvent;
import org.biojava.utils.ChangeSupport;
import org.biojava.utils.ChangeVetoException;
import org.biojavax.RankedCrossRef;
import org.biojavax.RichAnnotation;
import org.biojavax.RichObjectFactory;
import org.biojavax.SimpleRichAnnotation;
import org.biojavax.ontology.ComparableTerm;
/**
* A simple implementation of RichFeature.
* @author Richard Holland
* @author Mark Schreiber
* @author Bubba Puryear
* @author George Waldon
* @since 1.5
*/
public class SimpleRichFeature extends AbstractChangeable implements RichFeature {
private static int nextRank = 0;
private RichAnnotation notes = new SimpleRichAnnotation();
private ComparableTerm typeTerm;
private ComparableTerm sourceTerm;
private FeatureHolder parent;
private RichLocation location = RichLocation.EMPTY_LOCATION;
private Set crossrefs = new TreeSet();
private Set relations = new TreeSet();
private String name;
private int rank = SimpleRichFeature.nextRank++; // Auto-rank!
/**
* Creates a new instance of SimpleRichFeature based on a template.
* @param parent The parent feature holder.
* @param templ The template to construct the feature from.
* @throws ChangeVetoException if we don't want to be like the template.
* @throws InvalidTermException if any of the template terms are bad.
*/
public SimpleRichFeature(FeatureHolder parent, Feature.Template templ) throws ChangeVetoException, InvalidTermException {
if (parent==null) throw new IllegalArgumentException("Parent cannot be null");
if (templ==null) throw new IllegalArgumentException("Template cannot be null");
if (templ.type==null && templ.typeTerm==null) throw new IllegalArgumentException("Template type cannot be null");
if (templ.source==null && templ.sourceTerm==null) throw new IllegalArgumentException("Template source cannot be null");
if (templ.location==null) throw new IllegalArgumentException("Template location cannot be null");
this.setParent(parent);
this.setLocation(templ.location);
if (templ.typeTerm!=null) this.setTypeTerm(templ.typeTerm);
else this.setType(templ.type);
if (templ.sourceTerm!=null) this.setSourceTerm(templ.sourceTerm);
else this.setSource(templ.source);
if (templ.annotation instanceof RichAnnotation) {
this.notes.setNoteSet(((RichAnnotation)templ.annotation).getNoteSet());
} else {
this.notes = new SimpleRichAnnotation();
for (Iterator i = templ.annotation.keys().iterator(); i.hasNext(); ) {
Object key = i.next();
this.notes.setProperty(key, templ.annotation.getProperty(key));
}
}
if (templ instanceof RichFeature.Template) {
this.setRankedCrossRefs(((RichFeature.Template)templ).rankedCrossRefs);
this.setFeatureRelationshipSet(((RichFeature.Template)templ).featureRelationshipSet);
}
}
// Hibernate requirement - not for public use.
protected SimpleRichFeature() {}
/**
* {@inheritDoc}
*/
public Feature.Template makeTemplate() {
RichFeature.Template templ = new RichFeature.Template();
templ.annotation = this.notes;
templ.featureRelationshipSet = this.relations;
templ.rankedCrossRefs = this.crossrefs;
templ.location = this.location;
templ.sourceTerm = this.sourceTerm;
templ.source = this.sourceTerm.getName();
templ.typeTerm = this.typeTerm;
templ.type = this.typeTerm.getName();
return templ;
}
/**
* {@inheritDoc}
*/
public Annotation getAnnotation() { return getRichAnnotation(); }
/**
* {@inheritDoc}
*/
public RichAnnotation getRichAnnotation() { return this.notes; }
/**
* {@inheritDoc}
* Warning this method gives access to the original
* Collection not a copy. This is required by Hibernate. If you
* modify the object directly the behaviour may be unpredictable.
*/
public Set getNoteSet() { return this.notes.getNoteSet(); }
/**
* {@inheritDoc}
* Warning this method gives access to the original
* Collection not a copy. This is required by Hibernate. If you
* modify the object directly the behaviour may be unpredictable.
*/
public void setNoteSet(Set notes) throws ChangeVetoException { this.notes.setNoteSet(notes); }
// Hibernate use only
Set getLocationSet() {
// Convert the location into a set of BioSQL-compatible simple locations
// System.out.println("SimpleRichFeature.getLocationSet-featureId:"+featureId+", locsSet:"+locsSet+", getLocation:"+getLocation());
setTerm(locsSet, null);
Collection newlocs = RichLocation.Tools.flatten(this.location);
this.locsSet.retainAll(newlocs); // clear out forgotten ones
this.locsSet.addAll(newlocs); // add in new ones
setTerm(locsSet, ((RichLocation) getLocation()).getTerm());
// System.out.println("SimpleRichFeature.getLocationSet-featureId:"+featureId+", locsSet:"+locsSet+", this:"+this+", getLocation:"+getLocation());
return this.locsSet; // original for Hibernate purposes
}
private final static void setTerm(final Collection theCollection, final ComparableTerm theTerm) {
final Iterator l = theCollection.iterator();
while(l.hasNext()) {
final RichLocation location = (RichLocation) l.next();
try {
location.setTerm(theTerm);
} catch (Exception e) {
throw new RuntimeException("SimpleRichFeature.setTerm-unable to set term <"+theTerm+"> in location <"+location+">"+e);
}
}
}
// Hibernate use only
void setLocationSet(Set locs) throws ChangeVetoException {
this.locsSet = locs; // original kept for Hibernate purposes
// Construct a nice BioJavaX location from the set of BioSQL-compatible simple ones
this.location = RichLocation.Tools.construct(RichLocation.Tools.merge(locs));
if(locs.size() > 0)((RichLocation) location).setTerm(((RichLocation)locs.iterator().next()).getTerm());
// System.out.println("SimpleRichFeature.SETLocationSet-featureId:"+featureId+", locs:"+locs+", location:"+location);
}
private Set locsSet = new TreeSet();
/**
* {@inheritDoc}
*/
public void setName(String name) throws ChangeVetoException {
if(!this.hasListeners(RichFeature.NAME)) {
this.name = name;
} else {
ChangeEvent ce = new ChangeEvent(
this,
RichFeature.NAME,
name,
this.name
);
ChangeSupport cs = this.getChangeSupport(RichFeature.NAME);
synchronized(cs) {
cs.firePreChangeEvent(ce);
this.name = name;
cs.firePostChangeEvent(ce);
}
}
}
/**
* {@inheritDoc}
*/
public String getName() { return this.name; }
/**
* {@inheritDoc}
*/
public void setRank(int rank) throws ChangeVetoException {
if(!this.hasListeners(RichFeature.RANK)) {
this.rank = rank;
} else {
ChangeEvent ce = new ChangeEvent(
this,
RichFeature.RANK,
new Integer(rank),
new Integer(this.rank)
);
ChangeSupport cs = this.getChangeSupport(RichFeature.RANK);
synchronized(cs) {
cs.firePreChangeEvent(ce);
this.rank = rank;
cs.firePostChangeEvent(ce);
}
}
}
/**
* {@inheritDoc}
*/
public int getRank() { return this.rank; }
/**
* {@inheritDoc}
*/
public Sequence getSequence() {
FeatureHolder p = this.parent;
while (p instanceof Feature) p = ((Feature)p).getParent();
return (Sequence)p;
}
/**
* {@inheritDoc}
*/
public String getSource() { return this.sourceTerm.getName(); }
/**
* {@inheritDoc}
*/
public void setSource(String source) throws ChangeVetoException {
try {
this.setSourceTerm(RichObjectFactory.getDefaultOntology().getOrCreateTerm(source));
} catch (InvalidTermException e) {
throw new ChangeVetoException("Source term was rejected by the default ontology",e);
}
}
/**
* {@inheritDoc}
*/
public Term getSourceTerm() { return this.sourceTerm; }
/**
* {@inheritDoc}
*/
public void setSourceTerm(Term t) throws ChangeVetoException, InvalidTermException {
if (t==null) throw new IllegalArgumentException("Term cannot be null");
ComparableTerm comparableT;
if (t instanceof ComparableTerm) {
comparableT = (ComparableTerm) t;
} else {
comparableT = RichObjectFactory.getDefaultOntology().getOrImportTerm(t);
}
if(!this.hasListeners(RichFeature.SOURCETERM)) {
this.sourceTerm = comparableT;
} else {
ChangeEvent ce = new ChangeEvent(
this,
RichFeature.SOURCETERM,
comparableT,
this.sourceTerm
);
ChangeSupport cs = this.getChangeSupport(RichFeature.SOURCETERM);
synchronized(cs) {
cs.firePreChangeEvent(ce);
this.sourceTerm = comparableT;
cs.firePostChangeEvent(ce);
}
}
}
/**
* {@inheritDoc}
*/
public String getType() { return this.typeTerm.getName(); }
/**
* {@inheritDoc}
*/
public void setType(String type) throws ChangeVetoException {
try {
this.setTypeTerm(RichObjectFactory.getDefaultOntology().getOrCreateTerm(type));
} catch (InvalidTermException e) {
throw new ChangeVetoException("Type term was rejected by the default ontology",e);
}
}
/**
* {@inheritDoc}
*/
public Term getTypeTerm() { return this.typeTerm; }
/**
* {@inheritDoc}
*/
public void setTypeTerm(Term t) throws ChangeVetoException, InvalidTermException {
if (t==null) throw new IllegalArgumentException("Term cannot be null");
ComparableTerm comparableT;
if (t instanceof ComparableTerm) {
comparableT = (ComparableTerm) t;
} else {
comparableT = RichObjectFactory.getDefaultOntology().getOrImportTerm(t);
}
if(!this.hasListeners(RichFeature.TYPETERM)) {
this.typeTerm = comparableT;
} else {
ChangeEvent ce = new ChangeEvent(
this,
RichFeature.TYPETERM,
comparableT,
this.typeTerm
);
ChangeSupport cs = this.getChangeSupport(RichFeature.TYPETERM);
synchronized(cs) {
cs.firePreChangeEvent(ce);
this.typeTerm = comparableT;
cs.firePostChangeEvent(ce);
}
}
}
/**
* {@inheritDoc}
*/
public SymbolList getSymbols() { return this.location.symbols(this.getSequence()); }
/**
* {@inheritDoc}
*/
public Location getLocation() { return this.location; }
/**
* {@inheritDoc}
*/
public void setLocation(Location loc) throws ChangeVetoException {
// System.out.println("SimpleRichFeature.setLocation-featureId:"+featureId+", loc:"+loc);
if (loc==null) throw new IllegalArgumentException("Location cannot be null");
RichLocation richLoc = RichLocation.Tools.enrich(loc);
if(!this.hasListeners(RichFeature.LOCATION)) {
this.location = richLoc;
} else {
ChangeEvent ce = new ChangeEvent(
this,
RichFeature.LOCATION,
richLoc,
this.location
);
ChangeSupport cs = this.getChangeSupport(RichFeature.LOCATION);
synchronized(cs) {
cs.firePreChangeEvent(ce);
this.location = richLoc;
cs.firePostChangeEvent(ce);
}
}
this.location.setFeature(this);
// System.out.println("SimpleRichFeature.setLocation-location:"+location+(location instanceof RichLocation?", term:"+((RichLocation) location).getTerm():""));
}
/**
* {@inheritDoc}
*/
public FeatureHolder getParent() { return this.parent; }
/**
* {@inheritDoc}
*/
public void setParent(FeatureHolder parent) throws ChangeVetoException {
if (parent==null) throw new IllegalArgumentException("Parent cannot be null");
if(!this.hasListeners(RichFeature.PARENT)) {
this.parent = parent;
} else {
ChangeEvent ce = new ChangeEvent(
this,
RichFeature.PARENT,
parent,
this.parent
);
ChangeSupport cs = this.getChangeSupport(RichFeature.PARENT);
synchronized(cs) {
cs.firePreChangeEvent(ce);
this.parent = parent;
cs.firePostChangeEvent(ce);
}
}
}
/**
* {@inheritDoc}
* Warning this method gives access to the original
* Collection not a copy. This is required by Hibernate. If you
* modify the object directly the behaviour may be unpredictable.
*/
public Set getRankedCrossRefs() { return this.crossrefs; }
/**
* {@inheritDoc}
* Warning this method gives access to the original
* Collection not a copy. This is required by Hibernate. If you
* modify the object directly the behaviour may be unpredictable.
*/
public void setRankedCrossRefs(Set crossrefs) throws ChangeVetoException {
this.crossrefs = crossrefs; // original for Hibernate
}
/**
* {@inheritDoc}
*/
public void addRankedCrossRef(RankedCrossRef crossref) throws ChangeVetoException {
if (crossref==null) throw new IllegalArgumentException("Crossref cannot be null");
if(!this.hasListeners(RichFeature.CROSSREF)) {
this.crossrefs.add(crossref);
} else {
ChangeEvent ce = new ChangeEvent(
this,
RichFeature.CROSSREF,
crossref,
null
);
ChangeSupport cs = this.getChangeSupport(RichFeature.CROSSREF);
synchronized(cs) {
cs.firePreChangeEvent(ce);
this.crossrefs.add(crossref);
cs.firePostChangeEvent(ce);
}
}
}
/**
* {@inheritDoc}
*/
public void removeRankedCrossRef(RankedCrossRef crossref) throws ChangeVetoException {
if (crossref==null) throw new IllegalArgumentException("Crossref cannot be null");
if(!this.hasListeners(RichFeature.CROSSREF)) {
this.crossrefs.remove(crossref);
} else {
ChangeEvent ce = new ChangeEvent(
this,
RichFeature.CROSSREF,
null,
crossref
);
ChangeSupport cs = this.getChangeSupport(RichFeature.CROSSREF);
synchronized(cs) {
cs.firePreChangeEvent(ce);
this.crossrefs.remove(crossref);
cs.firePostChangeEvent(ce);
}
}
}
/**
* {@inheritDoc}
* Warning this method gives access to the original
* Collection not a copy. This is required by Hibernate. If you
* modify the object directly the behaviour may be unpredictable.
*/
public Set getFeatureRelationshipSet() { return this.relations; } // must be original for Hibernate
/**
* {@inheritDoc}
* Warning this method gives access to the original
* Collection not a copy. This is required by Hibernate. If you
* modify the object directly the behaviour may be unpredictable.
*/
public void setFeatureRelationshipSet(Set relationships) throws ChangeVetoException {
this.relations = relationships; // must be original for Hibernate
}
/**
* {@inheritDoc}
*/
public void addFeatureRelationship(RichFeatureRelationship relationship) throws ChangeVetoException {
if (relationship==null) throw new IllegalArgumentException("Relationship cannot be null");
if(!this.hasListeners(RichFeature.RELATION)) {
this.relations.add(relationship);
} else {
ChangeEvent ce = new ChangeEvent(
this,
RichFeature.RELATION,
relationship,
null
);
ChangeSupport cs = this.getChangeSupport(RichFeature.RELATION);
synchronized(cs) {
cs.firePreChangeEvent(ce);
this.relations.add(relationship);
cs.firePostChangeEvent(ce);
}
}
}
/**
* {@inheritDoc}
*/
public void removeFeatureRelationship(RichFeatureRelationship relationship) throws ChangeVetoException {
if (relationship==null) throw new IllegalArgumentException("Relationship cannot be null");
if(!this.hasListeners(RichFeature.RELATION)) {
this.relations.remove(relationship);
} else {
ChangeEvent ce = new ChangeEvent(
this,
RichFeature.RELATION,
null,
relationship
);
ChangeSupport cs = this.getChangeSupport(RichFeature.RELATION);
synchronized(cs) {
cs.firePreChangeEvent(ce);
this.relations.remove(relationship);
cs.firePostChangeEvent(ce);
}
}
}
// Converts relations into a set of child feature objects
private Set relationsToFeatureSet() {
Set features = new TreeSet();
for (Iterator i = this.relations.iterator(); i.hasNext(); ) {
RichFeatureRelationship r = (RichFeatureRelationship)i.next();
features.add(r.getSubject());
}
return features;
}
/**
* {@inheritDoc}
*/
public boolean containsFeature(Feature f) { return this.relationsToFeatureSet().contains(f); }
/**
* {@inheritDoc}
*/
public int countFeatures() { return this.relationsToFeatureSet().size(); }
/**
* {@inheritDoc}
*/
public Feature createFeature(Feature.Template ft) throws BioException, ChangeVetoException {
if (ft==null) throw new IllegalArgumentException("Template cannot be null");
RichFeature f;
try {
f = new SimpleRichFeature(this.parent, ft);
} catch (InvalidTermException e) {
throw new ChangeVetoException("Term was not accepted",e);
}
this.addFeatureRelationship(
new SimpleRichFeatureRelationship(this, f, SimpleRichFeatureRelationship.getContainsTerm(), 0)
);
return f;
}
/**
* {@inheritDoc}
*/
public Iterator features() { return this.relationsToFeatureSet().iterator(); }
/**
* {@inheritDoc}
*/
public FeatureHolder filter(FeatureFilter filter) {
boolean recurse = !FilterUtils.areProperSubset(filter, FeatureFilter.top_level);
return this.filter(filter, recurse);
}
/**
* {@inheritDoc}
*/
public FeatureHolder filter(FeatureFilter fc, boolean recurse) {
SimpleFeatureHolder fh = new SimpleFeatureHolder();
for (Iterator i = this.features(); i.hasNext(); ) {
Feature f = (RichFeature)i.next();
try {
if (fc.accept(f)) fh.addFeature(f);
} catch (ChangeVetoException e) {
throw new RuntimeException("Aaargh! Our feature was rejected!",e);
}
}
return fh;
}
/**
* {@inheritDoc}
*/
public FeatureFilter getSchema() { return FeatureFilter.all; }
/**
* {@inheritDoc}
*/
public void removeFeature(Feature f) throws ChangeVetoException, BioException {
for (Iterator i = this.relations.iterator(); i.hasNext(); ) {
RichFeatureRelationship r = (RichFeatureRelationship)i.next();
if (r.getSubject().equals(f)) i.remove();
}
}
/**
* {@inheritDoc}
* NOT IMPLEMENTED.
*/
public void setStrand(StrandedFeature.Strand strand) throws ChangeVetoException {
throw new ChangeVetoException("The strand is immutable on RichFeature objects.");
}
/**
* {@inheritDoc}
*/
public StrandedFeature.Strand getStrand() {
RichLocation.Strand s = this.location.getStrand();
if (s.equals(RichLocation.Strand.NEGATIVE_STRAND)) return StrandedFeature.NEGATIVE;
if (s.equals(RichLocation.Strand.POSITIVE_STRAND)) return StrandedFeature.POSITIVE;
else return StrandedFeature.UNKNOWN;
}
/**
* {@inheritDoc}
*/
public int hashCode() {
int code = 17;
// Hibernate comparison - we haven't been populated yet
if (this.parent==null) return code;
// Normal comparison
code = 31*code + this.rank;
code = 31*code + this.parent.hashCode();
code = 31*code + this.sourceTerm.hashCode();
code = 31*code + this.typeTerm.hashCode();
return code;
}
/**
* {@inheritDoc}
* Features are equal when they have the same rank, parent, type, and source.
* Features which are not instance of RichFeature are given a
* rank of zero.
*/
public boolean equals(Object o) {
if (! (o instanceof Feature)) return false;
// Hibernate comparison - we haven't been populated yet
if (this.parent==null) return false;
// Normal comparison
Feature fo = (Feature) o;
int ourRank = this.getRank();
int theirRank = fo instanceof RichFeature? ((RichFeature)fo).getRank() : 0;
if ( ourRank!=theirRank) return false;
if (! this.parent.equals(fo.getParent())) return false;
if (! this.typeTerm.equals(fo.getTypeTerm())) return false;
if (! this.sourceTerm.equals(fo.getSourceTerm())) return false;
return true;
}
/**
* {@inheritDoc}
* Features are sorted first by rank, then parent, type, and source.
* If both parents are not comparable then this part of the sorting
* is skipped. Features which are not instance of RichFeature are
* given a rank of zero.
*/
public int compareTo(Object o) {
if (o==this) return 0;
// Hibernate comparison - we haven't been populated yet
if (this.parent==null) return -1;
// Normal comparison
Feature them = (Feature)o;
int ourRank = this.getRank();
int theirRank = them instanceof RichFeature? ((RichFeature)them).getRank():0;
if (ourRank!=theirRank) return ourRank-theirRank;
if (this.parent instanceof Comparable &&
them.getParent() instanceof Comparable &&
!this.parent.equals(them.getParent()))
return ((Comparable)this.parent).compareTo(them.getParent());
if (! this.typeTerm.equals(them.getTypeTerm()))
return this.typeTerm.compareTo(them.getTypeTerm());
if (! this.sourceTerm.equals(them.getSourceTerm()))
return this.sourceTerm.compareTo(them.getSourceTerm());
if(this.parent.equals(them.getParent()))
return 0; // equality on non-comparable parents
else
return -1;
}
/**
* {@inheritDoc}
* Form: "(#rank) parent:type,source(location)"
*/
public String toString() {
return "(#"+this.rank+") "+this.parent+":"+this.getType()+","+this.getSource()+"("+this.location+")";
}
// Hibernate requirement - not for public use.
private Integer id;
/**
* Gets the Hibernate ID. Should be used with caution.
* @return the Hibernate ID, if using Hibernate.
*/
public Integer getId() { return this.id; }
/**
* Sets the Hibernate ID. Should be used with caution.
* @param id the Hibernate ID, if using Hibernate.
*/
public void setId(Integer id) { this.id = id;}
}