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 */
021package org.biojava.nbio.core.sequence.template;
022
023import org.biojava.nbio.core.sequence.compound.NucleotideCompound;
024import org.biojava.nbio.core.sequence.storage.ArrayListSequenceReader;
025import org.biojava.nbio.core.sequence.views.ComplementSequenceView;
026import org.biojava.nbio.core.sequence.views.ReversedSequenceView;
027import org.biojava.nbio.core.sequence.views.WindowedSequence;
028import org.biojava.nbio.core.util.CRC64Checksum;
029
030import java.io.IOException;
031import java.util.*;
032
033/**
034 * Provides a set of static methods to be used as static imports when needed
035 * across multiple Sequence implementations but inheritance gets in the way.
036 *
037 * It also provides a place to put utility methods whose application can
038 * be to a single class of Sequence e.g. {@link NucleotideCompound}
039 * {@link Sequence}; or to any Sequence e.g. looking for the
040 * {@link #getComposition(Sequence)} or {@link #getDistribution(Sequence)}
041 * for any type of Sequence.
042 *
043 * All of these methods assume that you can use the {@link Iterable} interface
044 * offered by the implementations of {@link Sequence} to provide all the
045 * compounds that implementation allows you to see. Since sequence should know
046 * nothing about its backing stores (apart from calling out to it) this should
047 * be true.
048 *
049 * @author ayates
050 */
051public class SequenceMixin {
052
053        /**
054         * For the given vargs of compounds this method counts the number of
055         * times those compounds appear in the given sequence
056         *
057         * @param sequence The {@link Sequence} to perform the count on
058         * @param compounds The compounds to look for
059         * @param <C> The type of compound we are looking for
060         * @return The number of times the given compounds appear in this Sequence
061         */
062        public static <C extends Compound> int countCompounds(
063                        Sequence<C> sequence, C... compounds) {
064                int count = 0;
065                Map<C, Integer> compositon = getComposition(sequence);
066                for (C compound : compounds) {
067                        if(compositon.containsKey(compound)) {
068                                count = compositon.get(compound) + count;
069                        }
070                }
071                return count;
072        }
073
074        /**
075         * Returns the count of GC in the given sequence
076         *
077         * @param sequence The {@link NucleotideCompound} {@link Sequence} to perform
078         * the GC analysis on
079         * @return The number of GC compounds in the sequence
080         */
081        public static int countGC(Sequence<NucleotideCompound> sequence) {
082                CompoundSet<NucleotideCompound> cs = sequence.getCompoundSet();
083                NucleotideCompound G = cs.getCompoundForString("G");
084                NucleotideCompound C = cs.getCompoundForString("C");
085                NucleotideCompound g = cs.getCompoundForString("g");
086                NucleotideCompound c = cs.getCompoundForString("c");
087                return countCompounds(sequence, G, C, g, c);
088        }
089
090        /**
091         * Returns the count of AT in the given sequence
092         *
093         * @param sequence The {@link NucleotideCompound} {@link Sequence} to perform
094         * the AT analysis on
095         * @return The number of AT compounds in the sequence
096         */
097        public static int countAT(Sequence<NucleotideCompound> sequence) {
098                CompoundSet<NucleotideCompound> cs = sequence.getCompoundSet();
099                NucleotideCompound A = cs.getCompoundForString("A");
100                NucleotideCompound T = cs.getCompoundForString("T");
101                NucleotideCompound a = cs.getCompoundForString("a");
102                NucleotideCompound t = cs.getCompoundForString("t");
103                return countCompounds(sequence, A, T, a, t);
104        }
105
106        /**
107         * Analogous to {@link #getComposition(Sequence)} but returns the
108         * distribution of that {@link Compound} over the given sequence.
109         *
110         * @param <C> The type of compound to look for
111         * @param sequence The type of sequence to look over
112         * @return Returns the decimal fraction of the compounds in the given
113         * sequence. Any compound not in the Map will return a fraction of 0.
114         */
115        public static <C extends Compound> Map<C, Double> getDistribution(Sequence<C> sequence) {
116                Map<C, Double> results = new HashMap<C, Double>();
117                Map<C, Integer> composition = getComposition(sequence);
118                double length = sequence.getLength();
119                for (Map.Entry<C, Integer> entry : composition.entrySet()) {
120                        double dist = entry.getValue().doubleValue() / length;
121                        results.put(entry.getKey(), dist);
122                }
123                return results;
124        }
125
126        /**
127         * Does a linear scan over the given Sequence and records the number of
128         * times each base appears. The returned map will return 0 if a compound
129         * is asked for and the Map has no record of it.
130         *
131         * @param <C> The type of compound to look for
132         * @param sequence The type of sequence to look over
133         * @return Counts for the instances of all compounds in the sequence
134         */
135        public static <C extends Compound> Map<C, Integer> getComposition(Sequence<C> sequence) {
136                Map<C, Integer> results = new HashMap<C, Integer>();
137
138                for (C currentCompound : sequence) {
139                        Integer currentInteger = results.get(currentCompound);
140                        if ( currentInteger == null)
141                                currentInteger = 0;
142                        currentInteger++;
143                        results.put(currentCompound, currentInteger);
144                }
145                return results;
146        }
147
148        /**
149         * Used as a way of sending a Sequence to a writer without the cost of
150         * converting to a full length String and then writing the data out
151         *
152         * @param <C> Type of compound
153         * @param writer The writer to send data to
154         * @param sequence The sequence to write out
155         * @throws IOException Thrown if we encounter a problem
156         */
157        public static <C extends Compound> void write(Appendable appendable, Sequence<C> sequence) throws IOException {
158                for(C compound: sequence) {
159                        appendable.append(compound.toString());
160                }
161        }
162
163        /**
164         * For the given Sequence this will return a {@link StringBuilder} object
165         * filled with the results of {@link Compound#toString()}. Does not
166         * used {@link #write(java.lang.Appendable, org.biojava.nbio.core.sequence.template.Sequence) }
167         * because of its {@link IOException} signature.
168         */
169        public static <C extends Compound> StringBuilder toStringBuilder(Sequence<C> sequence) {
170                StringBuilder sb = new StringBuilder(sequence.getLength());
171                for (C compound : sequence) {
172                        sb.append(compound.toString());
173                }
174                return sb;
175        }
176
177        /**
178         * Shortcut to {@link #toStringBuilder(org.biojava.nbio.core.sequence.template.Sequence)}
179         * which calls toString() on the resulting object.
180         */
181        public static <C extends Compound> String toString(Sequence<C> sequence) {
182                return toStringBuilder(sequence).toString();
183        }
184
185        /**
186         * For the given {@link Sequence} this will return a {@link List} filled with
187         * the Compounds of that {@link Sequence}.
188         */
189        public static <C extends Compound> List<C> toList(Sequence<C> sequence) {
190                List<C> list = new ArrayList<C>(sequence.getLength());
191                for (C compound : sequence) {
192                        list.add(compound);
193                }
194                return list;
195        }
196
197        /**
198         * Performs a linear search of the given Sequence for the given compound.
199         * Once we find the compound we return the position.
200         */
201        public static <C extends Compound> int indexOf(Sequence<C> sequence,
202                        C compound) {
203                int index = 1;
204                for (C currentCompound : sequence) {
205                        if (currentCompound.equals(compound)) {
206                                return index;
207                        }
208                        index++;
209                }
210                return 0;
211        }
212
213        /**
214         * Performs a reversed linear search of the given Sequence by wrapping
215         * it in a {@link ReversedSequenceView} and passing it into
216         * {@link #indexOf(Sequence, Compound)}. We then inverse the index coming
217         * out of it.
218         */
219        public static <C extends Compound> int lastIndexOf(Sequence<C> sequence,
220                        C compound) {
221                int index = indexOf(new ReversedSequenceView<C>(sequence), compound);
222                return (sequence.getLength() - index)+1;
223        }
224
225        /**
226         * Creates a simple sequence iterator which moves through a sequence going
227         * from 1 to the length of the Sequence. Modification of the Sequence is not
228         * allowed.
229         */
230        public static <C extends Compound> Iterator<C> createIterator(
231                        Sequence<C> sequence) {
232                return new SequenceIterator<C>(sequence);
233        }
234
235        /**
236         * Creates a simple sub sequence view delimited by the given start and end.
237         */
238        public static <C extends Compound> SequenceView<C> createSubSequence(
239                        Sequence<C> sequence, int start, int end) {
240                return new SequenceProxyView<C>(sequence, start, end);
241        }
242
243        /**
244         * Implements sequence shuffling by first materializing the given
245         * {@link Sequence} into a {@link List}, applying
246         * {@link Collections#shuffle(List)} and then returning the shuffled
247         * elements in a new instance of {@link SequenceBackingStore} which behaves
248         * as a {@link Sequence}.
249         */
250        public static <C extends Compound> Sequence<C> shuffle(Sequence<C> sequence) {
251                List<C> compounds = sequence.getAsList();
252                Collections.shuffle(compounds);
253                return new ArrayListSequenceReader<C>(compounds,
254                                sequence.getCompoundSet());
255        }
256
257        /**
258         * Performs a simple CRC64 checksum on any given sequence.
259         */
260        public static <C extends Compound> String checksum(Sequence<C> sequence) {
261                CRC64Checksum checksum = new CRC64Checksum();
262                for (C compound : sequence) {
263                        checksum.update(compound.getShortName());
264                }
265                return checksum.toString();
266        }
267
268        /**
269         * Produces kmers of the specified size e.g. ATGTGA returns two views which
270         * have ATG TGA
271         *
272         * @param <C> Compound to use
273         * @param sequence Sequence to build from
274         * @param kmer Kmer size
275         * @return The list of non-overlapping K-mers
276         */
277        public static <C extends Compound> List<SequenceView<C>> nonOverlappingKmers(Sequence<C> sequence, int kmer) {
278                List<SequenceView<C>> l = new ArrayList<SequenceView<C>>();
279                WindowedSequence<C> w = new WindowedSequence<C>(sequence, kmer);
280                for(SequenceView<C> view: w) {
281                        l.add(view);
282                }
283                return l;
284        }
285
286        /**
287         * Used to generate overlapping k-mers such i.e. ATGTA will give rise to
288         * ATG, TGT & GTA
289         *
290         * @param <C> Compound to use
291         * @param sequence Sequence to build from
292         * @param kmer Kmer size
293         * @return The list of overlapping K-mers
294         */
295        public static <C extends Compound> List<SequenceView<C>> overlappingKmers(Sequence<C> sequence, int kmer) {
296                List<SequenceView<C>> l = new ArrayList<SequenceView<C>>();
297                List<Iterator<SequenceView<C>>> windows
298                                = new ArrayList<Iterator<SequenceView<C>>>();
299
300                for(int i=1; i<=kmer; i++) {
301                        if(i == 1) {
302                                windows.add(new WindowedSequence<C>(sequence, kmer).iterator());
303                        }
304                        else {
305                                SequenceView<C> sv = sequence.getSubSequence(i, sequence.getLength());
306                                windows.add(new WindowedSequence<C>(sv, kmer).iterator());
307                        }
308                }
309
310                OUTER: while(true) {
311                        for(int i=0; i<kmer; i++) {
312                                Iterator<SequenceView<C>> iterator = windows.get(i);
313                                boolean breakLoop=true;
314                                if(iterator.hasNext()) {
315                                        l.add(iterator.next());
316                                        breakLoop = false;
317                                }
318                                if(breakLoop) {
319                                        break OUTER;
320                                }
321                        }
322                }
323                return l;
324        }
325
326        /**
327         * A method which attempts to do the right thing when is comes to a
328         * reverse/reverse complement
329         *
330         * @param <C> The type of compound
331         * @param sequence The input sequence
332         * @return The inverted sequence which is optionally complemented
333         */
334        @SuppressWarnings({ "unchecked" })
335        public static <C extends Compound> SequenceView<C> inverse(Sequence<C> sequence) {
336                SequenceView<C> reverse = new ReversedSequenceView<C>(sequence);
337                if(sequence.getCompoundSet().isComplementable()) {
338                        return new ComplementSequenceView(reverse);
339                }
340                return reverse;
341        }
342
343        /**
344         * A case-insensitive manner of comparing two sequence objects together.
345         * We will throw out any compounds which fail to match on their sequence
346         * length & compound sets used. The code will also bail out the moment
347         * we find something is wrong with a Sequence. Cost to run is linear to
348         * the length of the Sequence.
349         *
350         * @param <C> The type of compound
351         * @param source Source sequence to assess
352         * @param target Target sequence to assess
353         * @return Boolean indicating if the sequences matched ignoring case
354         */
355        public static <C extends Compound> boolean sequenceEqualityIgnoreCase(Sequence<C> source, Sequence<C> target) {
356                return baseSequenceEquality(source, target, true);
357        }
358
359        /**
360         * A case-sensitive manner of comparing two sequence objects together.
361         * We will throw out any compounds which fail to match on their sequence
362         * length & compound sets used. The code will also bail out the moment
363         * we find something is wrong with a Sequence. Cost to run is linear to
364         * the length of the Sequence.
365         *
366         * @param <C> The type of compound
367         * @param source Source sequence to assess
368         * @param target Target sequence to assess
369         * @return Boolean indicating if the sequences matched
370         */
371        public static <C extends Compound> boolean sequenceEquality(Sequence<C> source, Sequence<C> target) {
372                return baseSequenceEquality(source, target, false);
373        }
374
375        private static <C extends Compound> boolean baseSequenceEquality(Sequence<C> source, Sequence<C> target, boolean ignoreCase) {
376                boolean equal = true;
377                if(
378                                source.getLength() == target.getLength() &&
379                                source.getCompoundSet().equals(target.getCompoundSet())) {
380                        Iterator<C> sIter = source.iterator();
381                        Iterator<C> tIter = target.iterator();
382                        while(sIter.hasNext()) {
383                                C s = sIter.next();
384                                C t = tIter.next();
385                                boolean cEqual = (ignoreCase) ? s.equalsIgnoreCase(t) : s.equals(t);
386                                if(!cEqual) {
387                                        equal = false;
388                                        break;
389                                }
390                        }
391                }
392                else {
393                        equal = false;
394                }
395                return equal;
396        }
397
398        /**
399         * A basic sequence iterator which iterates over the given Sequence by
400         * biological index. This assumes your sequence supports random access
401         * and performs well when doing these operations.
402         *
403         * @author ayates
404         *
405         * @param <C> Type of compound to return
406         */
407        public static class SequenceIterator<C extends Compound>
408                        implements Iterator<C> {
409
410                private final Sequence<C> sequence;
411                private final int length;
412                private int currentPosition = 0;
413
414                public SequenceIterator(Sequence<C> sequence) {
415                        this.sequence = sequence;
416                        this.length = sequence.getLength();
417                }
418
419
420                @Override
421                public boolean hasNext() {
422                        return (currentPosition < length);
423                }
424
425
426                @Override
427                public C next() {
428                        if(!hasNext()) {
429                                throw new NoSuchElementException("Exhausted sequence of elements");
430                        }
431                        return sequence.getCompoundAt(++currentPosition);
432                }
433
434                @Override
435                public void remove() {
436                        throw new UnsupportedOperationException("Cannot remove() on a SequenceIterator");
437                }
438        }
439}