JAL-1236 TODO: fix up parser to cope with the currently defined custom header......
[jalview.git] / src / jalview / io / ClustalFile.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.AlignmentAnnotation;
24 import jalview.datamodel.Sequence;
25 import jalview.datamodel.SequenceI;
26 import jalview.util.Format;
27
28 import java.io.IOException;
29 import java.util.HashMap;
30 import java.util.Map;
31 import java.util.StringTokenizer;
32 import java.util.Vector;
33
34 public class ClustalFile extends AlignFile
35 {
36
37   public ClustalFile()
38   {
39     _initHeader();
40   }
41
42   public ClustalFile(String inFile, DataSourceType sourceType)
43           throws IOException
44   {
45     super(inFile, sourceType);
46     _initHeader();
47   }
48
49   public ClustalFile(FileParse source) throws IOException
50   {
51     super(source);
52     _initHeader();
53   }
54
55   private void _initHeader()
56   {
57     try {
58         clustalHeader = jalview.bin.Cache.getDefault("CLUSTAL_HEADER","CLUSTAL");
59     } catch (Error e) {};
60
61   }
62
63   @Override
64   public void initData()
65   {
66     super.initData();
67   }
68
69   @Override
70   public void parse() throws IOException
71   {
72     int i = 0;
73     boolean flag = false;
74     boolean top = false;
75     StringBuffer pssecstr = new StringBuffer();
76     StringBuffer consstr = new StringBuffer();
77     Vector<String> headers = new Vector<>();
78     Map<String, StringBuffer> seqhash = new HashMap<>();
79     StringBuffer tempseq;
80     String line, id;
81     StringTokenizer str;
82
83     try
84     {
85       while ((line = nextLine()) != null)
86       {
87         if (line.length() == 0)
88         {
89           top = true;
90         }
91         boolean isConservation = line.startsWith(SPACE)
92                 || line.startsWith(TAB);
93         if (!isConservation)
94         {
95           str = new StringTokenizer(line);
96
97           if (str.hasMoreTokens())
98           {
99             id = str.nextToken();
100             // TODO: JAL-1236 other tokens may be indicative of a header for Clustal format
101             if (id.equalsIgnoreCase("CLUSTAL"))
102             {
103               flag = true;
104             }
105             else
106             {
107               if (flag)
108               {
109                 if (seqhash.containsKey(id))
110                 {
111                   tempseq = seqhash.get(id);
112                 }
113                 else
114                 {
115                   tempseq = new StringBuffer();
116                   seqhash.put(id, tempseq);
117                 }
118
119                 if (!(headers.contains(id)))
120                 {
121                   headers.addElement(id);
122                 }
123
124                 if (str.hasMoreTokens())
125                 {
126                   tempseq.append(str.nextToken());
127                 }
128                 top = false;
129               }
130             }
131           }
132           else
133           {
134             flag = true;
135           }
136         }
137         else
138         {
139           if (line.matches("\\s+(-|\\.|\\(|\\[|\\]|\\))+"))
140           {
141             if (top)
142             {
143               pssecstr.append(line.trim());
144             }
145             else
146             {
147               consstr.append(line.trim());
148             }
149           }
150         }
151       }
152     } catch (IOException e)
153     {
154       System.err.println("Exception parsing clustal file " + e);
155       e.printStackTrace();
156     }
157
158     if (flag)
159     {
160       this.noSeqs = headers.size();
161
162       // Add sequences to the hash
163       for (i = 0; i < headers.size(); i++)
164       {
165         if (seqhash.get(headers.elementAt(i)) != null)
166         {
167           if (maxLength < seqhash.get(headers.elementAt(i)).toString()
168                   .length())
169           {
170             maxLength = seqhash.get(headers.elementAt(i)).toString()
171                     .length();
172           }
173
174           Sequence newSeq = parseId(headers.elementAt(i).toString());
175           newSeq.setSequence(
176                   seqhash.get(headers.elementAt(i).toString()).toString());
177
178           seqs.addElement(newSeq);
179         }
180         else
181         {
182           System.err.println("Clustal File Reader: Can't find sequence for "
183                   + headers.elementAt(i));
184         }
185       }
186       AlignmentAnnotation lastssa = null;
187       if (pssecstr.length() == maxLength)
188       {
189         Vector<AlignmentAnnotation> ss = new Vector<>();
190         AlignmentAnnotation ssa = lastssa = StockholmFile
191                 .parseAnnotationRow(ss, "secondary structure",
192                         pssecstr.toString());
193         ssa.label = "Secondary Structure";
194         annotations.addElement(ssa);
195       }
196       if (consstr.length() == maxLength)
197       {
198         Vector<AlignmentAnnotation> ss = new Vector<>();
199         AlignmentAnnotation ssa = StockholmFile.parseAnnotationRow(ss,
200                 "secondary structure", consstr.toString());
201         ssa.label = "Consensus Secondary Structure";
202         if (lastssa == null || !lastssa.getRNAStruc()
203                 .equals(ssa.getRNAStruc().replace('-', '.')))
204         {
205           annotations.addElement(ssa);
206         }
207       }
208     }
209   }
210   /**
211    * clustal header - customise if needed
212    */
213   public String clustalHeader = "CLUSTAL";
214   
215   @Override
216   public String print(SequenceI[] s, boolean jvsuffix)
217   {
218     StringBuffer out = new StringBuffer(clustalHeader + newline + newline);
219
220     int max = 0;
221     int maxid = 0;
222
223     int i = 0;
224
225     while ((i < s.length) && (s[i] != null))
226     {
227       String tmp = printId(s[i], jvsuffix);
228
229       max = Math.max(max, s[i].getLength());
230
231       if (tmp.length() > maxid)
232       {
233         maxid = tmp.length();
234       }
235
236       i++;
237     }
238
239     if (maxid < 15)
240     {
241       maxid = 15;
242     }
243
244     maxid++;
245
246     int len = 60;
247     int nochunks = (max / len) + (max % len > 0 ? 1 : 0);
248
249     for (i = 0; i < nochunks; i++)
250     {
251       int j = 0;
252
253       while ((j < s.length) && (s[j] != null))
254       {
255         out.append(new Format("%-" + maxid + "s")
256                 .form(printId(s[j], jvsuffix) + " "));
257
258         int chunkStart = i * len;
259         int chunkEnd = chunkStart + len;
260
261         int length = s[j].getLength();
262         if ((chunkEnd < length) && (chunkStart < length))
263         {
264           out.append(s[j].getSequenceAsString(chunkStart, chunkEnd));
265         }
266         else
267         {
268           if (chunkStart < length)
269           {
270             out.append(s[j].getSequenceAsString().substring(chunkStart));
271           }
272         }
273
274         out.append(newline);
275         j++;
276       }
277
278       out.append(newline);
279     }
280
281     return out.toString();
282   }
283 }