JAL-3438 spotless for 2.11.2.0
[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       System.err.println("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         System.err.println("MSFFile Parser: Can't find sequence for "
173                 + headers.get(i));
174       }
175     }
176   }
177
178   /**
179    * DOCUMENT ME!
180    * 
181    * @param seq
182    *          DOCUMENT ME!
183    * 
184    * @return DOCUMENT ME!
185    */
186   public int checkSum(String seq)
187   {
188     int check = 0;
189     String sequence = seq.toUpperCase(Locale.ROOT);
190
191     for (int i = 0; i < sequence.length(); i++)
192     {
193       try
194       {
195
196         int value = sequence.charAt(i);
197         if (value != -1)
198         {
199           check += (i % 57 + 1) * value;
200         }
201       } catch (Exception e)
202       {
203         System.err.println("Exception during MSF Checksum calculation");
204         e.printStackTrace();
205       }
206     }
207
208     return check % 10000;
209   }
210
211   /**
212    * DOCUMENT ME!
213    * 
214    * @param s
215    *          DOCUMENT ME!
216    * @param is_NA
217    *          DOCUMENT ME!
218    * 
219    * @return DOCUMENT ME!
220    */
221   @Override
222   public String print(SequenceI[] sqs, boolean jvSuffix)
223   {
224
225     boolean is_NA = Comparison.isNucleotide(sqs);
226
227     SequenceI[] s = new SequenceI[sqs.length];
228
229     StringBuilder out = new StringBuilder(256);
230     out.append("!!").append(is_NA ? "NA" : "AA")
231             .append("_MULTIPLE_ALIGNMENT 1.0");
232     // TODO: JBPNote : Jalview doesn't remember NA or AA yet.
233     out.append(newline);
234     out.append(newline);
235     int max = 0;
236     int maxid = 0;
237     int i = 0;
238
239     while ((i < sqs.length) && (sqs[i] != null))
240     {
241       /*
242        * modify to MSF format: uses '.' for internal gaps, 
243        * and '~' for leading or trailing gaps
244        */
245       String seqString = sqs[i].getSequenceAsString().replace('-', '.');
246
247       StringBuilder sb = new StringBuilder(seqString);
248
249       for (int ii = 0; ii < sb.length(); ii++)
250       {
251         if (sb.charAt(ii) == '.')
252         {
253           sb.setCharAt(ii, '~');
254         }
255         else
256         {
257           break;
258         }
259       }
260
261       for (int ii = sb.length() - 1; ii > 0; ii--)
262       {
263         if (sb.charAt(ii) == '.')
264         {
265           sb.setCharAt(ii, '~');
266         }
267         else
268         {
269           break;
270         }
271       }
272       s[i] = new Sequence(sqs[i].getName(), sb.toString(),
273               sqs[i].getStart(), sqs[i].getEnd());
274
275       if (sb.length() > max)
276       {
277         max = sb.length();
278       }
279
280       i++;
281     }
282
283     Format maxLenpad = new Format(
284             "%" + (new String("" + max)).length() + "d");
285     Format maxChkpad = new Format(
286             "%" + (new String("1" + max)).length() + "d");
287     i = 0;
288
289     int bigChecksum = 0;
290     int[] checksums = new int[s.length];
291     while (i < s.length)
292     {
293       checksums[i] = checkSum(s[i].getSequenceAsString());
294       bigChecksum += checksums[i];
295       i++;
296     }
297
298     long maxNB = 0;
299     out.append("   MSF: " + s[0].getLength() + "   Type: "
300             + (is_NA ? "N" : "P") + "    Check:  " + (bigChecksum % 10000)
301             + "   ..");
302     out.append(newline);
303     out.append(newline);
304     out.append(newline);
305
306     String[] nameBlock = new String[s.length];
307     String[] idBlock = new String[s.length];
308
309     i = 0;
310     while ((i < s.length) && (s[i] != null))
311     {
312
313       nameBlock[i] = new String("  Name: " + printId(s[i], jvSuffix) + " ");
314
315       idBlock[i] = new String("Len: " + maxLenpad.form(s[i].getLength())
316               + "  Check: " + maxChkpad.form(checksums[i])
317               + "  Weight: 1.00" + newline);
318
319       if (s[i].getName().length() > maxid)
320       {
321         maxid = s[i].getName().length();
322       }
323
324       if (nameBlock[i].length() > maxNB)
325       {
326         maxNB = nameBlock[i].length();
327       }
328
329       i++;
330     }
331
332     if (maxid < 10)
333     {
334       maxid = 10;
335     }
336
337     if (maxNB < 15)
338     {
339       maxNB = 15;
340     }
341
342     Format nbFormat = new Format("%-" + maxNB + "s");
343
344     for (i = 0; (i < s.length) && (s[i] != null); i++)
345     {
346       out.append(nbFormat.form(nameBlock[i]) + idBlock[i]);
347     }
348
349     maxid++;
350     out.append(newline);
351     out.append(newline);
352     out.append("//");
353     out.append(newline);
354     out.append(newline);
355     int len = 50;
356
357     int nochunks = (max / len) + (max % len > 0 ? 1 : 0);
358
359     for (i = 0; i < nochunks; i++)
360     {
361       int j = 0;
362
363       while ((j < s.length) && (s[j] != null))
364       {
365         String name = printId(s[j], jvSuffix);
366
367         out.append(new Format("%-" + maxid + "s").form(name + " "));
368
369         for (int k = 0; k < 5; k++)
370         {
371           int start = (i * 50) + (k * 10);
372           int end = start + 10;
373
374           int length = s[j].getLength();
375           if ((end < length) && (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 }