/* * 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.seq.impl; import java.util.Collections; import java.util.Iterator; import org.biojava.bio.Annotatable; import org.biojava.bio.Annotation; import org.biojava.bio.BioError; import org.biojava.bio.BioException; import org.biojava.bio.SimpleAnnotation; 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.RealizingFeatureHolder; import org.biojava.bio.seq.Sequence; import org.biojava.bio.seq.SimpleFeatureHolder; import org.biojava.bio.symbol.Location; import org.biojava.bio.symbol.SymbolList; import org.biojava.ontology.OntoTools; import org.biojava.ontology.Term; import org.biojava.utils.AbstractChangeable; import org.biojava.utils.ChangeEvent; import org.biojava.utils.ChangeForwarder; import org.biojava.utils.ChangeListener; import org.biojava.utils.ChangeSupport; import org.biojava.utils.ChangeType; import org.biojava.utils.ChangeVetoException; /** * A no-frills implementation of a feature. * * @author Matthew Pocock * @author Thomas Down * @author Kalle N�slund * @author Paul Seed * @author Len Trigg * @see org.biojavax.bio.seq.SimpleRichFeature */ public class SimpleFeature extends AbstractChangeable implements Feature, RealizingFeatureHolder, java.io.Serializable { private transient ChangeListener annotationForwarder; private transient ChangeListener featureForwarder; /** * The FeatureHolder that we will delegate the FeatureHolder interface too. * This is lazily instantiated. */ private SimpleFeatureHolder featureHolder; /** * The location of this feature. */ private Location loc; /** * The type of this feature - something like Exon. * This is included for cheap interoperability with GFF. */ private String type; /** * The source of this feature - the program that generated it. * This is included for cheap interoperability with GFF. */ private String source; /** * Our parent FeatureHolder. */ private FeatureHolder parent; /** * The annotation object. * This is lazily instantiated. */ private Annotation annotation; private Term typeTerm; private Term sourceTerm; /** * A utility function to retrieve the feature holder delegate, creating it if * necessary. * * @return the FeatureHolder delegate */ protected SimpleFeatureHolder getFeatureHolder() { if(featureHolder == null) { featureHolder = new SimpleFeatureHolder(); } return featureHolder; } /** * A utility function to find out if the feature holder delegate has been * instantiated yet. If it has not, we may avoid instantiating it by returning * some pre-canned result. * * @return true if the feature holder delegate has been created and false * otherwise */ protected boolean featureHolderAllocated() { return featureHolder != null; } protected ChangeSupport getChangeSupport(ChangeType ct) { ChangeSupport cs = super.getChangeSupport(ct); if( (annotationForwarder == null) && (ct.isMatchingType(Annotatable.ANNOTATION) || Annotatable.ANNOTATION.isMatchingType(ct)) ) { annotationForwarder = new ChangeForwarder.Retyper(this, cs, Annotation.PROPERTY); getAnnotation().addChangeListener( annotationForwarder, Annotatable.ANNOTATION ); } if( (featureForwarder == null) && (ct == null || ct == FeatureHolder.FEATURES) ) { featureForwarder = new ChangeForwarder( this, cs ); getFeatureHolder().addChangeListener( featureForwarder, FeatureHolder.FEATURES ); } return cs; } public Location getLocation() { return loc; } public void setLocation(Location loc) throws ChangeVetoException { if(hasListeners()) { ChangeSupport cs = getChangeSupport(LOCATION); synchronized(cs) { ChangeEvent ce = new ChangeEvent(this, LOCATION, loc, this.loc); cs.firePreChangeEvent(ce); this.loc = loc; cs.firePostChangeEvent(ce); } } else { this.loc = loc; } } public Term getTypeTerm() { return typeTerm; } public String getType() { if(type != null) { return type; } else if (typeTerm != null) { return typeTerm.getName(); } else { return ""; } } public void setType(String type) throws ChangeVetoException { if(hasListeners()) { ChangeSupport cs = getChangeSupport(TYPE); synchronized(cs) { ChangeEvent ce = new ChangeEvent(this, TYPE, type, this.type); cs.firePreChangeEvent(ce); this.type = type; cs.firePostChangeEvent(ce); } } else { this.type = type; } } public void setTypeTerm(Term t) throws ChangeVetoException { if(hasListeners()) { ChangeSupport cs = getChangeSupport(TYPE); synchronized (cs) { ChangeEvent ce_term = new ChangeEvent(this, TYPETERM, t, this.getTypeTerm()); ChangeEvent ce_name = new ChangeEvent(this, TYPE, t.getName(), this.getType()); cs.firePreChangeEvent(ce_term); cs.firePreChangeEvent(ce_name); this.typeTerm = t; cs.firePostChangeEvent(ce_term); cs.firePostChangeEvent(ce_name); } } else { this.typeTerm = t; } } public String getSource() { if(source != null) { return source; } else if (sourceTerm != null) { return sourceTerm.getName(); } else { return ""; } } public Term getSourceTerm() { return sourceTerm; } public FeatureHolder getParent() { return parent; } public void setSource(String source) throws ChangeVetoException { if(hasListeners()) { ChangeSupport cs = getChangeSupport(SOURCE); synchronized(cs) { ChangeEvent ce = new ChangeEvent(this, SOURCE, this.source, source); cs.firePreChangeEvent(ce); this.source = source; cs.firePostChangeEvent(ce); } } else { this.source = source; } } public void setSourceTerm(Term t) throws ChangeVetoException { if(hasListeners()) { ChangeSupport cs = getChangeSupport(TYPE); synchronized (cs) { ChangeEvent ce_term = new ChangeEvent(this, SOURCETERM, t, this.getSourceTerm()); ChangeEvent ce_name = new ChangeEvent(this, SOURCE, t.getName(), this.getSource()); cs.firePreChangeEvent(ce_term); cs.firePreChangeEvent(ce_name); this.sourceTerm = t; cs.firePostChangeEvent(ce_term); cs.firePostChangeEvent(ce_name); } } else { this.sourceTerm = t; } } public Sequence getSequence() { FeatureHolder fh = this; while (fh instanceof Feature) { fh = ((Feature) fh).getParent(); } try { return (Sequence) fh; } catch (ClassCastException ex) { throw new BioError("Feature doesn't seem to have a Sequence ancestor: " + fh); } } public Annotation getAnnotation() { if(annotation == null) annotation = new SimpleAnnotation(); return annotation; } public SymbolList getSymbols() { return getLocation().symbols(getSequence()); } public int countFeatures() { if(featureHolderAllocated()) return getFeatureHolder().countFeatures(); return 0; } public Iterator features() { if(featureHolderAllocated()) return getFeatureHolder().features(); return Collections.EMPTY_LIST.iterator(); } public void removeFeature(Feature f) throws ChangeVetoException { getFeatureHolder().removeFeature(f); } public boolean containsFeature(Feature f) { if(featureHolderAllocated()) { return getFeatureHolder().containsFeature(f); } else { return false; } } public FeatureHolder filter(FeatureFilter ff) { FeatureFilter childFilter = new FeatureFilter.Not(FeatureFilter.top_level); if (FilterUtils.areDisjoint(ff, childFilter)) { return FeatureHolder.EMPTY_FEATURE_HOLDER; } else if (featureHolderAllocated()) { return getFeatureHolder().filter(ff); } else { return FeatureHolder.EMPTY_FEATURE_HOLDER; } } public FeatureHolder filter(FeatureFilter ff, boolean recurse) { if(featureHolderAllocated()) return getFeatureHolder().filter(ff, recurse); return FeatureHolder.EMPTY_FEATURE_HOLDER; } public Feature.Template makeTemplate() { Feature.Template ft = new Feature.Template(); fillTemplate(ft); return ft; } protected void fillTemplate(Feature.Template ft) { ft.location = getLocation(); ft.type = getType(); ft.source = getSource(); ft.annotation = getAnnotation(); ft.sourceTerm = getSourceTerm(); ft.typeTerm = getTypeTerm(); } /** * Create a SimpleFeature on the given sequence. * The feature is created underneath the parent FeatureHolder * and populated directly from the template fields. However, * if the template annotation is the Annotation.EMPTY_ANNOTATION, * an empty SimpleAnnotation is attached to the feature instead. * @param sourceSeq the source sequence * @param parent the parent sequence or feature * @param template the template for the feature */ public SimpleFeature(Sequence sourceSeq, FeatureHolder parent, Feature.Template template) { if (template.location == null) { throw new IllegalArgumentException( "Location can not be null. Did you mean Location.EMPTY_LOCATION? " + template.toString() ); } if(!(parent instanceof Feature) && !(parent instanceof Sequence)) { throw new IllegalArgumentException("Parent must be sequence or feature, not: " + parent.getClass() + " " + parent); } if (template.location.getMin() < 1 || template.location.getMax() > sourceSeq.length()) { //throw new IllegalArgumentException("Location " + template.location.toString() + " is outside 1.." + sourceSeq.length()); } this.parent = parent; this.loc = template.location; this.typeTerm = template.typeTerm != null ? template.typeTerm : OntoTools.ANY; this.sourceTerm = template.sourceTerm != null ? template.sourceTerm : OntoTools.ANY; this.type = template.type != null ? template.type : typeTerm.getName(); this.source = template.source != null ? template.source : sourceTerm.getName(); if (this.type == null) { throw new NullPointerException("Either type or typeTerm must have a non-null value"); } if (this.source == null) { throw new NullPointerException("Either source or sourceTerm must have a non-null value"); } this.annotation = template.annotation != null ? new SimpleAnnotation(template.annotation) : null; } public String toString() { return "Feature " + getType() + " " + getSource() + " " + getLocation(); } public Feature realizeFeature(FeatureHolder fh, Feature.Template templ) throws BioException { try { RealizingFeatureHolder rfh = (RealizingFeatureHolder) getParent(); return rfh.realizeFeature(fh, templ); } catch (ClassCastException ex) { throw new BioException("Couldn't propagate feature creation request."); } } public Feature createFeature(Feature.Template temp) throws BioException, ChangeVetoException { Feature f = realizeFeature(this, temp); getFeatureHolder().addFeature(f); return f; } public int hashCode() { return makeTemplate().hashCode(); } public boolean equals(Object o) { if (! (o instanceof Feature)) { return false; } Feature fo = (Feature) o; if (! fo.getSequence().equals(getSequence())) return false; return makeTemplate().equals(fo.makeTemplate()); } public FeatureFilter getSchema() { return new FeatureFilter.ByParent(new FeatureFilter.ByFeature(this)); } }