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 * Created on DATE
021 *
022 */
023
024package org.biojava.nbio.core.sequence;
025
026import org.biojava.nbio.core.sequence.io.util.IOUtils;
027import org.biojava.nbio.core.sequence.template.Compound;
028import org.biojava.nbio.core.sequence.template.CompoundSet;
029import org.biojava.nbio.core.sequence.template.LightweightProfile;
030import org.biojava.nbio.core.sequence.template.Sequence;
031
032import java.util.ArrayList;
033import java.util.Collections;
034import java.util.List;
035
036/**
037 * Implements a minimal data structure for reading and writing a sequence alignment.  The full {@code Profile} data
038 * structure in the alignment module provides additional functionality.
039 *
040 * @author Scooter Willis
041 * @author Mark Chapman
042 */
043public class MultipleSequenceAlignment<S extends Sequence<C>, C extends Compound> implements LightweightProfile<S, C> {
044
045        private List<S> sequences = new ArrayList<S>();
046        private Integer length = null;
047
048        /**
049         * A sequence that has been aligned to other sequences will have inserts.
050         * @param sequence
051         */
052        public void addAlignedSequence(S sequence){
053                if(length == null){
054                        length = sequence.getLength();
055                }
056                if(sequence.getLength() != length){
057                        throw new IllegalArgumentException(sequence.getAccession() + " length = " + sequence.getLength() +
058                                        " not equal to MSA length = " + length);
059                }
060                sequences.add(sequence);
061        }
062
063        /**
064         * Remove a sequence
065         * @param sequence
066         * @return flag
067         */
068        public boolean removeAlignedSequence(S sequence){
069                return sequences.remove(sequence);
070        }
071//methods for LightweightProfile
072
073        /**
074         * Uses bioIndex starting at 1 instead of 0
075         * @param listIndex
076         * @return sequence
077         */
078
079
080        @Override
081        public S getAlignedSequence(int listIndex) {
082                return sequences.get(listIndex - 1);
083        }
084
085        /**
086         * Get the list of sequences
087         * @return list of sequences
088         */
089        @Override
090        public List<S> getAlignedSequences() {
091                return Collections.unmodifiableList(sequences);
092        }
093
094        /**
095         * Get a list of compounds at a sequence position
096         * @param alignmentIndex
097         * @return compounds
098         */
099        @Override
100        public List<C> getCompoundsAt(int alignmentIndex) {
101                List<C> column = new ArrayList<C>();
102                for (S s : sequences) {
103                        column.add(s.getCompoundAt(alignmentIndex));
104                }
105                return Collections.unmodifiableList(column);
106        }
107
108        /**
109         * Get the Compounds defined in the first sequence
110         * @return get compound set
111         */
112        @Override
113        public CompoundSet<C> getCompoundSet() {
114                return sequences.get(0).getCompoundSet();
115        }
116
117        /**
118         * Get the length of the MSA where it is assumed that
119         * all sequence position
120         * @return length of MSA
121         */
122        @Override
123        public int getLength() {
124                return length;
125        }
126
127        /**
128         * Get the number of sequences in the MSA
129         * @return nr of sequences
130         */
131        @Override
132        public int getSize() {
133                return sequences.size();
134        }
135
136        /**
137         * Get a string representation of the MSA with a fixed width
138         * @param width
139         * @return String
140         */
141        @Override
142        public String toString(int width) {
143                return toString(width, null, IOUtils.getIDFormat(sequences), true, true, true, false);
144        }
145
146        /**
147         * Support for different MSA formats
148         * @param format
149         * @return String in one of the supported file formats.
150         */
151        @Override
152        public String toString(StringFormat format) {
153                switch (format) {
154                case ALN:
155                case CLUSTALW:
156                default:
157                        return toString(60, String.format("CLUSTAL W MSA from BioJava%n%n"), IOUtils.getIDFormat(sequences) +
158                                        "   ", true, false, true, false);
159                case FASTA:
160                        return toString(60, null, ">%s%n", false, false, false, false);
161                case GCG:
162                case MSF:
163                        return toString(50, IOUtils.getGCGHeader(sequences), IOUtils.getIDFormat(sequences), true, false, false,
164                                        false);
165                case PDBWEB:
166                        return toString(60, null, "%s", true, false, true, true);
167                }
168        }
169
170        /**
171         * String representation of the MSA
172         * @return String
173         */
174
175        @Override
176        public String toString() {
177                return toString(getLength(), null, null, false, false, false, false);
178        }
179
180        // helper methods
181
182        /**
183         * Helper method that does all the formating work
184         * @param width
185         * @param header
186         * @param idFormat
187         * @param interlaced
188         * @param aligIndices
189         * @param aligConservation
190         * @param webDisplay
191         * @return String
192         */
193        // creates formatted String
194        private String toString(int width, String header, String idFormat, boolean interlaced, boolean aligIndices,
195                        boolean aligConservation, boolean webDisplay) {
196
197                // TODO handle circular alignments
198                StringBuilder s = (header == null) ? new StringBuilder() : new StringBuilder(header);
199
200                if (webDisplay && sequences.size() == 2) {
201                        s.append("<div><pre>");
202                }
203
204                width = Math.max(1, width);
205                if (interlaced) {
206                        String aligIndFormat = "%-" + Math.max(1, width / 2) + "d %" + Math.max(1, width - (width / 2) - 1) +
207                                        "d%n";
208                        for (int i = 0; i < getLength(); i += width) {
209                                int start = i + 1, end = Math.min(getLength(), i + width);
210                                if (i > 0) {
211                                        s.append(String.format("%n"));
212                                }
213                                if (aligIndices) {
214                                        if (end < i + width) {
215                                                int line = end - start + 1;
216                                                aligIndFormat = "%-" + Math.max(1, line / 2) + "d %" + Math.max(1, line - (line / 2) - 1) +
217                                                                "d%n";
218                                        }
219                                        if (idFormat != null) {
220                                                s.append(String.format(idFormat, ""));
221                                        }
222                                        s.append(String.format(aligIndFormat, start, end));
223                                }
224                                int counter = 0;
225                                for (S as : sequences) {
226                                        counter++;
227                                        if (webDisplay && sequences.size() == 2) {
228                                                printSequenceAlignmentWeb(s, counter, idFormat, start, end);
229                                        } else {
230                                                if (idFormat != null) {
231                                                        s.append(String.format(idFormat, as.getAccession()));
232                                                }
233                                                s.append(as.getSubSequence(start, end).getSequenceAsString());
234                                                s.append(String.format("%n"));
235                                        }
236                                        if (aligConservation && sequences.size() == 2 && counter == 1) {
237                                                printConservation(s, idFormat, start, end, webDisplay);
238                                        }
239                                }
240                        }
241                } else {
242                        for (S as : sequences) {
243                                if (idFormat != null) {
244                                        s.append(String.format(idFormat, as.getAccession()));
245                                }
246                                for (int i = 0; i < getLength(); i += width) {
247                                        int start = i + 1, end = Math.min(getLength(), i + width);
248                                        s.append(as.getSubSequence(start, end).getSequenceAsString());
249                                        s.append(String.format("%n"));
250                                }
251                        }
252                }
253
254                if (webDisplay && aligConservation && sequences.size() == 2) {
255                        s.append(IOUtils.getPDBLegend());
256                }
257                return s.toString();
258        }
259
260        /**
261         *
262         * @param s
263         * @param counter
264         * @param idFormat
265         * @param start
266         * @param end
267         */
268        private void printSequenceAlignmentWeb(StringBuilder s, int counter, String idFormat, int start, int end) {
269                S as = sequences.get(counter - 1), seq1 = sequences.get(0), seq2 = sequences.get(1);
270
271                if (idFormat != null) {
272                        s.append(String.format(idFormat, as.getAccession()));
273                }
274
275                String mySeq = as.getSubSequence(start, end).getSequenceAsString();
276                String s1 = seq1.getSubSequence(start, end).getSequenceAsString();
277                String s2 = seq2.getSubSequence(start, end).getSequenceAsString();
278                CompoundSet<C> cs = getCompoundSet();
279
280                for (int i = 0; i < s1.length(); i++) {
281                        if (i >= s2.length() || i >= mySeq.length())
282                                break;
283                        char c1 = s1.charAt(i);
284                        char c2 = s2.charAt(i);
285                        char c = mySeq.charAt(i);
286                        s.append(IOUtils.getPDBCharacter(true, c1, c2, cs.compoundsEquivalent(seq1.getCompoundAt(i),
287                                        seq2.getCompoundAt(i)), c));
288                }
289
290                s.append(String.format("%n"));
291        }
292
293        /**
294         *
295         * @param s
296         * @param idFormat
297         * @param start
298         * @param end
299         * @param webDisplay
300         */
301        private void printConservation(StringBuilder s, String idFormat, int start, int end, boolean webDisplay) {
302                S seq1 = sequences.get(0), seq2 = sequences.get(1);
303
304                if (idFormat != null) {
305                        AccessionID ac1 = sequences.get(0).getAccession();
306                        String id1 = (ac1 == null) ? "null" : ac1.getID();
307                        id1 = id1.replaceAll(".", " ");
308                        s.append(String.format(idFormat, id1));
309                }
310
311                String s1 = seq1.getSubSequence(start, end).getSequenceAsString();
312                String s2 = seq2.getSubSequence(start, end).getSequenceAsString();
313                CompoundSet<C> cs = getCompoundSet();
314
315                for (int i = 0; i < s1.length(); i++) {
316                        if (i >= s2.length())
317                                break;
318                        char c1 = s1.charAt(i);
319                        char c2 = s2.charAt(i);
320                        s.append(IOUtils.getPDBConservation(webDisplay, c1, c2, cs.compoundsEquivalent(seq1.getCompoundAt(i),
321                                        seq2.getCompoundAt(i))));
322                }
323
324                s.append(String.format("%n"));
325        }
326
327}