Merge branch 'develop' into feature/JAL-3187linkedFeatures
[jalview.git] / src / jalview / ext / htsjdk / VCFReader.java
1 package jalview.ext.htsjdk;
2
3 import jalview.bin.Cache;
4
5 import java.io.Closeable;
6 import java.io.File;
7 import java.io.IOException;
8
9 import htsjdk.samtools.util.CloseableIterator;
10 import htsjdk.variant.variantcontext.VariantContext;
11 import htsjdk.variant.vcf.VCFFileReader;
12 import htsjdk.variant.vcf.VCFHeader;
13
14 /**
15  * A thin wrapper for htsjdk classes to read either plain, or compressed, or
16  * compressed and indexed VCF files
17  */
18 public class VCFReader implements Closeable, Iterable<VariantContext>
19 {
20   private static final String GZ = "gz";
21
22   private static final String TBI_EXTENSION = ".tbi";
23
24   private static final String CSI_EXTENSION = ".csi";
25
26   private boolean indexed;
27
28   private VCFFileReader reader;
29
30   /**
31    * Constructor given a raw or compressed VCF file or a (csi or tabix) index file
32    * <p>
33    * If the file path ends in ".tbi" or ".csi", <em>or</em> appending one of these
34    * extensions gives a valid file path, open as indexed, else as unindexed.
35    * 
36    * @param f
37    * @throws IOException
38    */
39   public VCFReader(String filePath) throws IOException
40   {
41     indexed = false;
42     if (filePath.endsWith(TBI_EXTENSION)
43             || filePath.endsWith(CSI_EXTENSION))
44     {
45       indexed = true;
46       filePath = filePath.substring(0, filePath.length() - 4);
47     }
48     else if (new File(filePath + TBI_EXTENSION).exists())
49     {
50       indexed = true;
51     }
52     else if (new File(filePath + CSI_EXTENSION).exists())
53     {
54       indexed = true;
55     }
56
57     /*
58      * we pass the name of the unindexed file to htsjdk,
59      * with a flag to assert whether it is indexed
60      */
61     File file = new File(filePath);
62     if (file.exists())
63     {
64       reader = new VCFFileReader(file, indexed);
65     }
66     else
67     {
68       Cache.log.error("File not found: " + filePath);
69     }
70   }
71
72   @Override
73   public void close() throws IOException
74   {
75     if (reader != null)
76     {
77       reader.close();
78     }
79   }
80
81   /**
82    * Returns an iterator over VCF variants in the file. The client should call
83    * close() on the iterator when finished with it.
84    */
85   @Override
86   public CloseableIterator<VariantContext> iterator()
87   {
88     return reader == null ? null : reader.iterator();
89   }
90
91   /**
92    * Queries for records overlapping the region specified. Note that this method
93    * is performant if the VCF file is indexed, and may be very slow if it is
94    * not.
95    * <p>
96    * Client code should call close() on the iterator when finished with it.
97    * 
98    * @param chrom
99    *          the chromosome to query
100    * @param start
101    *          query interval start
102    * @param end
103    *          query interval end
104    * @return
105    */
106   public CloseableIterator<VariantContext> query(final String chrom,
107           final int start, final int end)
108   {
109     if (reader == null)
110     {
111       return null;
112     }
113     if (indexed)
114     {
115       return reader.query(chrom, start, end);
116     }
117     else
118     {
119       return queryUnindexed(chrom, start, end);
120     }
121   }
122
123   /**
124    * Returns an iterator over variant records read from a flat file which
125    * overlap the specified chromosomal positions. Call close() on the iterator
126    * when finished with it!
127    * 
128    * @param chrom
129    * @param start
130    * @param end
131    * @return
132    */
133   protected CloseableIterator<VariantContext> queryUnindexed(
134           final String chrom, final int start, final int end)
135   {
136     final CloseableIterator<VariantContext> it = reader.iterator();
137     
138     return new CloseableIterator<VariantContext>()
139     {
140       boolean atEnd = false;
141
142       // prime look-ahead buffer with next matching record
143       private VariantContext next = findNext();
144
145       private VariantContext findNext()
146       {
147         if (atEnd)
148         {
149           return null;
150         }
151         VariantContext variant = null;
152         while (it.hasNext())
153         {
154           variant = it.next();
155           int vstart = variant.getStart();
156
157           if (vstart > end)
158           {
159             atEnd = true;
160             close();
161             return null;
162           }
163
164           int vend = variant.getEnd();
165           // todo what is the undeprecated way to get
166           // the chromosome for the variant?
167           if (chrom.equals(variant.getContig()) && (vstart <= end)
168                   && (vend >= start))
169           {
170             return variant;
171           }
172         }
173         return null;
174       }
175
176       @Override
177       public boolean hasNext()
178       {
179         boolean hasNext = !atEnd && (next != null);
180         if (!hasNext)
181         {
182           close();
183         }
184         return hasNext;
185       }
186
187       @Override
188       public VariantContext next()
189       {
190         /*
191          * return the next match, and then re-prime
192          * it with the following one (if any)
193          */
194         VariantContext temp = next;
195         next = findNext();
196         return temp;
197       }
198
199       @Override
200       public void remove()
201       {
202         // not implemented
203       }
204
205       @Override
206       public void close()
207       {
208         it.close();
209       }
210     };
211   }
212
213   /**
214    * Returns an object that models the VCF file headers
215    * 
216    * @return
217    */
218   public VCFHeader getFileHeader()
219   {
220     return reader == null ? null : reader.getFileHeader();
221   }
222
223   /**
224    * Answers true if we are processing a tab-indexed VCF file, false if it is a
225    * plain text (uncompressed) file.
226    * 
227    * @return
228    */
229   public boolean isIndex()
230   {
231     return indexed;
232   }
233 }