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