001/**
002 * Copyright (C) 2010-2015 The Roslin Institute <contact andy.law@roslin.ed.ac.uk>
003 *
004 * This file is part of JEnsembl: a Java API to Ensembl data sources developed by the
005 * Bioinformatics Group at The Roslin Institute, The Royal (Dick) School of
006 * Veterinary Studies, University of Edinburgh.
007 *
008 * Project hosted at: http://jensembl.sourceforge.net
009 *
010 * This is free software: you can redistribute it and/or modify
011 * it under the terms of the GNU General Public License (version 3) as published by
012 * the Free Software Foundation.
013 *
014 * This software is distributed in the hope that it will be useful,
015 * but WITHOUT ANY WARRANTY; without even the implied warranty of
016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
017 * GNU General Public License for more details.
018 *
019 * You should have received a copy of the GNU General Public License
020 * in this software distribution. If not, see: http://opensource.org/licenses/gpl-3.0.html
021 */
022package uk.ac.roslin.ensembl.model;
023
024import java.io.Serializable;
025
026/**
027 * In general a Coordinate must have a non-null non-zero value for both start and end.
028 * DNA coordinate systems are complicated by the absence of zero (i.e. ...-3,-2,-1,+1,+2,+3...).
029 */
030public class Coordinate implements Comparable<Coordinate>, Serializable {
031
032    Integer start = null;//never allowed to be zero
033    Integer end = null;// never allowed to be zero
034    Strand strand = null;
035    private Integer strandInt = null;
036
037    /**
038     * Parameterless constructor. Only to be used by for Mybatis (object mapping API) etc.
039     * In general a Coordinate must have a non null non zero value for both start and end.
040     */
041    public Coordinate() {
042    }
043
044    /**
045     * Utility constructor that takes start and stop integer positions with
046     * no defined Strand.
047     * ZERO and null locations are disallowed and will throw IllegalArgument RuntimeExceptions.
048     * @param begin
049     * @param stop 
050     */
051    public Coordinate(Integer begin , Integer stop) {
052        this(begin, stop, 0);
053    }
054
055    /**
056     * Utility constructor that takes start and stop integer positions and an 
057     * Integer defining the Strand to be used (FORWARD_STRAND for '+1'
058     * and REVERSE_STRAND for '-1' - otherwise NULL). ZERO and null locations are disallowed 
059     * and will throw IllegalArgument RuntimeExceptions.
060     * @param begin
061     * @param stop
062     * @param strandID 
063     */
064    public Coordinate(Integer begin, Integer stop, Integer strandID) {
065        this(begin, stop, Strand.strand(strandID));
066    }
067
068    /**
069     * Full constructor that takes start and stop integer positions and a
070     * specified Strand (FORWARD_STRAND, REVERSE_STRAND,  otherwise NULL). 
071     * ZERO and null locations are disallowed and will throw IllegalArgument RuntimeExceptions.
072     * @param begin
073     * @param stop
074     * @param _strand 
075     */
076    public Coordinate(Integer begin, Integer stop, Coordinate.Strand _strand) {
077        //disallow zero and null in a Coordinate
078        if (begin==null || begin==0 ||
079                stop==null || stop ==0) {
080            throw new IllegalArgumentException("The position 0 is meaningless in the Ensembl DNA world."
081                    +" Use -1 for one base upstream or +1 for the first base.");
082        }        
083        if (begin <= stop) {
084            start=begin;
085            end = stop;
086        } else {
087            start=stop;
088            end=begin;
089        }               
090        this.strand =_strand;
091    }
092
093    /**
094     * Returns String representation in of format '1 - 5987 REVERSE_STRAND',  '1 - 5987 FORWARD_STRAND', '1 - 5987 UNSPECIFIED_STRAND' etc.
095     * @return String representation of this Coordinate as 'start - stop (Strand)'
096     */
097    @Override
098    public String toString() {
099        String out = "";
100        out += this.start + " - " + this.end + " ("
101                + ((this.strand != null) ? this.strand.toString() : "UNSPECIFIED_STRAND") + ")";
102        return out;
103    }
104
105    /**
106     * Returns String representation in format '1 - 2345' etc.
107     * @return String representation of this Coordinate as 'start - stop'
108     */
109    public String toShortString() {
110
111        if (start != null && end != null && start.equals(end)) {
112            return start.toString();
113        } else {
114            return this.start + " - " + this.end;
115        }
116    }
117
118    /**
119     * Public enumeration of the two alternate Strands, FORWARD and REVERSE.
120     * In Ensembl these are represented as +1 and -1 respectively.
121     * 
122     * 5'--------------FORWARDS------------->3'
123     * 
124     * 3'&lt;-------------ESREVER---------------5'
125     */
126    public static enum Strand implements Serializable {
127
128        FORWARD_STRAND,
129        REVERSE_STRAND;
130
131        /**
132         * Static  Strand provider returns FORWARD_STRAND for '+1'
133         * and REVERSE_STRAND for '-1' - otherwise NULL
134         * @param i
135         * @return Singleton Strand object 
136         */
137        public static Strand strand(Integer i) {
138            if (i==null) {
139                return null;
140            } else if (i == 1) {
141                return FORWARD_STRAND;
142            } else if (i == -1) {
143                return REVERSE_STRAND;
144            } else {
145                return null;
146            }
147        }
148    }
149
150    public Integer getStart() {
151        return start;
152    }
153
154    public Integer getEnd() {
155        return end;
156    }
157
158    public Strand getStrand() {
159        return strand;
160    }
161
162    /**
163     * public setter for the start of the Coordinate, will swap start and end if new start is more than the end. 
164     * Use of ZERO (0) or null is disallowed and will throw an IllegalArgument RuntimeException. 
165     * @param b 
166     */    
167    public void setStart(Integer b) throws IllegalArgumentException {
168        if ( b==null || b==0 ) {
169            throw new IllegalArgumentException("The position 0 is meaningless in the Ensembl DNA world."
170                    +" Use -1 for one base upstream or +1 for the first base.");
171        }
172        start=b;
173        if (end==null) {
174            end = b;
175        }
176        if (end<start) {
177            start = end;
178            end = b;
179        } 
180    }
181
182    /**
183     * public setter for the End of the Coordinate, will swap start and end if new end is less than start.
184     * Use of ZERO (0) or null is disallowed and will throw an IllegalArgument RuntimeException.
185     * @param e 
186     */
187    public void setEnd(Integer e) throws IllegalArgumentException {
188        if (e==null || e==0 ) {
189            throw new IllegalArgumentException("The position 0 is meaningless in the Ensembl DNA world."
190                    +" Use -1 for one base upstream or +1 for the first base.");
191        }     
192        end = e;
193        if (start==null){
194            start = e;
195        }
196        if (end<start) {
197            end = start;
198            start = e; 
199        }
200    }
201    
202    /**
203     * public setter for the start and end of the Coordinate, will swap start and end if  end is less than start.
204     * Use of ZERO (0) or null is disallowed and will throw an IllegalArgument RuntimeException.
205     * @param b
206     * @param e 
207     */
208    public void setValues(Integer b, Integer e) {
209        if (e==null || e==0 || b==null || b==0 ) {
210            throw new IllegalArgumentException("The position 0 is meaningless in the Ensembl DNA world."
211                    +" Use -1 for one base upstream or +1 for the first base.");
212        }   
213        if (b<e) {
214            start = b;
215            end = e;
216        } else {
217            start = e; 
218            end = b;
219        }
220        
221    }
222
223    public void setStrand(Strand s) {
224        strand = s;
225    }
226
227    /**
228     * Utility method to set the Strand using an Integer (FORWARD_STRAND for '+1'
229     * and REVERSE_STRAND for '-1' - otherwise NULL).
230     * @param s 
231     */
232    public void setStrandInt(Integer s) {
233        strandInt = s;
234        strand = Coordinate.Strand.strand(s);
235    }
236
237    public Integer getStrandInt() {
238        return strandInt;
239    }
240
241    /**
242     * Returns the length of this Coordinate - taking into consideration whether 
243     * the range spans the 'absent' Zero. 
244     * @return span length of the coordinate
245     */
246    public Integer getLength() {
247        //an unitialized Coordinate has no length
248        if (this.getStart()==null || this.getEnd()==null) {
249            return null;
250        }
251        // a single base
252        if (this.getStart()==this.getEnd()) {
253            return 1;
254        }
255        //if coordinates totally -ive or totally +ive
256        if (this.getEnd()<1 || this.getStart()>0) {
257            return this.getEnd()-this.getStart()+1;
258        } 
259        //coordinates span the -1/+1 junction
260        else {
261            return this.getEnd()-this.getStart();
262        }
263    }
264
265   /**
266    * Implements compareTo(another Coordinate) method for the Comparable interface.
267    * It orders Coordinate objects by the plus strand start, or the plus strand
268    * end if the starts are identical. If two Coordinates start at the same position, 
269    * the the longer one is put first, this has consequences for getting the end coordinate of a set.
270    * Zero (0) != Identity because the comparison doesn't consider the strand, just the order.
271    * NB. Take care with Integer comparisons. 
272    * @param o the compared other Coordinate
273    * @return -1 if this object is upstream of 'o', 1 if downstream of 'o', 0 if coincident or not comparable
274    */
275    @Override
276    public int compareTo(Coordinate o) {
277
278        //dont compare if any nulls
279        if (this.start == null || this.end == null
280                || o.getStart() == null || o.getEnd() == null) {
281            return 0;
282        }
283
284
285        if (!this.start.equals(o.getStart())) {
286
287            if (this.start.compareTo(o.getStart()) < 0) {
288                return -1;
289            } else {
290                return 1;
291            }
292
293        } else {
294            //put the longest one first
295            //this has consequences for getting the end coordinate of a set
296            if (this.end.compareTo(o.getEnd()) < 0) {
297                return 1;
298            } else if (this.end.compareTo(o.getEnd()) > 0) {
299                return -1;
300            } else {
301                //the coordinates are identical
302                return 0;
303            }
304        }
305    }
306
307    /**
308     * Tests whether this Coordinate lies TOTALLY within the range of another test Coordinate.
309     * @param test
310     * @return Boolean 
311     */
312    public Boolean liesWithinCoordinate(Coordinate test) {
313        Boolean out = false;
314
315        //dont compare if any nulls
316        if (test.getStart() == null || test.getEnd() == null
317                || this.start == null || this.end == null) {
318            return out;
319        }
320
321        if (this.start >= test.getStart() && this.end <= test.getEnd()) {
322            out = true;
323        }
324
325        return out;
326    }
327
328    /**
329     * Returns whether this Coordinate overlaps with the extent of a test Coordinate.
330     * @param test
331     * @return Boolean
332     */
333    public Boolean overlaps(Coordinate test) {
334
335        boolean out = false;
336
337        //dont compare if any nulls
338        if (this.start == null || this.end == null || test.getEnd() == null || test.getStart() == null) {
339            out = false;
340        } else if ((this.liesWithinCoordinate(test)) || (test.liesWithinCoordinate(this))
341                || (this.start <= test.getStart() && this.end >= test.getStart())
342                || (this.end >= test.getEnd() && this.start <= test.getEnd())) {
343            out = true;
344        }
345
346        return out;
347    }
348
349    /**
350     * Returns the overlap extent (as a Coordinate) between this Coordinate and a test Coordinate.
351     * @param test
352     * @return Coordinate
353     */
354    public Coordinate getOverlap(Coordinate test) {
355
356        Coordinate out = null;
357        Integer testStart = test.getStart();
358        Integer testEnd = test.getEnd();
359
360        //dont compare if any nulls, or if there is no overlap
361        if (this.start == null || this.end == null || test.getEnd() == null || test.getStart() == null
362                || !this.overlaps(test)) {
363            out = null;
364        } else {
365            if (this.start >= test.getStart() && this.start <= test.getEnd()) {
366                testStart = this.start;
367            }
368            if (this.end >= test.getStart() && this.end <= test.getEnd()) {
369                testEnd = this.end;
370            }
371            out = new Coordinate(testStart, testEnd, 1);
372        }
373
374        return out;
375    }
376
377    
378    public Boolean containsPoint(Integer point) {
379        if (this.start==null || this.end==null || point==null) {
380            return false;
381        }
382        return (this.start<=point && this.end>=point);
383    }
384}