--- src/java/org/apache/poi/ss/formula/Formula.java (revision 1796972) +++ src/java/org/apache/poi/ss/formula/Formula.java (working copy) @@ -37,11 +37,12 @@ private static final Formula EMPTY = new Formula(new byte[0], 0); - /** immutable */ - private final byte[] _byteEncoding; - private final int _encodedTokenLen; + /** immutable, lazily populated */ + private byte[] _byteEncoding; + private int _encodedTokenLen = -1; + private Ptg[] _ptgTokens; - private Formula(byte[] byteEncoding, int encodedTokenLen) { + private Formula(final byte[] byteEncoding, final int encodedTokenLen) { _byteEncoding = byteEncoding.clone(); _encodedTokenLen = encodedTokenLen; // if (false) { // set to true to eagerly check Ptg decoding @@ -58,6 +59,14 @@ // } // } } + + private Formula(final Ptg[] ptgs) { + // cachedTokens should only be provided by a source that promises not to mutate the array + // Since this class itself doesn't mutate _ptgTokens, as long as this promise is honored, + // _ptgTokens is immutable. + _ptgTokens = ptgs.clone(); + } + /** * Convenience method for {@link #read(int, LittleEndianInput, int)} */ @@ -78,9 +87,49 @@ } public Ptg[] getTokens() { + if (_ptgTokens == null) { + readPtgTokensFromBytes(); + } + return _ptgTokens; + } + + private byte[] getEncodedBytes() { + if (_byteEncoding == null) { + readBytesFromPtgTokens(); + } + return _byteEncoding; + } + + private int getEncodedTokenLen() { + if (_getEncodedTokenLen <= 0) { + readBytesFromPtgTokens(); + } + return _getEncodedTokenLen; + } + + private void readPtgTokensFromBytes() { + if (_byteEncoding == null || _getEncodedTokenLen <= 0) { + // EMPTY + return; + } LittleEndianInput in = new LittleEndianByteArrayInputStream(_byteEncoding); - return Ptg.readTokens(_encodedTokenLen, in); + _ptgTokens = Ptg.readTokens(_encodedTokenLen, in); } + + private void readBytesFromPtgTokens() { + if (_ptgTokens == null) { + // EMPTY + return; + } + final int totalSize = Ptg.getEncodedSize(_ptgTokens); + final byte[] encodedData = new byte[totalSize]; + Ptg.serializePtgs(ptgs, encodedData, 0); + final int encodedTokenLen = Ptg.getEncodedSizeWithoutArrayData(ptgs); + + _byteEncoding = encodedData; + _encodedTokenLen = encodedTokenLen; + } + /** * Writes The formula encoding is includes: * */ public void serialize(LittleEndianOutput out) { - out.writeShort(_encodedTokenLen); - out.write(_byteEncoding); + out.writeShort(getEncodedTokenLen()); + out.write(getEncodedBytes()); } public void serializeTokens(LittleEndianOutput out) { - out.write(_byteEncoding, 0, _encodedTokenLen); + out.write(getEncodedBytes(), 0, getEncodedTokenLen()); } public void serializeArrayConstantData(LittleEndianOutput out) { - int len = _byteEncoding.length-_encodedTokenLen; - out.write(_byteEncoding, _encodedTokenLen, len); + byte[] bytes = getEncodedBytes(); + int encodedLen = getEncodedTokenLen(); + int len = bytes.length - encodedLen; + out.write(bytes, encodedLen, len); } @@ -113,7 +164,7 @@ * Note - this value is different to tokenDataLength */ public int getEncodedSize() { - return 2 + _byteEncoding.length; + return 2 + getEncodedBytes().length; } /** * This method is often used when the formula length does not appear immediately before @@ -123,7 +174,8 @@ * the leading ushort field, nor any trailing array constant data. */ public int getEncodedTokenSize() { - return _encodedTokenLen; + + return getEncodedTokenLen(); } /** @@ -132,15 +184,11 @@ * @param ptgs may be null * @return Never null (Possibly empty if the supplied ptgs is null) */ - public static Formula create(Ptg[] ptgs) { + public static Formula create(final Ptg[] ptgs) { if (ptgs == null || ptgs.length < 1) { return EMPTY; } - int totalSize = Ptg.getEncodedSize(ptgs); - byte[] encodedData = new byte[totalSize]; - Ptg.serializePtgs(ptgs, encodedData, 0); - int encodedTokenLen = Ptg.getEncodedSizeWithoutArrayData(ptgs); - return new Formula(encodedData, encodedTokenLen); + return new Formula(ptgs); } /** * Gets the {@link Ptg} array from the supplied {@link Formula}. @@ -172,7 +220,7 @@ * belongs to. null if this formula is not part of an array or shared formula. */ public CellReference getExpReference() { - byte[] data = _byteEncoding; + final byte[] data = getEncodedBytes(); if (data.length != 5) { // tExp and tTbl are always 5 bytes long, and the only ptg in the formula return null; @@ -185,11 +233,11 @@ default: return null; } - int firstRow = LittleEndian.getUShort(data, 1); - int firstColumn = LittleEndian.getUShort(data, 3); + final int firstRow = LittleEndian.getUShort(data, 1); + final int firstColumn = LittleEndian.getUShort(data, 3); return new CellReference(firstRow, firstColumn); } public boolean isSame(Formula other) { - return Arrays.equals(_byteEncoding, other._byteEncoding); + return Arrays.equals(getEncodedBytes(), other.getEncodedBytes()); } }