JAL-2344 separated AlignmentFileI into AlignmentFileReaderI,
[jalview.git] / src / jalview / io / AlignFile.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.AlignmentI;
25 import jalview.datamodel.Sequence;
26 import jalview.datamodel.SequenceGroup;
27 import jalview.datamodel.SequenceI;
28 import jalview.util.MessageManager;
29
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.Enumeration;
33 import java.util.Hashtable;
34 import java.util.List;
35 import java.util.Vector;
36
37 /**
38  * DOCUMENT ME!
39  * 
40  * @author $author$
41  * @version $Revision$
42  */
43 public abstract class AlignFile extends FileParse implements
44         AlignmentFileReaderI, AlignmentFileWriterI
45 {
46   int noSeqs = 0;
47
48   int maxLength = 0;
49
50   /**
51    * Sequences to be added to form a new alignment. TODO: remove vector in this
52    * class
53    */
54   protected Vector<SequenceI> seqs;
55
56   /**
57    * annotation to be added to generated alignment object
58    */
59   protected Vector<AlignmentAnnotation> annotations;
60
61   /**
62    * SequenceGroups to be added to the alignment object
63    */
64   protected List<SequenceGroup> seqGroups;
65
66   /**
67    * Properties to be added to generated alignment object
68    */
69   private Hashtable properties;
70
71   long start;
72
73   long end;
74
75   private boolean parseCalled;
76
77   /**
78    * Creates a new AlignFile object.
79    */
80   public AlignFile()
81   {
82     // Shouldn't we init data structures (JBPNote: not sure - initData is for
83     // initialising the structures used for reading from a datasource, and the
84     // bare constructor hasn't got any datasource)
85     initData();
86   }
87
88   public AlignFile(SequenceI[] seqs)
89   {
90     this();
91     setSeqs(seqs);
92   }
93
94   /**
95    * Constructor which parses the data from a file of some specified type.
96    * 
97    * @param dataObject
98    *          Filename, URL or Pasted String to read from.
99    * @param sourceType
100    *          What type of file to read from (File, URL, Pasted String)
101    */
102   public AlignFile(String dataObject, DataSourceType sourceType)
103           throws IOException
104   {
105     this(true, dataObject, sourceType);
106   }
107
108   /**
109    * Constructor which (optionally delays) parsing of data from a file of some
110    * specified type.
111    * 
112    * @param parseImmediately
113    *          if false, need to call 'doParse()' to begin parsing data
114    * @param dataObject
115    *          Filename, URL or Pasted String to read from.
116    * @param sourceType
117    *          What type of file to read from (File, URL)
118    * @throws IOException
119    */
120   public AlignFile(boolean parseImmediately, String dataObject,
121           DataSourceType sourceType)
122           throws IOException
123   {
124     super(dataObject, sourceType);
125     initData();
126     if (parseImmediately)
127     {
128       doParse();
129     }
130   }
131
132   /**
133    * Attempt to read from the position where some other parsing process left
134    * off.
135    * 
136    * @param source
137    * @throws IOException
138    */
139   public AlignFile(FileParse source) throws IOException
140   {
141     this(true, source);
142   }
143
144   /**
145    * Construct a new parser to read from the position where some other parsing
146    * process left
147    * 
148    * @param parseImmediately
149    *          if false, need to call 'doParse()' to begin parsing data
150    * @param source
151    */
152   public AlignFile(boolean parseImmediately, FileParse source)
153           throws IOException
154   {
155     super(source);
156     initData();
157     if (parseImmediately)
158     {
159       doParse();
160     }
161   }
162
163   /**
164    * called if parsing was delayed till after parser was constructed
165    * 
166    * @throws IOException
167    */
168   public void doParse() throws IOException
169   {
170     if (parseCalled)
171     {
172       throw new IOException(
173               "Implementation error: Parser called twice for same data.\n"
174                       + "Need to call initData() again before parsing can be reattempted.");
175     }
176     parseCalled = true;
177     parse();
178     // sets the index of each sequence in the alignment
179     for (int i = 0, c = seqs.size(); i < c; i++)
180     {
181       seqs.get(i).setIndex(i);
182     }
183   }
184
185   /**
186    * Return the seqs Vector
187    */
188   public Vector<SequenceI> getSeqs()
189   {
190     return seqs;
191   }
192
193   public List<SequenceGroup> getSeqGroups()
194   {
195     return seqGroups;
196   }
197
198   /**
199    * Return the Sequences in the seqs Vector as an array of Sequences
200    */
201   @Override
202   public SequenceI[] getSeqsAsArray()
203   {
204     SequenceI[] s = new SequenceI[seqs.size()];
205
206     for (int i = 0; i < seqs.size(); i++)
207     {
208       s[i] = seqs.elementAt(i);
209     }
210
211     return s;
212   }
213
214   /**
215    * called by AppletFormatAdapter to generate an annotated alignment, rather
216    * than bare sequences.
217    * 
218    * @param al
219    */
220   @Override
221   public void addAnnotations(AlignmentI al)
222   {
223     addProperties(al);
224     for (int i = 0; i < annotations.size(); i++)
225     {
226       // detect if annotations.elementAt(i) rna secondary structure
227       // if so then do:
228       /*
229        * SequenceFeature[] pairArray =
230        * Rna.GetBasePairsFromAlignmentAnnotation(annotations.elementAt(i));
231        * Rna.HelixMap(pairArray);
232        */
233       AlignmentAnnotation an = annotations.elementAt(i);
234       an.validateRangeAndDisplay();
235       al.addAnnotation(an);
236     }
237
238   }
239
240   /**
241    * register sequence groups on the alignment for **output**
242    * 
243    * @param al
244    */
245   public void addSeqGroups(AlignmentI al)
246   {
247     this.seqGroups = al.getGroups();
248
249   }
250
251   /**
252    * Add any additional information extracted from the file to the alignment
253    * properties.
254    * 
255    * @note implicitly called by addAnnotations()
256    * @param al
257    */
258   public void addProperties(AlignmentI al)
259   {
260     if (properties != null && properties.size() > 0)
261     {
262       Enumeration keys = properties.keys();
263       Enumeration vals = properties.elements();
264       while (keys.hasMoreElements())
265       {
266         al.setProperty(keys.nextElement(), vals.nextElement());
267       }
268     }
269   }
270
271   /**
272    * Store a non-null key-value pair in a hashtable used to set alignment
273    * properties note: null keys will raise an error, null values will result in
274    * the key/value pair being silently ignored.
275    * 
276    * @param key
277    *          - non-null key object
278    * @param value
279    *          - non-null value
280    */
281   protected void setAlignmentProperty(Object key, Object value)
282   {
283     if (key == null)
284     {
285       throw new Error(
286               MessageManager
287                       .getString("error.implementation_error_cannot_have_null_alignment"));
288     }
289     if (value == null)
290     {
291       return; // null properties are ignored.
292     }
293     if (properties == null)
294     {
295       properties = new Hashtable();
296     }
297     properties.put(key, value);
298   }
299
300   protected Object getAlignmentProperty(Object key)
301   {
302     if (properties != null && key != null)
303     {
304       return properties.get(key);
305     }
306     return null;
307   }
308
309   /**
310    * Initialise objects to store sequence data in.
311    */
312   protected void initData()
313   {
314     seqs = new Vector<SequenceI>();
315     annotations = new Vector<AlignmentAnnotation>();
316     seqGroups = new ArrayList<SequenceGroup>();
317     parseCalled = false;
318   }
319
320   /**
321    * DOCUMENT ME!
322    * 
323    * @param s
324    *          DOCUMENT ME!
325    */
326   @Override
327   public void setSeqs(SequenceI[] s)
328   {
329     seqs = new Vector<SequenceI>();
330
331     for (int i = 0; i < s.length; i++)
332     {
333       seqs.addElement(s[i]);
334     }
335   }
336
337   /**
338    * This method must be implemented to parse the contents of the file.
339    */
340   public abstract void parse() throws IOException;
341
342   /**
343    * A general parser for ids.
344    * 
345    * @String id Id to be parsed
346    */
347   Sequence parseId(String id)
348   {
349     Sequence seq = null;
350     id = id.trim();
351     int space = id.indexOf(" ");
352     if (space > -1)
353     {
354       seq = new Sequence(id.substring(0, space), "");
355       String desc = id.substring(space + 1);
356       seq.setDescription(desc);
357
358       /*
359        * it is tempting to parse Ensembl style gene description e.g.
360        * chromosome:GRCh38:7:140696688:140721955:1 and set the
361        * start position of the sequence, but this causes much confusion
362        * for reverse strand feature locations
363        */
364     }
365     else
366     {
367       seq = new Sequence(id, "");
368     }
369
370     return seq;
371   }
372
373   /**
374    * Creates the output id. Adds prefix Uniprot format source|id and optionally
375    * suffix Jalview /start-end
376    * 
377    * @param jvsuffix
378    * 
379    * @String id Id to be parsed
380    */
381   String printId(SequenceI seq, boolean jvsuffix)
382   {
383     return seq.getDisplayId(jvsuffix);
384   }
385
386   String printId(SequenceI seq)
387   {
388     return printId(seq, true);
389   }
390
391   /**
392    * vector of String[] treeName, newickString pairs
393    */
394   Vector<String[]> newickStrings = null;
395
396   protected void addNewickTree(String treeName, String newickString)
397   {
398     if (newickStrings == null)
399     {
400       newickStrings = new Vector<String[]>();
401     }
402     newickStrings.addElement(new String[] { treeName, newickString });
403   }
404
405   protected int getTreeCount()
406   {
407     return newickStrings == null ? 0 : newickStrings.size();
408   }
409
410   @Override
411   public void addGroups(AlignmentI al)
412   {
413
414     for (SequenceGroup sg : getSeqGroups())
415     {
416       al.addGroup(sg);
417     }
418   }
419
420   protected void addSequence(SequenceI seq)
421   {
422     seqs.add(seq);
423   }
424 }