/* * The MIT License * * Copyright (c) 2014 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 picard.illumina.parser; import htsjdk.samtools.util.CloserUtil; import htsjdk.samtools.util.IOUtil; import picard.PicardException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; /** * Load a file containing 8-byte records like this: * tile number: 4-byte int * number of clusters in tile: 4-byte int * Number of records to read is determined by reaching EOF. */ public class TileIndex implements Iterable<TileIndex.TileIndexRecord> { private final File tileIndexFile; private final List<TileIndexRecord> tiles = new ArrayList<TileIndexRecord>(); TileIndex(final File tileIndexFile) { try { this.tileIndexFile = tileIndexFile; final InputStream is = IOUtil.maybeBufferInputStream(new FileInputStream(tileIndexFile)); final ByteBuffer buf = ByteBuffer.allocate(8); buf.order(ByteOrder.LITTLE_ENDIAN); int absoluteRecordIndex = 0; int numTiles = 0; while (readTileIndexRecord(buf.array(), buf.capacity(), is)) { buf.rewind(); buf.limit(buf.capacity()); final int tile = buf.getInt(); // Note: not handling unsigned ints > 2^31, but could if one of these exceptions is thrown. if (tile < 0) throw new PicardException("Tile number too large in " + tileIndexFile.getAbsolutePath()); final int numClusters = buf.getInt(); if (numClusters < 0) throw new PicardException("Cluster size too large in " + tileIndexFile.getAbsolutePath()); tiles.add(new TileIndexRecord(tile, numClusters, absoluteRecordIndex, numTiles++)); absoluteRecordIndex += numClusters; } CloserUtil.close(is); } catch (final IOException e) { throw new PicardException("Problem reading " + tileIndexFile.getAbsolutePath(), e); } } public File getFile() { return tileIndexFile; } public int getNumTiles() { return tiles.size(); } private boolean readTileIndexRecord(final byte[] buf, final int numBytes, final InputStream is) throws IOException { int totalBytesRead = 0; while (totalBytesRead < numBytes) { final int bytesRead = is.read(buf, totalBytesRead, numBytes - totalBytesRead); if (bytesRead == -1) { if (totalBytesRead != 0) { throw new PicardException(tileIndexFile.getAbsolutePath() + " has incomplete last block"); } else return false; } totalBytesRead += bytesRead; } return true; } public List<Integer> getTiles() { final List<Integer> ret = new ArrayList<Integer>(tiles.size()); for (final TileIndexRecord rec : tiles) ret.add(rec.tile); return ret; } public List<String> verify(final List<Integer> expectedTiles) { final Set<Integer> tileSet = new HashSet<Integer>(tiles.size()); for (final TileIndexRecord rec : tiles) tileSet.add(rec.tile); final List<String> failures = new LinkedList<String>(); for (final int expectedTile : expectedTiles) { if (!tileSet.contains(expectedTile)) { failures.add("Tile " + expectedTile + " not found in " + tileIndexFile.getAbsolutePath()); } } return failures; } @Override public Iterator<TileIndexRecord> iterator() { return tiles.iterator(); } /** * @throws java.util.NoSuchElementException if tile is not found */ public TileIndexRecord findTile(final int tileNumber) { for (final TileIndexRecord rec : this) { if (rec.tile == tileNumber) return rec; if (rec.tile > tileNumber) { break; } } throw new NoSuchElementException(String.format("Tile %d not found in %s", tileNumber, tileIndexFile)); } public static class TileIndexRecord { /** * Number of the tile, e.g. 11101. These don't necessarily start at 0, and there may be gaps. */ final int tile; final int numClustersInTile; public int getNumClustersInTile() { return numClustersInTile; } public int getZeroBasedTileNumber() { return zeroBasedTileNumber; } /** * I.e. the sum of numClustersInTile for all tiles preceding this one. */ final int indexOfFirstClusterInTile; /** * A contiguous numbering of tiles starting at 0. */ final int zeroBasedTileNumber; private TileIndexRecord(final int tile, final int numClustersInTile, final int indexOfFirstClusterInTile, final int zeroBasedTileNumber) { this.tile = tile; this.numClustersInTile = numClustersInTile; this.indexOfFirstClusterInTile = indexOfFirstClusterInTile; this.zeroBasedTileNumber = zeroBasedTileNumber; } } }