/*
* Copyright 2002-2015 Drew Noakes
*
* More information about this project is available at:
*
* https://drewnoakes.com/code/exif/
* https://github.com/drewnoakes/metadata-extractor
*/
package tim.prune.jpeg.drew;
import java.io.IOException;
/**
* Provides methods to read specific values from a byte array,
* with a consistent, checked exception structure for issues.
*
* @author Drew Noakes https://drewnoakes.com
*/
public class ByteArrayReader
{
private final byte[] _buffer;
private boolean _isMotorolaByteOrder = true;
public ByteArrayReader(byte[] buffer)
{
if (buffer == null)
throw new NullPointerException();
_buffer = buffer;
}
public void setMotorolaByteOrder(boolean motorolaByteOrder)
{
_isMotorolaByteOrder = motorolaByteOrder;
}
public long getLength()
{
return _buffer.length;
}
protected byte getByte(int index)
{
return _buffer[index];
}
protected void validateIndex(int index, int bytesRequested) throws ExifException
{
if (!isValidIndex(index, bytesRequested))
throw new ExifException("Invalid index " + index);
}
private boolean isValidIndex(int index, int bytesRequested)
{
return bytesRequested >= 0
&& index >= 0
&& ((long)index + (long)bytesRequested) <= (long)_buffer.length;
}
public byte[] getBytes(int index, int count) throws ExifException
{
validateIndex(index, count);
byte[] bytes = new byte[count];
System.arraycopy(_buffer, index, bytes, 0, count);
return bytes;
}
/**
* Returns an unsigned 8-bit int calculated from one byte of data at the specified index.
*
* @param index position within the data buffer to read byte
* @return the 8 bit int value, between 0 and 255
* @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
*/
public short getUInt8(int index) throws ExifException
{
validateIndex(index, 1);
return (short) (getByte(index) & 0xFF);
}
/**
* Returns a signed 8-bit int calculated from one byte of data at the specified index.
*
* @param index position within the data buffer to read byte
* @return the 8 bit int value, between 0x00 and 0xFF
* @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
*/
public byte getInt8(int index) throws ExifException
{
validateIndex(index, 1);
return getByte(index);
}
/**
* Returns an unsigned 16-bit int calculated from two bytes of data at the specified index.
*
* @param index position within the data buffer to read first byte
* @return the 16 bit int value, between 0x0000 and 0xFFFF
* @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
*/
public int getUInt16(int index) throws ExifException
{
validateIndex(index, 2);
if (_isMotorolaByteOrder) {
// Motorola - MSB first
return (getByte(index ) << 8 & 0xFF00) |
(getByte(index + 1) & 0xFF);
} else {
// Intel ordering - LSB first
return (getByte(index + 1) << 8 & 0xFF00) |
(getByte(index ) & 0xFF);
}
}
/**
* Returns a signed 16-bit int calculated from two bytes of data at the specified index (MSB, LSB).
*
* @param index position within the data buffer to read first byte
* @return the 16 bit int value, between 0x0000 and 0xFFFF
* @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
*/
public short getInt16(int index) throws ExifException
{
validateIndex(index, 2);
if (_isMotorolaByteOrder) {
// Motorola - MSB first
return (short) (((short)getByte(index ) << 8 & (short)0xFF00) |
((short)getByte(index + 1) & (short)0xFF));
} else {
// Intel ordering - LSB first
return (short) (((short)getByte(index + 1) << 8 & (short)0xFF00) |
((short)getByte(index ) & (short)0xFF));
}
}
/**
* Get a 32-bit unsigned integer from the buffer, returning it as a long.
*
* @param index position within the data buffer to read first byte
* @return the unsigned 32-bit int value as a long, between 0x00000000 and 0xFFFFFFFF
* @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
*/
public long getUInt32(int index) throws ExifException
{
validateIndex(index, 4);
if (_isMotorolaByteOrder) {
// Motorola - MSB first (big endian)
return (((long)getByte(index )) << 24 & 0xFF000000L) |
(((long)getByte(index + 1)) << 16 & 0xFF0000L) |
(((long)getByte(index + 2)) << 8 & 0xFF00L) |
(((long)getByte(index + 3)) & 0xFFL);
} else {
// Intel ordering - LSB first (little endian)
return (((long)getByte(index + 3)) << 24 & 0xFF000000L) |
(((long)getByte(index + 2)) << 16 & 0xFF0000L) |
(((long)getByte(index + 1)) << 8 & 0xFF00L) |
(((long)getByte(index )) & 0xFFL);
}
}
/**
* Returns a signed 32-bit integer from four bytes of data at the specified index the buffer.
*
* @param index position within the data buffer to read first byte
* @return the signed 32 bit int value, between 0x00000000 and 0xFFFFFFFF
* @throws IOException the buffer does not contain enough bytes to service the request, or index is negative
*/
public int getInt32(int index) throws ExifException
{
validateIndex(index, 4);
if (_isMotorolaByteOrder) {
// Motorola - MSB first (big endian)
return (getByte(index ) << 24 & 0xFF000000) |
(getByte(index + 1) << 16 & 0xFF0000) |
(getByte(index + 2) << 8 & 0xFF00) |
(getByte(index + 3) & 0xFF);
} else {
// Intel ordering - LSB first (little endian)
return (getByte(index + 3) << 24 & 0xFF000000) |
(getByte(index + 2) << 16 & 0xFF0000) |
(getByte(index + 1) << 8 & 0xFF00) |
(getByte(index ) & 0xFF);
}
}
/**
* Creates a String from the _data buffer starting at the specified index,
* and ending where byte=='\0'
or where length==maxLength
.
*
* @param index The index within the buffer at which to start reading the string.
* @param maxLengthBytes The maximum number of bytes to read. If a zero-byte is not reached within this limit,
* reading will stop and the string will be truncated to this length.
* @return The read string.
* @throws IOException The buffer does not contain enough bytes to satisfy this request.
*/
public String getNullTerminatedString(int index, int maxLengthBytes) throws ExifException
{
// NOTE currently only really suited to single-byte character strings
byte[] bytes = getBytes(index, maxLengthBytes);
// Count the number of non-null bytes
int length = 0;
while (length < bytes.length && bytes[length] != '\0')
length++;
return new String(bytes, 0, length);
}
public String getString(int index, int bytesRequested) throws ExifException
{
// TODO: validate index
return new String(getBytes(index, bytesRequested));
}
}