/* * BioJava development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public Licence. This should * be distributed with the code. If you do not have a copy, * see: * * http://www.gnu.org/copyleft/lesser.html * * Copyright for this code is held jointly by the individual * authors. These should be listed in @author doc comments. * * For more information on the BioJava project and its aims, * or to join the biojava-l mailing list, visit the home page * at: * * http://www.biojava.org/ */ package org.biojava.bio.symbol; import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.ListIterator; import org.biojava.bio.BioError; /** * Circular view onto an underlying Location instance. If the location overlaps * the origin of the sequence the underlying location will be a CompoundLocation * Note that in this case isContiguous() will return false. This behaviour is * desirable for proper treatment of the location with LocationTools. * To find if a location overlaps the origin use the overlapsOrigin() method *

* Note also that as a location that overlaps the origin is a compound location it's * min will be 1 and its max will be length (by default). In these cases it is imperative to * use the block iterator if you wish to know the 'true' min and max (bearing in mind that * there is no logical value for a min or max on a circular sequence). *

*

* The symbols() method has been overridden to handle the weirdness of a * location crossing the origin. *

* * @author Matthew Pocock * @author Mark Schreiber * @since 1.2 */ public class CircularLocation extends AbstractLocationDecorator { private final int length; private int fivePrimeEnd; private int threePrimeEnd; private LinkedList fivePrimeSortedBlocks; private final boolean overlaps; public final int getLength() { return length; } /** * Does the Location overlap the origin (position 1) of the sequence? *

If it does the Location will internally be composed of a CompoundLocation. * @return true if it does, false otherwise */ public boolean overlapsOrigin(){ return overlaps; } /** * Constructs a CircularLocation by wrapping another Location * It is preferable to use LocationTools to make CircularLocations * @param wrapped the Location to wrap. * @param length the length of the Sequence */ public CircularLocation(Location wrapped, int length) { this(wrapped, length, wrapped.getMin()); } /** * Makes a CircularLocation where the 5' end of the Location is specified. The * fivePrimeEnd parameter must match one of the minimum coordinates * of the wrapped Location. This allows the creation of Locations * whose polarity can be specified. This method is used internally by LocationTools.union() * and by the other constructor. * * @param wrapped the Location to map to a Circle * @param length the lenght of the circle * @param fivePrimeEnd the 5' polar end of the sequence * @throws IllegalArgumentException if the 5' end doesn't match the min coordinate * of either wrapped or one of its blocks. */ public CircularLocation(Location wrapped, int length, int fivePrimeEnd){ super(wrapped); this.length = length; this.overlaps = CircularLocationTools.overlapsOrigin(this); //the 5' end must be the min of one of the blocks of the wrapped location fivePrimeSortedBlocks = new LinkedList(); for(Iterator i = wrapped.blockIterator(); i.hasNext();){ Location loc = (Location)i.next(); fivePrimeSortedBlocks.add(loc); if(loc.getMin() == fivePrimeEnd){ this.fivePrimeEnd = fivePrimeEnd; } } //reorder blocks so that block with 5' end is the first block Collections.sort(fivePrimeSortedBlocks, Location.naturalOrder); //check the first item to see if it is the five prime end while(((Location)fivePrimeSortedBlocks.getFirst()).getMin() != get5PrimeEnd()){ //pop it off and send it to the back of the list Object o = fivePrimeSortedBlocks.removeFirst(); fivePrimeSortedBlocks.addLast(o); } //sort out the 3'End threePrimeEnd = ((Location)fivePrimeSortedBlocks.getLast()).getMax(); if(get5PrimeEnd() == 0){ throw new IllegalArgumentException( "The 5' End must be either the minimum of the wrapped location or the minimum of one of its components"); } } protected Location decorate(Location loc) { return new CircularLocation(loc, getLength()); } public boolean contains(int p) { int pp = p % getLength() + (super.getMin() / getLength()); return getWrapped().contains(pp); } public Location intersection(Location l) { return LocationTools.intersection(this,l); } public boolean overlaps(Location l) { return LocationTools.overlaps(this,l); } public Location union(Location l) { return LocationTools.union(this,l); } public boolean contains(Location l) { return LocationTools.contains(this,l); } public boolean equals(Object o){ if((o instanceof Location)==false) return false; return LocationTools.areEqual(this, (Location)o); } public String toString() { if (fivePrimeSortedBlocks.size() > 1) { StringBuffer sb = new StringBuffer(); sb.append(get5PrimeEnd() + ", " + ((Location)fivePrimeSortedBlocks.getLast()).getMax() + " {"); Iterator i = fivePrimeSortedBlocks.iterator(); if(i.hasNext()) sb.append("(" + i.next() + ")"); while(i.hasNext()) sb.append(", (" + i.next() + ")"); sb.append("}"); sb.append(" (circular)"); return sb.substring(0); }else{ return getWrapped().toString() + " (circular)"; } } /** * Delegates to the wrapped location. Currently as locations that cross * the origin are wrapped CompoundLocations they are not considered contiguous. * This is desirable from the point of view of logical operations as it greatly * simplifies the calculations of things such as contains, overlaps etc. * @return true if the location is contiguous and doesn't overlap the origin */ public boolean isContiguous() { return getWrapped().isContiguous(); } /** * The point at which indicates the 5' end of the Location. This is needed as * compound circular locations have polarity. For example (18..30, 1..12) is * not the same as (1..12, 18..30). The 5' coordinate is derived during * construction of the Location. In particular during a union operation * the 5' coordinate is generally the 5' coordinate of the first location in * the union. In the case where this cannot be logically maintained the 5' * coordinate will revert to getMin() * * @return the most 5' coordinate * @see #fivePrimeBlockIterator() * @see #getMin() * @see #get3PrimeEnd() */ public int get5PrimeEnd(){ return fivePrimeEnd; } /** * @return the most 3' coordinate * @see #fivePrimeBlockIterator() * @see #getMax() * @see #get5PrimeEnd() */ public int get3PrimeEnd(){ return threePrimeEnd; } /** * This will give you the coordinate of the minimum point contained by this * Location. Generally this will be the leftmost end however if the Location * crosses the origin then it will actually be the origin that is the minimum * point and the method will return 1. If you want to be guarenteed of getting * the leftmost coordinate then call get5PrimeEnd() * @return the minimum coord * @see #get5PrimeEnd() */ public int getMin(){ return super.getMin(); } /** * This will give you the coordinate of the maximum point contained by this * Location. Generally this will be the rightmost end however if the Location * crosses the origin then it will actually be the point before the origin that is the maximum * point and the method will return getLength(). * If you want to be guarenteed of getting * the rightmost coordinate then call get3PrimeEnd() * @return the maximum coord * @see #get3PrimeEnd() */ public int getMax(){ return super.getMax(); } public SymbolList symbols(SymbolList seq) { SymbolList syms; Edit ed; //iterate over the Locations from the 5' end ListIterator i = this.fivePrimeBlockIterator(); syms = ((Location)i.next()).symbols(seq); while(i.hasNext()){ Location loc = (Location)i.next(); SymbolList add = loc.symbols(seq); ed = new Edit(syms.length()+1,0,add); try { syms.edit(ed); } catch (Exception ex) { throw new BioError("Illegal edit operation", ex); } } return syms; } /** * Iterates over the location blocks in order starting with the most 5' * @see #blockIterator() * @see #get5PrimeEnd() * @return a ListIterator */ public ListIterator fivePrimeBlockIterator() { return fivePrimeSortedBlocks.listIterator(); } }