2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
7 * Jalview is free software: you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation, either version 3
10 * of the License, or (at your option) any later version.
12 * Jalview is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15 * PURPOSE. See the GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19 * The Jalview Authors are detailed in the 'AUTHORS' file.
21 package jalview.datamodel;
23 import jalview.util.MessageManager;
25 import java.util.Vector;
27 public abstract class CigarBase
30 * Base class for compact idiosyncratic representation of gaps and aligned
31 * residues Regards to Tom Oldfield for his DynamicArray class. 17th July 2006
36 // nothing to be done (probably)
39 protected int length = 0;
41 protected int _inc_length = 10; // extension range for addition of new
45 protected char[] operation = null;
47 protected int[] range = null;
50 * Range of Hidden residues in seq (translated as deleted in seq)
52 public static final char D = 'D';
55 * Range of insertions to seq
57 public static final char I = 'I';
60 * Range of aligned residues
62 public static final char M = 'M';
64 static protected final char _case_shift = 'a' - 'A';
67 * Ugly function to get edited sequence string, start and end symbol positions
68 * and the deletion regions as an array of int pairs May return null for an
69 * empty cigar string. May return null for deletion ranges if there are none.
72 * - the symbol sequence to apply the cigar operations to (or null if
75 * - the symbol to use for Insert operations
76 * @return Object[] { String, int[] {start, startcol, end, endcol}, int[][3]
77 * {start, end, col} or null} the gapped sequence, first and last
78 * residue index, and the deletion ranges on the reference sequence
80 public Object[] getSequenceAndDeletions(String reference, char GapChar)
83 int[][] deletions = new int[length][];
84 int[][] trunc_deletions = null;
85 StringBuffer sq = new StringBuffer();
86 int cursor = 0, alcursor = 0, start = 0, startpos = 0, end = 0,
87 endpos = 0, delcount = -1;
88 boolean consecutive_del = false;
93 if (reference != null)
95 rlength = reference.length();
97 boolean modstart = true;
98 for (int i = 0; i < length; i++)
100 switch (operation[i])
103 if (!consecutive_del)
105 deletions[++delcount] = new int[] { cursor, 0, alcursor };
108 deletions[delcount][1] = cursor - 1;
109 consecutive_del = true;
112 consecutive_del = false;
113 for (int r = 0; r < range[i]; r++)
120 consecutive_del = false;
127 if (reference != null)
129 int sbend = cursor + range[i];
132 sq.append(reference.substring(cursor, rlength));
133 while (sbend-- >= rlength)
140 sq.append(reference.substring(cursor, sbend));
143 alcursor += range[i];
149 throw new Error(MessageManager.formatMessage(
150 "error.unknown_seq_cigar_operation", new String[]
151 { new StringBuffer(operation[i]).toString() }));
156 trunc_deletions = new int[delcount][];
157 System.arraycopy(deletions, 0, trunc_deletions, 0, delcount);
160 return new Object[] { ((reference != null) ? sq.toString() : null),
162 { start, startpos, end, endpos }, trunc_deletions };
165 protected void compact_operations()
168 if (operation == null)
172 char last = operation[0];
175 if (last == operation[i])
177 range[i - 1] += range[i];
181 System.arraycopy(range, i + 1, range, i, r);
182 System.arraycopy(operation, i + 1, operation, i, r);
188 last = operation[i++];
194 * turn a cigar string into a series of operation range pairs
198 * @return object[] {char[] operation, int[] range}
199 * @throws java.lang.Exception
200 * for improperly formated cigar strings or ones with unknown
203 public static Object[] parseCigarString(String cigarString)
207 for (int i = 0, l = cigarString.length(); i < l; i++)
209 char c = cigarString.charAt(i);
210 if (c == M || c == (M - _case_shift) || c == I
211 || c == (I - _case_shift) || c == D || c == (D - _case_shift))
216 char[] operation = new char[ops];
217 int[] range = new int[ops];
219 int i = 0, l = cigarString.length();
226 c = cigarString.charAt(j++);
227 } while (c >= '0' && c <= '9' && j < l);
228 if (j >= l && c >= '0' && c <= '9')
230 throw new Exception(MessageManager
231 .getString("exception.unterminated_cigar_string"));
235 String rangeint = cigarString.substring(i, j - 1);
236 range[op] = Integer.parseInt(rangeint);
238 } catch (Exception e)
240 throw new Error(MessageManager
241 .getString("error.implementation_bug_parse_cigar_string"));
243 if (c >= 'a' && c <= 'z')
247 if ((c == M || c == I || c == D))
253 throw new Exception(MessageManager.formatMessage(
254 "exception.unexpected_operation_cigar_string_pos",
256 { new StringBuffer(c).toString(),
257 Integer.valueOf(i).toString(), cigarString }));
260 return new Object[] { operation, range };
264 * add an operation to cigar string
271 public void addOperation(char op, int range)
273 if (op >= 'a' && op <= 'z')
277 if (op != M && op != D && op != I)
279 throw new Error(MessageManager.getString(
280 "error.implementation_error_invalid_operation_string"));
284 return; // No Operation to add.
289 MessageManager.getString("error.invalid_range_string"));
292 if (operation == null)
294 this.operation = new char[_inc_length];
295 this.range = new int[_inc_length];
297 if (length + 1 == operation.length)
299 char[] ops = this.operation;
300 this.operation = new char[length + 1 + _inc_length];
301 System.arraycopy(ops, 0, this.operation, 0, length);
303 int[] rng = this.range;
304 this.range = new int[length + 1 + _inc_length];
305 System.arraycopy(rng, 0, this.range, 0, length);
308 if ((length > 0) && (operation[length - 1] == op))
310 length--; // modify existing operation.
314 this.range[length] = 0; // reset range
316 this.operation[length] = op;
317 this.range[length++] += range;
321 * semi-efficient insert an operation on the current cigar string set at
322 * column pos (from 1) NOTE: Insertion operations simply extend width of cigar
323 * result - affecting registration of alignment Deletion ops will shorten
324 * length of result - and affect registration of alignment Match ops will also
325 * affect length of result - affecting registration of alignment (ie
326 * "10M".insert(4,I,3)->"4M3I3M") - (replace?) (ie
327 * "10M".insert(4,D,3)->"4M3D3M") - (shortens alignment) (ie
328 * "5I5M".insert(4,I,3)->"8I5M") - real insertion (ie
329 * "5I5M".insert(4,D,3)->"4I2D3M") - shortens aligment - I's are removed, Ms
330 * changed to Ds (ie "10M".insert(4,M,3)->"13M") - lengthens - Is changed to
331 * M, Ds changed to M. (ie "5I5M".insert(4,M,3)->"4I8M") - effectively shifts
332 * sequence left by 1 residue and extends it by 3 (
333 * "10D5M".insert(-1,M,3)->"3M7D5M") ( "10D5M".insert(0,M,3)->"7D8M") (
334 * "10D5M".insert(1,M,3)->"10D8M") ( "1M10D5M".insert(0,M,3)->"1M10D8M") (
335 * "1M10D5M".insert(1,M,3)->"
337 * if pos is beyond width - I operations are added before the operation
340 * int -1, 0-length of visible region, or greater to append new ops
341 * (with insertions in between)
345 * int public void addOperationAt(int pos, char op, int range) { int
346 * cursor = -1; // mark the position for the current operation being
347 * edited. int o = 0; boolean last_d = false; // previous op was a
348 * deletion. if (pos < -1) throw new Error("pos<-1 is not
349 * supported."); while (o<length) { if (operation[o] != D) { if (
350 * (cursor + this.range[o]) < pos) { cursor += this.range[o]; o++;
351 * last_d=false; } else { break; } } else { last_d=true; o++; } } if
352 * (o==length) { // must insert more operations before pos if
353 * (pos-cursor>0) addInsertion(pos-cursor); // then just add the new
354 * operation. Regardless of what it is. addOperation(op, range); }
355 * else { int diff = pos - cursor;
357 * int e_length = length-o; // new edit operation array length. //
358 * diff<0 - can only happen before first insertion or match. -
359 * affects op and all following // dif==0 - only when at first
360 * position of existing op - // diff>0 - must preserve some existing
361 * operations int[] e_range = new int[e_length];
362 * System.arraycopy(this.range, o, e_range, 0, e_length); char[] e_op
363 * = new char[e_length]; System.arraycopy(this.operation, o, e_op, 0,
364 * e_length); length = o; // can now use add_operation to extend
365 * list. int e_o=0; // current operation being edited. switch (op) {
366 * case M: switch (e_op[e_o]) { case M: if (last_d && diff <= 0) { //
367 * reduce D's, if possible if (range<=this.range[o-1]) { this.range[o
368 * - 1] -= range; } else { this.range[o-1]=0; } if
369 * (this.range[o-1]==0) o--; // lose this op. } e_range[e_o] +=
370 * range; // just add more matched residues break; case I: // change
371 * from insertion to match if (last_d && diff<=0) { // reduce D's, if
372 * possible if (range<=this.range[o-1]) { this.range[o - 1] -= range;
373 * } else { this.range[o-1]=0; } if (this.range[o-1]==0) o--; // lose
374 * this op. } e_range[e_o] break; default: throw new Inp }
376 * break; case I: break; case D: } break; default: throw new
377 * Error("Implementation Error: Unknown operation in addOperation!");
378 * } // finally, add remaining ops. while (e_o<e_length) {
379 * addOperation(e_op[e_o], e_range[e_o]); e_o++; } } }
382 * Mark residues from start to end (inclusive) as deleted from the alignment,
383 * and removes any insertions.
389 * @return deleted int - number of symbols marked as deleted
391 public int deleteRange(int start, int end)
396 // nothing to do here
399 if (start < 0 || start > end)
401 throw new Error(MessageManager.getString(
402 "error.implementation_error_delete_range_out_of_bounds"));
405 int cursor = 0; // mark the position for the current operation being edited.
406 int rlength = 1 + end - start; // number of positions to delete
409 boolean editing = false;
410 char[] oldops = operation;
411 int[] oldrange = range;
415 compact_operations();
416 while (o < oldlen && cursor <= end && rlength > 0)
420 // absorbed into new deleted region.
421 addDeleted(oldrange[o++]);
425 int remain = oldrange[o]; // number of op characters left to edit
428 if ((cursor + remain) <= start)
430 addOperation(oldops[o], oldrange[o]);
431 cursor += oldrange[o++];
432 continue; // next operation
435 // add operations before hidden region
436 if (start - cursor > 0)
438 addOperation(oldops[o], start - cursor);
439 remain -= start - cursor;
442 // start inserting new ops
443 if (o < oldlen && editing && rlength > 0 && remain > 0)
448 if (rlength > remain)
457 if (remain - rlength > 0)
459 this.addOperation(M, remain - rlength); // add remaining back.
466 if (remain - rlength > 0)
468 // only remove some gaps
469 addInsertion(remain - rlength);
475 MessageManager.getString("error.implementation_error")); // do
478 throw new Error(MessageManager.formatMessage(
479 "error.implementation_error_unknown_operation",
481 { new StringBuffer(oldops[o]).toString() }));
484 remain = oldrange[++o]; // number of op characters left to edit
490 addOperation(oldops[o], oldrange[o++]);
492 // if (cursor<(start+1)) {
493 // ran out of ops - nothing to do here ?
494 // addInsertion(start-cursor);
500 * Deleted regions mean that there will be discontinuous sequence numbering in
501 * the sequence returned by getSeq(char).
503 * @return true if there deletions
505 public boolean hasDeletedRegions()
507 for (int i = 0; i < length; i++)
509 if (operation[i] == D)
518 * enumerate the ranges on seq that are marked as deleted in this cigar
520 * @return int[] { vis_start, sym_start, length }
522 public int[] getDeletedRegions()
528 Vector dr = new Vector();
529 int cursor = 0, vcursor = 0;
530 for (int i = 0; i < length; i++)
532 switch (operation[i])
541 dr.addElement(new int[] { vcursor, cursor, range[i] });
549 int[] delregions = new int[dr.size() * 3];
550 for (int i = 0, l = dr.size(); i < l; i++)
552 int[] reg = (int[]) dr.elementAt(i);
553 delregions[i * 3] = reg[0];
554 delregions[i * 3 + 1] = reg[1];
555 delregions[i * 3 + 2] = reg[2];
561 * sum of ranges in cigar string
563 * @return int number of residues hidden, matched, or gaps inserted into
566 public int getFullWidth()
571 for (int i = 0; i < length; i++)
580 * Visible length of aligned sequence
582 * @return int length of including gaps and less hidden regions
584 public int getWidth()
589 for (int i = 0; i < length; i++)
591 if (operation[i] == M || operation[i] == I)
601 * mark a range of inserted residues
606 public void addInsertion(int range)
608 this.addOperation(I, range);
612 * mark the next range residues as hidden (not aligned) or deleted
617 public void addDeleted(int range)
619 this.addOperation(D, range);
623 * Modifies operation list to delete columns from start to end (inclusive)
624 * editing will remove insertion operations, and convert matches to deletions
630 * @return boolean true if residues were marked as deleted. public boolean
631 * deleteRange(int start, int end) { boolean deleted = false; int op =
632 * 0, prevop = -1, firstm = -1, lastm = -1, postop = -1; int width =
633 * 0; // zero'th column if (length > 0) { // find operation bracketing
634 * start of the range do { if (operation[op] != D) { width +=
635 * range[prevop = op]; } op++; } while (op < length && width < start);
636 * } if (width < start) { // run off end - add more operations up to
637 * deletion. addInsertion(start - width); } else { // edit existing
638 * operations. op = prevop; width -= range[prevop]; int[] oldrange =
639 * range; char[] oldops = operation; range = new int[oldrange.length];
640 * operation = new char[oldops.length]; if (op < length) { do { if
641 * (operation[op] != D) { width += range[postop = op]; } op++; } while
642 * (op < length && width <= end); } } if (deleted == true) {
643 * addDeleted(end - start + 1); } return deleted; }
646 * Return an ENSEMBL style cigar string where D may indicates excluded parts
649 * @return String of form ([0-9]+[IMD])+
651 public String getCigarstring()
653 StringBuffer cigarString = new StringBuffer();
654 for (int i = 0; i < length; i++)
656 cigarString.append("" + range[i]);
657 cigarString.append(operation[i]);
659 return cigarString.toString();