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