Better bounds checking in RecordInputStream. Removed rarely used methods readShortArray and putShortArray

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@701747 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-10-05 04:43:48 +00:00
parent 0e8dd31f05
commit 794d4051bb
4 changed files with 174 additions and 217 deletions

View File

@ -33,7 +33,7 @@ public class RecordInputStream extends InputStream {
/** Maximum size of a single record (minus the 4 byte header) without a continue*/
public final static short MAX_RECORD_DATA_SIZE = 8224;
private static final int INVALID_SID_VALUE = -1;
private InputStream in;
protected short currentSid;
protected short currentLength = -1;
@ -42,34 +42,34 @@ public class RecordInputStream extends InputStream {
protected byte[] data = new byte[MAX_RECORD_DATA_SIZE];
protected short recordOffset;
protected long pos;
private boolean autoContinue = true;
public RecordInputStream(InputStream in) throws RecordFormatException {
public RecordInputStream(InputStream in) throws RecordFormatException {
this.in = in;
try {
nextSid = LittleEndian.readShort(in);
//Dont increment the pos just yet (technically we are at the start of
//the record stream until nextRecord is called).
//Don't increment the pos just yet (technically we are at the start of
//the record stream until nextRecord is called).
} catch (IOException ex) {
throw new RecordFormatException("Error reading bytes", ex);
}
}
/** This method will read a byte from the current record*/
public int read() {
checkRecordPosition();
byte result = data[recordOffset];
recordOffset += 1;
pos += 1;
return result;
}
/** This method will read a byte from the current record*/
public int read() {
checkRecordPosition(LittleEndian.BYTE_SIZE);
byte result = data[recordOffset];
recordOffset += LittleEndian.BYTE_SIZE;
pos += LittleEndian.BYTE_SIZE;
return result;
}
public short getSid() {
return currentSid;
}
public short getLength() {
return currentLength;
}
@ -85,12 +85,11 @@ public class RecordInputStream extends InputStream {
public boolean hasNextRecord() {
return nextSid != INVALID_SID_VALUE;
}
/** Moves to the next record in the stream.
*
*
* <i>Note: The auto continue flag is reset to true</i>
*/
public void nextRecord() throws RecordFormatException {
if ((currentLength != -1) && (currentLength != recordOffset)) {
System.out.println("WARN. Unread "+remaining()+" bytes of record 0x"+Integer.toHexString(currentSid));
@ -100,7 +99,7 @@ public class RecordInputStream extends InputStream {
autoContinue = true;
try {
recordOffset = 0;
currentLength = LittleEndian.readShort(in);
currentLength = LittleEndian.readShort(in);
if (currentLength > MAX_RECORD_DATA_SIZE)
throw new RecordFormatException("The content of an excel record cannot exceed "+MAX_RECORD_DATA_SIZE+" bytes");
pos += LittleEndian.SHORT_SIZE;
@ -113,138 +112,124 @@ public class RecordInputStream extends InputStream {
// ex45582-22397.xls has one extra byte after the last record
// Excel reads that file OK
}
nextSid = INVALID_SID_VALUE;
nextSid = INVALID_SID_VALUE;
} else {
nextSid = LittleEndian.readShort(in);
if (nextSid == INVALID_SID_VALUE) {
throw new RecordFormatException("Found sid " + nextSid + " after record with sid 0x"
+ Integer.toHexString(currentSid).toUpperCase());
}
}
}
} catch (IOException ex) {
throw new RecordFormatException("Error reading bytes", ex);
}
}
public void setAutoContinue(boolean enable) {
this.autoContinue = enable;
this.autoContinue = enable;
}
public boolean getAutoContinue() {
return autoContinue;
}
protected void checkRecordPosition() {
if (remaining() <= 0) {
if (isContinueNext() && autoContinue) {
nextRecord();
}
else throw new ArrayIndexOutOfBoundsException();
}
}
/**
* Reads an 8 bit, signed value
*/
public byte readByte() {
checkRecordPosition();
byte result = data[recordOffset];
recordOffset += 1;
pos += 1;
return result;
}
/**
* Reads a 16 bit, signed value
*/
public short readShort() {
checkRecordPosition();
short result = LittleEndian.getShort(data, recordOffset);
recordOffset += LittleEndian.SHORT_SIZE;
pos += LittleEndian.SHORT_SIZE;
return result;
}
public int readInt() {
checkRecordPosition();
int result = LittleEndian.getInt(data, recordOffset);
recordOffset += LittleEndian.INT_SIZE;
pos += LittleEndian.INT_SIZE;
return result;
}
private void checkRecordPosition(int requiredByteCount) {
public long readLong() {
checkRecordPosition();
long result = LittleEndian.getLong(data, recordOffset);
recordOffset += LittleEndian.LONG_SIZE;
pos += LittleEndian.LONG_SIZE;
return result;
}
if (remaining() < requiredByteCount) {
if (isContinueNext() && autoContinue) {
nextRecord();
} else {
throw new ArrayIndexOutOfBoundsException();
}
}
}
/**
* Reads an 8 bit, signed value
*/
public byte readByte() {
checkRecordPosition(LittleEndian.BYTE_SIZE);
byte result = data[recordOffset];
recordOffset += LittleEndian.BYTE_SIZE;
pos += LittleEndian.BYTE_SIZE;
return result;
}
/**
* Reads a 16 bit, signed value
*/
public short readShort() {
checkRecordPosition(LittleEndian.SHORT_SIZE);
short result = LittleEndian.getShort(data, recordOffset);
recordOffset += LittleEndian.SHORT_SIZE;
pos += LittleEndian.SHORT_SIZE;
return result;
}
public int readInt() {
checkRecordPosition(LittleEndian.INT_SIZE);
int result = LittleEndian.getInt(data, recordOffset);
recordOffset += LittleEndian.INT_SIZE;
pos += LittleEndian.INT_SIZE;
return result;
}
public long readLong() {
checkRecordPosition(LittleEndian.LONG_SIZE);
long result = LittleEndian.getLong(data, recordOffset);
recordOffset += LittleEndian.LONG_SIZE;
pos += LittleEndian.LONG_SIZE;
return result;
}
/**
* Reads an 8 bit, unsigned value
*/
public short readUByte() {
return (short) (readByte() & 0x00FF);
}
/**
* Reads a 16 bit, unsigned value.
* @return
*/
public int readUShort() {
checkRecordPosition(LittleEndian.SHORT_SIZE);
int result = LittleEndian.getUShort(data, recordOffset);
recordOffset += LittleEndian.SHORT_SIZE;
pos += LittleEndian.SHORT_SIZE;
return result;
}
public double readDouble() {
checkRecordPosition(LittleEndian.DOUBLE_SIZE);
long valueLongBits = LittleEndian.getLong(data, recordOffset);
double result = Double.longBitsToDouble(valueLongBits);
if (Double.isNaN(result)) {
throw new RuntimeException("Did not expect to read NaN"); // (Because Excel typically doesn't write NaN
}
recordOffset += LittleEndian.DOUBLE_SIZE;
pos += LittleEndian.DOUBLE_SIZE;
return result;
}
/**
* Reads an 8 bit, unsigned value
*/
public short readUByte() {
short s = readByte();
if(s < 0) {
s += 256;
}
return s;
}
/**
* Reads a 16 bit,un- signed value.
* @return
*/
public int readUShort() {
checkRecordPosition();
int result = LittleEndian.getUShort(data, recordOffset);
recordOffset += LittleEndian.SHORT_SIZE;
pos += LittleEndian.SHORT_SIZE;
return result;
}
public double readDouble() {
checkRecordPosition();
long valueLongBits = LittleEndian.getLong(data, recordOffset);
double result = Double.longBitsToDouble(valueLongBits);
if (Double.isNaN(result)) {
throw new RuntimeException("Did not expect to read NaN");
}
recordOffset += LittleEndian.DOUBLE_SIZE;
pos += LittleEndian.DOUBLE_SIZE;
return result;
}
public short[] readShortArray() {
checkRecordPosition();
short[] arr = LittleEndian.getShortArray(data, recordOffset);
final int size = (2 * (arr.length +1));
recordOffset += size;
pos += size;
return arr;
}
/**
* given a byte array of 16-bit unicode characters, compress to 8-bit and
* return a string
*
* { 0x16, 0x00 } -0x16
*
* given a byte array of 16-bit unicode characters, compress to 8-bit and
* return a string
*
* { 0x16, 0x00 } -0x16
*
* @param length the length of the final string
* @return the converted string
* @exception IllegalArgumentException if len is too large (i.e.,
* there is not enough data in string to create a String of that
* length)
*/
* there is not enough data in string to create a String of that
* length)
*/
public String readUnicodeLEString(int length) {
if ((length < 0) || (((remaining() / 2) < length) && !isContinueNext())) {
throw new IllegalArgumentException("Illegal length - asked for " + length + " but only " + (remaining()/2) + " left!");
@ -258,11 +243,11 @@ public class RecordInputStream extends InputStream {
if(compressByte != 1) throw new IllegalArgumentException("compressByte in continue records must be 1 while reading unicode LE string");
}
char ch = (char)readShort();
buf.append(ch);
buf.append(ch);
}
return buf.toString();
}
public String readCompressedUnicode(int length) {
if ((length < 0) || ((remaining() < length) && !isContinueNext())) {
throw new IllegalArgumentException("Illegal length " + length);
@ -277,23 +262,23 @@ public class RecordInputStream extends InputStream {
}
byte b = readByte();
char ch = (char)(0x00FF & b); // avoid sex
buf.append(ch);
buf.append(ch);
}
return buf.toString();
return buf.toString();
}
/** Returns an excel style unicode string from the bytes reminaing in the record.
* <i>Note:</i> Unicode strings differ from <b>normal</b> strings due to the addition of
* formatting information.
*
*
* @return The unicode string representation of the remaining bytes.
*/
public UnicodeString readUnicodeString() {
return new UnicodeString(this);
}
/** Returns the remaining bytes for the current record.
*
*
* @return The remaining bytes of the current record.
*/
public byte[] readRemainder() {
@ -304,39 +289,39 @@ public class RecordInputStream extends InputStream {
pos += size;
return result;
}
/** Reads all byte data for the current record, including any
* that overlaps into any following continue records.
*
*
* @deprecated Best to write a input stream that wraps this one where there is
* special sub record that may overlap continue records.
*/
*/
public byte[] readAllContinuedRemainder() {
//Using a ByteArrayOutputStream is just an easy way to get a
//growable array of the data.
ByteArrayOutputStream out = new ByteArrayOutputStream(2*MAX_RECORD_DATA_SIZE);
while (isContinueNext()) {
byte[] b = readRemainder();
byte[] b = readRemainder();
out.write(b, 0, b.length);
nextRecord();
}
byte[] b = readRemainder();
out.write(b, 0, b.length);
byte[] b = readRemainder();
out.write(b, 0, b.length);
return out.toByteArray();
}
/** The remaining number of bytes in the <i>current</i> record.
*
*
* @return The number of bytes remaining in the current record
*/
public int remaining() {
return (currentLength - recordOffset);
}
/** Returns true iif a Continue record is next in the excel stream
*
/** Returns true iif a Continue record is next in the excel stream
*
* @return True when a ContinueRecord is next.
*/
public boolean isContinueNext() {

View File

@ -1,4 +1,3 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@ -15,66 +14,66 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record;
import org.apache.poi.util.*;
import org.apache.poi.util.LittleEndian;
/**
* The series list record defines the series displayed as an overlay to the main chart record.
* NOTE: This source is automatically generated please do not modify this file. Either subclass or
* remove the record in src/records/definitions.
*
* The series list record defines the series displayed as an overlay to the main chart record.<br/>
* TODO - does this record (0x1016) really exist. It doesn't seem to be referenced in either the OOO or MS doc
*
* @author Glen Stampoultzis (glens at apache.org)
*/
public class SeriesListRecord
extends Record
{
public final static short sid = 0x1016;
public final class SeriesListRecord extends Record {
public final static short sid = 0x1016;
private short[] field_1_seriesNumbers;
public SeriesListRecord()
{
public SeriesListRecord(short[] seriesNumbers) {
field_1_seriesNumbers = seriesNumbers;
}
public SeriesListRecord(RecordInputStream in)
{
field_1_seriesNumbers = in.readShortArray();
public SeriesListRecord(RecordInputStream in) {
int nItems = in.readUShort();
short[] ss = new short[nItems];
for (int i = 0; i < nItems; i++) {
ss[i] = in.readShort();
}
field_1_seriesNumbers = ss;
}
public String toString()
{
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("[SERIESLIST]\n");
buffer.append(" .seriesNumbers = ")
.append(" (").append( getSeriesNumbers() ).append(" )");
buffer.append(System.getProperty("line.separator"));
buffer.append(" .seriesNumbers= ").append(" (").append( getSeriesNumbers() ).append(" )");
buffer.append("\n");
buffer.append("[/SERIESLIST]\n");
return buffer.toString();
}
public int serialize(int offset, byte[] data)
{
int pos = 0;
public int serialize(int offset, byte[] data) {
LittleEndian.putShort(data, 0 + offset, sid);
LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4));
int nItems = field_1_seriesNumbers.length;
int dataSize = 2 + 2 * nItems;
LittleEndian.putUShort(data, 0 + offset, sid);
LittleEndian.putUShort(data, 2 + offset, dataSize);
LittleEndian.putShortArray(data, 4 + offset + pos, field_1_seriesNumbers);
LittleEndian.putUShort(data, 4 + offset, nItems);
int pos = offset + 6;
for (int i = 0; i < nItems; i++) {
LittleEndian.putUShort(data, pos, field_1_seriesNumbers[i]);
pos += 2;
}
return getRecordSize();
return 4 + dataSize;
}
/**
* Size of record (exluding 4 byte header)
*/
public int getRecordSize()
{
return 4 + field_1_seriesNumbers.length * 2 + 2;
@ -86,34 +85,23 @@ public class SeriesListRecord
}
public Object clone() {
SeriesListRecord rec = new SeriesListRecord();
rec.field_1_seriesNumbers = field_1_seriesNumbers;
return rec;
return new SeriesListRecord((short[]) field_1_seriesNumbers.clone());
}
/**
* Get the series numbers field for the SeriesList record.
*/
public short[] getSeriesNumbers()
{
public short[] getSeriesNumbers() {
return field_1_seriesNumbers;
}
/**
* Set the series numbers field for the SeriesList record.
*/
public void setSeriesNumbers(short[] field_1_seriesNumbers)
{
public void setSeriesNumbers(short[] field_1_seriesNumbers) {
this.field_1_seriesNumbers = field_1_seriesNumbers;
}
} // END OF CLASS
}

View File

@ -250,20 +250,6 @@ public final class LittleEndian implements LittleEndianConsts {
putNumber(data, offset, value, LittleEndianConsts.BYTE_SIZE);
}
/**
* put a array of shorts into a byte array
*
*@param data the byte array
*@param offset a starting offset into the byte array
*@param value the short array
*/
public static void putShortArray(final byte[] data, final int offset, final short[] value) {
putNumber(data, offset, value.length, SHORT_SIZE);
for (int i = 0; i < value.length; i++) {
putNumber(data, offset + 2 + (i * 2), value[i], SHORT_SIZE);
}
}
/**
* put an unsigned short value into a byte array
*

View File

@ -29,7 +29,7 @@ import junit.framework.TestCase;
* @author Glen Stampoultzis (glens at apache.org)
*/
public final class TestSeriesListRecord extends TestCase {
byte[] data = new byte[] {
private static final byte[] data = {
(byte)0x02,(byte)0x00,(byte)0x01,(byte)0x20,(byte)0xff,(byte)0xf0
};
@ -43,10 +43,8 @@ public final class TestSeriesListRecord extends TestCase {
assertEquals( 4 + 6, record.getRecordSize() );
}
public void testStore()
{
SeriesListRecord record = new SeriesListRecord();
record.setSeriesNumbers( new short[] { (short)0x2001, (short)0xf0ff } );
public void testStore() {
SeriesListRecord record = new SeriesListRecord(new short[] { (short)0x2001, (short)0xf0ff } );
byte [] recordBytes = record.serialize();
assertEquals(recordBytes.length - 4, data.length);