/* * 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.io.Serializable; import java.util.AbstractList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import org.biojava.bio.Annotation; import org.biojava.bio.BioException; import org.biojava.bio.BioRuntimeException; import org.biojava.bio.seq.io.SymbolTokenization; import org.biojava.utils.AbstractChangeable; import org.biojava.utils.ChangeEvent; import org.biojava.utils.ChangeForwarder; import org.biojava.utils.ChangeSupport; import org.biojava.utils.ChangeType; import org.biojava.utils.ChangeVetoException; /** *

* Abstract helper implementation of the SymbolList core interface. * To produce a concrete SymbolList implementation, you need only * implement the getAlphabet, length and * symbolAt methods. Iterators and sublists are * handled for you automatically. *

* *

* This class makes many custom SymbolList implementations * very quick to implement. * See org.biojava.bio.seq.tools.ComplementSymbolList * for an example of this. *

* *

* To make a mutable SymbolList, override the implementation of edit to perform * the apropreate edit. If your implementation of SymbolList is a view onto an * underlying SymbolList, then you must forward any apropreate edit requests * to that list, and forward all events from the underlying list to your * listeners. *

* * @author Thomas Down * @author Matthew Pocock */ public abstract class AbstractSymbolList extends AbstractChangeable implements SymbolList { protected AbstractSymbolList() { } public Iterator iterator() { return new SymbolIterator(1, length()); } public SymbolList subList(int start, int end) { if (start < 1 || end > length()) { throw new IndexOutOfBoundsException( "Sublist index out of bounds " + length() + ":" + start + "," + end ); } if (end < start) { throw new IllegalArgumentException( "end must not be lower than start: start=" + start + ", end=" + end ); } return new SubList(start, end); } public List toList() { return new ListView(this); } public String seqString() { try { SymbolTokenization toke = getAlphabet().getTokenization("default"); return toke.tokenizeSymbolList(this); } catch (BioException ex) { throw new BioRuntimeException("Couldn't tokenize sequence", ex); } } public String subStr(int start, int end) { return subList(start, end).seqString(); } public void edit(Edit edit) throws IllegalAlphabetException, ChangeVetoException { throw new ChangeVetoException( "AbstractSymbolList is immutable" ); } /** * Provides logical equality for two SymbolLists that share the same list * of canonical symbols */ public boolean equals(Object o){ if(this == o) return true;//just for optimality if(o == null) return false; if(!(o instanceof SymbolList)) return false; return compare(this, (SymbolList)o); } private volatile int hashCode = 0; //for caching purposes public int hashCode(){ if(hashCode == 0){ int result = 17; for(Iterator i = iterator(); i.hasNext();){ result = 37*result + i.next().hashCode(); } hashCode = result; } return hashCode; } public String toString() { return super.toString() + " length: " + length(); } private boolean compare(SymbolList sl1, SymbolList sl2){ if (!sl1.getAlphabet().equals(sl2.getAlphabet())) { return false; } if (sl1.length() != sl2.length()) { return false; } Iterator si1 = sl1.iterator(); Iterator si2 = sl2.iterator(); while (si1.hasNext()) { if (! (si1.next() == si2.next())) { return false; } } return true; } /** *

* An Iterator over each Symbol in a range of a SymbolList. *

* *

* Objects of this type are returned by * AbstractSymbolList.iterator. *

* * @author Thomas Down */ private class SymbolIterator implements Iterator, Serializable { private int max; private int pos; /** * Construct a SymbolIterator object that will return the symbols from * min to max inclusive. * * @param min the first index to return * @param max the last index to return */ public SymbolIterator(int min, int max) { this.max = max; pos = min; } protected SymbolIterator() { } public boolean hasNext() { return (pos <= max); } public Object next() { if (pos > max) { throw new NoSuchElementException(); } return symbolAt(pos++); } public void remove() { throw new UnsupportedOperationException(); } } /** *

* Implements a list view of a SymbolList. *

* *

* Objects of this type are instantiated by * AbstractSymbolList.subList. *

* * @author Thomas Down * @author Matthew Pocock */ private class SubList extends AbstractChangeable implements SymbolList, Serializable { private int start, end; private transient EditTranslater editTranslater = null; private transient ChangeForwarder annotationForwarder = null; public SubList(int start, int end) { this.start = start; this.end = end; } public Alphabet getAlphabet() { return AbstractSymbolList.this.getAlphabet(); } public Iterator iterator() { return new SymbolIterator(start, end); } public int length() { return end - start + 1; } public Symbol symbolAt(int pos) { if (pos < 1 || pos > length()) { throw new IndexOutOfBoundsException( "Symbol index out of bounds " + length() + ":" + pos ); } return AbstractSymbolList.this.symbolAt(pos + start - 1); } public SymbolList subList(int sstart, int send) { if (sstart < 1 || send > length()) { throw new IndexOutOfBoundsException( "Sublist index out of bounds " + length() + ":" + sstart + "," + send ); } if (send < sstart) { throw new IndexOutOfBoundsException( "Requested end must not be lower than start: start=" + sstart + ", end=" + send ); } return new SubList(sstart + start - 1, send + start - 1); } public String seqString() { try { SymbolTokenization toke = getAlphabet().getTokenization("token"); return toke.tokenizeSymbolList(this); } catch (BioException ex) { throw new BioRuntimeException("Couldn't tokenize sequence", ex); } } public String subStr(int start, int end) { return subList(start, end).seqString(); } public List toList() { return new ListView(this); } // fixme: doesn't do range checking on edit object public void edit(Edit edit) throws IllegalAlphabetException, ChangeVetoException { AbstractSymbolList.this.edit(new Edit( edit.pos + this.start - 1, edit.length, edit.replacement )); } protected ChangeSupport getChangeSupport(ChangeType changeType) { ChangeSupport cs = super.getChangeSupport(changeType); if( (SymbolList.EDIT.isMatchingType(changeType) || changeType.isMatchingType(SymbolList.EDIT)) && (editTranslater == null) ) { editTranslater = new EditTranslater(this, cs, start, end); AbstractSymbolList.this.addChangeListener(editTranslater, SymbolList.EDIT); } if( ((changeType == null) || (changeType == Annotation.PROPERTY)) && (annotationForwarder == null) ) { annotationForwarder = new ChangeForwarder.Retyper(this, cs, Annotation.PROPERTY); AbstractSymbolList.this.addChangeListener(annotationForwarder, Annotation.PROPERTY); } return cs; } /** * Provides logical equality for two SymbolLists that share the same list * of canonical symbols */ public boolean equals(Object o){ if(this == o) return true;//just for optimality if(o == null) return false; if(!(o instanceof SymbolList)) return false; return compare(this, (SymbolList)o); } private volatile int hashCode = 0; //for caching purposes public int hashCode(){ if(hashCode == 0){ int result = 17; for(Iterator i = iterator(); i.hasNext();){ result = 37*result + i.next().hashCode(); } hashCode = result; } return hashCode; } public String toString() { return super.toString() + " start: " + start + " end: " + end /*(+ " parent: " + AbstractSymbolList.this.toString() [causes infinite loop -- odd] */; } } /** *

* Implements a list view of a SymbolList. *

* *

* Objects of this type are instantiated by * AbstractSymbolList.asList. *

* * @author Thomas Down */ private static class ListView extends AbstractList implements Serializable { private SymbolList rl; /** * Build a new ListView to view a SymbolList as a list. * * @param symList the SymbolList to view */ ListView(SymbolList symList) { this.rl = symList; } public Object get(int pos) { return rl.symbolAt(pos + 1); } public int size() { return rl.length(); } } /** * This adapter screens all edit events to see if they overlap with a window * of interest. If they do, then a new edit event is built for the overlapping * region and pased on to all listeners. * * @author Matthew Pocock */ public class EditScreener extends ChangeForwarder { protected final int min; protected final int max; public EditScreener( Object source, ChangeSupport cs, int min, int max ) { super(source, cs); this.min = min; this.max = max; } protected ChangeEvent generateEvent(ChangeEvent ce) { ChangeType ct = ce.getType(); if(ct == SymbolList.EDIT) { Object change = ce.getChange(); if( (change != null) && (change instanceof Edit) ) { Edit edit = (Edit) change; int start = edit.pos; int end = start + edit.length - 1; // inclusive if( (start <= max) && (end >= min) ) { // they overlap return new ChangeEvent( getSource(), ct, edit, null, ce ); } } } return null; } } /** * This translates edit events that fall within a window into window co-ordinates. * * @author Matthew Pocock */ public class EditTranslater extends EditScreener { public EditTranslater( Object source, ChangeSupport cs, int min, int max ) { super(source, cs, min, max); } protected ChangeEvent generateEvent(ChangeEvent ce) { ce = super.generateEvent(ce); if(ce != null) { Edit edit = (Edit) ce.getChange(); return new ChangeEvent( ce.getSource(), ce.getType(), new Edit(edit.pos - min, edit.length, edit.replacement), null, ce.getChainedEvent() ); } return null; } } }