001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.bio.seq.impl;
023
024import java.util.Collections;
025import java.util.Iterator;
026
027import org.biojava.bio.Annotatable;
028import org.biojava.bio.Annotation;
029import org.biojava.bio.BioError;
030import org.biojava.bio.BioException;
031import org.biojava.bio.SimpleAnnotation;
032import org.biojava.bio.seq.Feature;
033import org.biojava.bio.seq.FeatureFilter;
034import org.biojava.bio.seq.FeatureHolder;
035import org.biojava.bio.seq.FilterUtils;
036import org.biojava.bio.seq.RealizingFeatureHolder;
037import org.biojava.bio.seq.Sequence;
038import org.biojava.bio.seq.SimpleFeatureHolder;
039import org.biojava.bio.symbol.Location;
040import org.biojava.bio.symbol.SymbolList;
041import org.biojava.ontology.OntoTools;
042import org.biojava.ontology.Term;
043import org.biojava.utils.AbstractChangeable;
044import org.biojava.utils.ChangeEvent;
045import org.biojava.utils.ChangeForwarder;
046import org.biojava.utils.ChangeListener;
047import org.biojava.utils.ChangeSupport;
048import org.biojava.utils.ChangeType;
049import org.biojava.utils.ChangeVetoException;
050
051/**
052 * A no-frills implementation of a feature.
053 * 
054 * @author Matthew Pocock
055 * @author Thomas Down
056 * @author Kalle N�slund
057 * @author Paul Seed
058 * @author Len Trigg
059 * @see org.biojavax.bio.seq.SimpleRichFeature
060 */
061
062public class SimpleFeature
063extends
064AbstractChangeable
065implements
066Feature,
067RealizingFeatureHolder,
068java.io.Serializable
069{
070        private transient ChangeListener annotationForwarder;
071        private transient ChangeListener featureForwarder;
072
073        /**
074         * The FeatureHolder that we will delegate the FeatureHolder interface too.
075         * This is lazily instantiated.
076         */
077        private SimpleFeatureHolder featureHolder;
078
079        /**
080         * The location of this feature.
081         */
082        private Location loc;
083        /**
084         * The type of this feature - something like Exon.
085         * This is included for cheap interoperability with GFF.
086         */
087        private String type;
088        /**
089         * The source of this feature - the program that generated it.
090         * This is included for cheap interoperability with GFF.
091         */
092        private String source;
093        /**
094         * Our parent FeatureHolder.
095         */
096        private FeatureHolder parent;
097        /**
098         * The annotation object.
099         * This is lazily instantiated.
100         */
101        private Annotation annotation;
102
103        private Term typeTerm;
104        private Term sourceTerm;
105
106        /**
107         * A utility function to retrieve the feature holder delegate, creating it if
108         * necessary.
109         *
110         * @return  the FeatureHolder delegate
111         */
112        protected SimpleFeatureHolder getFeatureHolder() {
113                if(featureHolder == null) {
114                        featureHolder = new SimpleFeatureHolder();
115                }
116                return featureHolder;
117        }
118
119        /**
120         * A utility function to find out if the feature holder delegate has been
121         * instantiated yet. If it has not, we may avoid instantiating it by returning
122         * some pre-canned result.
123         *
124         * @return true if the feature holder delegate has been created and false
125         *         otherwise
126         */
127        protected boolean featureHolderAllocated() {
128                return featureHolder != null;
129        }
130
131        protected ChangeSupport getChangeSupport(ChangeType ct) {
132                ChangeSupport cs = super.getChangeSupport(ct);
133
134                if(
135                                (annotationForwarder == null) &&
136                                (ct.isMatchingType(Annotatable.ANNOTATION) || Annotatable.ANNOTATION.isMatchingType(ct))
137                ) {
138                        annotationForwarder =
139                                new ChangeForwarder.Retyper(this, cs, Annotation.PROPERTY);
140                        getAnnotation().addChangeListener(
141                                        annotationForwarder,
142                                        Annotatable.ANNOTATION
143                        );
144                }
145
146                if(
147                                (featureForwarder == null) &&
148                                (ct == null || ct == FeatureHolder.FEATURES)
149                ) {
150                        featureForwarder = new ChangeForwarder(
151                                        this,
152                                        cs
153                        );
154                        getFeatureHolder().addChangeListener(
155                                        featureForwarder,
156                                        FeatureHolder.FEATURES
157                        );
158                }
159
160                return cs;
161        }
162
163        public Location getLocation() {
164                return loc;
165        }
166
167        public void setLocation(Location loc)
168        throws ChangeVetoException {
169                if(hasListeners()) {
170                        ChangeSupport cs = getChangeSupport(LOCATION);
171                        synchronized(cs) {
172                                ChangeEvent ce = new ChangeEvent(this, LOCATION, loc, this.loc);
173                                synchronized(ce) {
174                                        cs.firePreChangeEvent(ce);
175                                }
176                                this.loc = loc;
177                                synchronized(ce) {
178                                        cs.firePostChangeEvent(ce);
179                                }
180                        }
181                } else {
182                        this.loc = loc;
183                }
184        }
185
186        public Term getTypeTerm() {
187                return typeTerm;
188        }
189
190        public String getType() {
191                if(type != null) {
192                        return type;
193                } else if (typeTerm != null) {
194                        return typeTerm.getName();
195                } else {
196                        return "";
197                }
198        }
199
200        public void setType(String type)
201        throws ChangeVetoException {
202                if(hasListeners()) {
203                        ChangeSupport cs = getChangeSupport(TYPE);
204                        synchronized(cs) {
205                                ChangeEvent ce = new ChangeEvent(this, TYPE, type, this.type);
206                                synchronized(ce) {
207                                        cs.firePreChangeEvent(ce);
208                                }
209                                this.type = type;
210                                synchronized(ce) {
211                                        cs.firePostChangeEvent(ce);
212                                }
213                        }
214                } else {
215                        this.type = type;
216                }
217        }
218
219        public void setTypeTerm(Term t)
220        throws ChangeVetoException
221        {
222                if(hasListeners()) {
223                        ChangeSupport cs = getChangeSupport(TYPE);
224                        synchronized (cs) {
225                                ChangeEvent ce_term = new ChangeEvent(this, TYPETERM, t, this.getTypeTerm());
226                                ChangeEvent ce_name = new ChangeEvent(this, TYPE, t.getName(), this.getType());
227                                cs.firePreChangeEvent(ce_term);
228                                cs.firePreChangeEvent(ce_name);
229                                this.typeTerm = t;
230                                cs.firePostChangeEvent(ce_term);
231                                cs.firePostChangeEvent(ce_name);
232                        }
233                } else {
234                        this.typeTerm = t;
235                }
236        }
237
238        public String getSource() {
239                if(source != null) {
240                        return source;
241                } else if (sourceTerm != null) {
242                        return sourceTerm.getName();
243                } else {
244                        return "";
245                }
246        }
247
248        public Term getSourceTerm() {
249                return sourceTerm;
250        }
251
252        public FeatureHolder getParent() {
253                return parent;
254        }
255
256        public void setSource(String source)
257        throws ChangeVetoException {
258                if(hasListeners()) {
259                        ChangeSupport cs = getChangeSupport(SOURCE);
260                        synchronized(cs) {
261                                ChangeEvent ce = new ChangeEvent(this, SOURCE, this.source, source);
262                                cs.firePreChangeEvent(ce);
263                                this.source = source;
264                                cs.firePostChangeEvent(ce);
265                        }
266                } else {
267                        this.source = source;
268                }
269        }
270
271
272        public void setSourceTerm(Term t)
273        throws ChangeVetoException
274        {
275                if(hasListeners()) {
276                        ChangeSupport cs = getChangeSupport(TYPE);
277                        synchronized (cs) {
278                                ChangeEvent ce_term = new ChangeEvent(this, SOURCETERM, t, this.getSourceTerm());
279                                ChangeEvent ce_name = new ChangeEvent(this, SOURCE, t.getName(), this.getSource());
280                                cs.firePreChangeEvent(ce_term);
281                                cs.firePreChangeEvent(ce_name);
282                                this.sourceTerm = t;
283                                cs.firePostChangeEvent(ce_term);
284                                cs.firePostChangeEvent(ce_name);
285                        }
286                } else {
287                        this.sourceTerm = t;
288                }
289        }
290
291        public Sequence getSequence() {
292                FeatureHolder fh = this;
293                while (fh instanceof Feature) {
294                        fh = ((Feature) fh).getParent();
295                }
296                try {
297                        return (Sequence) fh;
298                } catch (ClassCastException ex) {
299                        throw new BioError("Feature doesn't seem to have a Sequence ancestor: " + fh);
300                }
301        }
302
303        public Annotation getAnnotation() {
304                if(annotation == null)
305                        annotation = new SimpleAnnotation();
306                return annotation;
307        }
308
309        public SymbolList getSymbols() {
310                return getLocation().symbols(getSequence());
311        }
312
313        public int countFeatures() {
314                if(featureHolderAllocated())
315                        return getFeatureHolder().countFeatures();
316                return 0;
317        }
318
319        public Iterator features() {
320                if(featureHolderAllocated())
321                        return getFeatureHolder().features();
322                return Collections.EMPTY_LIST.iterator();
323        }
324
325        public void removeFeature(Feature f)
326        throws ChangeVetoException {
327                getFeatureHolder().removeFeature(f);
328        }
329
330        public boolean containsFeature(Feature f) {
331                if(featureHolderAllocated()) {
332                        return getFeatureHolder().containsFeature(f);
333                } else {
334                        return false;
335                }
336        }
337
338
339        public FeatureHolder filter(FeatureFilter ff) {
340                FeatureFilter childFilter = new FeatureFilter.Not(FeatureFilter.top_level);
341
342                if (FilterUtils.areDisjoint(ff, childFilter)) {
343                        return FeatureHolder.EMPTY_FEATURE_HOLDER;
344                } else if (featureHolderAllocated()) {
345                        return getFeatureHolder().filter(ff);
346                } else {
347                        return FeatureHolder.EMPTY_FEATURE_HOLDER;
348                }
349        }
350
351        public FeatureHolder filter(FeatureFilter ff, boolean recurse) {
352                if(featureHolderAllocated())
353                        return getFeatureHolder().filter(ff, recurse);
354                return FeatureHolder.EMPTY_FEATURE_HOLDER;
355        }
356
357        public Feature.Template makeTemplate() {
358                Feature.Template ft = new Feature.Template();
359                fillTemplate(ft);
360                return ft;
361        }
362
363        protected void fillTemplate(Feature.Template ft) {
364                ft.location = getLocation();
365                ft.type = getType();
366                ft.source = getSource();
367                ft.annotation = getAnnotation();
368                ft.sourceTerm = getSourceTerm();
369                ft.typeTerm = getTypeTerm();
370        }
371
372        /**
373         * Create a <code>SimpleFeature</code> on the given sequence.
374         * The feature is created underneath the parent <code>FeatureHolder</code>
375         * and populated directly from the template fields. However,
376         * if the template annotation is the <code>Annotation.EMPTY_ANNOTATION</code>,
377         * an empty <code>SimpleAnnotation</code> is attached to the feature instead.
378         * @param sourceSeq the source sequence
379         * @param parent the parent sequence or feature
380         * @param template the template for the feature
381         */
382        public SimpleFeature(Sequence sourceSeq,
383                        FeatureHolder parent,
384                        Feature.Template template) {
385                if (template.location == null) {
386                        throw new IllegalArgumentException(
387                                        "Location can not be null. Did you mean Location.EMPTY_LOCATION? " +
388                                        template.toString()
389                        );
390                }
391                if(!(parent instanceof Feature) && !(parent instanceof Sequence)) {
392                        throw new IllegalArgumentException("Parent must be sequence or feature, not: " + parent.getClass() + " " + parent);
393                }
394
395                if (template.location.getMin() < 1 || template.location.getMax() > sourceSeq.length()) {
396                        //throw new IllegalArgumentException("Location " + template.location.toString() + " is outside 1.." + sourceSeq.length());
397                }
398
399                this.parent = parent;
400                this.loc = template.location;
401                this.typeTerm = template.typeTerm != null ? template.typeTerm : OntoTools.ANY;
402                this.sourceTerm = template.sourceTerm != null ? template.sourceTerm : OntoTools.ANY;
403                this.type = template.type != null ? template.type : typeTerm.getName();
404                this.source = template.source != null ? template.source : sourceTerm.getName();
405                if (this.type == null) {
406                        throw new NullPointerException("Either type or typeTerm must have a non-null value");
407                }
408                if (this.source == null) {
409                        throw new NullPointerException("Either source or sourceTerm must have a non-null value");
410                }
411                this.annotation = template.annotation != null ? new SimpleAnnotation(template.annotation) : null;
412        }
413
414        public String toString() {
415                return "Feature " + getType() + " " +
416                getSource() + " " + getLocation();
417        }
418
419        public Feature realizeFeature(FeatureHolder fh, Feature.Template templ)
420        throws BioException
421        {
422                try {
423                        RealizingFeatureHolder rfh = (RealizingFeatureHolder) getParent();
424                        return rfh.realizeFeature(fh, templ);
425                } catch (ClassCastException ex) {
426                        throw new BioException("Couldn't propagate feature creation request.");
427                }
428        }
429
430        public Feature createFeature(Feature.Template temp)
431        throws BioException, ChangeVetoException
432        {
433                Feature f = realizeFeature(this, temp);
434                getFeatureHolder().addFeature(f);
435                return f;
436        }
437
438        public int hashCode() {
439                return makeTemplate().hashCode();
440        }
441
442        public boolean equals(Object o) {
443                if (! (o instanceof Feature)) {
444                        return false;
445                }
446
447                Feature fo = (Feature) o;
448                if (! fo.getSequence().equals(getSequence()))
449                        return false;
450
451                return makeTemplate().equals(fo.makeTemplate());
452        }
453
454        public FeatureFilter getSchema() {
455                return new FeatureFilter.ByParent(new FeatureFilter.ByFeature(this));
456        }
457}