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.symbol; 023 024import java.io.Serializable; 025import java.util.AbstractList; 026import java.util.Iterator; 027import java.util.List; 028import java.util.NoSuchElementException; 029 030import org.biojava.bio.Annotation; 031import org.biojava.bio.BioException; 032import org.biojava.bio.BioRuntimeException; 033import org.biojava.bio.seq.io.SymbolTokenization; 034import org.biojava.utils.AbstractChangeable; 035import org.biojava.utils.ChangeEvent; 036import org.biojava.utils.ChangeForwarder; 037import org.biojava.utils.ChangeSupport; 038import org.biojava.utils.ChangeType; 039import org.biojava.utils.ChangeVetoException; 040 041/** 042 * <p> 043 * Abstract helper implementation of the SymbolList core interface. To produce a 044 * concrete SymbolList implementation, you need only implement the 045 * <code>getAlphabet</code>, <code>length</code> and <code>symbolAt</code> 046 * methods. Iterators and sublists are handled for you automatically. 047 * </p> 048 * 049 * <p> 050 * This class makes many custom SymbolList implementations very quick to 051 * implement. See org.biojava.bio.seq.tools.ComplementSymbolList for an example 052 * of this. 053 * </p> 054 * 055 * <p> 056 * To make a mutable SymbolList, override the implementation of edit to perform 057 * the apropreate edit. If your implementation of SymbolList is a view onto an 058 * underlying SymbolList, then you must forward any apropreate edit requests to 059 * that list, and forward all events from the underlying list to your listeners. 060 * </p> 061 * 062 * @author Thomas Down 063 * @author Matthew Pocock 064 */ 065 066public abstract class AbstractSymbolList extends AbstractChangeable implements 067 SymbolList { 068 protected AbstractSymbolList() { 069 } 070 071 public Iterator<Symbol> iterator() { 072 return new SymbolIterator(1, length()); 073 } 074 075 public SymbolList subList(int start, int end) { 076 if (start < 1 || end > length()) { 077 throw new IndexOutOfBoundsException("Sublist index out of bounds " 078 + length() + ":" + start + "," + end); 079 } 080 081 if (end < start) { 082 throw new IllegalArgumentException( 083 "end must not be lower than start: start=" + start 084 + ", end=" + end); 085 } 086 return new SubList<Symbol>(start, end); 087 } 088 089 public List<Symbol> toList() { 090 return new ListView<Symbol>(this); 091 } 092 093 public String seqString() { 094 try { 095 SymbolTokenization toke = getAlphabet().getTokenization("default"); 096 return toke.tokenizeSymbolList(this); 097 } catch (BioException ex) { 098 throw new BioRuntimeException("Couldn't tokenize sequence", ex); 099 } 100 } 101 102 public String subStr(int start, int end) { 103 return subList(start, end).seqString(); 104 } 105 106 public void edit(Edit edit) throws IllegalAlphabetException, 107 ChangeVetoException { 108 throw new ChangeVetoException("AbstractSymbolList is immutable"); 109 } 110 111 /** 112 * Provides logical equality for two SymbolLists that share the same list of 113 * canonical symbols 114 */ 115 public boolean equals(Object o) { 116 if (this == o) 117 return true;// just for optimality 118 if (o == null) 119 return false; 120 if (!(o instanceof SymbolList)) 121 return false; 122 return compare(this, (SymbolList) o); 123 } 124 125 private volatile int hashCode = 0; // for caching purposes 126 127 public int hashCode() { 128 if (hashCode == 0) { 129 int result = 17; 130 for (Iterator<Symbol> i = iterator(); i.hasNext();) { 131 result = 37 * result + i.next().hashCode(); 132 } 133 hashCode = result; 134 } 135 return hashCode; 136 } 137 138 public String toString() { 139 return super.toString() + " length: " + length(); 140 } 141 142 private boolean compare(SymbolList sl1, SymbolList sl2) { 143 if (!sl1.getAlphabet().equals(sl2.getAlphabet())) { 144 return false; 145 } 146 147 if (sl1.length() != sl2.length()) { 148 return false; 149 } 150 151 Iterator<Symbol> si1 = sl1.iterator(); 152 Iterator<Symbol> si2 = sl2.iterator(); 153 while (si1.hasNext()) { 154 if (!(si1.next() == si2.next())) { 155 return false; 156 } 157 } 158 return true; 159 } 160 161 /** 162 * <p> 163 * An Iterator over each Symbol in a range of a SymbolList. 164 * </p> 165 * 166 * <p> 167 * Objects of this type are returned by 168 * <code>AbstractSymbolList.iterator</code>. 169 * </p> 170 * 171 * @author Thomas Down 172 */ 173 174 private class SymbolIterator implements Iterator<Symbol>, Serializable { 175 /** 176 * Generated Serial Version ID. 177 */ 178 private static final long serialVersionUID = -8542733313998842964L; 179 private int max; 180 private int pos; 181 182 /** 183 * Construct a SymbolIterator object that will return the symbols from 184 * min to max inclusive. 185 * 186 * @param min 187 * the first index to return 188 * @param max 189 * the last index to return 190 */ 191 192 public SymbolIterator(int min, int max) { 193 this.max = max; 194 pos = min; 195 } 196 197 // protected SymbolIterator() { 198 // } 199 200 public boolean hasNext() { 201 return (pos <= max); 202 } 203 204 public Symbol next() { 205 if (pos > max) { 206 throw new NoSuchElementException(); 207 } 208 return symbolAt(pos++); 209 } 210 211 public void remove() { 212 throw new UnsupportedOperationException(); 213 } 214 } 215 216 /** 217 * <p> 218 * Implements a list view of a SymbolList. 219 * </p> 220 * 221 * <p> 222 * Objects of this type are instantiated by 223 * <code>AbstractSymbolList.subList</code>. 224 * </p> 225 * 226 * @author Thomas Down 227 * @author Matthew Pocock 228 */ 229 230 private class SubList<T extends Symbol> extends AbstractChangeable 231 implements SymbolList, Serializable { 232 /** 233 * Generated serial version identifier. 234 */ 235 private static final long serialVersionUID = -4299952037536894744L; 236 237 private int start, end; 238 239 private transient EditTranslater editTranslater = null; 240 private transient ChangeForwarder annotationForwarder = null; 241 242 public SubList(int start, int end) { 243 this.start = start; 244 this.end = end; 245 } 246 247 public Alphabet getAlphabet() { 248 return AbstractSymbolList.this.getAlphabet(); 249 } 250 251 public Iterator<Symbol> iterator() { 252 return new SymbolIterator(start, end); 253 } 254 255 public int length() { 256 return end - start + 1; 257 } 258 259 public Symbol symbolAt(int pos) { 260 if (pos < 1 || pos > length()) { 261 throw new IndexOutOfBoundsException( 262 "Symbol index out of bounds " + length() + ":" + pos); 263 } 264 return AbstractSymbolList.this.symbolAt(pos + start - 1); 265 } 266 267 public SymbolList subList(int sstart, int send) { 268 if (sstart < 1 || send > length()) { 269 throw new IndexOutOfBoundsException( 270 "Sublist index out of bounds " + length() + ":" 271 + sstart + "," + send); 272 } 273 274 if (send < sstart) { 275 throw new IndexOutOfBoundsException( 276 "Requested end must not be lower than start: start=" 277 + sstart + ", end=" + send); 278 } 279 280 return new SubList<Symbol>(sstart + start - 1, send + start - 1); 281 } 282 283 public String seqString() { 284 try { 285 SymbolTokenization toke = getAlphabet() 286 .getTokenization("token"); 287 return toke.tokenizeSymbolList(this); 288 } catch (BioException ex) { 289 throw new BioRuntimeException("Couldn't tokenize sequence", ex); 290 } 291 } 292 293 public String subStr(int start, int end) { 294 return subList(start, end).seqString(); 295 } 296 297 public List<Symbol> toList() { 298 return new ListView<Symbol>(this); 299 } 300 301 // fixme: doesn't do range checking on edit object 302 public void edit(Edit edit) throws IllegalAlphabetException, 303 ChangeVetoException { 304 AbstractSymbolList.this.edit(new Edit(edit.pos + this.start - 1, 305 edit.length, edit.replacement)); 306 } 307 308 protected ChangeSupport getChangeSupport(ChangeType changeType) { 309 ChangeSupport cs = super.getChangeSupport(changeType); 310 311 if ((SymbolList.EDIT.isMatchingType(changeType) || changeType 312 .isMatchingType(SymbolList.EDIT)) 313 && (editTranslater == null)) { 314 editTranslater = new EditTranslater(this, cs, start, end); 315 AbstractSymbolList.this.addChangeListener(editTranslater, 316 SymbolList.EDIT); 317 } 318 319 if (((changeType == null) || (changeType == Annotation.PROPERTY)) 320 && (annotationForwarder == null)) { 321 annotationForwarder = new ChangeForwarder.Retyper(this, cs, 322 Annotation.PROPERTY); 323 AbstractSymbolList.this.addChangeListener(annotationForwarder, 324 Annotation.PROPERTY); 325 } 326 327 return cs; 328 } 329 330 /** 331 * Provides logical equality for two SymbolLists that share the same 332 * list of canonical symbols 333 */ 334 public boolean equals(Object o) { 335 if (this == o) 336 return true;// just for optimality 337 if (o == null) 338 return false; 339 if (!(o instanceof SymbolList)) 340 return false; 341 return compare(this, (SymbolList) o); 342 } 343 344 private volatile int hashCode = 0; // for caching purposes 345 346 public int hashCode() { 347 if (hashCode == 0) { 348 int result = 17; 349 for (Iterator<Symbol> i = iterator(); i.hasNext();) { 350 result = 37 * result + i.next().hashCode(); 351 } 352 hashCode = result; 353 } 354 return hashCode; 355 } 356 357 public String toString() { 358 return super.toString() + " start: " + start + " end: " + end /* 359 * (+ 360 * " parent: " 361 * + 362 * AbstractSymbolList 363 * . 364 * this. 365 * toString 366 * () 367 * [causes 368 * infinite 369 * loop 370 * -- 371 * odd] 372 */; 373 } 374 } 375 376 /** 377 * <p> 378 * Implements a list view of a SymbolList. 379 * </p> 380 * 381 * <p> 382 * Objects of this type are instantiated by 383 * <code>AbstractSymbolList.asList</code>. 384 * </p> 385 * 386 * @author Thomas Down 387 */ 388 389 private static class ListView<T extends Symbol> extends AbstractList<T> 390 implements Serializable { 391 /** 392 * Generated serial version identifier. 393 */ 394 private static final long serialVersionUID = 1481437420273354153L; 395 private SymbolList rl; 396 397 /** 398 * Build a new ListView to view a SymbolList as a list. 399 * 400 * @param symList 401 * the SymbolList to view 402 */ 403 404 ListView(SymbolList symList) { 405 this.rl = symList; 406 } 407 408 @SuppressWarnings("unchecked") 409 public T get(int pos) { 410 return (T) rl.symbolAt(pos + 1); 411 } 412 413 public int size() { 414 return rl.length(); 415 } 416 } 417 418 /** 419 * This adapter screens all edit events to see if they overlap with a window 420 * of interest. If they do, then a new edit event is built for the 421 * overlapping region and pased on to all listeners. 422 * 423 * @author Matthew Pocock 424 */ 425 public class EditScreener extends ChangeForwarder { 426 protected final int min; 427 protected final int max; 428 429 public EditScreener(Object source, ChangeSupport cs, int min, int max) { 430 super(source, cs); 431 this.min = min; 432 this.max = max; 433 } 434 435 protected ChangeEvent generateEvent(ChangeEvent ce) { 436 ChangeType ct = ce.getType(); 437 if (ct == SymbolList.EDIT) { 438 Object change = ce.getChange(); 439 if ((change != null) && (change instanceof Edit)) { 440 Edit edit = (Edit) change; 441 int start = edit.pos; 442 int end = start + edit.length - 1; // inclusive 443 if ((start <= max) && (end >= min)) { 444 // they overlap 445 return new ChangeEvent(getSource(), ct, edit, null, ce); 446 } 447 } 448 } 449 return null; 450 } 451 } 452 453 /** 454 * This translates edit events that fall within a window into window 455 * co-ordinates. 456 * 457 * @author Matthew Pocock 458 */ 459 public class EditTranslater extends EditScreener { 460 public EditTranslater(Object source, ChangeSupport cs, int min, int max) { 461 super(source, cs, min, max); 462 } 463 464 protected ChangeEvent generateEvent(ChangeEvent ce) { 465 ce = super.generateEvent(ce); 466 if (ce != null) { 467 Edit edit = (Edit) ce.getChange(); 468 return new ChangeEvent(ce.getSource(), ce.getType(), new Edit( 469 edit.pos - min, edit.length, edit.replacement), null, 470 ce.getChainedEvent()); 471 } 472 return null; 473 } 474 } 475}