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