/* * Copyright (c) 2012 The Broad Institute * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package htsjdk.variant.variantcontext.writer; import htsjdk.samtools.SAMSequenceDictionary; import htsjdk.samtools.SAMSequenceRecord; import htsjdk.samtools.util.LocationAware; import htsjdk.tribble.index.DynamicIndexCreator; import htsjdk.tribble.index.Index; import htsjdk.tribble.index.IndexCreator; import htsjdk.tribble.index.IndexFactory; import htsjdk.tribble.index.TribbleIndexCreator; import htsjdk.variant.variantcontext.VariantContext; import htsjdk.variant.vcf.VCFHeader; import java.io.File; import java.io.IOException; import java.io.OutputStream; /** * this class writes VCF files */ abstract class IndexingVariantContextWriter implements VariantContextWriter { private final String name; private final File location; private final SAMSequenceDictionary refDict; private OutputStream outputStream; private LocationAware locationSource = null; private IndexCreator indexer = null; private IndexingVariantContextWriter(final String name, final File location, final OutputStream output, final SAMSequenceDictionary refDict) { this.name = name; this.location = location; this.outputStream = output; this.refDict = refDict; } /** * Create a VariantContextWriter with an associated index using the default index creator * * @param name the name of this writer (i.e. the file name or stream) * @param location the path to the output file * @param output the output stream to write to * @param refDict the reference dictionary * @param enableOnTheFlyIndexing is OTF indexing enabled? */ protected IndexingVariantContextWriter(final String name, final File location, final OutputStream output, final SAMSequenceDictionary refDict, final boolean enableOnTheFlyIndexing) { this(name, location, output, refDict); if ( enableOnTheFlyIndexing ) { initIndexingWriter(new DynamicIndexCreator(location, IndexFactory.IndexBalanceApproach.FOR_SEEK_TIME)); } } /** * Create a VariantContextWriter with an associated index using a custom index creator * * @param name the name of this writer (i.e. the file name or stream) * @param location the path to the output file * @param output the output stream to write to * @param refDict the reference dictionary * @param enableOnTheFlyIndexing is OTF indexing enabled? * @param idxCreator the custom index creator. NOTE: must be initialized */ protected IndexingVariantContextWriter(final String name, final File location, final OutputStream output, final SAMSequenceDictionary refDict, final boolean enableOnTheFlyIndexing, final IndexCreator idxCreator) { this(name, location, output, refDict); if ( enableOnTheFlyIndexing ) { // TODO: Handle non-Tribble IndexCreators initIndexingWriter(idxCreator); } } private void initIndexingWriter(final IndexCreator idxCreator) { indexer = idxCreator; if (outputStream instanceof LocationAware) { locationSource = (LocationAware)outputStream; } else { final PositionalOutputStream positionalOutputStream = new PositionalOutputStream(outputStream); locationSource = positionalOutputStream; outputStream = positionalOutputStream; } } public OutputStream getOutputStream() { return outputStream; } public String getStreamName() { return name; } public abstract void writeHeader(VCFHeader header); /** * attempt to close the VCF file */ public void close() { try { // close the underlying output stream outputStream.close(); // close the index stream (keep it separate to help debugging efforts) if (indexer != null) { if (indexer instanceof TribbleIndexCreator) { setIndexSequenceDictionary((TribbleIndexCreator)indexer, refDict); } final Index index = indexer.finalizeIndex(locationSource.getPosition()); index.writeBasedOnFeatureFile(location); } } catch (final IOException e) { throw new RuntimeException("Unable to close index for " + getStreamName(), e); } } /** * @return the reference sequence dictionary used for the variant contexts being written */ public SAMSequenceDictionary getRefDict() { return refDict; } /** * add a record to the file * * @param vc the Variant Context object */ public void add(final VariantContext vc) { // if we are doing on the fly indexing, add the record ***before*** we write any bytes if ( indexer != null ) indexer.addFeature(vc, locationSource.getPosition()); } /** * Returns a reasonable "name" for this writer, to display to the user if something goes wrong * * @param location * @param stream * @return */ protected static final String writerName(final File location, final OutputStream stream) { return location == null ? stream.toString() : location.getAbsolutePath(); } // a constant we use for marking sequence dictionary entries in the Tribble index property list private static final String SequenceDictionaryPropertyPredicate = "DICT:"; private static void setIndexSequenceDictionary(final TribbleIndexCreator indexCreator, final SAMSequenceDictionary dict) { for (final SAMSequenceRecord seq : dict.getSequences()) { final String contig = SequenceDictionaryPropertyPredicate + seq.getSequenceName(); final String length = String.valueOf(seq.getSequenceLength()); indexCreator.addProperty(contig,length); } } } /** * Wraps output stream in a manner which keeps track of the position within the file and allowing writes * at arbitrary points */ final class PositionalOutputStream extends OutputStream implements LocationAware { private final OutputStream out; private long position = 0; public PositionalOutputStream(final OutputStream out) { this.out = out; } public final void write(final byte[] bytes) throws IOException { write(bytes, 0, bytes.length); } public final void write(final byte[] bytes, final int startIndex, final int numBytes) throws IOException { position += numBytes; out.write(bytes, startIndex, numBytes); } public final void write(final int c) throws IOException { position++; out.write(c); } public final long getPosition() { return position; } @Override public void close() throws IOException { super.close(); out.close(); } }