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