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 * Created on 01-21-2010
021 */
022package org.biojava.nbio.core.sequence.location.template;
023
024import org.biojava.nbio.core.sequence.AccessionID;
025import org.biojava.nbio.core.sequence.Strand;
026import org.biojava.nbio.core.sequence.storage.JoiningSequenceReader;
027import org.biojava.nbio.core.sequence.template.ComplementCompound;
028import org.biojava.nbio.core.sequence.template.Compound;
029import org.biojava.nbio.core.sequence.template.CompoundSet;
030import org.biojava.nbio.core.sequence.template.Sequence;
031import org.biojava.nbio.core.sequence.views.ComplementSequenceView;
032import org.biojava.nbio.core.sequence.views.ReversedSequenceView;
033import org.biojava.nbio.core.util.Hashcoder;
034
035import java.io.Serializable;
036import java.util.ArrayList;
037import java.util.Collections;
038import java.util.Iterator;
039import java.util.List;
040
041import static java.lang.String.format;
042import static org.biojava.nbio.core.util.Equals.classEqual;
043import static org.biojava.nbio.core.util.Equals.equal;
044
045/**
046 * Base abstraction of a location which encodes for the majority of important
047 * features about a location such as the start, end and strand
048 *
049 * @author ayates
050 * @author Paolo Pavan
051 */
052public abstract class AbstractLocation implements Serializable, Location {
053
054        private static final long serialVersionUID = 1L;
055
056        //TODO Need to have the Sequence lookup resolver here; see the next one as well
057        //TODO Need a way of late binding of start/stop
058
059        private Point start;
060        private Point end;
061        private Strand strand;
062        private List<Location> subLocations;
063        private boolean circular;
064        private boolean betweenCompounds;
065        private AccessionID accession;
066
067        private boolean partialOn5prime = false;
068        private boolean partialOn3prime = false;
069
070
071
072        protected AbstractLocation() {
073                super();
074        }
075
076        /**
077         * Default constructor
078         *
079         * @param start start of the location
080         * @param end end of the location
081         * @param strand strand it is located on
082         * @param circular Boolean which says if the current location was circular
083         * or not
084         * @param betweenCompounds Indicates the location lies at the position between
085         * a pair of bases; means the bases must be next to each other (and
086         * therefore cannot be complex)
087         * @param subLocations Sub locations which composes this location
088         */
089        public AbstractLocation(Point start, Point end, Strand strand,
090                        boolean circular, boolean betweenCompounds,
091                        List<Location> subLocations) {
092                this(start, end, strand, circular, betweenCompounds, null, subLocations);
093        }
094
095        /**
096         * Default constructor
097         *
098         * @param start start of the location
099         * @param end end of the location
100         * @param strand strand it is located on
101         * @param circular Boolean which says if the current location was circular
102         * or not
103         * @param betweenCompounds Indicates the location lies at the position between
104         * a pair of bases; means the bases must be next to each other (and
105         * therefore cannot be complex)
106         * @param accession The accession ID to link this location to
107         * @param subLocations Sub locations which composes this location
108         */
109        public AbstractLocation(Point start, Point end, Strand strand,
110                        boolean circular, boolean betweenCompounds, AccessionID accession,
111                        List<Location> subLocations) {
112                this.start = start;
113                this.end = end;
114                this.strand = strand;
115                this.circular = circular;
116                this.betweenCompounds = betweenCompounds;
117                this.accession = accession;
118                this.subLocations = subLocations==null? null : Collections.unmodifiableList(subLocations);
119                assertLocation();
120        }
121
122        protected void assertLocation() {
123                if (isCircular() && !isComplex()) {
124                        throw new IllegalStateException("Cannot have a circular "
125                                        + "location which is not complex");
126                }
127
128                int st = getStart().getPosition();
129                int e = getEnd().getPosition();
130
131                if (st > e) {
132                        throw new IllegalStateException(
133                                        String.format("Start (%d) is greater than end (%d); "
134                                        + "this is an incorrect format",
135                                        st, e));
136                }
137
138                if(isBetweenCompounds() && isComplex()) {
139                        throw new IllegalStateException("Cannot have a complex location "
140                                        + "which is located between a pair of compounds");
141                }
142
143                if(isBetweenCompounds() && (st + 1) != e) {
144                        throw new IllegalStateException(
145                                        String.format("Start (%d) is not next to end (%d)", st, e));
146                }
147
148        }
149
150
151        @Override
152        public Point getEnd() {
153                return end;
154        }
155
156
157        @Override
158        public Point getStart() {
159                return start;
160        }
161
162
163        @Override
164        public int getLength() {
165                return (getEnd().getPosition() - getStart().getPosition()) + 1;
166        }
167
168
169        @Override
170        public Strand getStrand() {
171                return strand;
172        }
173
174
175        @Override
176        public List<Location> getSubLocations() {
177                if(subLocations == null) {
178                        return Collections.emptyList();
179                }
180                return subLocations;
181        }
182
183
184        @Override
185        public boolean isComplex() {
186                return !getSubLocations().isEmpty();
187        }
188
189
190        @Override
191        public AccessionID getAccession() {
192                return accession;
193        }
194
195        public boolean isPartialOn5prime() {
196                return partialOn5prime;
197        }
198
199        public void setPartialOn5prime(boolean partialOn5prime) {
200                this.partialOn5prime = partialOn5prime;
201        }
202
203        public boolean isPartialOn3prime() {
204                return partialOn3prime;
205        }
206
207        public void setPartialOn3prime(boolean partialOn3prime) {
208                this.partialOn3prime = partialOn3prime;
209        }
210
211        public boolean isPartial() {
212                return partialOn5prime || partialOn3prime;
213        }
214
215        /**
216         * Iterates through all known sub-locations for this location but does
217         * not descend
218         */
219
220        @Override
221        public Iterator<Location> iterator() {
222                List<Location> list;
223                if(isComplex()) {
224                        list = getSubLocations();
225                }
226                else {
227                        list = new ArrayList<Location>();
228                        list.add(this);
229                }
230                return list.iterator();
231        }
232
233        /**
234         * Returns the normalised list of sub locations i.e. only those locations
235         * which do not have a sub location. Useful for when you need to get
236         * the exact elements of a location back for sub sequences.
237         */
238
239        @Override
240        public List<Location> getRelevantSubLocations() {
241                return getAllSubLocations(this);
242        }
243
244        /**
245         * Here to allow for recursion
246         */
247        private List<Location> getAllSubLocations(Location location) {
248                List<Location> flatSubLocations = new ArrayList<Location>();
249                for (Location l : location.getSubLocations()) {
250                        if (l.isComplex()) {
251                                flatSubLocations.addAll(getAllSubLocations(l));
252                        }
253                        else {
254                                flatSubLocations.add(l);
255                        }
256                }
257                return flatSubLocations;
258        }
259
260
261        @Override
262        public boolean equals(Object obj) {
263                boolean equals = false;
264                if (classEqual(this, obj)) {
265                        AbstractLocation l = (AbstractLocation) obj;
266                        equals = (equal(getStart(), l.getStart())
267                                        && equal(getEnd(), l.getEnd())
268                                        && equal(getStrand(), l.getStrand())
269                                        && equal(isCircular(), l.isCircular())
270                                        && equal(isBetweenCompounds(), l.isBetweenCompounds())
271                                        && equal(getSubLocations(), l.getSubLocations())
272                                        && equal(getAccession(), l.getAccession()));
273                }
274                return equals;
275        }
276
277
278        @Override
279        public int hashCode() {
280                int r = Hashcoder.SEED;
281                r = Hashcoder.hash(r, getStart());
282                r = Hashcoder.hash(r, getEnd());
283                r = Hashcoder.hash(r, getStrand());
284                r = Hashcoder.hash(r, isCircular());
285                r = Hashcoder.hash(r, isBetweenCompounds());
286                r = Hashcoder.hash(r, getSubLocations());
287                r = Hashcoder.hash(r, getAccession());
288                return r;
289        }
290
291
292        @Override
293        public boolean isCircular() {
294                return circular;
295        }
296
297
298        @Override
299        public boolean isBetweenCompounds() {
300                return betweenCompounds;
301        }
302
303        //TODO Support the accession based lookup system; maybe still require a different impl?
304
305        /**
306         * If circular this will return the sequence represented by the sub
307         * locations joined. If not circular then we get the Sequence for the
308         * outer-bounds defined by this location.
309         */
310
311        @Override
312        public <C extends Compound> Sequence<C> getSubSequence(Sequence<C> sequence) {
313                if(isCircular()) {
314                        List<Sequence<C>> sequences = new ArrayList<Sequence<C>>();
315                        for(Location l: this) {
316                                sequences.add(l.getSubSequence(sequence));
317                        }
318                        return new JoiningSequenceReader<C>(sequence.getCompoundSet(), sequences);
319                }
320                return reverseSequence(sequence.getSubSequence(
321                                getStart().getPosition(), getEnd().getPosition()));
322        }
323
324        /**
325         *
326         */
327
328        @Override
329        public <C extends Compound> Sequence<C> getRelevantSubSequence(Sequence<C> sequence) {
330                List<Sequence<C>> sequences = new ArrayList<Sequence<C>>();
331                for(Location l: getRelevantSubLocations()) {
332                        sequences.add(l.getSubSequence(sequence));
333                }
334                return new JoiningSequenceReader<C>(sequence.getCompoundSet(), sequences);
335        }
336
337        /**
338         * Reverses and (if possible) complements the Sequence so as to represent
339         * the reverse strand (if one exists). Also does checking to see if the
340         * location we are on is on the reverse strand or not.
341         */
342        @SuppressWarnings("unchecked")
343        protected <C extends Compound> Sequence<C> reverseSequence(Sequence<C> sequence) {
344                if(getStrand() != Strand.NEGATIVE) {
345                        return sequence;
346                }
347
348                Sequence<C> reversed = new ReversedSequenceView<C>(sequence);
349                // "safe" operation as we have tried to check this
350                if(canComplement(sequence)) {
351                        Sequence<ComplementCompound> casted = (Sequence<ComplementCompound>) reversed;
352                        ComplementSequenceView<ComplementCompound> complement =
353                                        new ComplementSequenceView<ComplementCompound>(casted);
354                        return (Sequence<C>)complement;
355                }
356                return reversed;
357        }
358
359        /**
360         * Uses the Sequence's CompoundSet to decide if a compound can
361         * be assgined to ComplementCompound meaning it can complement
362         */
363        protected <C extends Compound> boolean canComplement(Sequence<C> sequence) {
364                CompoundSet<C> compoundSet = sequence.getCompoundSet();
365                Compound c = compoundSet.getAllCompounds().iterator().next();
366                return ComplementCompound.class.isAssignableFrom(c.getClass());
367        }
368
369
370        @Override
371        public String toString() {
372                String circ = (isCircular()) ? " - circular" : "";
373                String between = (isBetweenCompounds()) ? "^" : "..";
374                return format("%d%s%d(%s%s)", getStart().getPosition(), between, getEnd().getPosition(),
375                                getStrand().getStringRepresentation(), circ);
376        }
377
378        protected void setCircular(boolean circular) {
379                this.circular = circular;
380        }
381
382        protected void setEnd(Point end) {
383                this.end = end;
384        }
385
386        protected void setStart(Point start) {
387                this.start = start;
388        }
389
390        public void setStrand(Strand strand) {
391                this.strand = strand;
392        }
393
394        public void setBetweenCompounds(boolean betweenCompounds) {
395                this.betweenCompounds = betweenCompounds;
396        }
397
398        public void setSubLocations(List<Location> subLocations) {
399                this.subLocations = subLocations;
400        }
401
402        public void setAccession(AccessionID accession) {
403                this.accession = accession;
404        }
405}