Index: src/java/org/apache/poi/hssf/util/LazilyConcatenatedByteArray.java =================================================================== --- src/java/org/apache/poi/hssf/util/LazilyConcatenatedByteArray.java (revision 0) +++ src/java/org/apache/poi/hssf/util/LazilyConcatenatedByteArray.java (revision 10929) @@ -0,0 +1,86 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + + +package org.apache.poi.hssf.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Utility for delaying the concatenation of multiple byte arrays. Doing this up-front + * causes significantly more copying, which for a large number of byte arrays can cost + * a large amount of time. + * + * @author Trejkaz (trejkaz at trypticon.org) + */ +public class LazilyConcatenatedByteArray { + private final List arrays = new ArrayList(1); + + /** + * Clears the array (sets the concatenated length back to zero. + */ + public void clear() { + arrays.clear(); + } + + /** + * Concatenates an array onto the end of our array. + * This is a relatively fast operation. + * + * @param array the array to concatenate. + * @throws IllegalArgumentException if {@code array} is {@code null}. + */ + public void concatenate(byte[] array) { + if (array == null) { + throw new IllegalArgumentException("array cannot be null"); + } + arrays.add(array); + } + + /** + * Gets the concatenated contents as a single byte array. + * + * This is a slower operation, but the concatenated array is stored off as a single + * array again so that subsequent calls will not perform additional copying. + * + * @return the byte array. Returns {@code null} if no data has been placed into it. + */ + public byte[] toArray() { + if (arrays.isEmpty()) { + return null; + } else if (arrays.size() > 1) { + int totalLength = 0; + for (byte[] array : arrays) { + totalLength += array.length; + } + + byte[] concatenated = new byte[totalLength]; + int destPos = 0; + for (byte[] array : arrays) { + System.arraycopy(array, 0, concatenated, destPos, array.length); + destPos += array.length; + } + + arrays.clear(); + arrays.add(concatenated); + } + + return arrays.get(0); + } +} + Index: src/java/org/apache/poi/hssf/record/AbstractEscherHolderRecord.java =================================================================== --- src/java/org/apache/poi/hssf/record/AbstractEscherHolderRecord.java (revision 10923) +++ src/java/org/apache/poi/hssf/record/AbstractEscherHolderRecord.java (revision 10929) @@ -28,6 +28,7 @@ import org.apache.poi.ddf.EscherRecordFactory; import org.apache.poi.ddf.NullEscherSerializationListener; import org.apache.poi.util.LittleEndian; +import org.apache.poi.hssf.util.LazilyConcatenatedByteArray; /** * The escher container record is used to hold escher records. It is abstract and @@ -47,7 +48,7 @@ } private List escherRecords; - private byte[] rawData; + private LazilyConcatenatedByteArray rawDataContainer = new LazilyConcatenatedByteArray(); public AbstractEscherHolderRecord() @@ -60,7 +61,7 @@ escherRecords = new ArrayList(); if (! DESERIALISE ) { - rawData = in.readRemainder(); + rawDataContainer.concatenate(in.readRemainder()); } else { @@ -70,6 +71,7 @@ } protected void convertRawBytesToEscherRecords() { + byte[] rawData = getRawData(); convertToEscherRecords(0, rawData.length, rawData); } private void convertToEscherRecords( int offset, int size, byte[] data ) @@ -109,6 +111,7 @@ { LittleEndian.putShort( data, 0 + offset, getSid() ); LittleEndian.putShort( data, 2 + offset, (short) ( getRecordSize() - 4 ) ); + byte[] rawData = getRawData(); if ( escherRecords.size() == 0 && rawData != null ) { LittleEndian.putShort(data, 0 + offset, getSid()); @@ -129,7 +132,9 @@ } public int getRecordSize() { + byte[] rawData = getRawData(); if (escherRecords.size() == 0 && rawData != null) { + // XXX: It should be possible to derive this without concatenating the array, too. return rawData.length; } int size = 0; @@ -229,30 +234,23 @@ */ public void join( AbstractEscherHolderRecord record ) { - int length = this.rawData.length + record.getRawData().length; - byte[] data = new byte[length]; - System.arraycopy( rawData, 0, data, 0, rawData.length ); - System.arraycopy( record.getRawData(), 0, data, rawData.length, record.getRawData().length ); - rawData = data; + rawDataContainer.concatenate(record.getRawData()); } public void processContinueRecord( byte[] record ) { - int length = this.rawData.length + record.length; - byte[] data = new byte[length]; - System.arraycopy( rawData, 0, data, 0, rawData.length ); - System.arraycopy( record, 0, data, rawData.length, record.length ); - rawData = data; + rawDataContainer.concatenate(record); } public byte[] getRawData() { - return rawData; + return rawDataContainer.toArray(); } public void setRawData( byte[] rawData ) { - this.rawData = rawData; + rawDataContainer.clear(); + rawDataContainer.concatenate(rawData); } /** @@ -260,6 +258,7 @@ */ public void decode() { + byte[] rawData = getRawData(); convertToEscherRecords(0, rawData.length, rawData ); } }