Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / jalview / io / MSFfile.java
1 /*
2  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3  * Copyright (C) $$Year-Rel$$ The Jalview Authors
4  * 
5  * This file is part of Jalview.
6  * 
7  * Jalview is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License 
9  * as published by the Free Software Foundation, either version 3
10  * of the License, or (at your option) any later version.
11  *  
12  * Jalview is distributed in the hope that it will be useful, but 
13  * WITHOUT ANY WARRANTY; without even the implied warranty 
14  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
15  * PURPOSE.  See the GNU General Public License for more details.
16  * 
17  * You should have received a copy of the GNU General Public License
18  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
19  * The Jalview Authors are detailed in the 'AUTHORS' file.
20  */
21 package jalview.io;
22
23 import java.util.Locale;
24
25 import jalview.datamodel.Sequence;
26 import jalview.datamodel.SequenceI;
27 import jalview.util.Comparison;
28 import jalview.util.Format;
29
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.Hashtable;
33 import java.util.List;
34 import java.util.StringTokenizer;
35
36 /**
37  * DOCUMENT ME!
38  * 
39  * @author $author$
40  * @version $Revision$
41  */
42 public class MSFfile extends AlignFile
43 {
44
45   /**
46    * Creates a new MSFfile object.
47    */
48   public MSFfile()
49   {
50   }
51
52   /**
53    * Creates a new MSFfile object.
54    * 
55    * @param inFile
56    *          DOCUMENT ME!
57    * @param type
58    *          DOCUMENT ME!
59    * 
60    * @throws IOException
61    *           DOCUMENT ME!
62    */
63   public MSFfile(String inFile, DataSourceType type) throws IOException
64   {
65     super(inFile, type);
66   }
67
68   public MSFfile(FileParse source) throws IOException
69   {
70     super(source);
71   }
72
73   /**
74    * Read and parse MSF sequence data
75    */
76   @Override
77   public void parse() throws IOException
78   {
79     boolean seqFlag = false;
80     List<String> headers = new ArrayList<String>();
81     Hashtable<String, StringBuilder> seqhash = new Hashtable<String, StringBuilder>();
82
83     try
84     {
85       String line;
86       while ((line = nextLine()) != null)
87       {
88         StringTokenizer str = new StringTokenizer(line);
89
90         String key = null;
91         while (str.hasMoreTokens())
92         {
93           String inStr = str.nextToken();
94
95           // If line has header information add to the headers vector
96           if (inStr.indexOf("Name:") != -1)
97           {
98             key = str.nextToken();
99             headers.add(key);
100           }
101
102           // if line has // set SeqFlag so we know sequences are coming
103           if (inStr.indexOf("//") != -1)
104           {
105             seqFlag = true;
106           }
107
108           // Process lines as sequence lines if seqFlag is set
109           if ((inStr.indexOf("//") == -1) && seqFlag)
110           {
111             // sequence id is the first field
112             key = inStr;
113
114             StringBuilder tempseq;
115
116             // Get sequence from hash if it exists
117             if (seqhash.containsKey(key))
118             {
119               tempseq = seqhash.get(key);
120             }
121             else
122             {
123               tempseq = new StringBuilder(64);
124               seqhash.put(key, tempseq);
125             }
126
127             // loop through the rest of the words
128             while (str.hasMoreTokens())
129             {
130               // append the word to the sequence
131               String sequenceBlock = str.nextToken();
132               tempseq.append(sequenceBlock);
133             }
134           }
135         }
136       }
137     } catch (IOException e)
138     {
139       jalview.bin.Console.errPrintln("Exception parsing MSFFile " + e);
140       e.printStackTrace();
141     }
142
143     this.noSeqs = headers.size();
144
145     // Add sequences to the hash
146     for (int i = 0; i < headers.size(); i++)
147     {
148       if (seqhash.get(headers.get(i)) != null)
149       {
150         String head = headers.get(i);
151         String seq = seqhash.get(head).toString();
152
153         if (maxLength < head.length())
154         {
155           maxLength = head.length();
156         }
157
158         /*
159          * replace ~ (leading/trailing positions) with the gap character;
160          * use '.' as this is the internal gap character required by MSF
161          */
162         seq = seq.replace('~', '.');
163
164         Sequence newSeq = parseId(head);
165
166         newSeq.setSequence(seq);
167
168         seqs.addElement(newSeq);
169       }
170       else
171       {
172         jalview.bin.Console
173                 .errPrintln("MSFFile Parser: Can't find sequence for "
174                         + headers.get(i));
175       }
176     }
177   }
178
179   /**
180    * DOCUMENT ME!
181    * 
182    * @param seq
183    *          DOCUMENT ME!
184    * 
185    * @return DOCUMENT ME!
186    */
187   public int checkSum(String seq)
188   {
189     int check = 0;
190     String sequence = seq.toUpperCase(Locale.ROOT);
191
192     for (int i = 0; i < sequence.length(); i++)
193     {
194       try
195       {
196
197         int value = sequence.charAt(i);
198         if (value != -1)
199         {
200           check += (i % 57 + 1) * value;
201         }
202       } catch (Exception e)
203       {
204         jalview.bin.Console
205                 .errPrintln("Exception during MSF Checksum calculation");
206         e.printStackTrace();
207       }
208     }
209
210     return check % 10000;
211   }
212
213   /**
214    * DOCUMENT ME!
215    * 
216    * @param s
217    *          DOCUMENT ME!
218    * @param is_NA
219    *          DOCUMENT ME!
220    * 
221    * @return DOCUMENT ME!
222    */
223   @Override
224   public String print(SequenceI[] sqs, boolean jvSuffix)
225   {
226
227     boolean is_NA = Comparison.isNucleotide(sqs);
228
229     SequenceI[] s = new SequenceI[sqs.length];
230
231     StringBuilder out = new StringBuilder(256);
232     out.append("!!").append(is_NA ? "NA" : "AA")
233             .append("_MULTIPLE_ALIGNMENT 1.0");
234     // TODO: JBPNote : Jalview doesn't remember NA or AA yet.
235     out.append(newline);
236     out.append(newline);
237     int max = 0;
238     int maxid = 0;
239     int i = 0;
240
241     while ((i < sqs.length) && (sqs[i] != null))
242     {
243       /*
244        * modify to MSF format: uses '.' for internal gaps, 
245        * and '~' for leading or trailing gaps
246        */
247       String seqString = sqs[i].getSequenceAsString().replace('-', '.');
248
249       StringBuilder sb = new StringBuilder(seqString);
250
251       for (int ii = 0; ii < sb.length(); ii++)
252       {
253         if (sb.charAt(ii) == '.')
254         {
255           sb.setCharAt(ii, '~');
256         }
257         else
258         {
259           break;
260         }
261       }
262
263       for (int ii = sb.length() - 1; ii > 0; ii--)
264       {
265         if (sb.charAt(ii) == '.')
266         {
267           sb.setCharAt(ii, '~');
268         }
269         else
270         {
271           break;
272         }
273       }
274       s[i] = new Sequence(sqs[i].getName(), sb.toString(),
275               sqs[i].getStart(), sqs[i].getEnd());
276
277       if (sb.length() > max)
278       {
279         max = sb.length();
280       }
281
282       i++;
283     }
284
285     Format maxLenpad = new Format(
286             "%" + (new String("" + max)).length() + "d");
287     Format maxChkpad = new Format(
288             "%" + (new String("1" + max)).length() + "d");
289     i = 0;
290
291     int bigChecksum = 0;
292     int[] checksums = new int[s.length];
293     while (i < s.length)
294     {
295       checksums[i] = checkSum(s[i].getSequenceAsString());
296       bigChecksum += checksums[i];
297       i++;
298     }
299
300     long maxNB = 0;
301     out.append("   MSF: " + s[0].getLength() + "   Type: "
302             + (is_NA ? "N" : "P") + "    Check:  " + (bigChecksum % 10000)
303             + "   ..");
304     out.append(newline);
305     out.append(newline);
306     out.append(newline);
307
308     String[] nameBlock = new String[s.length];
309     String[] idBlock = new String[s.length];
310
311     i = 0;
312     while ((i < s.length) && (s[i] != null))
313     {
314
315       nameBlock[i] = new String("  Name: " + printId(s[i], jvSuffix) + " ");
316
317       idBlock[i] = new String("Len: " + maxLenpad.form(s[i].getLength())
318               + "  Check: " + maxChkpad.form(checksums[i])
319               + "  Weight: 1.00" + newline);
320
321       if (s[i].getName().length() > maxid)
322       {
323         maxid = s[i].getName().length();
324       }
325
326       if (nameBlock[i].length() > maxNB)
327       {
328         maxNB = nameBlock[i].length();
329       }
330
331       i++;
332     }
333
334     if (maxid < 10)
335     {
336       maxid = 10;
337     }
338
339     if (maxNB < 15)
340     {
341       maxNB = 15;
342     }
343
344     Format nbFormat = new Format("%-" + maxNB + "s");
345
346     for (i = 0; (i < s.length) && (s[i] != null); i++)
347     {
348       out.append(nbFormat.form(nameBlock[i]) + idBlock[i]);
349     }
350
351     maxid++;
352     out.append(newline);
353     out.append(newline);
354     out.append("//");
355     out.append(newline);
356     out.append(newline);
357     int len = 50;
358
359     int nochunks = (max / len) + (max % len > 0 ? 1 : 0);
360
361     for (i = 0; i < nochunks; i++)
362     {
363       int j = 0;
364
365       while ((j < s.length) && (s[j] != null))
366       {
367         String name = printId(s[j], jvSuffix);
368
369         out.append(new Format("%-" + maxid + "s").form(name + " "));
370
371         for (int k = 0; k < 5; k++)
372         {
373           int start = (i * 50) + (k * 10);
374           int end = start + 10;
375
376           int length = s[j].getLength();
377           if ((end < length) && (start < length))
378           {
379             out.append(s[j].getSequence(start, end));
380
381             if (k < 4)
382             {
383               out.append(" ");
384             }
385             else
386             {
387               out.append(newline);
388             }
389           }
390           else
391           {
392             if (start < length)
393             {
394               out.append(s[j].getSequenceAsString().substring(start));
395               out.append(newline);
396             }
397             else
398             {
399               if (k == 0)
400               {
401                 out.append(newline);
402               }
403             }
404           }
405         }
406
407         j++;
408       }
409
410       out.append(newline);
411     }
412
413     return out.toString();
414   }
415 }