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