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.ArrayList;
025import java.util.Iterator;
026import java.util.List;
027
028import org.biojava.bio.Annotatable;
029import org.biojava.bio.Annotation;
030import org.biojava.bio.BioException;
031import org.biojava.bio.seq.Feature;
032import org.biojava.bio.seq.FeatureFilter;
033import org.biojava.bio.seq.FeatureHolder;
034import org.biojava.bio.seq.FilterUtils;
035import org.biojava.bio.seq.RemoteFeature;
036import org.biojava.bio.seq.Sequence;
037import org.biojava.bio.seq.StrandedFeature;
038import org.biojava.bio.seq.db.IllegalIDException;
039import org.biojava.bio.seq.projection.ProjectedFeatureHolder;
040import org.biojava.bio.seq.projection.Projection;
041import org.biojava.bio.seq.projection.ReparentContext;
042import org.biojava.bio.seq.projection.TranslateFlipContext;
043import org.biojava.bio.symbol.Alphabet;
044import org.biojava.bio.symbol.Edit;
045import org.biojava.bio.symbol.FuzzyLocation;
046import org.biojava.bio.symbol.Location;
047import org.biojava.bio.symbol.LocationTools;
048import org.biojava.bio.symbol.RangeLocation;
049import org.biojava.bio.symbol.Symbol;
050import org.biojava.bio.symbol.SymbolList;
051import org.biojava.ontology.InvalidTermException;
052import org.biojava.ontology.Term;
053import org.biojava.utils.ChangeEvent;
054import org.biojava.utils.ChangeForwarder;
055import org.biojava.utils.ChangeListener;
056import org.biojava.utils.ChangeSupport;
057import org.biojava.utils.ChangeType;
058import org.biojava.utils.ChangeVetoException;
059
060/**
061 * View a sub-section of a given sequence object, including all the
062 * features intersecting that region.
063 *
064 * <p>
065 * All features entirely contained within the region are projected by just
066 * translating their locations. The features that overlap the region are
067 * replaced by RemoteFeature instances with fuzzy locations that are
068 * trunchated to fit inside the sub-section. All features not contained by
069 * the region are not projected.
070 * </p>
071 *
072 * @author Thomas Down
073 * @author Matthew Pocock
074 * @since 1.2
075 */
076
077public class SubSequence
078implements Sequence, java.io.Serializable
079{
080        private final Sequence parent;
081        private final SymbolList symbols;
082        private final String name;
083        private final String uri;
084        private final Annotation annotation;
085        private final RangeLocation parentLocation;
086
087        private transient ProjectedFeatureHolder features;
088        private transient ChangeSupport changeSupport;
089        private transient ChangeListener seqListener;
090        protected transient ChangeForwarder annotationForwarder;
091
092        private void allocChangeSupport() {
093                if (seqListener == null) {
094                        installSeqListener();
095                }
096                changeSupport = new ChangeSupport();
097        }
098
099        private void installSeqListener() {
100                seqListener = new ChangeListener() {
101                        public void preChange(ChangeEvent cev)
102                        throws ChangeVetoException {
103                                if (changeSupport != null) {
104                                        changeSupport.firePreChangeEvent(makeChainedEvent(cev));
105                                }
106                        }
107
108                        public void postChange(ChangeEvent cev) {
109                                if (changeSupport != null) {
110                                        changeSupport.firePostChangeEvent(makeChainedEvent(cev));
111                                }
112                        }
113
114                        private ChangeEvent makeChainedEvent(ChangeEvent cev) {
115                                return new ChangeEvent(SubSequence.this,
116                                                FeatureHolder.FEATURES,
117                                                null, null,
118                                                cev);
119                        }
120                };
121                parent.addChangeListener(seqListener, FeatureHolder.FEATURES);
122        }
123
124        /**
125         * Construct a new SubSequence of the specified sequence.
126         *   Generally you would use the SequenceTools.subSequence() methods
127         *  to get an instance of this class.
128         * @param seq A sequence to view
129         * @param start The start of the range to view
130         * @param end The end of the range to view
131         * @param name Name for the subsequence
132         * @throws java.lang.IndexOutOfBoundsException is the start or end position is illegal.
133         */
134        public SubSequence(Sequence seq,
135                        final int start,
136                        final int end,
137                        final String name)
138        {
139                this.parent = seq;
140                this.parentLocation = new RangeLocation(start, end);
141                this.symbols = seq.subList(start, end);
142                this.name = name;
143                this.uri = seq.getURN() + "?start=" + start + ";end=" + end;
144                this.annotation = seq.getAnnotation();
145        }
146
147        /**
148         * Construct a new SubSequence of the specified sequence. Generally
149         * you would use the SequenceTools.subSequence() methods to get an
150         * instance of this class.
151         *
152         * @param seq A sequence to view
153         * @param start The start of the range to view
154         * @param end The end of the range to view
155         * @throws java.lang.IndexOutOfBoundsException if the start or end position is illegal.
156         */
157
158        public SubSequence(Sequence seq,
159                        final int start,
160                        final int end) {
161                this(seq, start, end, seq.getName() + " (" + start + " - " + end + ")");
162        }
163
164        //
165        // SymbolList stuff
166        //
167
168        public Symbol symbolAt(int pos) {
169                return symbols.symbolAt(pos);
170        }
171
172        public Alphabet getAlphabet() {
173                return symbols.getAlphabet();
174        }
175
176        public SymbolList subList(int start, int end) {
177                return symbols.subList(start, end);
178        }
179
180        public String seqString() {
181                return symbols.seqString();
182        }
183
184        public String subStr(int start, int end) {
185                return symbols.subStr(start, end);
186        }
187
188        public List toList() {
189                return symbols.toList();
190        }
191
192        public int length() {
193                return symbols.length();
194        }
195
196        public Iterator iterator() {
197                return symbols.iterator();
198        }
199
200        public void edit(Edit edit)
201        throws ChangeVetoException {
202                throw new ChangeVetoException("Can't edit SubSequences");
203        }
204
205        //
206        // Implements featureholder
207        //
208
209        public int countFeatures() {
210                return getFeatures().countFeatures();
211        }
212
213        public Iterator features() {
214                return getFeatures().features();
215        }
216
217        public FeatureHolder filter(FeatureFilter ff, boolean recurse) {
218                return getFeatures().filter(ff, recurse);
219        }
220
221        public FeatureHolder filter(FeatureFilter ff) {
222                return getFeatures().filter(ff);
223        }
224
225        public boolean containsFeature(Feature f) {
226                return getFeatures().containsFeature(f);
227        }
228
229        public Feature createFeature(Feature.Template templ)
230        throws BioException, ChangeVetoException {
231
232                ProjectedFeatureHolder featureHolder = getFeatures();
233
234                Feature f = null;
235                synchronized (featureHolder) {
236                        f = featureHolder.createFeature(templ);
237                }
238                return f;
239
240        }
241
242        public void removeFeature(Feature f)
243        throws ChangeVetoException, BioException {
244                ProjectedFeatureHolder featureHolder = getFeatures();
245
246                synchronized (featureHolder){
247                        featureHolder.removeFeature(f);
248                }
249        }
250
251        public FeatureFilter getSchema() {
252                return getFeatures().getSchema();
253        }
254
255        protected ProjectedFeatureHolder getFeatures() {
256                if (features == null) {
257                        FeatureHolder clipped = new ProjectedFeatureHolder(
258                                        new SubProjectedFeatureContext(parent, parentLocation) );
259                        features = new ProjectedFeatureHolder(
260                                        new TranslateFlipContext(this,
261                                                        clipped,
262                                                        - parentLocation.getMin() + 1) );
263                }
264                return features;
265        }
266
267        //
268        // Identifiable
269        //
270
271        public String getName() {
272                return name;
273        }
274
275        public String getURN() {
276                return uri;
277        }
278
279        //
280        // Annotatable
281        //
282
283        public Annotation getAnnotation() {
284                return annotation;
285        }
286
287        /**
288         * Return the parent sequence of which this is a partial view
289         *
290         * @since 1.3
291         */
292
293        public Sequence getSequence() {
294                return this.parent;
295        }
296
297        public int getStart() {
298                return parentLocation.getMin();
299        }
300
301        public int getEnd() {
302                return parentLocation.getMax();
303        }
304
305        public void addChangeListener(ChangeListener cl, ChangeType ct) {
306                if (changeSupport == null) {
307                        allocChangeSupport();
308                }
309
310                if (annotationForwarder == null && ct == Annotatable.ANNOTATION) {
311                        annotationForwarder =
312                                new ChangeForwarder.Retyper(this, changeSupport, Annotation.PROPERTY);
313                        getAnnotation().addChangeListener(annotationForwarder,
314                                        Annotatable.ANNOTATION);
315                }
316
317                changeSupport.addChangeListener(cl, ct);
318        }
319
320        public void addChangeListener(ChangeListener cl) {
321                addChangeListener(cl, ChangeType.UNKNOWN);
322        }
323
324        public void removeChangeListener(ChangeListener cl, ChangeType ct) {
325                if (changeSupport != null) {
326                        changeSupport.removeChangeListener(cl, ct);
327                }
328        }
329
330        public void removeChangeListener(ChangeListener cl) {
331                removeChangeListener(cl, ChangeType.UNKNOWN);
332        }
333
334        public boolean isUnchanging(ChangeType ct) {
335                return parent.isUnchanging(ct);
336        }
337
338        /**
339         * TargetContext that implements the mapping between the parent sequence and this
340         * sub-sequence.
341         *
342         * <p>
343         * This context is public because all contexts must be public, and not because
344         * it is usefull to you or part of the API. Don't use it directly.
345         * </p>
346         *
347         * <p>
348         * The context extends TranslateFlipContext so that it can translate all
349         * features within the parent location to being from index 1 in the
350         * projection. It also transforms and reverts locations so that locations
351         * falling outside the parent location are trunchated and replaced by
352         * fuzzy locations. Lastly, features with fuzzy locations are replaced by
353         * RemoteFeature instances.
354         *</p>
355         *
356         * @author Matthew Pocock
357         * @author Thomas Down
358         * @since 1.4
359         */
360        public static class SubProjectedFeatureContext
361        extends ReparentContext {
362                private static final FeatureFilter REMOTE_FILTER =
363                        new FeatureFilter.ByClass(RemoteFeature.class);
364
365                private static RemoteFeature.Resolver resolver
366                = new RemoteFeature.Resolver() {
367                        public Feature resolve(RemoteFeature rFeat) throws IllegalIDException, BioException {
368                                if(!(rFeat instanceof SubRemote)) {
369                                        throw new BioException("Unable to resolve feature: " + rFeat);
370                                }
371
372                                return rFeat.getRemoteFeature();
373                        }
374                };
375
376                private final RangeLocation parentLocation;
377                private final FeatureFilter remoteLocationFilter;
378                private final FeatureFilter clippingFilter;
379
380                private SubProjectedFeatureContext(FeatureHolder wrapped,
381                                RangeLocation parentLocation) {
382                        super(FeatureHolder.EMPTY_FEATURE_HOLDER,
383                                        new LazyFilterFeatureHolder(wrapped,
384                                                        FilterUtils.overlapsLocation(
385                                                                        parentLocation)));
386
387                        this.parentLocation = parentLocation;
388                        this.remoteLocationFilter = new FeatureFilter.Not(
389                                        FilterUtils.containedByLocation(parentLocation));
390                        this.clippingFilter = FilterUtils.overlapsLocation(parentLocation);
391                }
392
393                public Location projectLocation(Location toTransform) {
394                        if(LocationTools.overlaps(parentLocation, toTransform) &&
395                                        !LocationTools.contains(parentLocation, toTransform))
396                        {
397                                toTransform = fuzzyize(toTransform);
398                        }
399
400                        return toTransform;
401                }
402
403                public Feature projectFeature(Feature origFeat) {
404                        if(remoteLocationFilter.accept(origFeat)) {
405                                Location loc = fuzzyize(origFeat.getLocation());
406                                List regions = makeRegions(origFeat.getLocation(),
407                                                origFeat.getSequence().getName());
408
409                                origFeat = new SubRemote(origFeat, loc, regions);
410                        }
411
412                        return super.projectFeature(origFeat);
413                }
414
415                public Feature revertFeature(Feature projFeat) {
416                        Feature origFeat = super.revertFeature(projFeat);
417
418                        if(!(origFeat instanceof Projection) &&
419                                        (origFeat instanceof SubRemote) )
420                        {
421                                origFeat = ((SubRemote) origFeat).getRemoteFeature();
422                        }
423
424                        return origFeat;
425                }
426
427                public FeatureHolder projectChildFeatures(Feature f, FeatureHolder parent) {
428                        return new LazyFilterFeatureHolder(super.projectChildFeatures(f, parent),
429                                        clippingFilter);
430                }
431
432                private Location fuzzyize(Location loc) {
433                        List locList = new ArrayList();
434
435                        for(Iterator i = loc.blockIterator(); i.hasNext(); ) {
436                                Location l = (Location) i.next();
437                                if(LocationTools.overlaps(parentLocation, l)) {
438                                        boolean fuzzyLeft = l.getMin() < parentLocation.getMin();
439                                        boolean fuzzyRight = l.getMax() > parentLocation.getMax();
440
441                                        if(fuzzyLeft || fuzzyRight) {
442                                                locList.add(new FuzzyLocation(Math.min(l.getMin(), parentLocation.getMin()),
443                                                                Math.max(l.getMax(), parentLocation.getMax()),
444                                                                Math.max(l.getMin(), parentLocation.getMin()),
445                                                                Math.min(l.getMax(), parentLocation.getMax()),
446                                                                fuzzyLeft,
447                                                                fuzzyRight,
448                                                                FuzzyLocation.RESOLVE_INNER
449                                                ));
450                                        } else {
451                                                locList.add(l);
452                                        }
453                                }
454                        }
455
456                        return LocationTools.union(locList);
457                }
458
459                private List makeRegions(Location oldLoc, String seqID) {
460                        List regions = new ArrayList();
461
462                        for(Iterator i = oldLoc.blockIterator(); i.hasNext(); ) {
463                                Location loc = (Location) i.next();
464
465                                if(!LocationTools.overlaps(loc, parentLocation)) {
466                                        regions.add(new RemoteFeature.Region(loc, seqID, true));
467                                } else if(LocationTools.contains(parentLocation, loc)) {
468                                        regions.add(new RemoteFeature.Region(loc, null, false));
469                                } else {
470                                        // straddles boundary
471                                        Location remote = LocationTools.subtract(loc, parentLocation);
472                                        Location local = LocationTools.subtract(parentLocation, loc);
473                                        RemoteFeature.Region remoteR = new RemoteFeature.Region(remote, seqID, true);
474                                        RemoteFeature.Region localR = new RemoteFeature.Region(local, null, false);
475
476                                        if(remote.getMin() < local.getMin()) {
477                                                regions.add(remoteR);
478                                                regions.add(localR);
479                                        } else {
480                                                regions.add(localR);
481                                                regions.add(remoteR);
482                                        }
483                                }
484                        }
485
486                        return regions;
487                }
488
489                protected FilterUtils.FilterTransformer getReverter() {
490                        final FilterUtils.FilterTransformer delegate = super.getReverter();
491
492                        return new FilterUtils.FilterTransformer() {
493                                public FeatureFilter transform(FeatureFilter filt) {
494                                        // I may have got this logic wrong
495                                        if (filt.equals(REMOTE_FILTER)) {
496                                                filt = remoteLocationFilter;
497                                        }
498
499                                        filt = new FeatureFilter.And(
500                                                        filt,
501                                                        FilterUtils.overlapsLocation(parentLocation)
502                                        );
503
504                                        filt = delegate.transform(filt);
505
506                                        return filt;
507                                }
508                        };
509                }
510
511                protected FilterUtils.FilterTransformer getTransformer() {
512                        return super.getTransformer();
513                }
514
515                // fixme: this isn't wired to forward events
516                // however, the context should be skipping these anyhow during
517                // projectFeature and restoreFeature.
518                private class SubRemote
519                implements RemoteFeature {
520                        private final Feature wrapped;
521                        private final Location loc;
522                        private final List regionList;
523
524                        public SubRemote(Feature wrapped, Location loc, List regionList) {
525                                this.wrapped = wrapped;
526                                this.loc = loc;
527                                this.regionList = regionList;
528                        }
529
530                        public Location getLocation() {
531                                return loc;
532                        }
533
534                        public void setLocation(Location loc)
535                        throws ChangeVetoException {
536                                throw new ChangeVetoException("Can't set location: " + loc +
537                                                " on " + this);
538                        }
539
540                        public List getRegions() {
541                                return regionList;
542                        }
543
544                        public RemoteFeature.Resolver getResolver() {
545                                return resolver;
546                        }
547
548                        public Feature getRemoteFeature() {
549                                return wrapped;
550                        }
551
552                        public StrandedFeature.Strand getStrand() {
553                                if(wrapped instanceof StrandedFeature) {
554                                        return ((StrandedFeature) wrapped).getStrand();
555                                } else {
556                                        return StrandedFeature.UNKNOWN;
557                                }
558                        }
559
560                        public void setStrand(StrandedFeature.Strand strand)
561                        throws ChangeVetoException
562                        {
563                                if(wrapped instanceof StrandedFeature) {
564                                        ((StrandedFeature) wrapped).setStrand(strand);
565                                } else {
566                                        throw new ChangeVetoException("Can't set strand. The underlying feature is not stranded: " + wrapped);
567                                }
568                        }
569
570                        public String getType() {
571                                return wrapped.getType();
572                        }
573
574                        public void setType(String type)
575                        throws ChangeVetoException {
576                                wrapped.setType(type);
577                        }
578
579                        public Term getTypeTerm() {
580                                return wrapped.getTypeTerm();
581                        }
582
583                        public void setTypeTerm(Term t) throws ChangeVetoException, InvalidTermException {
584                                wrapped.setTypeTerm(t);
585                        }
586
587                        public Term getSourceTerm() {
588                                return wrapped.getSourceTerm();
589                        }
590
591                        public void setSourceTerm(Term t) throws ChangeVetoException, InvalidTermException {
592                                wrapped.setSourceTerm(t);
593                        }
594
595                        public String getSource() {
596                                return wrapped.getSource();
597                        }
598
599                        public void setSource(String source)
600                        throws ChangeVetoException {
601                                wrapped.setSource(source);
602                        }
603
604                        public SymbolList getSymbols() {
605                                return wrapped.getSymbols();
606                        }
607
608                        public FeatureHolder getParent() {
609                                return wrapped.getParent();
610                        }
611
612                        public Sequence getSequence() {
613                                return wrapped.getSequence();
614                        }
615
616                        public Feature.Template makeTemplate() {
617                                return wrapped.makeTemplate();
618                        }
619
620                        public int countFeatures() {
621                                return wrapped.countFeatures();
622                        }
623
624                        public Iterator features() {
625                                return wrapped.features();
626                        }
627
628                        public FeatureHolder filter(FeatureFilter fc, boolean recurse) {
629                                return wrapped.filter(fc, recurse);
630                        }
631
632                        public FeatureHolder filter(FeatureFilter filter) {
633                                return wrapped.filter(filter);
634                        }
635
636                        public Feature createFeature(Feature.Template ft)
637                        throws BioException, ChangeVetoException {
638                                return wrapped.createFeature(ft);
639                        }
640
641                        public void removeFeature(Feature f)
642                        throws ChangeVetoException, BioException {
643                                wrapped.removeFeature(f);
644                        }
645
646                        public boolean containsFeature(Feature f) {
647                                return wrapped.containsFeature(f);
648                        }
649
650                        public FeatureFilter getSchema() {
651                                return wrapped.getSchema();
652                        }
653
654                        public void addChangeListener(ChangeListener cl) {
655                                wrapped.addChangeListener(cl);
656                        }
657
658                        public void addChangeListener(ChangeListener cl, ChangeType ct) {
659                                wrapped.addChangeListener(cl, ct);
660                        }
661
662                        public void removeChangeListener(ChangeListener cl) {
663                                wrapped.removeChangeListener(cl);
664                        }
665
666                        public void removeChangeListener(ChangeListener cl, ChangeType ct) {
667                                wrapped.removeChangeListener(cl, ct);
668                        }
669
670                        public boolean isUnchanging(ChangeType ct) {
671                                return wrapped.isUnchanging(ct);
672                        }
673
674                        public Annotation getAnnotation() {
675                                return wrapped.getAnnotation();
676                        }
677                }
678        }
679}