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