/*
* 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 org.broadinstitute.gatk.engine.filters;
import htsjdk.samtools.filter.SamRecordFilter;
import htsjdk.samtools.SAMRecord;
import htsjdk.samtools.util.CloseableIterator;
import htsjdk.samtools.util.CloserUtil;
import org.broadinstitute.gatk.engine.ReadMetrics;
import java.util.*;
/**
* Filtering Iterator which takes a filter and an iterator and iterates
* through only those records which are not rejected by the filter.
* @author Mark DePristo
*/
public class CountingFilteringIterator implements CloseableIterator<SAMRecord> {
private final ReadMetrics globalRuntimeMetrics;
private final ReadMetrics privateRuntimeMetrics;
private final Iterator<SAMRecord> iterator;
private final List<CountingReadFilter> filters = new ArrayList<>();
private SAMRecord next = null;
// wrapper around ReadFilters to count the number of filtered reads
private final class CountingReadFilter extends ReadFilter {
protected final ReadFilter readFilter;
protected long counter = 0L;
public CountingReadFilter(final ReadFilter readFilter) {
this.readFilter = readFilter;
}
@Override
public boolean filterOut(final SAMRecord record) {
final boolean result = readFilter.filterOut(record);
if ( result )
counter++;
return result;
}
}
/**
* Constructor
*
* @param metrics metrics to accumulate on the nature of filtered reads.
* @param iterator the backing iterator
* @param filters the filter (which may be a FilterAggregator)
*/
public CountingFilteringIterator(ReadMetrics metrics, Iterator<SAMRecord> iterator, Collection<ReadFilter> filters) {
this.globalRuntimeMetrics = metrics;
privateRuntimeMetrics = new ReadMetrics();
this.iterator = iterator;
for ( final ReadFilter filter : filters )
this.filters.add(new CountingReadFilter(filter));
next = getNextRecord();
}
/**
* Returns true if the iteration has more elements.
*
* @return true if the iteration has more elements. Otherwise returns false.
*/
public boolean hasNext() {
return next != null;
}
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration
* @throws java.util.NoSuchElementException
*/
public SAMRecord next() {
if (next == null) {
throw new NoSuchElementException("Iterator has no more elements.");
}
final SAMRecord result = next;
next = getNextRecord();
return result;
}
/**
* Required method for Iterator API.
*
* @throws UnsupportedOperationException
*/
public void remove() {
throw new UnsupportedOperationException("Remove() not supported by CountingFilteringIterator");
}
public void close() {
CloserUtil.close(iterator);
for ( final CountingReadFilter filter : filters )
privateRuntimeMetrics.setFilterCount(filter.readFilter.getClass().getSimpleName(), filter.counter);
// update the global metrics with all the data we collected here
globalRuntimeMetrics.incrementMetrics(privateRuntimeMetrics);
}
/**
* Gets the next record from the underlying iterator that passes the filter
*
* @return SAMRecord the next filter-passing record
*/
private SAMRecord getNextRecord() {
while (iterator.hasNext()) {
SAMRecord record = iterator.next();
// update only the private copy of the metrics so that we don't need to worry about race conditions
// that can arise when trying to update the global copy; it was agreed that this is the cleanest solution.
privateRuntimeMetrics.incrementNumReadsSeen();
boolean filtered = false;
for(SamRecordFilter filter: filters) {
if(filter.filterOut(record)) {
filtered = true;
break;
}
}
if(!filtered) return record;
}
return null;
}
}