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