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}