/** * JHOVE2 - Next-generation architecture for format-aware characterization * * Copyright (c) 2009 by The Regents of the University of California, * Ithaka Harbors, Inc., and The Board of Trustees of the Leland Stanford * Junior University. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * o Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * o Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * o Neither the name of the University of California/California Digital * Library, Ithaka Harbors/Portico, or Stanford University, nor the names of * its contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package org.jhove2.core.io; import java.io.BufferedInputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.channels.FileChannel; import org.jhove2.core.Invocation; import org.jhove2.core.JHOVE2; /** * Abstract JHOVE2 inputter. * * @author mstrong, slabrams */ public abstract class AbstractInput implements Input { /** Buffer to hold data from channel. */ protected ByteBuffer buffer; /** Starting offset of the current buffer. */ protected long bufferOffset; /** Current buffer size. */ protected int bufferSize; /** Current byte order of buffer. */ protected ByteOrder byteOrder; /** AbstractInput channel. */ protected FileChannel channel; /** File underlying the inputable. */ protected File file; /** Temporary file deletion status: true if delete on close. */ protected boolean deleteTempFileOnClose; /** InputStream underlying the inputable. */ protected InputStream stream; /** Buffer type. */ protected Type bufferType; /** * Current position relative to the beginning of the inputable, as a byte * offset. equal to buffer offset + buffer position */ protected long inputablePosition; /** Temporary backing file status: true if temporary. */ protected boolean isTemp; /** Size, in bytes. */ //protected long fileSize; /** Buffer size, in bytes. */ protected int maxBufferSize; /** * Instantiate a new, big-endian <code>AbstractInput</code>. * @param jhove2 JHOVE2 framework object * @param file * Java {@link java.io.File} underlying the inputable * @param isTemp * Temporary file status: true if a temporary file * @throws FileNotFoundException * File not found * @throws IOException * I/O exception instantiating input */ public AbstractInput(JHOVE2 jhove2, File file, boolean isTemp) throws FileNotFoundException, IOException { this(jhove2, file, isTemp, ByteOrder.BIG_ENDIAN); } /** * Instantiate a new <code>AbstractInput</code>. * @param jhove2 JHOVE2 framework object * @param file * Java {@link java.io.File} underlying the inputable * @param isTemp * Temporary file status: true if temporary * @param deleteTempFileOnClose Temporary file deletion flag: if true * delete temporary file on close@param order * Byte order * @throws FileNotFoundException * File not found * @throws IOException * I/O exception instantiating input */ public AbstractInput(JHOVE2 jhove2, File file, boolean isTemp, ByteOrder order) throws FileNotFoundException, IOException { this.file = file; this.isTemp = isTemp; this.byteOrder = order; Invocation inv = jhove2.getInvocation(); this.maxBufferSize = inv.getBufferSize(); this.deleteTempFileOnClose = inv.getDeleteTempFilesOnClose(); if (!file.isDirectory()) { this.stream = new BufferedInputStream(new FileInputStream(file), this.maxBufferSize); //this.fileSize = file.length(); this.inputablePosition = 0L; RandomAccessFile raf = new RandomAccessFile(file, "r"); this.channel = raf.getChannel(); } } /** * Close the inputable. * * @see org.jhove2.core.io.Input#close() */ @Override public void close() throws IOException { this.buffer = null; if (this.stream != null) { this.stream.close(); this.stream = null; } if (this.channel != null) { this.channel.close(); this.channel = null; } if (this.file != null) { if (this.isTemp && this.deleteTempFileOnClose) { this.file.delete(); this.file = null; } } } /** * Get the {@link java.nio.ByteBuffer} underlying the Input. If the * buffer position is locally manipulated, the resetBuffer() method must * be invoked prior to reading the Input. * @see org.jhove2.core.io.Input#resetBuffer() * * @return Buffer underlying the Input * @see org.jhove2.core.io.Input#getBuffer() * @see org.jhove2.core.io.Input#resetBuffer() */ @Override public ByteBuffer getBuffer() { return this.buffer; } /** * Get byte order. * * @return Byte order */ public ByteOrder getByteOrder() { return this.byteOrder; } /** Get buffer type. * @return Buffer type */ @Override public Type getBufferType() { return this.bufferType; } /** * Get current buffer offset from the beginning of the inputable, in bytes. * This offset is the beginning of the buffer. * * Buffer Offset = channel position - buffer size * * Note: the channel position is the location in the inputable where data * will next be read from or written to. * * @return Current buffer offset, in bytes * @see org.jhove2.core.io.Input#getBufferOffset() */ @Override public long getBufferOffset() { return this.bufferOffset; } /** * Get current buffer size, in bytes. * * @return Current buffer size, in bytes * @see org.jhove2.core.io.Input#getBufferSize() */ @Override public int getBufferSize() { return this.bufferSize; } /** * Get the current buffer as a <code>byte[]</code> array. * * @return Byte array * @see org.jhove2.core.io.Input#getByteArray() */ @Override public byte[] getByteArray() { byte[] buffer = null; if (this.buffer != null) { if (this.buffer.hasArray()) { buffer = this.buffer.array(); } else { buffer = new byte[this.buffer.limit()]; /* Instead of using buffer.mark() and reset(), we explicitly * save the current the position and reset it after getting * the next buffer. */ int mark_position = this.buffer.position(); this.buffer.position(0); this.buffer.get(buffer); this.buffer.position(mark_position); } } return buffer; } /** * Get maximum buffer size, in bytes. * * @return Maximum buffer size, in bytes * @see org.jhove2.core.io.Input#getMaxBufferSize() */ @Override public int getMaxBufferSize() { return this.maxBufferSize; } /** * Get the next buffer's worth of data from the channel. * * @return Number of bytes actually read, possibly 0 or -1 if EOF * @throws IOException */ protected long getNextBuffer() throws IOException { if (this.buffer != null && this.channel != null) { this.buffer.clear(); int n = this.channel.read(this.buffer); this.buffer.flip(); this.bufferOffset = this.channel.position() - n; this.bufferSize = n; this.inputablePosition = this.bufferOffset + this.buffer.position(); return this.bufferSize; } return EOF; } /** * Get the current position in the inputable, as a byte offset. * * @return Current position, as a byte offset * @see org.jhove2.core.io.Input#getPosition() */ @Override public long getPosition() { return this.inputablePosition; } /** Get UTF-16BE Unicode character at the current position. This * implicitly advances the current position by two bytes. * @return Character at the current position * @see org.jhove2.core.io.Input#readChar() */ @Override public char readChar() throws EOFException, IOException { char ch; int charValue = 0; int remaining = this.buffer.limit() - this.buffer.position(); if (remaining < 2) { for (int i = 0; i < remaining; i++) { /* * LITTLE_ENDIAN - shift byte value then add to accumlative * value */ if (byteOrder == ByteOrder.LITTLE_ENDIAN) { int in = (((int) this.buffer.get() & 0xff)); in <<= (8 * i); charValue += in; } else { /* BIG_ENDIAN - shift accumulative value then add byte value */ charValue <<= 8; charValue += (((int) this.buffer.get() & 0xff)); } } if (getNextBuffer() == EOF) { throw new EOFException(); } for (int i = remaining; i < 2; i++) { if (byteOrder == ByteOrder.LITTLE_ENDIAN) { int in = (((int) this.buffer.get() & 0xff)); in <<= (8 * i); charValue += in; } else { charValue <<= 8; charValue += (((int) this.buffer.get() & 0xff)); } } ch = (char) charValue; } else { ch = this.buffer.getChar(); } this.inputablePosition += 2L; return ch; } /** * Get signed byte at the current position. This implicitly advances the * current position by one byte. * * @return Signed byte at the current position, or -1 if EOF * @see org.jhove2.core.io.Input#readSignedByte() */ @Override public byte readSignedByte() throws IOException { if (this.buffer.position() >= this.buffer.limit()) { if (getNextBuffer() == EOF) { return EOF; } } byte b = this.buffer.get(); this.inputablePosition += 1; return b; } /** * Get signed (four byte) integer at the current position. This implicitly * advances the current position by four bytes. * * @return signed integer at the current position, or -1 if EOF * @see org.jhove2.core.io.Input#readUnsignedInt() */ @Override public int readSignedInt() throws IOException { int in = 0; int byteValue = 0; int remaining = this.buffer.limit() - this.buffer.position(); if (remaining < 4) { for (int i = 0; i < remaining; i++) { /* * LITTLE_ENDIAN - shift byte value then add to accumlative * value */ if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((int) this.buffer.get() & 0xff)); byteValue <<= (8 * i); in += byteValue; } else { /* BIG_ENDIAN - shift accumulative value then add byte value */ in <<= 8; in += (((int) this.buffer.get() & 0xff)); } } if (getNextBuffer() == EOF) { return EOF; } for (int i = remaining; i < 4; i++) { if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((int) this.buffer.get() & 0xff)); byteValue <<= (8 * i); in += byteValue; } else { in <<= 8; in += (((int) this.buffer.get() & 0xff)); } } } else { in = this.buffer.getInt(); } this.inputablePosition += 4L; return in; } /** * Get signed long integer at the current position. This implicitly advances * the current position by eight bytes. * * @return signed long integer at the current position, or -1 if EOF * @see org.jhove2.core.io.Input#readSignedLong() */ @Override public long readSignedLong() throws IOException { long in = 0; int byteValue = 0; int remaining = this.buffer.limit() - this.buffer.position(); if (remaining < 8) { for (int i = 0; i < remaining; i++) { /* * LITTLE_ENDIAN - shift byte value then add to accumlative * value */ if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((int) this.buffer.get() & 0xff)); byteValue <<= (8 * i); in += byteValue; } else { /* BIG_ENDIAN - shift accumulative value then add byte value */ in <<= 8; in += (((int) this.buffer.get() & 0xff)); } } if (getNextBuffer() == EOF) { return EOF; } for (int i = remaining; i < 8; i++) { if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((int) this.buffer.get() & 0xff)); byteValue <<= (8 * i); in += byteValue; } else { in <<= 8; in += (((int) this.buffer.get() & 0xff)); } } } else { in = this.buffer.getLong(); } this.inputablePosition += 8L; return in; } /** * Get signed double float point at the current position. This implicitly advances * the current position by eight bytes. * * @return signed double floating point at the current position, or -1 if EOF * @see org.jhove2.core.io.Input#readDouble() */ @Override public double readDouble() throws IOException { double in = 0.0F; long longbits = 0; long byteValue = 0; int remaining = this.buffer.limit() - this.buffer.position(); if (remaining < 8) { for (int i = 0; i < remaining; i++) { /* * LITTLE_ENDIAN - shift byte value then add to accumlative * value */ if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = ((long) this.buffer.get() & 0xffL); byteValue <<= (8 * i); longbits += byteValue; } else { /* BIG_ENDIAN - shift accumulative value then add byte value */ longbits <<= 8; longbits += (((long) this.buffer.get() & 0xffL)); } } if (getNextBuffer() == EOF) { return EOF; } for (int i = remaining; i < 8; i++) { if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((int) this.buffer.get() & 0xff)); byteValue <<= (8 * i); longbits += byteValue; } else { longbits <<= 8; longbits += (((long) this.buffer.get() & 0xffL)); } } in = Double.longBitsToDouble(longbits); } else { in = this.buffer.getDouble(); } this.inputablePosition += 8L; return in; } /** * Get signed 32 bit floating point float at the current position. This implicitly advances * the current position by four bytes. * * @return signed 32 bit floating point float at the current position, or -1 if EOF * @see org.jhove2.core.io.Input#readFloat() */ @Override public float readFloat() throws IOException { float in = 0.0F; int intbits = 0; int byteValue = 0; int remaining = this.buffer.limit() - this.buffer.position(); if (remaining < 4) { for (int i = 0; i < remaining; i++) { /* * LITTLE_ENDIAN - shift byte value then add to accumlative * value */ if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = ((int) this.buffer.get() & 0xff); byteValue <<= (8 * i); intbits += byteValue; } else { /* BIG_ENDIAN - shift accumulative value then add byte value */ intbits <<= 8; intbits += (((long) this.buffer.get() & 0xff)); } } if (getNextBuffer() == EOF) { return EOF; } for (int i = remaining; i < 4; i++) { if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((int) this.buffer.get() & 0xff)); byteValue <<= (8 * i); intbits += byteValue; } else { intbits <<= 8; intbits += (((int) this.buffer.get() & 0xff)); } } in = Float.intBitsToFloat(intbits); } else { in = this.buffer.getFloat(); } this.inputablePosition += 4L; return in; } /** * Get signed short integer at the current position. This implicitly * advances the current position by two bytes. * * @return Signed short integer at the current position, or -1 if EOF * @see org.jhove2.core.io.Input#readSignedShort() */ @Override public short readSignedShort() throws IOException { { short in = 0; int byteValue = 0; int remaining = this.buffer.limit() - this.buffer.position(); if (remaining < 2) { for (int i = 0; i < remaining; i++) { /* * LITTLE_ENDIAN - shift byte value then add to accumlative * value */ if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((int) this.buffer.get() & 0xff)); byteValue <<= (8 * i); in += byteValue; } else { /* * BIG_ENDIAN - shift accumulative value then add byte * value */ in <<= 8; in += (((int) this.buffer.get() & 0xff)); } } if (getNextBuffer() == EOF) { return EOF; } for (int i = remaining; i < 2; i++) { if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((int) this.buffer.get() & 0xff)); byteValue <<= (8 * i); in += byteValue; } else { in <<= 8; in += (((int) this.buffer.get() & 0xff)); } } } else { in = this.buffer.getShort(); } this.inputablePosition += 2L; return in; } } /** * Get unsigned byte at the current position. This implicitly advances the * current position by one byte. * * @return Unsigned byte at the current position, or -1 if EOF * @see org.jhove2.core.io.Input#readUnsignedByte() */ @Override public short readUnsignedByte() throws IOException { if (this.buffer.position() >= this.buffer.limit()) { if (getNextBuffer() == EOF) { return EOF; } } short b = this.buffer.get(); b &= 0xffL; this.inputablePosition += 1L; return b; } /** * Get unsigned (four byte) integer at the current position. This implicitly * advances the current position by four bytes. * * @return Unsigned short integer at the current position, or -1 if EOF * @see org.jhove2.core.io.Input#readUnsignedInt() */ @Override public long readUnsignedInt() throws IOException { long in = 0L; long byteValue = 0L; int remaining = this.buffer.limit() - this.buffer.position(); if (remaining < 4) { for (int i = 0; i < remaining; i++) { /* * LITTLE_ENDIAN - shift byte value then add to accumlative * value */ if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((long) this.buffer.get() & 0xffL)); byteValue <<= (8 * i); in += byteValue; } else { /* BIG_ENDIAN - shift accumulative value then add byte value */ in <<= 8; in += (((long) this.buffer.get() & 0xffL)); } } if (getNextBuffer() == EOF) { return EOF; } for (int i = remaining; i < 4; i++) { if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((long) this.buffer.get() & 0xffL)); byteValue <<= (8 * i); in += byteValue; } else { in <<= 8; in += (((long) this.buffer.get() & 0xffL)); } } } else { in = this.buffer.getInt(); in &= 0xffffffffL; } this.inputablePosition += 4L; return in; } /** * Get unsigned short (two byte) integer at the the current position. This * implicitly advances the current position by two bytes. * * @return Unsigned integer at the current position, or -1 if EOF * @see org.jhove2.core.io.Input#readUnsignedShort() */ @Override public int readUnsignedShort() throws IOException { int sh = 0; int byteValue = 0; int remaining = this.buffer.limit() - this.buffer.position(); if (remaining < 2) { for (int i = 0; i < remaining; i++) { /* * LITTLE_ENDIAN - shift byte value then add to accumlative * value */ if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((int) this.buffer.get() & 0xff)); byteValue <<= (8 * i); sh += byteValue; } else { /* BIG_ENDIAN - shift accumulative value then add byte value */ sh <<= 8; sh += (((int) this.buffer.get() & 0xffL)); } } if (getNextBuffer() == EOF) { return EOF; } for (int i = remaining; i < 4; i++) { if (byteOrder == ByteOrder.LITTLE_ENDIAN) { byteValue = (((int) this.buffer.get() & 0xff)); byteValue <<= (8 * i); sh += byteValue; } else { sh <<= 8; sh += (((int) this.buffer.get() & 0xffL)); } } } else { sh = this.buffer.getShort(); sh &= 0xffffL; } this.inputablePosition += 2L; return sh; } /** Reset the buffer position. This method is only necessary after * a buffer has been retrieved via getBuffer() and the position of * that local copy has been manipulated. * @see org.jhove2.core.io.Input#getBuffer() */ @Override public void resetBuffer() { this.buffer.position(0); this.inputablePosition = this.bufferOffset; } /** * Set byte order: big-endian or little-endian. * * @param order * Byte order * @see org.jhove2.core.io.Input#setByteOrder(ByteOrder) */ @Override public void setByteOrder(ByteOrder order) { this.buffer.order(order); this.byteOrder = this.buffer.order(); } /** * Set delete temporary files flag; if true, delete files. * * @param flag * Delete temporary files flag * @see org.jhove2.core.io.Input#setDeleteTempOnClose() */ @Override public void setDeleteTempFileOnClose(boolean flag) { this.deleteTempFileOnClose = flag; } /** * Set the current position, as a byte offset * * @param position * Current position, as a byte offset * @throws IOException * @see org.jhove2.core.io.Input#setPosition(long) */ @Override public void setPosition(long position) throws IOException { /* Determine if the new position is within the current buffer. */ int pos = this.buffer.position(); int lim = this.buffer.limit(); long del = position - this.inputablePosition; if (-del > pos || del > lim - pos - 1) { this.channel.position(position); getNextBuffer(); } else { this.buffer.position(pos + (int) del); this.inputablePosition = position; } } /** Set buffer type. * @param type Buffer type */ @Override public void setBufferType(Type type) { this.bufferType = type; } }