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'<-------------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}