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}