]> source.dussan.org Git - poi.git/commitdiff
made ArrayPtg immutable
authorJosh Micich <josh@apache.org>
Sun, 15 Nov 2009 06:04:03 +0000 (06:04 +0000)
committerJosh Micich <josh@apache.org>
Sun, 15 Nov 2009 06:04:03 +0000 (06:04 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@836332 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/hssf/record/formula/ArrayPtg.java
src/java/org/apache/poi/hssf/record/formula/Ptg.java
src/testcases/org/apache/poi/hssf/record/formula/TestArrayPtg.java

index fd34081e6f5c04232139123a1f06ed37b9fda38b..0058985ae349333329f10c72abdc87b8a7a2c1c4 100644 (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;
+               }
+       }
 }
index 7e2ad66a466f84bb2a91bccc9711215c74299fef..f2857a7dfa480770c11abfe462e6a52d943e1591 100644 (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
index 586fa6000370f3c32626274bb33fde82be6a3bcc..b7eed27dff80056f85a5f1762966066aff632689 100644 (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;
+       }
 }