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 021package org.biojava.bio.symbol; 022 023import java.util.Collections; 024import java.util.Iterator; 025import java.util.LinkedList; 026import java.util.ListIterator; 027 028import org.biojava.bio.BioError; 029 030 031 032/** 033 * Circular view onto an underlying Location instance. If the location overlaps 034 * the origin of the sequence the underlying location will be a CompoundLocation 035 * Note that in this case isContiguous() will return false. This behaviour is 036 * desirable for proper treatment of the location with LocationTools. 037 * To find if a location overlaps the origin use the overlapsOrigin() method 038 * <p> 039 * Note also that as a location that overlaps the origin is a compound location it's 040 * min will be 1 and its max will be length (by default). In these cases it is imperative to 041 * use the block iterator if you wish to know the 'true' min and max (bearing in mind that 042 * there is no logical value for a min or max on a circular sequence). 043 * </p> 044 * <p> 045 * The symbols() method has been overridden to handle the weirdness of a 046 * location crossing the origin. 047 * </p> 048 * 049 * @author Matthew Pocock 050 * @author Mark Schreiber 051 * @since 1.2 052 */ 053 054public class CircularLocation 055 056extends AbstractLocationDecorator { 057 058 private final int length; 059 private int fivePrimeEnd; 060 private int threePrimeEnd; 061 private LinkedList fivePrimeSortedBlocks; 062 063 private final boolean overlaps; 064 065 066 067 public final int getLength() { 068 069 return length; 070 071 } 072 073 074 /** 075 * Does the Location overlap the origin (position 1) of the sequence? 076 * <p> If it does the Location will internally be composed of a CompoundLocation. 077 * @return true if it does, false otherwise 078 */ 079 public boolean overlapsOrigin(){ 080 081 return overlaps; 082 083 } 084 085 086 /** 087 * Constructs a CircularLocation by wrapping another Location 088 * <strong>It is preferable to use LocationTools to make CircularLocations</strong> 089 * @param wrapped the Location to wrap. 090 * @param length the length of the Sequence 091 */ 092 public CircularLocation(Location wrapped, int length) { 093 this(wrapped, length, wrapped.getMin()); 094 } 095 096 /** 097 * Makes a CircularLocation where the 5' end of the Location is specified. The 098 * <code>fivePrimeEnd</code> parameter must match one of the minimum coordinates 099 * of the wrapped <code>Location</code>. This allows the creation of Locations 100 * whose polarity can be specified. This method is used internally by <code>LocationTools.union()</code> 101 * and by the other constructor. 102 * 103 * @param wrapped the <code>Location</code> to map to a Circle 104 * @param length the lenght of the circle 105 * @param fivePrimeEnd the 5' polar end of the sequence 106 * @throws IllegalArgumentException if the 5' end doesn't match the min coordinate 107 * of either <code>wrapped</code> or one of its blocks. 108 */ 109 public CircularLocation(Location wrapped, int length, int fivePrimeEnd){ 110 super(wrapped); 111 this.length = length; 112 this.overlaps = CircularLocationTools.overlapsOrigin(this); 113 114 //the 5' end must be the min of one of the blocks of the wrapped location 115 fivePrimeSortedBlocks = new LinkedList(); 116 for(Iterator i = wrapped.blockIterator(); i.hasNext();){ 117 Location loc = (Location)i.next(); 118 fivePrimeSortedBlocks.add(loc); 119 if(loc.getMin() == fivePrimeEnd){ 120 this.fivePrimeEnd = fivePrimeEnd; 121 } 122 } 123 124 //reorder blocks so that block with 5' end is the first block 125 Collections.sort(fivePrimeSortedBlocks, Location.naturalOrder); 126 //check the first item to see if it is the five prime end 127 while(((Location)fivePrimeSortedBlocks.getFirst()).getMin() != get5PrimeEnd()){ 128 //pop it off and send it to the back of the list 129 Object o = fivePrimeSortedBlocks.removeFirst(); 130 fivePrimeSortedBlocks.addLast(o); 131 } 132 133 //sort out the 3'End 134 threePrimeEnd = ((Location)fivePrimeSortedBlocks.getLast()).getMax(); 135 136 if(get5PrimeEnd() == 0){ 137 throw new IllegalArgumentException( 138 "The 5' End must be either the minimum of the wrapped location or the minimum of one of its components"); 139 } 140 } 141 142 143 144 protected Location decorate(Location loc) { 145 146 return new CircularLocation(loc, getLength()); 147 148 } 149 150 151 152 public boolean contains(int p) { 153 154 int pp = p % getLength() + (super.getMin() / getLength()); 155 156 157 158 return getWrapped().contains(pp); 159 160 } 161 162 163 164 165 166 public Location intersection(Location l) { 167 168 return LocationTools.intersection(this,l); 169 170 } 171 172 public boolean overlaps(Location l) { 173 174 return LocationTools.overlaps(this,l); 175 176 } 177 178 public Location union(Location l) { 179 180 return LocationTools.union(this,l); 181 182 } 183 184 public boolean contains(Location l) { 185 186 return LocationTools.contains(this,l); 187 188 } 189 190 public boolean equals(Object o){ 191 192 if((o instanceof Location)==false) return false; 193 194 return LocationTools.areEqual(this, (Location)o); 195 196 } 197 198 199 public String toString() { 200 if (fivePrimeSortedBlocks.size() > 1) { 201 202 StringBuffer sb = new StringBuffer(); 203 sb.append(get5PrimeEnd() + ", " + 204 ((Location)fivePrimeSortedBlocks.getLast()).getMax() + " {"); 205 Iterator i = fivePrimeSortedBlocks.iterator(); 206 if(i.hasNext()) 207 sb.append("(" + i.next() + ")"); 208 209 while(i.hasNext()) 210 sb.append(", (" + i.next() + ")"); 211 212 sb.append("}"); 213 214 sb.append(" (circular)"); 215 return sb.substring(0); 216 217 }else{ 218 return getWrapped().toString() + " (circular)"; 219 } 220 221 222 } 223 224 225 /** 226 * Delegates to the wrapped location. Currently as locations that cross 227 * the origin are wrapped CompoundLocations they are not considered contiguous. 228 * This is desirable from the point of view of logical operations as it greatly 229 * simplifies the calculations of things such as contains, overlaps etc. 230 * @return true if the location is contiguous and doesn't overlap the origin 231 */ 232 public boolean isContiguous() { 233 return getWrapped().isContiguous(); 234 } 235 236 /** 237 * The point at which indicates the 5' end of the Location. This is needed as 238 * compound circular locations have polarity. For example (18..30, 1..12) is 239 * not the same as (1..12, 18..30). The 5' coordinate is derived during 240 * construction of the Location. In particular during a union operation 241 * the 5' coordinate is generally the 5' coordinate of the first location in 242 * the union. In the case where this cannot be logically maintained the 5' 243 * coordinate will revert to <code>getMin()</code> 244 * 245 * @return the most 5' coordinate 246 * @see #fivePrimeBlockIterator() 247 * @see #getMin() 248 * @see #get3PrimeEnd() 249 */ 250 public int get5PrimeEnd(){ 251 return fivePrimeEnd; 252 } 253 254 /** 255 * @return the most 3' coordinate 256 * @see #fivePrimeBlockIterator() 257 * @see #getMax() 258 * @see #get5PrimeEnd() 259 */ 260 public int get3PrimeEnd(){ 261 return threePrimeEnd; 262 } 263 264 /** 265 * This will give you the coordinate of the minimum point contained by this 266 * Location. Generally this will be the leftmost end however if the Location 267 * crosses the origin then it will actually be the origin that is the minimum 268 * point and the method will return 1. If you want to be guarenteed of getting 269 * the leftmost coordinate then call <code>get5PrimeEnd()</code> 270 * @return the minimum coord 271 * @see #get5PrimeEnd() 272 */ 273 public int getMin(){ 274 return super.getMin(); 275 } 276 277 /** 278 * This will give you the coordinate of the maximum point contained by this 279 * Location. Generally this will be the rightmost end however if the Location 280 * crosses the origin then it will actually be the point before the origin that is the maximum 281 * point and the method will return <code>getLength()</code>. 282 * If you want to be guarenteed of getting 283 * the rightmost coordinate then call <code>get3PrimeEnd()</code> 284 * @return the maximum coord 285 * @see #get3PrimeEnd() 286 */ 287 public int getMax(){ 288 return super.getMax(); 289 } 290 291 292 public SymbolList symbols(SymbolList seq) { 293 SymbolList syms; 294 Edit ed; 295 296 //iterate over the Locations from the 5' end 297 ListIterator i = this.fivePrimeBlockIterator(); 298 syms = ((Location)i.next()).symbols(seq); 299 300 while(i.hasNext()){ 301 Location loc = (Location)i.next(); 302 SymbolList add = loc.symbols(seq); 303 ed = new Edit(syms.length()+1,0,add); 304 try { 305 syms.edit(ed); 306 } 307 catch (Exception ex) { 308 throw new BioError("Illegal edit operation", ex); 309 } 310 } 311 return syms; 312 } 313 314 /** 315 * Iterates over the location blocks in order starting with the most 5' 316 * @see #blockIterator() 317 * @see #get5PrimeEnd() 318 * @return a ListIterator 319 */ 320 public ListIterator fivePrimeBlockIterator() { 321 return fivePrimeSortedBlocks.listIterator(); 322 } 323 324} 325