48f65cf9f5e241922aabf9715e65e5398806c033
[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.Hashtable;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Map.Entry;
29 import java.util.Vector;
30
31 import jalview.datamodel.AlignmentAnnotation;
32 import jalview.datamodel.AlignmentI;
33 import jalview.datamodel.Sequence;
34 import jalview.datamodel.SequenceGroup;
35 import jalview.datamodel.SequenceI;
36 import jalview.util.MessageManager;
37
38 /**
39  * DOCUMENT ME!
40  * 
41  * @author $author$
42  * @version $Revision$
43  */
44 public abstract class AlignFile extends FileParse
45         implements AlignmentFileReaderI, AlignmentFileWriterI
46 {
47   int noSeqs = 0;
48
49   int maxLength = 0;
50
51   /**
52    * Sequences to be added to form a new alignment. TODO: remove vector in this
53    * class
54    */
55   protected Vector<SequenceI> seqs;
56
57   /**
58    * annotation to be added to generated alignment object
59    */
60   protected Vector<AlignmentAnnotation> annotations;
61
62   /**
63    * SequenceGroups to be added to the alignment object
64    */
65   protected List<SequenceGroup> seqGroups;
66
67   /**
68    * Properties to be added to generated alignment object
69    */
70   private Map<String, String> properties;
71
72   long start;
73
74   long end;
75
76   /**
77    * true if parse() has been called
78    */
79   private boolean parseCalled = false;
80
81   private boolean parseImmediately = true;
82
83   private boolean dataClosed = false;
84
85   /**
86    * @return if doParse() was called at construction time
87    */
88   protected boolean isParseImmediately()
89   {
90     return parseImmediately;
91   }
92
93   /**
94    * Creates a new AlignFile object.
95    */
96   public AlignFile()
97   {
98     // Shouldn't we init data structures (JBPNote: not sure - initData is for
99     // initialising the structures used for reading from a datasource, and the
100     // bare constructor hasn't got any datasource)
101     initData();
102   }
103
104   public AlignFile(SequenceI[] seqs)
105   {
106     this();
107     setSeqs(seqs);
108   }
109
110   /**
111    * Constructor which parses the data from a file of some specified type.
112    * 
113    * @param dataObject
114    *          Filename, URL or Pasted String to read from.
115    * @param sourceType
116    *          What type of file to read from (File, URL, Pasted String)
117    */
118   public AlignFile(Object dataObject, DataSourceType sourceType)
119           throws IOException
120   {
121     this(true, dataObject, sourceType);
122   }
123
124   /**
125    * Constructor which (optionally delays) parsing of data from a file of some
126    * specified type.
127    * 
128    * @param parseImmediately
129    *          if false, need to call 'doParse()' to begin parsing data
130    * @param dataObject
131    *          Filename, URL or Pasted String to read from.
132    * @param sourceType
133    *          What type of file to read from (File, URL)
134    * @throws IOException
135    */
136   public AlignFile(boolean parseImmediately, Object dataObject,
137           DataSourceType sourceType) throws IOException
138   {
139     // BH allows File or String
140     super(dataObject, sourceType);
141     initData();
142     if (parseImmediately)
143     {
144       doParse();
145     }
146   }
147
148   /**
149    * Attempt to read from the position where some other parsing process left
150    * off.
151    * 
152    * @param source
153    * @throws IOException
154    */
155   public AlignFile(FileParse source) throws IOException
156   {
157     this(true, source);
158   }
159
160   /**
161    * Construct a new parser to read from the position where some other parsing
162    * process left
163    * 
164    * @param parseImmediately
165    *          if false, need to call 'doParse()' to begin parsing data
166    * @param source
167    */
168   public AlignFile(boolean parseImmediately, FileParse source)
169           throws IOException
170   {
171     this(parseImmediately, source, true);
172   }
173
174   public AlignFile(boolean parseImmediately, FileParse source,
175           boolean closeData) throws IOException
176   {
177     super(source);
178     initData();
179
180     // stash flag in case parse needs to know if it has to autoconfigure or was
181     // configured after construction
182     this.parseImmediately = parseImmediately;
183
184     if (parseImmediately)
185     {
186       doParse(closeData);
187     }
188   }
189
190   /**
191    * called if parsing was delayed till after parser was constructed
192    * 
193    * @throws IOException
194    */
195   public void doParse() throws IOException
196   {
197     doParse(true);
198   }
199
200   public void doParse(boolean closeData) throws IOException
201   {
202     if (parseCalled)
203     {
204       throw new IOException(
205               "Implementation error: Parser called twice for same data.\n"
206                       + "Need to call initData() again before parsing can be reattempted.");
207     }
208     parseCalled = true;
209     parse();
210     if (closeData && !dataClosed)
211     {
212       dataIn.close();
213       dataClosed = true;
214     }
215   }
216
217   /**
218    * Return the seqs Vector
219    */
220   public Vector<SequenceI> getSeqs()
221   {
222     return seqs;
223   }
224
225   public List<SequenceGroup> getSeqGroups()
226   {
227     return seqGroups;
228   }
229
230   /**
231    * Return the Sequences in the seqs Vector as an array of Sequences
232    */
233   @Override
234   public SequenceI[] getSeqsAsArray()
235   {
236     SequenceI[] s = new SequenceI[seqs.size()];
237
238     for (int i = 0; i < seqs.size(); i++)
239     {
240       s[i] = seqs.elementAt(i);
241     }
242
243     return s;
244   }
245
246   /**
247    * called by AppletFormatAdapter to generate an annotated alignment, rather
248    * than bare sequences.
249    * 
250    * @param al
251    */
252   @Override
253   public void addAnnotations(AlignmentI al)
254   {
255     addProperties(al);
256     for (int i = 0; i < annotations.size(); i++)
257     {
258       // detect if annotations.elementAt(i) rna secondary structure
259       // if so then do:
260       /*
261        * SequenceFeature[] pairArray =
262        * Rna.GetBasePairsFromAlignmentAnnotation(annotations.elementAt(i));
263        * Rna.HelixMap(pairArray);
264        */
265       AlignmentAnnotation an = annotations.elementAt(i);
266       an.validateRangeAndDisplay();
267       al.addAnnotation(an);
268     }
269
270   }
271
272   /**
273    * register sequence groups on the alignment for **output**
274    * 
275    * @param al
276    */
277   public void addSeqGroups(AlignmentI al)
278   {
279     this.seqGroups = al.getGroups();
280
281   }
282
283   /**
284    * Add any additional information extracted from the file to the alignment
285    * properties.
286    * 
287    * @note implicitly called by addAnnotations()
288    * @param al
289    */
290   public void addProperties(AlignmentI al)
291   {
292     if (properties != null)
293     {
294       for (Entry<String, String> prop : properties.entrySet())
295       {
296         al.setProperty(prop.getKey(), prop.getValue());
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(String key, String value)
312   {
313     if (key == null)
314     {
315       throw new Error(
316               "Implementation error: Cannot have null alignment property key.");
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 String getAlignmentProperty(String 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<>();
344     annotations = new Vector<>();
345     seqGroups = new ArrayList<>();
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<>();
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<>();
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 }