Java tutorial
/** * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package savant.file; import java.io.File; import java.io.IOException; import java.net.URI; import java.util.Arrays; import java.util.List; import java.util.Map; import net.sf.samtools.util.SeekableBufferedStream; import net.sf.samtools.util.SeekableFileStream; import net.sf.samtools.util.SeekableStream; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import savant.util.MiscUtils; import savant.util.NetworkUtils; import savant.util.SavantFileUtils; public class SavantROFile implements ROFile { private static final Log LOG = LogFactory.getLog(SavantROFile.class); private final SeekableStream seekStream; private FileTypeHeader fileTypeHeader; private Map<String, long[]> referenceMap; private List<FieldType> fields; private long headerOffset; private URI uri; private byte[] oneByteBuf = new byte[1]; private long filePointer = 0; public static final int CURRENT_FILE_VERSION = 2; public static final List<Integer> SUPPORTED_FILE_VERSIONS = Arrays.asList(2); /** * Construct a Savant file from a local file. * * @param file a local file * @throws IOException * @throws SavantFileNotFormattedException if file is not formatted in Savant format (no magic number) * @throws SavantUnsupportedVersionException if file is formatted in a currently unsupported version */ public SavantROFile(File file) throws IOException, SavantFileNotFormattedException { uri = file.toURI(); LOG.debug("Adding RO File: " + file); LOG.debug("URI is: " + this.uri); seekStream = new SeekableBufferedStream(new SeekableFileStream(file)); init(); } /** * Construct a Savant file from a local file, with a given file type * * @param file file path * @param ft file type * @throws IOException if existing file type does not match that given * @throws SavantFileNotFormattedException if file is not formatted in Savant format (no magic number) * @throws SavantUnsupportedVersionException if file is formatted in a currently unsupported version */ public SavantROFile(File file, FileType ft) throws IOException, SavantFileNotFormattedException { this(file); if (!fileTypeHeader.fileType.equals(ft)) { if (fileTypeHeader.fileType == FileType.INTERVAL_GFF && ft == FileType.INTERVAL_GENERIC) { } else { throw new IOException("Wrong file type"); } } } /** * Construct a Savant file from a URI * * @param uri http, ftp (or file) URI * @throws IOException * @throws SavantFileNotFormattedException if file is not formatted in Savant format (no magic number) * @throws SavantUnsupportedVersionException if file is formatted in a currently unsupported version */ public SavantROFile(URI uri) throws IOException, SavantFileNotFormattedException { this.uri = uri.normalize(); seekStream = NetworkUtils.getSeekableStreamForURI(uri); init(); } /** * Construct a Savant file from a URI, with a given file type * * @param uri HTTP URI * @param ft file type * @throws IOException if existing file type does not match that given * @throws SavantFileNotFormattedException if file is not formatted in Savant format (no magic number) * @throws SavantUnsupportedVersionException if file is formatted in a currently unsupported version */ public SavantROFile(URI uri, FileType ft) throws IOException, SavantFileNotFormattedException { this(uri); if (!fileTypeHeader.fileType.equals(ft)) { if (fileTypeHeader.fileType == FileType.INTERVAL_GFF && ft == FileType.INTERVAL_GENERIC) { return; } throw new IOException("Wrong file type"); } } @SuppressWarnings("deprecation") private void init() throws IOException, SavantFileNotFormattedException { LOG.debug("Reading file type"); this.fileTypeHeader = SavantFileUtils.readFileTypeHeader(this); if (fileTypeHeader.fileType == null) throw new SavantFileNotFormattedException("This file does not appear to be formatted. Format now?"); if (!isSupportedVersion(fileTypeHeader.version)) { throw new SavantUnsupportedVersionException(fileTypeHeader.version, getSupportedVersions()); } if (LOG.isDebugEnabled()) { LOG.debug("File type: " + this.fileTypeHeader.fileType); LOG.debug("Done... at " + getFilePointer() + " bytes"); LOG.debug("Reading fields"); } this.fields = SavantFileUtils.readFieldsHeader(this); if (LOG.isDebugEnabled()) { LOG.debug(fields); LOG.debug("Number of fields: " + fields.size()); LOG.debug("Reading reference<-> data map"); } referenceMap = SavantFileUtils.readReferenceMap(this); if (LOG.isDebugEnabled()) { for (String refname : referenceMap.keySet()) { long[] vals = referenceMap.get(refname); LOG.debug("Reference " + refname + " at " + vals[0] + " of length " + vals[1]); } } LOG.debug("Making note of offset: " + getFilePointer()); headerOffset = getFilePointer(); } @Override public synchronized long seek(String reference, long pos) throws IOException { //FIXME!!! if (!this.containsDataForReference(reference) && !this.containsDataForReference(MiscUtils.homogenizeSequence(reference))) { //FIXME: temporary fix for chrx != x issue LOG.debug("No data for reference: " + reference); return -1; } else if (pos >= this.getReferenceLength(reference) && pos >= this.getReferenceLength(reference.substring(reference.length() - 1))) { //ditto ^^ LOG.debug("End of data for reference: " + reference); return -1; } else { long refoffset = getReferenceOffset(reference); //FIXME: temporary fix for chrx != x issue if (refoffset == -1) { refoffset = getReferenceOffset(MiscUtils.homogenizeSequence(reference)); } if (LOG.isDebugEnabled()) LOG.debug("Seeking to " + (pos + refoffset + headerOffset) + " pos=" + pos + " ref=" + reference + " refoffset=" + refoffset + " headeroffset=" + headerOffset + " file=" + this.uri.toString()); seek(pos + refoffset + headerOffset); return pos + refoffset + headerOffset; } } @Override public synchronized void seek(long pos) throws IOException { if (filePointer != pos) { seekStream.seek(pos); filePointer = pos; if (LOG.isDebugEnabled()) { LOG.debug("Seeking to " + pos); LOG.debug("warning: consider calling seek (string reference, long pos) instead"); } } } @Override public synchronized void close() throws IOException { seekStream.close(); } @Override public synchronized long getFilePointer() throws IOException { return filePointer; } @Override public synchronized long length() throws IOException { return seekStream.length(); } @Override public synchronized int read() throws IOException { int bytesRead = seekStream.read(oneByteBuf, 0, 1); if (bytesRead != -1) { int result = oneByteBuf[0]; filePointer++; return result; } else return -1; } @Override public synchronized int read(byte[] b) throws IOException { int result = seekStream.read(b, 0, b.length); if (result != -1) filePointer += result; return result; } @Override public synchronized int read(byte[] b, int off, int len) throws IOException { int result = seekStream.read(b, off, len); if (result != -1) filePointer += result; return result; } @Override public synchronized byte readByte() throws IOException { byte result = (byte) (read() & 0xFF); return result; } @Override public synchronized double readDouble() throws IOException { byte[] bytes = new byte[8]; int result = read(bytes); if (result != 8) { LOG.warn("Could not read 8 bytes for a double"); throw new IOException("At EOF"); } long longBits = ((long) bytes[0] & 0xFF) << 56 | ((long) bytes[1] & 0xFF) << 48 | ((long) bytes[2] & 0xFF) << 40 | ((long) bytes[3] & 0xFF) << 32 | ((long) bytes[4] & 0xFF) << 24 | ((long) bytes[5] & 0xFF) << 16 | ((long) bytes[6] & 0xFF) << 8 | ((long) bytes[7] & 0xFF); return Double.longBitsToDouble(longBits); } @Override public synchronized float readFloat() throws IOException { byte[] bytes = new byte[4]; int result = read(bytes); if (result != 4) { LOG.warn("Could not read 4 bytes for float"); throw new IOException("At EOF"); } int intBits = (bytes[0] & 0xFF) << 24 | (bytes[1] & 0xFF) << 16 | (bytes[2] & 0xFF) << 8 | bytes[3] & 0xFF; return Float.intBitsToFloat(intBits); } @Override public synchronized int readInt() throws IOException { byte[] bytes = new byte[4]; int result = read(bytes); if (result != 4) { LOG.warn("Could not read 4 bytes for int"); throw new IOException("At EOF"); } int intBits = (bytes[0] & 0xFF) << 24 | (bytes[1] & 0xFF) << 16 | (bytes[2] & 0xFF) << 8 | bytes[3] & 0xFF; return intBits; } @Override public synchronized String readLine() throws IOException { byte[] readBytes = new byte[2]; StringBuilder sb = new StringBuilder(); while (true) { int read = read(readBytes); if (read != 2) { LOG.warn("Unable to read 2 bytes"); return sb.toString(); } char theChar = (char) (readBytes[0] << 8 | readBytes[1]); if (theChar == '\r') { theChar = (char) (readBytes[0] << 8 | readBytes[1]); } if (theChar == '\n') { return sb.toString(); } sb.append(theChar); } } @Override public synchronized long readLong() throws IOException { byte[] bytes = new byte[8]; int result = read(bytes); if (result != 8) { LOG.warn("Could not read 8 bytes for a long"); throw new IOException("At EOF"); } long longBits = ((long) bytes[0] & 0xFF) << 56 | ((long) bytes[1] & 0xFF) << 48 | ((long) bytes[2] & 0xFF) << 40 | ((long) bytes[3] & 0xFF) << 32 | ((long) bytes[4] & 0xFF) << 24 | ((long) bytes[5] & 0xFF) << 16 | ((long) bytes[6] & 0xFF) << 8 | ((long) bytes[7] & 0xFF); return longBits; } @Override public List<FieldType> getFields() { return this.fields; } @Override public Map<String, long[]> getReferenceMap() { return referenceMap; } @Override public long getHeaderOffset() { return headerOffset; } @Override public void setHeaderOffset(long offset) { this.headerOffset = offset; } public boolean isSupportedVersion(int version) { return SUPPORTED_FILE_VERSIONS.contains(version); } public String getSupportedVersions() { StringBuilder sb = new StringBuilder(); for (Integer version : SUPPORTED_FILE_VERSIONS) { sb.append(version).append(" "); } return sb.toString().trim(); } public long getReferenceOffset(String reference) { if (!this.containsDataForReference(reference)) { return -1; } return this.referenceMap.get(reference)[0]; } public long getReferenceLength(String reference) { if (!this.containsDataForReference(reference)) { return -1; } return this.referenceMap.get(reference)[1]; } public boolean containsDataForReference(String reference) { return this.referenceMap.containsKey(reference); } public FileType getFileType() { return fileTypeHeader.fileType; } public URI getURI() { LOG.debug("Getting URI: " + this.uri); return this.uri; } }