made ArrayPtg immutable

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@836332 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2009-11-15 06:04:03 +00:00
parent d26eb6a54d
commit 5fe8f2bdd9
3 changed files with 132 additions and 89 deletions

View File

@ -43,22 +43,23 @@ public final class ArrayPtg extends Ptg {
*/
public static final int PLAIN_TOKEN_SIZE = 1+RESERVED_FIELD_LEN;
private static final byte[] DEFAULT_RESERVED_DATA = new byte[RESERVED_FIELD_LEN];
// TODO - fix up field visibility and subclasses
private final byte[] field_1_reserved;
// 7 bytes of data (stored as an int, short and byte here)
private final int _reserved0Int;
private final int _reserved1Short;
private final int _reserved2Byte;
// data from these fields comes after the Ptg data of all tokens in current formula
private int token_1_columns;
private short token_2_rows;
private Object[] token_3_arrayValues;
private final int _nColumns;
private final int _nRows;
private final Object[] _arrayValues;
public ArrayPtg(LittleEndianInput in) {
field_1_reserved = new byte[RESERVED_FIELD_LEN];
// TODO - add readFully method to RecordInputStream
for(int i=0; i< RESERVED_FIELD_LEN; i++) {
field_1_reserved[i] = in.readByte();
}
ArrayPtg(int reserved0, int reserved1, int reserved2, int nColumns, int nRows, Object[] arrayValues) {
_reserved0Int = reserved0;
_reserved1Short = reserved1;
_reserved2Byte = reserved2;
_nColumns = nColumns;
_nRows = nRows;
_arrayValues = arrayValues;
}
/**
* @param values2d array values arranged in rows
@ -67,10 +68,10 @@ public final class ArrayPtg extends Ptg {
int nColumns = values2d[0].length;
int nRows = values2d.length;
// convert 2-d to 1-d array (row by row according to getValueIndex())
token_1_columns = (short) nColumns;
token_2_rows = (short) nRows;
_nColumns = (short) nColumns;
_nRows = (short) nRows;
Object[] vv = new Object[token_1_columns * token_2_rows];
Object[] vv = new Object[_nColumns * _nRows];
for (int r=0; r<nRows; r++) {
Object[] rowData = values2d[r];
for (int c=0; c<nColumns; c++) {
@ -78,21 +79,23 @@ public final class ArrayPtg extends Ptg {
}
}
token_3_arrayValues = vv;
field_1_reserved = DEFAULT_RESERVED_DATA;
_arrayValues = vv;
_reserved0Int = 0;
_reserved1Short = 0;
_reserved2Byte = 0;
}
/**
* @return 2-d array (inner index is rowIx, outer index is colIx)
*/
public Object[][] getTokenArrayValues() {
if (token_3_arrayValues == null) {
if (_arrayValues == null) {
throw new IllegalStateException("array values not read yet");
}
Object[][] result = new Object[token_2_rows][token_1_columns];
for (int r = 0; r < token_2_rows; r++) {
Object[][] result = new Object[_nRows][_nColumns];
for (int r = 0; r < _nRows; r++) {
Object[] rowData = result[r];
for (int c = 0; c < token_1_columns; c++) {
rowData[c] = token_3_arrayValues[getValueIndex(c, r)];
for (int c = 0; c < _nColumns; c++) {
rowData[c] = _arrayValues[getValueIndex(c, r)];
}
}
return result;
@ -102,33 +105,12 @@ public final class ArrayPtg extends Ptg {
return false;
}
/**
* Read in the actual token (array) values. This occurs
* AFTER the last Ptg in the expression.
* See page 304-305 of Excel97-2007BinaryFileFormat(xls)Specification.pdf
*/
public void readTokenValues(LittleEndianInput in) {
int nColumns = in.readUByte();
short nRows = in.readShort();
//The token_1_columns and token_2_rows do not follow the documentation.
//The number of physical rows and columns is actually +1 of these values.
//Which is not explicitly documented.
nColumns++;
nRows++;
token_1_columns = nColumns;
token_2_rows = nRows;
int totalCount = nRows * nColumns;
token_3_arrayValues = ConstantValueParser.parse(in, totalCount);
}
public String toString() {
StringBuffer sb = new StringBuffer("[ArrayPtg]\n");
sb.append("nRows = ").append(getRowCount()).append("\n");
sb.append("nCols = ").append(getColumnCount()).append("\n");
if (token_3_arrayValues == null) {
if (_arrayValues == null) {
sb.append(" #values#uninitialised#\n");
} else {
sb.append(" ").append(toFormulaString());
@ -141,36 +123,38 @@ public final class ArrayPtg extends Ptg {
* @return the index into the internal 1D array for the specified column and row
*/
/* package */ int getValueIndex(int colIx, int rowIx) {
if(colIx < 0 || colIx >= token_1_columns) {
if(colIx < 0 || colIx >= _nColumns) {
throw new IllegalArgumentException("Specified colIx (" + colIx
+ ") is outside the allowed range (0.." + (token_1_columns-1) + ")");
+ ") is outside the allowed range (0.." + (_nColumns-1) + ")");
}
if(rowIx < 0 || rowIx >= token_2_rows) {
if(rowIx < 0 || rowIx >= _nRows) {
throw new IllegalArgumentException("Specified rowIx (" + rowIx
+ ") is outside the allowed range (0.." + (token_2_rows-1) + ")");
+ ") is outside the allowed range (0.." + (_nRows-1) + ")");
}
return rowIx * token_1_columns + colIx;
return rowIx * _nColumns + colIx;
}
public void write(LittleEndianOutput out) {
out.writeByte(sid + getPtgClass());
out.write(field_1_reserved);
out.writeInt(_reserved0Int);
out.writeShort(_reserved1Short);
out.writeByte(_reserved2Byte);
}
public int writeTokenValueBytes(LittleEndianOutput out) {
out.writeByte(token_1_columns-1);
out.writeShort(token_2_rows-1);
ConstantValueParser.encode(out, token_3_arrayValues);
return 3 + ConstantValueParser.getEncodedSize(token_3_arrayValues);
out.writeByte(_nColumns-1);
out.writeShort(_nRows-1);
ConstantValueParser.encode(out, _arrayValues);
return 3 + ConstantValueParser.getEncodedSize(_arrayValues);
}
public short getRowCount() {
return token_2_rows;
public int getRowCount() {
return _nRows;
}
public short getColumnCount() {
return (short)token_1_columns;
public int getColumnCount() {
return _nColumns;
}
/** This size includes the size of the array Ptg plus the Array Ptg Token value size*/
@ -178,7 +162,7 @@ public final class ArrayPtg extends Ptg {
return PLAIN_TOKEN_SIZE
// data written after the all tokens:
+ 1 + 2 // column, row
+ ConstantValueParser.getEncodedSize(token_3_arrayValues);
+ ConstantValueParser.getEncodedSize(_arrayValues);
}
public String toFormulaString() {
@ -192,7 +176,7 @@ public final class ArrayPtg extends Ptg {
if (x > 0) {
b.append(",");
}
Object o = token_3_arrayValues[getValueIndex(x, y)];
Object o = _arrayValues[getValueIndex(x, y)];
b.append(getConstantText(o));
}
}
@ -223,4 +207,61 @@ public final class ArrayPtg extends Ptg {
public byte getDefaultOperandClass() {
return Ptg.CLASS_ARRAY;
}
/**
* Represents the initial plain tArray token (without the constant data that trails the whole
* formula). Objects of this class are only temporary and cannot be used as {@link Ptg}s.
* These temporary objects get converted to {@link ArrayPtg} by the
* {@link #finishReading(LittleEndianInput)} method.
*/
static final class Initial extends Ptg {
private final int _reserved0;
private final int _reserved1;
private final int _reserved2;
public Initial(LittleEndianInput in) {
_reserved0 = in.readInt();
_reserved1 = in.readUShort();
_reserved2 = in.readUByte();
}
private static RuntimeException invalid() {
throw new IllegalStateException("This object is a partially initialised tArray, and cannot be used as a Ptg");
}
public byte getDefaultOperandClass() {
throw invalid();
}
public int getSize() {
return PLAIN_TOKEN_SIZE;
}
public boolean isBaseToken() {
return false;
}
public String toFormulaString() {
throw invalid();
}
public void write(LittleEndianOutput out) {
throw invalid();
}
/**
* Read in the actual token (array) values. This occurs
* AFTER the last Ptg in the expression.
* See page 304-305 of Excel97-2007BinaryFileFormat(xls)Specification.pdf
*/
public ArrayPtg finishReading(LittleEndianInput in) {
int nColumns = in.readUByte();
short nRows = in.readShort();
//The token_1_columns and token_2_rows do not follow the documentation.
//The number of physical rows and columns is actually +1 of these values.
//Which is not explicitly documented.
nColumns++;
nRows++;
int totalCount = nRows * nColumns;
Object[] arrayValues = ConstantValueParser.parse(in, totalCount);
ArrayPtg result = new ArrayPtg(_reserved0, _reserved1, _reserved2, nColumns, nRows, arrayValues);
result.setClass(getPtgClass());
return result;
}
}
}

View File

@ -51,28 +51,26 @@ public abstract class Ptg implements Cloneable {
public static Ptg[] readTokens(int size, LittleEndianInput in) {
List<Ptg> temp = new ArrayList<Ptg>(4 + size / 2);
int pos = 0;
List<Ptg> arrayPtgs = null;
boolean hasArrayPtgs = false;
while (pos < size) {
Ptg ptg = Ptg.createPtg(in);
if (ptg instanceof ArrayPtg) {
if (arrayPtgs == null) {
arrayPtgs = new ArrayList<Ptg>(5);
}
arrayPtgs.add(ptg);
pos += ArrayPtg.PLAIN_TOKEN_SIZE;
} else {
pos += ptg.getSize();
if (ptg instanceof ArrayPtg.Initial) {
hasArrayPtgs = true;
}
pos += ptg.getSize();
temp.add(ptg);
}
if(pos != size) {
throw new RuntimeException("Ptg array size mismatch");
}
if (arrayPtgs != null) {
for (int i=0;i<arrayPtgs.size();i++) {
ArrayPtg p = (ArrayPtg)arrayPtgs.get(i);
p.readTokenValues(in);
if (hasArrayPtgs) {
Ptg[] result = toPtgArray(temp);
for (int i=0;i<result.length;i++) {
if (result[i] instanceof ArrayPtg.Initial) {
result[i] = ((ArrayPtg.Initial) result[i]).finishReading(in);
}
}
return result;
}
return toPtgArray(temp);
}
@ -101,7 +99,7 @@ public abstract class Ptg implements Cloneable {
int baseId = id & 0x1F | 0x20;
switch (baseId) {
case ArrayPtg.sid: return new ArrayPtg(in); // 0x20, 0x40, 0x60
case ArrayPtg.sid: return new ArrayPtg.Initial(in);//0x20, 0x40, 0x60
case FuncPtg.sid: return FuncPtg.create(in); // 0x21, 0x41, 0x61
case FuncVarPtg.sid: return FuncVarPtg.create(in);//0x22, 0x42, 0x62
case NamePtg.sid: return new NamePtg(in); // 0x23, 0x43, 0x63

View File

@ -49,14 +49,16 @@ public final class TestArrayPtg extends TestCase {
2, 2, 0, 0, 70, 71, // "FG"
};
private static ArrayPtg create(byte[] initialData, byte[] constantData) {
ArrayPtg.Initial ptgInit = new ArrayPtg.Initial(TestcaseRecordInputStream.createLittleEndian(initialData));
return ptgInit.finishReading(TestcaseRecordInputStream.createLittleEndian(constantData));
}
/**
* Lots of problems with ArrayPtg's encoding of
* Lots of problems with ArrayPtg's decoding and encoding of the element value data
*/
public void testReadWriteTokenValueBytes() {
ArrayPtg ptg = new ArrayPtg(TestcaseRecordInputStream.createLittleEndian(ENCODED_PTG_DATA));
ptg.readTokenValues(TestcaseRecordInputStream.createLittleEndian(ENCODED_CONSTANT_DATA));
ArrayPtg ptg = create(ENCODED_PTG_DATA, ENCODED_CONSTANT_DATA);
assertEquals(3, ptg.getColumnCount());
assertEquals(2, ptg.getRowCount());
Object[][] values = ptg.getTokenArrayValues();
@ -78,12 +80,12 @@ public final class TestArrayPtg extends TestCase {
assertTrue(Arrays.equals(ENCODED_CONSTANT_DATA, outBuf));
}
/**
* Excel stores array elements column by column. This test makes sure POI does the same.
*/
public void testElementOrdering() {
ArrayPtg ptg = new ArrayPtg(TestcaseRecordInputStream.createLittleEndian(ENCODED_PTG_DATA));
ptg.readTokenValues(TestcaseRecordInputStream.createLittleEndian(ENCODED_CONSTANT_DATA));
ArrayPtg ptg = create(ENCODED_PTG_DATA, ENCODED_CONSTANT_DATA);
assertEquals(3, ptg.getColumnCount());
assertEquals(2, ptg.getRowCount());
@ -113,10 +115,7 @@ public final class TestArrayPtg extends TestCase {
}
public void testToFormulaString() {
ArrayPtg ptg = new ArrayPtg(TestcaseRecordInputStream.createLittleEndian(ENCODED_PTG_DATA));
ptg.readTokenValues(TestcaseRecordInputStream.createLittleEndian(ENCODED_CONSTANT_DATA));
ArrayPtg ptg = create(ENCODED_PTG_DATA, ENCODED_CONSTANT_DATA);
String actualFormula;
try {
actualFormula = ptg.toFormulaString();
@ -139,9 +138,7 @@ public final class TestArrayPtg extends TestCase {
}
private static void confirmOperandClassDecoding(byte operandClass) {
byte[] fullData = new byte[ENCODED_PTG_DATA.length + ENCODED_CONSTANT_DATA.length];
System.arraycopy(ENCODED_PTG_DATA, 0, fullData, 0, ENCODED_PTG_DATA.length);
System.arraycopy(ENCODED_CONSTANT_DATA, 0, fullData, ENCODED_PTG_DATA.length, ENCODED_CONSTANT_DATA.length);
byte[] fullData = concat(ENCODED_PTG_DATA, ENCODED_CONSTANT_DATA);
// Force encoded operand class for tArray
fullData[0] = (byte) (ArrayPtg.sid + operandClass);
@ -153,4 +150,11 @@ public final class TestArrayPtg extends TestCase {
ArrayPtg aPtg = (ArrayPtg) ptgs[0];
assertEquals(operandClass, aPtg.getPtgClass());
}
private static byte[] concat(byte[] a, byte[] b) {
byte[] result = new byte[a.length + b.length];
System.arraycopy(a, 0, result, 0, a.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}
}