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; 023 024import org.biojava.nbio.core.exceptions.ParserException; 025import org.biojava.nbio.core.sequence.AccessionID; 026import org.biojava.nbio.core.sequence.Strand; 027import org.biojava.nbio.core.sequence.location.template.Location; 028import org.biojava.nbio.core.sequence.location.template.Point; 029 030import java.util.ArrayList; 031import java.util.HashSet; 032import java.util.List; 033import java.util.Set; 034 035/** 036 * Helper methods for use with the Location classes. Taking its 037 * inspiration from the RichSequence.Tools class from the old BioJava 038 */ 039public class LocationHelper { 040 041 /** 042 * Used as a thin wrapper to the {@link #location(java.util.List, java.lang.String) } 043 * method to bring the given location list together as a join (the default 044 * type) 045 */ 046 public static Location location(List<Location> subLocations) { 047 return location(subLocations, "join"); 048 } 049 050 /** 051 * Builds a location from a List of locations; this can be circular or 052 * linear joins. The code expects that these locations are in 053 * a sensible format. 054 * 055 * @param subLocations The list of locations to use to build the location. 056 * If given a list of size 1 we will return that location. 057 * @param type The type of join for this location; defaults to join 058 * @return 059 */ 060 public static Location location(List<Location> subLocations, String type) { 061 if (subLocations.size() == 1) { 062 return subLocations.get(0); 063 } 064 065 boolean circular = detectCicular(subLocations); 066 Strand strand = detectStrand(subLocations); 067 Point start = detectStart(subLocations); 068 Point end = detectEnd(subLocations, circular); 069 Location l; 070 if ("join".equals(type)) { 071 l = new SimpleLocation(start, end, strand, circular, subLocations); 072 } 073 else if ("order".equals(type)) { 074 l = new InsdcLocations.OrderLocation(start, end, strand, circular, subLocations); 075 } 076 else if ("one-of".equals(type)) { 077 l = new InsdcLocations.OneOfLocation(subLocations); 078 } 079 else if ("group".equals(type)) { 080 l = new InsdcLocations.GroupLocation(start, end, strand, circular, subLocations); 081 } 082 else if ("bond".equals(type)) { 083 l = new InsdcLocations.BondLocation(subLocations); 084 } 085 else { 086 throw new ParserException("Unknown join type " + type); 087 } 088 089 return l; 090 } 091 092 /** 093 * Returns a location object which unlike the location constructors 094 * allows you to input reverse coordinates and will convert 095 * these into the right location on the positive strand. 096 */ 097 public static Location location(int start, int end, Strand strand, int length) { 098 int min = Math.min(start, end); 099 //if this is true then we have a coord on the +ve strand even though Strand could be negative 100 boolean isReverse = (min != start); 101 if (isReverse) { 102 return new SimpleLocation( 103 new SimplePoint(start).reverse(length), 104 new SimplePoint(end).reverse(length), 105 strand); 106 } 107 return new SimpleLocation(start, end, strand); 108 } 109 110 /** 111 * Converts a location which defines the outer bounds of a circular 112 * location and splits it into the required portions. Unlike any 113 * other location builder this allows you to express your input 114 * location on the reverse strand 115 * 116 * @param location The location which currently expresses the outer 117 * bounds of a circular location. 118 * @param length The length of the circular genomic unit 119 * @return The circular location; can optionally return a normal non 120 * circular location if the one you give is within the bounds of 121 * the length 122 */ 123 public static Location circularLocation(int start, int end, Strand strand, int length) { 124 125 int min = Math.min(start, end); 126 int max = Math.max(start, end); 127 //Tells us we're dealing with something that's not _right_ 128 boolean isReverse = (min != start); 129 130 if (min > length) { 131 throw new IllegalArgumentException("Cannot process a " 132 + "location whose lowest coordinate is less than " 133 + "the given length " + length); 134 } 135 136 //If max positon was less than length the return a normal location 137 if (max <= length) { 138 return location(start, end, strand, length); 139 } 140 141 //Fine for forward coords (i..e start < end) 142 int modStart = modulateCircularIndex(start, length); 143 int modEnd = modulateCircularIndex(end, length); 144 int numberOfPasses = completeCircularPasses(Math.max(start, end), length); 145 146 if (isReverse) { 147 int reversedModStart = new SimplePoint(modStart).reverse(length).getPosition(); 148 int reversedModEnd = new SimplePoint(modEnd).reverse(length).getPosition(); 149 modStart = reversedModStart; 150 modEnd = reversedModEnd; 151 start = reversedModStart; 152 //+1 to number of passes to skip the run encoded by the start 153 end = (length * (numberOfPasses + 1)) + modEnd; 154 } 155 156 List<Location> locations = new ArrayList<Location>(); 157 locations.add(new SimpleLocation(modStart, length, strand)); 158 for (int i = 0; i < numberOfPasses; i++) { 159 locations.add(new SimpleLocation(1, length, strand)); 160 } 161 locations.add(new SimpleLocation(1, modEnd, strand)); 162 return new SimpleLocation(new SimplePoint(start), 163 new SimplePoint(end), strand, true, false, locations); 164 } 165 166 private static interface LocationPredicate { 167 boolean accept(Location previous, Location current); 168 } 169 170 /** 171 * Scans through a list of locations to find the Location with the 172 * lowest start 173 */ 174 public static Location getMin(List<Location> locations) { 175 return scanLocations(locations, new LocationPredicate() { 176 @Override 177 public boolean accept(Location previous, Location current) { 178 int res = current.getStart().compareTo(previous.getStart()); 179 return res < 0; 180 } 181 }); 182 } 183 184 /** 185 * Scans through a list of locations to find the Location with the 186 * highest end 187 */ 188 public static Location getMax(List<Location> locations) { 189 return scanLocations(locations, new LocationPredicate() { 190 @Override 191 public boolean accept(Location previous, Location current) { 192 int res = current.getEnd().compareTo(previous.getEnd()); 193 return res > 0; 194 } 195 }); 196 } 197 198 /** 199 * Used for scanning through a list of locations; assumes the 200 * locations given will have at least one value otherwise 201 * we will get a null pointer 202 */ 203 private static Location scanLocations(List<Location> locations, LocationPredicate predicate) { 204 Location location = null; 205 for (Location l : locations) { 206 if (location == null) { 207 location = l; 208 } 209 else { 210 if (predicate.accept(location, l)) { 211 location = l; 212 } 213 } 214 } 215 return location; 216 } 217 218 /** 219 * Takes a point on a circular location and moves it left until it falls 220 * at the earliest possible point that represents the same base. 221 * 222 * @param index Index of the position to work with 223 * @param seqLength Length of the Sequence 224 * @return The shifted point 225 */ 226 public static int modulateCircularIndex(int index, int seqLength) { 227 // Dummy case 228 if (seqLength == 0) { 229 return index; 230 } 231 // Modulate 232 while (index > seqLength) { 233 index -= seqLength; 234 } 235 return index; 236 } 237 238 /** 239 * Works in a similar way to modulateCircularLocation but returns 240 * the number of complete passes over a Sequence length a circular 241 * location makes i.e. if we have a sequence of length 10 and the 242 * location 3..52 we make 4 complete passes through the genome to 243 * go from position 3 to position 52. 244 */ 245 public static int completeCircularPasses(int index, int seqLength) { 246 int count = 0; 247 while (index > seqLength) { 248 count++; 249 index -= seqLength; 250 } 251 return count - 1; 252 } 253 254 /** 255 * Loops through the given list of locations and returns true if it looks 256 * like they represent a circular location. Detection cannot happen if 257 * we do not have consistent accessions 258 */ 259 public static boolean detectCicular(List<Location> subLocations) { 260 boolean isCircular = false; 261 if(! consistentAccessions(subLocations)) 262 return isCircular; 263 264 int lastMax = 0; 265 for (Location sub : subLocations) { 266 if (sub.getEnd().getPosition() > lastMax) { 267 lastMax = sub.getEnd().getPosition(); 268 } 269 else { 270 isCircular = true; 271 break; 272 } 273 } 274 return isCircular; 275 } 276 277 /** 278 * Scans a list of locations and returns true if all the given locations 279 * are linked to the same sequence. A list of null accessioned locations 280 * is the same as a list where the accession is the same 281 * 282 * @param subLocations The locations to scan 283 * @return Returns a boolean indicating if this is consistently accessioned 284 */ 285 public static boolean consistentAccessions(List<Location> subLocations) { 286 Set<AccessionID> set = new HashSet<AccessionID>(); 287 for(Location sub: subLocations) { 288 set.add(sub.getAccession()); 289 } 290 return set.size() == 1; 291 } 292 293 /** 294 * Loops through the given list of locations and returns the consensus 295 * Strand class. If the class switches then we will return an undefined 296 * strand 297 */ 298 public static Strand detectStrand(List<Location> subLocations) { 299 Strand strand = subLocations.get(0).getStrand(); 300 for (Location sub : subLocations) { 301 if (strand != sub.getStrand()) { 302 strand = Strand.UNDEFINED; 303 break; 304 } 305 } 306 return strand; 307 } 308 309 /** 310 * Assumes that the first element is the start & clones it 311 */ 312 public static Point detectStart(List<Location> subLocations) { 313 return subLocations.get(0).getStart().clonePoint(); 314 } 315 316 /** 317 * This will attempt to find what the last point is and returns that 318 * position. If the location is circular this will return the total length 319 * of the location and does not mean the maximum point on the Sequence 320 * we may find the locations on 321 */ 322 public static Point detectEnd(List<Location> subLocations, boolean isCircular) { 323 int end = 0; 324 Point lastPoint = null; 325 if(isCircular) { 326 for (Location sub : subLocations) { 327 lastPoint = sub.getEnd(); 328 end += lastPoint.getPosition(); 329 } 330 } 331 else { 332 lastPoint = subLocations.get(subLocations.size()-1).getEnd(); 333 end = lastPoint.getPosition(); 334 } 335 return new SimplePoint(end, lastPoint.isUnknown(), lastPoint.isUncertain()); 336 } 337}