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 */ 021 022package org.biojava.bio.alignment; 023 024import java.util.ArrayList; 025import java.util.Hashtable; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Map; 029import java.util.NoSuchElementException; 030import java.util.TreeSet; 031import java.util.Vector; 032 033import org.biojava.bio.BioError; 034import org.biojava.bio.BioException; 035import org.biojava.bio.symbol.Alphabet; 036import org.biojava.bio.symbol.AlphabetManager; 037import org.biojava.bio.symbol.Edit; 038import org.biojava.bio.symbol.GappedSymbolList; 039import org.biojava.bio.symbol.IllegalSymbolException; 040import org.biojava.bio.symbol.Location; 041import org.biojava.bio.symbol.RangeLocation; 042import org.biojava.bio.symbol.SimpleGappedSymbolList; 043import org.biojava.bio.symbol.Symbol; 044import org.biojava.bio.symbol.SymbolList; 045import org.biojava.utils.ChangeEvent; 046import org.biojava.utils.ChangeSupport; 047import org.biojava.utils.ChangeVetoException; 048 049/** 050 * <p> 051 * FlexibleAlignment is a class which implements UnequalLengthAlignment, 052 * ARAlignment and EditableAlignment <b>It places no restriction on where any 053 * sequence can be in the alignment so there could be gaps in the alignment. You 054 * tell it where to put the sequence, it will do it. I think I will be adding an 055 * Exception NonContinuousAlignmentException. STILL UNDER CONSTRUCTION. 056 * seqString does not work because there it does not seem to support 057 * tokenization 'token' this is true for SimpleAlignment too.</b> 058 * 059 * @author David Waring 060 * @author Matthew Pocock 061 */ 062public class FlexibleAlignment extends AbstractULAlignment implements 063 ARAlignment, EditableAlignment { 064 065 protected Map<Object, AlignmentElement> data; 066 protected List<String> labelOrder; 067 protected Location alignmentRange; 068 List<Alphabet> alphaList = new ArrayList<Alphabet>(); 069 070 /** 071 * construct this object with the reference sequence which can either be a 072 * gappedSymbolList or not label in all cases refers to an object that holds 073 * the display name (generally just a String). since more than one sequence 074 * in an alignment could have the same name this works as long as the labels 075 * are different objects even though they may hold the same name. 076 */ 077 078 public FlexibleAlignment(List<AlignmentElement> seqList) 079 throws BioException { 080 data = new Hashtable<Object, AlignmentElement>(); 081 labelOrder = new Vector<String>(); 082 alignmentRange = new RangeLocation(1, 1); 083 084 int k = 0; 085 // go through the list make sure that all seqs are GappedSymbolLists 086 for (Iterator<AlignmentElement> i = seqList.iterator(); i.hasNext();) { 087 AlignmentElement ae = i.next(); 088 String label = ae.getLabel(); 089 Location loc = ae.getLoc(); 090 SymbolList seq = ae.getSymbolList(); 091 alphaList.add(seq.getAlphabet()); 092 if (!(seq instanceof GappedSymbolList)) { 093 seq = new SimpleGappedSymbolList(seq); 094 ae = new SimpleAlignmentElement(label, seq, loc); 095 } 096 data.put(label, ae); 097 labelOrder.add(label); 098 int min = lesser(alignmentRange.getMin(), loc.getMin()); 099 int max = greater(alignmentRange.getMax(), loc.getMax()); 100 alignmentRange = new RangeLocation(min, max); 101 k++; 102 } 103 this.alphabet = AlphabetManager.getCrossProductAlphabet(alphaList); 104 try { 105 resetRange(); 106 } catch (ChangeVetoException e) { 107 throw new BioError("Should not have a problem here"); 108 } 109 110 } 111 112 private int getOrder(Object label) throws Exception { 113 for (int i = 0; i < labelOrder.size(); i++) { 114 if (labelOrder.get(i).equals(label)) 115 return i; 116 } 117 throw new Exception("did not find label"); 118 } 119 120 /** 121 * add a new a alignment usings a location to the reference sequence. This 122 * should either contain no gaps or it should be relative to a reference 123 * sequence that already has the gaps added 124 */ 125 126 public synchronized void addSequence(AlignmentElement ae) 127 throws ChangeVetoException, BioException { 128 ChangeSupport cs; 129 ChangeEvent cevt; 130 131 String label = ae.getLabel(); 132 SymbolList seq = ae.getSymbolList(); 133 Location loc = ae.getLoc(); 134 135 // give the listeners a change to veto this 136 // create a new change event ->the EDIT is a static final variable of 137 // type ChangeType in SymbolList interface 138 cevt = new ChangeEvent(this, ARAlignment.ADD_LABEL, label); 139 cs = getChangeSupport(ARAlignment.ADD_LABEL); 140 141 // let the listeners know what we want to do 142 cs.firePreChangeEvent(cevt); 143 if (!(seq instanceof GappedSymbolList)) { 144 seq = new SimpleGappedSymbolList(seq); 145 ae = new SimpleAlignmentElement(label, seq, loc); 146 } 147 data.put(label, ae); 148 labelOrder.add(label); 149 alphaList.add(seq.getAlphabet()); 150 this.alphabet = AlphabetManager.getCrossProductAlphabet(alphaList); 151 152 int min = lesser(alignmentRange.getMin(), loc.getMin()); 153 int max = greater(alignmentRange.getMax(), loc.getMax()); 154 alignmentRange = new RangeLocation(min, max); 155 resetRange(); 156 157 cs.firePostChangeEvent(cevt); 158 159 } 160 161 public synchronized void removeSequence(Object label) 162 throws ChangeVetoException { 163 164 ChangeSupport cs; 165 ChangeEvent cevt; 166 167 // give the listeners a change to veto this 168 // create a new change event ->the EDIT is a static final variable of 169 // type ChangeType in SymbolList interface 170 cevt = new ChangeEvent(this, ARAlignment.REMOVE_LABEL, label); 171 cs = getChangeSupport(ARAlignment.REMOVE_LABEL); 172 173 // let the listeners know what we want to do 174 cs.firePreChangeEvent(cevt); 175 try { 176 alphaList.remove(getOrder(label)); 177 this.alphabet = AlphabetManager.getCrossProductAlphabet(alphaList); 178 } catch (Throwable e) { 179 e.printStackTrace(); 180 } 181 data.remove(label); 182 labelOrder.remove(label); 183 resetRange(); 184 cs.firePostChangeEvent(cevt); 185 186 } 187 188 // /////////////////////// 189 // methods from Interface UnequalLengthAlignment 190 // /////////////////////// 191 192 /** 193 * The location of an individual SymbolList relative to overall Alignment 194 */ 195 public Location locInAlignment(Object label) throws NoSuchElementException { 196 return getAE(label).getLoc(); 197 } 198 199 public List<Object> getLabelsAt(int column) 200 throws IndexOutOfBoundsException { 201 if (column < 1 || column > this.length()) 202 throw new IndexOutOfBoundsException(); 203 List<Object> labelList = new ArrayList<Object>(); 204 Location loc; 205 Object label; 206 for (Iterator<Object> labelIterator = data.keySet().iterator(); labelIterator 207 .hasNext();) { 208 label = labelIterator.next(); 209 loc = getAE(label).getLoc(); 210 if (loc.contains(column)) 211 labelList.add(label); 212 } 213 return labelList; 214 } 215 216 // /////////////////////// 217 // methods from Interface Alignment 218 // ////////////////////// 219 220 public synchronized int length() { 221 return alignmentRange.getMax() - alignmentRange.getMin() + 1; 222 } 223 224 public Alphabet getAlphabet() { 225 return alphabet; 226 } 227 228 /** 229 * getLabels will return a list of labels in left to right order 230 */ 231 232 public synchronized List<String> getLabels() { 233 TreeSet<String> sorted = new TreeSet<String>( 234 new LeftRightLocationComparator<String>()); 235 sorted.addAll(labelOrder); 236 return new Vector<String>(sorted); 237 } 238 239 /** 240 * This gets the symbol for an individual sequence at position in the 241 * overall alignment If the sequence is not aligned at that location it 242 * returns null 243 */ 244 245 public synchronized Symbol symbolAt(String label, int column) 246 throws NoSuchElementException, IndexOutOfBoundsException { 247 248 SymbolList seq = symbolListForLabel(label); 249 int cloc = posInSeq(label, column); 250 Symbol symbol = null; 251 // debug (label.toString() + " " + column + ":" + cloc); 252 if (seq == null) { 253 // debug("seq is null"); 254 } 255 256 try { 257 symbol = seq.symbolAt(cloc); 258 } catch (IndexOutOfBoundsException e) { 259 // leave symbol == null 260 } 261 return symbol; 262 } 263 264 /** 265 * 266 * @param label 267 * @return 268 * @throws NoSuchElementException 269 */ 270 public synchronized SymbolList symbolListForLabel(String label) 271 throws NoSuchElementException { 272 return getAE(label).getSymbolList(); 273 } 274 275 // methods from interface EditableAlignment 276 277 public synchronized void edit(Object label, Edit edit) 278 throws ChangeVetoException { 279 throw new BioError("Not implemented yet"); 280 } 281 282 /** 283 * loc in this case is the Alignment Location 284 */ 285 public synchronized void shiftAtAlignmentLoc(Object label, Location loc, 286 int offset) throws ChangeVetoException, 287 IllegalAlignmentEditException, IndexOutOfBoundsException { 288 289 Location sourceLoc = locInSeq(label, loc); 290 shiftAtSequenceLoc(label, sourceLoc, offset); 291 292 } 293 294 /** 295 * loc in this case is the SymbolList Location 296 */ 297 public synchronized void shiftAtSequenceLoc(Object label, Location loc, 298 int offset) throws ChangeVetoException, 299 IllegalAlignmentEditException, IndexOutOfBoundsException { 300 301 ChangeSupport csgap; 302 ChangeEvent cegap; 303 ChangeSupport csloc; 304 ChangeEvent celoc; 305 celoc = new ChangeEvent(this, EditableAlignment.LOCATION, label); 306 csloc = getChangeSupport(EditableAlignment.LOCATION); 307 cegap = new ChangeEvent(this, EditableAlignment.GAPS, label); 308 csgap = getChangeSupport(EditableAlignment.GAPS); 309 310 int caseValue = 0; 311 int absOffset = Math.abs(offset); 312 Location seqLoc = locInAlignment(label); 313 AlignmentElement ae = getAE(label); 314 SymbolList seq = ae.getSymbolList(); 315 Location newLoc; 316 int min = loc.getMin(); 317 int max = loc.getMax(); 318 if (min < 1 || max > seq.length()) { 319 throw new IndexOutOfBoundsException(); 320 } 321 if (offset == 0) { 322 return; 323 } 324 if (offset > 1) 325 caseValue += 1; 326 if (min == 1) 327 caseValue += 2; 328 if (max == seq.length()) 329 caseValue += 4; 330 331 switch (caseValue) { 332 333 case 0: // internal shift to left 334 if (!allGaps(seq, min + offset, min - 1)) { 335 throw new IllegalAlignmentEditException(); 336 } 337 csgap.firePreChangeEvent(cegap); 338 339 ((GappedSymbolList) seq).addGapsInView(max + 1, absOffset); 340 removeGaps((GappedSymbolList) seq, min - absOffset, absOffset); 341 csgap.firePostChangeEvent(cegap); 342 break; 343 344 case 1: // internal shift to right 345 if (!allGaps(seq, max + 1, max + offset)) { 346 throw new IllegalAlignmentEditException(); 347 } 348 csgap.firePreChangeEvent(cegap); 349 removeGaps((GappedSymbolList) seq, max + 1, offset); 350 ((GappedSymbolList) seq).addGapsInView(min, offset); 351 csgap.firePostChangeEvent(cegap); 352 break; 353 354 case 2: // left end shift to left 355 csgap.firePreChangeEvent(cegap); 356 csloc.firePreChangeEvent(celoc); 357 ((GappedSymbolList) seq).addGapsInView(max + 1, absOffset); 358 newLoc = new RangeLocation(seqLoc.getMin() - absOffset, seqLoc 359 .getMax()); 360 ae.setLoc(newLoc); 361 resetRange(); 362 csloc.firePostChangeEvent(celoc); 363 csgap.firePostChangeEvent(cegap); 364 break; 365 366 case 3: // left end shift to right 367 if (!allGaps(seq, max + 1, max + offset)) { 368 throw new IllegalAlignmentEditException(); 369 } 370 csgap.firePreChangeEvent(cegap); 371 csloc.firePreChangeEvent(celoc); 372 removeGaps((GappedSymbolList) seq, max + 1, offset); 373 newLoc = new RangeLocation(seqLoc.getMin() + offset, seqLoc 374 .getMax()); 375 ae.setLoc(newLoc); 376 resetRange(); 377 csloc.firePostChangeEvent(celoc); 378 csgap.firePostChangeEvent(cegap); 379 break; 380 381 case 4: // right end shift to left 382 if (!allGaps(seq, min - absOffset, min - 1)) { 383 throw new IllegalAlignmentEditException(); 384 } 385 csgap.firePreChangeEvent(cegap); 386 csloc.firePreChangeEvent(celoc); 387 removeGaps((GappedSymbolList) seq, min - absOffset, absOffset); 388 newLoc = new RangeLocation(seqLoc.getMin(), seqLoc.getMax() 389 + offset); 390 ae.setLoc(newLoc); 391 resetRange(); 392 csloc.firePostChangeEvent(celoc); 393 csgap.firePostChangeEvent(cegap); 394 break; 395 396 case 5: // right end shift to right 397 csgap.firePreChangeEvent(cegap); 398 csloc.firePreChangeEvent(celoc); 399 ((GappedSymbolList) seq).addGapsInView(min, offset); 400 newLoc = new RangeLocation(seqLoc.getMin(), seqLoc.getMax() 401 + offset); 402 ae.setLoc(newLoc); 403 resetRange(); 404 csloc.firePostChangeEvent(celoc); 405 csgap.firePostChangeEvent(cegap); 406 break; 407 408 case 6: // whole seq shift to left 409 debug("Shifting all to left " + absOffset); 410 shift(label, offset); 411 break; 412 413 case 7: // whole seq shift to right 414 debug("Shifting all to right " + absOffset); 415 shift(label, offset); 416 break; 417 418 default: 419 debug("OOOPS something is wrong " + loc.toString() + " " 420 + absOffset); 421 return; 422 } 423 } 424 425 /** 426 * because there is a bug in GappedSymbolList 427 */ 428 429 public synchronized void removeGaps(GappedSymbolList seq, int start, 430 int length) { 431 try { 432 // seq.removeGaps (start , length); 433 // because there is a bug in GappedSymbolList we do it one at a time 434 for (int i = 1; i <= length; i++) { 435 seq.removeGap(start); 436 } 437 } catch (IllegalSymbolException e) { 438 throw new BioError("We should have tested for this already"); 439 } 440 } 441 442 /** 443 * make sure that all Symbols in this range are gaps 444 */ 445 446 protected synchronized boolean allGaps(SymbolList seq, int start, int end) { 447 448 Symbol gs = seq.getAlphabet().getGapSymbol(); 449 for (int i = start; i <= end; i++) { 450 if (!(seq.symbolAt(i).equals(gs))) { 451 return false; 452 } 453 } 454 return true; 455 } 456 457 /** 458 * check that begining is at 1 otherwise shift everything over 459 */ 460 protected synchronized void resetRange() throws ChangeVetoException { 461 462 int min = 0;// just for the compiler 463 int max = 0;// just for the compiler 464 int lMin; 465 int lMax; 466 int count = 1; 467 // get the current range from all labels 468 for (Iterator<String> i = getLabels().iterator(); i.hasNext();) { 469 Object label = i.next(); 470 lMin = locInAlignment(label).getMin(); 471 lMax = locInAlignment(label).getMax(); 472 if (count == 1) { 473 min = lMin; 474 } else { 475 min = lesser(min, lMin); 476 } 477 if (count == 1) { 478 max = lMax; 479 } else { 480 max = greater(max, lMax); 481 } 482 count++; 483 } 484 alignmentRange = new RangeLocation(min, max); 485 if (min != 1) { 486 int offset = 1 - alignmentRange.getMin(); 487 shiftAll(offset); 488 alignmentRange = new RangeLocation( 489 alignmentRange.getMin() + offset, alignmentRange.getMax() 490 + offset); 491 } 492 } 493 494 protected synchronized void shiftAll(int offset) throws ChangeVetoException { 495 List<String> lList = getLabels(); 496 for (Iterator<String> i = lList.iterator(); i.hasNext();) { 497 Object label = i.next(); 498 shift(label, offset); 499 } 500 } 501 502 /** 503 * moves the whole sequence 504 */ 505 506 protected synchronized void shift(Object label, int offset) 507 throws ChangeVetoException { 508 ChangeSupport csloc; 509 ChangeEvent celoc; 510 celoc = new ChangeEvent(this, EditableAlignment.LOCATION, label); 511 csloc = getChangeSupport(EditableAlignment.LOCATION); 512 Location oLoc = locInAlignment(label); 513 Location nLoc = new RangeLocation(oLoc.getMin() + offset, oLoc.getMax() 514 + offset); 515 csloc.firePreChangeEvent(celoc); 516 debug("shifting " + label.toString()); 517 getAE(label).setLoc(nLoc); 518 resetRange(); 519 debug("shifted " + label); 520 csloc.firePostChangeEvent(celoc); 521 } 522 523 // utility methods 524 protected int greater(int x, int y) { 525 int greatest = (x > y) ? x : y; 526 return greatest; 527 } 528 529 protected int lesser(int x, int y) { 530 int least = (x < y) ? x : y; 531 return least; 532 } 533 534 protected AlignmentElement getAE(Object label) 535 throws NoSuchElementException { 536 if (!(data.containsKey(label))) 537 ; 538 return data.get(label); 539 } 540 541 /** 542 * get the position in the sequence corresponding to the postion within the 543 * alignment 544 */ 545 546 protected synchronized int posInSeq(Object label, int column) 547 throws NoSuchElementException, IndexOutOfBoundsException { 548 if (column < 1 || column > this.length()) { 549 throw new IndexOutOfBoundsException(); 550 } 551 Location loc = locInAlignment(label); 552 return (column - loc.getMin() + 1); 553 } 554 555 protected synchronized Location locInSeq(Object label, Location viewLoc) 556 throws NoSuchElementException, IndexOutOfBoundsException { 557 int min = posInSeq(label, viewLoc.getMin()); 558 int max = posInSeq(label, viewLoc.getMax()); 559 return new RangeLocation(min, max); 560 } 561}