/********************************************************************* * * Copyright (C) 2002 Andrew Khan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ***************************************************************************/ package jxl.write.biff; import java.io.OutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.HashMap; import common.Assert; import common.Logger; import jxl.biff.BaseCompoundFile; import jxl.biff.IntegerHelper; import jxl.read.biff.BiffException; /** * Writes out a compound file * * Header block is -1 * Excel data is e..n (where is the head extension blocks, normally 0 and * n is at least 8) * Summary information (8 blocks) * Document summary (8 blocks) * BBD is block p..q (where p=e+n+16 and q-p+1 is the number of BBD blocks) * Property storage block is q+b...r (normally 1 block) (where b is the number * of BBD blocks) */ final class CompoundFile extends BaseCompoundFile { /** * The logger */ private static Logger logger = Logger.getLogger(CompoundFile.class); /** * The stream to which the jumbled up data is written to */ private OutputStream out; /** * The organized biff records which form the actual excel data */ private byte[] excelData; /** * The size of the array */ private int size; /** * The size the excel data should be in order to comply with the * general compound file format */ private int requiredSize; /** * The number of blocks it takes to store the big block depot */ private int numBigBlockDepotBlocks; /** * The number of blocks it takes to store the small block depot chain */ private int numSmallBlockDepotChainBlocks; /** * The number of blocks it takes to store the small block depot */ private int numSmallBlockDepotBlocks; /** * The number of extension blocks required for the header to describe * the BBD */ private int numExtensionBlocks; /** * The extension block for the header */ private int extensionBlock; /** * The number of blocks it takes to store the excel data */ private int excelDataBlocks; /** * The start block of the root entry */ private int rootStartBlock; /** * The start block of the excel data */ private int excelDataStartBlock; /** * The start block of the big block depot */ private int bbdStartBlock; /** * The number of big blocks required for additional property sets */ private int additionalPropertyBlocks; /** * The total number of property sets in this compound file */ private int numPropertySets; /** * The number of blocks required to store the root entry property sets * and small block depot */ private int numRootEntryBlocks; /** * The list of additional, non standard property sets names */ private ArrayList additionalPropertySets; /** * A hash map of the original property sets keyed on name */ private HashMap readPropertySets; /** * The array of standard property set mappings */ private int[] standardPropertySetMappings; private ReadPropertyStorage rootEntryPropertySet; /** * Structure used to store the property set and the data */ private static final class ReadPropertyStorage { PropertyStorage propertyStorage; byte[] data; int number; ReadPropertyStorage(PropertyStorage ps, byte[] d, int n) { propertyStorage = ps; data = d; number = n; } } // The following member variables are used across methods when // writing out the big block depot /** * The current position within the bbd. Used when writing out the * BBD */ private int bbdPos; /** * The current bbd block */ private byte[] bigBlockDepot; /** * Constructor * * @param l the length of the data * @param os the output stream to write to * @param data the excel data * @param rcf the read compound */ public CompoundFile(byte[] data, int l, OutputStream os, jxl.read.biff.CompoundFile rcf) throws CopyAdditionalPropertySetsException, IOException { super(); size = l; excelData = data; readAdditionalPropertySets(rcf); numRootEntryBlocks = 1; numPropertySets = 4 + (additionalPropertySets != null ? additionalPropertySets.size() : 0); if (additionalPropertySets != null) { /* try { rootEntryPropertySet = new ReadPropertyStorage(rcf.getPropertySet(ROOT_ENTRY_NAME), rcf.getStream(ROOT_ENTRY_NAME), 0); int blocks = rootEntryPropertySet.data.length >= SMALL_BLOCK_THRESHOLD ? getBigBlocksRequired(rootEntryPropertySet.data.length) : SMALL_BLOCK_THRESHOLD / BIG_BLOCK_SIZE; additionalPropertyBlocks += blocks; } catch(BiffException e) { e.printStackTrace(); } */ numRootEntryBlocks += getBigBlocksRequired (additionalPropertySets.size() * PROPERTY_STORAGE_BLOCK_SIZE); } logger.debug("root entry requires " + numRootEntryBlocks + " blocks"); int blocks = getBigBlocksRequired(l); // First pad the data out so that it fits nicely into a whole number // of blocks if (l < SMALL_BLOCK_THRESHOLD) { requiredSize = SMALL_BLOCK_THRESHOLD; } else { requiredSize = blocks * BIG_BLOCK_SIZE; } out = os; // logger.debug("smallBlockDepot requires " + numSmallBlockDepotBlocks + " big blocks"); // Do the calculations excelDataBlocks = requiredSize/BIG_BLOCK_SIZE; numBigBlockDepotBlocks = 1; int blockChainLength = (BIG_BLOCK_SIZE - BIG_BLOCK_DEPOT_BLOCKS_POS)/4; int startTotalBlocks = excelDataBlocks + 8 + // summary block 8 + // document information additionalPropertyBlocks + numRootEntryBlocks; int totalBlocks = startTotalBlocks + numBigBlockDepotBlocks; // Calculate the number of BBD blocks needed to hold this info numBigBlockDepotBlocks = (int) Math.ceil( (double) totalBlocks / (double) (BIG_BLOCK_SIZE/4)); // Does this affect the total? totalBlocks = startTotalBlocks + numBigBlockDepotBlocks; // And recalculate numBigBlockDepotBlocks = (int) Math.ceil( (double) totalBlocks / (double) (BIG_BLOCK_SIZE/4)); // Does this affect the total? totalBlocks = startTotalBlocks + numBigBlockDepotBlocks; // See if the excel bbd chain can fit into the header block. // Remember to allow for the end of chain indicator if (numBigBlockDepotBlocks > blockChainLength - 1 ) { // Sod it - we need an extension block. We have to go through // the whole tiresome calculation again extensionBlock = 0; // Compute the number of extension blocks int bbdBlocksLeft = numBigBlockDepotBlocks - blockChainLength + 1; numExtensionBlocks = (int) Math.ceil((double) bbdBlocksLeft / (double) (BIG_BLOCK_SIZE/4 - 1)); // Modify the total number of blocks required and recalculate the // the number of bbd blocks totalBlocks = startTotalBlocks + numExtensionBlocks + numBigBlockDepotBlocks; numBigBlockDepotBlocks = (int) Math.ceil( (double) totalBlocks / (double) (BIG_BLOCK_SIZE/4)); // The final total totalBlocks = startTotalBlocks + numExtensionBlocks + numBigBlockDepotBlocks; } else { extensionBlock = -2; numExtensionBlocks = 0; } // Set the excel data start block to be after the header (and // its extensions) excelDataStartBlock = numExtensionBlocks; logger.debug("excelDataStartBlock " + excelDataStartBlock); // Set the bbd start block to be after all the excel data bbdStartBlock = excelDataStartBlock + excelDataBlocks + additionalPropertyBlocks + 16; logger.debug("bbdStartBlock " + bbdStartBlock); // Set the root start block to be after all the big block depot blocks rootStartBlock = bbdStartBlock + numBigBlockDepotBlocks; if (totalBlocks != rootStartBlock + numRootEntryBlocks) { logger.warn("Root start block and total blocks are inconsistent " + " generated file may be corrupt"); logger.warn("RootStartBlock " + rootStartBlock + " totalBlocks " + totalBlocks); } } /** * Reads the additional property sets from the read in compound file * * @return the number of blocks needed to store these property sets */ private void readAdditionalPropertySets (jxl.read.biff.CompoundFile readCompoundFile) throws CopyAdditionalPropertySetsException, IOException { if (readCompoundFile == null) { return; } additionalPropertySets = new ArrayList(); readPropertySets = new HashMap(); String[] psnames = readCompoundFile.getPropertySetNames(); int blocksRequired = 0; standardPropertySetMappings = new int[STANDARD_PROPERTY_SETS.length]; for (int i = 0 ; i < psnames.length ; i++) { // Add it to the hash map for later PropertyStorage ps = readCompoundFile.getPropertySet(psnames[i]); // If the name is non standard, then retrieve the property set // information boolean standard = false; for (int j = 0 ; j < STANDARD_PROPERTY_SETS.length && !standard ; j++) { if (psnames[i].equalsIgnoreCase(STANDARD_PROPERTY_SETS[j])) { standard = true; ReadPropertyStorage rps = new ReadPropertyStorage(ps, null, i); readPropertySets.put(psnames[i], rps); } } if (!standard) { try { byte[] data = null; if (ps.size > 0 ) { data = readCompoundFile.getStream(ps.name); } else { data = new byte[0]; } ReadPropertyStorage rps = new ReadPropertyStorage(ps, data, i); readPropertySets.put(psnames[i], rps); additionalPropertySets.add(rps); int blocks = data.length >= SMALL_BLOCK_THRESHOLD ? getBigBlocksRequired(data.length) : SMALL_BLOCK_THRESHOLD / BIG_BLOCK_SIZE; blocksRequired += blocks; } catch (BiffException e) { logger.error(e); throw new CopyAdditionalPropertySetsException(); } } } additionalPropertyBlocks = blocksRequired; } /** * Writes out the excel file in OLE compound file format * * @exception IOException */ public void write() throws IOException { writeHeader(); writeExcelData(); writeDocumentSummaryData(); writeSummaryData(); writeAdditionalPropertySets(); writeBigBlockDepot(); writePropertySets(); // Don't flush or close the stream - this is handled by the enclosing File // object } /** * Writes out any additional property sets */ private void writeAdditionalPropertySets() throws IOException { if (additionalPropertySets == null) { return; } /* logger.debug("Writing property set " + rootEntryPropertySet.propertyStorage.name); int numBlocks2 = rootEntryPropertySet.data.length >= SMALL_BLOCK_THRESHOLD ? getBigBlocksRequired(rootEntryPropertySet.data.length) : SMALL_BLOCK_THRESHOLD / BIG_BLOCK_SIZE; int requiredSize2 = numBlocks2 * BIG_BLOCK_SIZE; out.write(rootEntryPropertySet.data, 0, rootEntryPropertySet.data.length); byte[] padding2 = new byte[requiredSize2 - rootEntryPropertySet.data.length]; out.write(padding2, 0, padding2.length); logger.debug("data length " + rootEntryPropertySet.data.length + " Padding " + padding2.length); */ for (Iterator i = additionalPropertySets.iterator(); i.hasNext() ;) { ReadPropertyStorage rps = (ReadPropertyStorage) i.next(); byte[] data = rps.data; logger.debug("Writing property set " + rps.propertyStorage.name); int numBlocks = data.length >= SMALL_BLOCK_THRESHOLD ? getBigBlocksRequired(data.length) : SMALL_BLOCK_THRESHOLD / BIG_BLOCK_SIZE; int requiredSize = numBlocks * BIG_BLOCK_SIZE; out.write(data, 0, data.length); byte[] padding = new byte[requiredSize - data.length]; out.write(padding, 0, padding.length); } } /** * Writes out the excel data, padding it out with empty bytes as * necessary * Also write out empty * * @exception IOException */ private void writeExcelData() throws IOException { logger.debug("num excel data blocks " + excelDataBlocks + " excelData size " + requiredSize); out.write(excelData, 0, size); byte[] padding = new byte[requiredSize - size]; out.write(padding); } /** * Write out the document summary data. This is just blank * * @exception IOException */ private void writeDocumentSummaryData() throws IOException { byte[] padding = new byte[SMALL_BLOCK_THRESHOLD]; // Write out the summary information out.write(padding); } /** * Write out the summary data. This is just blank * * @exception IOException */ private void writeSummaryData() throws IOException { byte[] padding = new byte[SMALL_BLOCK_THRESHOLD]; // Write out the summary information out.write(padding); } /** * Writes the compound file header * * @exception IOException */ private void writeHeader() throws IOException { logger.debug("num extensions blocks for header: " + numExtensionBlocks); // Build up the header array byte[] headerBlock = new byte[BIG_BLOCK_SIZE]; byte[] extensionBlockData = new byte[BIG_BLOCK_SIZE * numExtensionBlocks]; // Copy in the identifier System.arraycopy(IDENTIFIER, 0, headerBlock, 0, IDENTIFIER.length); // Copy in some magic values - no idea what they mean headerBlock[0x18] = 0x3e; headerBlock[0x1a] = 0x3; headerBlock[0x1c] = (byte) 0xfe; headerBlock[0x1d] = (byte) 0xff; headerBlock[0x1e] = 0x9; headerBlock[0x20] = 0x6; headerBlock[0x39] = 0x10; // Set the number of BBD blocks IntegerHelper.getFourBytes(numBigBlockDepotBlocks, headerBlock, NUM_BIG_BLOCK_DEPOT_BLOCKS_POS); // Set the small block depot chain to -2 ie. no small block chain IntegerHelper.getFourBytes(-2, headerBlock, SMALL_BLOCK_DEPOT_BLOCK_POS); // Set the extension block IntegerHelper.getFourBytes(extensionBlock, headerBlock, EXTENSION_BLOCK_POS); // Set the number of extension blocks to be the number of BBD blocks - 1 IntegerHelper.getFourBytes(numExtensionBlocks, headerBlock, NUM_EXTENSION_BLOCK_POS); // Set the root start block IntegerHelper.getFourBytes(rootStartBlock, headerBlock, ROOT_START_BLOCK_POS); // Set the block numbers for the BBD. Set the BBD running // after the excel data and summary information int pos = BIG_BLOCK_DEPOT_BLOCKS_POS; // See how many blocks fit into the header int blocksToWrite = Math.min(numBigBlockDepotBlocks, (BIG_BLOCK_SIZE - BIG_BLOCK_DEPOT_BLOCKS_POS)/4); int extensionBlock = 0; int blocksWritten = 0; for (int i = 0 ; i < blocksToWrite; i++) { IntegerHelper.getFourBytes(bbdStartBlock + i, headerBlock, pos); pos += 4; blocksWritten++; } // Pad out the rest of the header with blanks for (int i = pos; i < BIG_BLOCK_SIZE; i++) { headerBlock[i] = (byte) 0xff; } out.write(headerBlock); // Write out the extension blocks pos = 0; for (int extBlock = 0; extBlock < numExtensionBlocks; extBlock++) { blocksToWrite = Math.min(numBigBlockDepotBlocks - blocksWritten, BIG_BLOCK_SIZE/4 -1); for(int j = 0 ; j < blocksToWrite; j++) { IntegerHelper.getFourBytes(bbdStartBlock + blocksWritten + j, extensionBlockData, pos); pos += 4; } blocksWritten += blocksToWrite; // Indicate the next block, or the termination of the chain int nextBlock = (blocksWritten == numBigBlockDepotBlocks) ? -2 : extBlock+1 ; IntegerHelper.getFourBytes(nextBlock, extensionBlockData, pos); pos +=4; } if (numExtensionBlocks > 0) { // Pad out the rest of the extension block with blanks for (int i = pos; i < extensionBlockData.length; i++) { extensionBlockData[i] = (byte) 0xff; } out.write(extensionBlockData); } } /** * Checks that the data can fit into the current BBD block. If not, * then it moves on to the next block * * @exception IOException */ private void checkBbdPos() throws IOException { if (bbdPos >= BIG_BLOCK_SIZE) { // Write out the extension block. This will simply be the next block out.write(bigBlockDepot); // Create a new block bigBlockDepot = new byte[BIG_BLOCK_SIZE]; bbdPos = 0; } } /** * Writes out the big block chain * * @param startBlock the starting block of the big block chain * @param numBlocks the number of blocks in the chain * @exception IOException */ private void writeBlockChain(int startBlock, int numBlocks) throws IOException { int blocksToWrite = numBlocks - 1; int blockNumber = startBlock + 1; while (blocksToWrite > 0) { int bbdBlocks = Math.min(blocksToWrite, (BIG_BLOCK_SIZE - bbdPos)/4); for (int i = 0 ; i < bbdBlocks; i++) { IntegerHelper.getFourBytes(blockNumber, bigBlockDepot, bbdPos); bbdPos +=4 ; blockNumber++; } blocksToWrite -= bbdBlocks; checkBbdPos(); } // Write the end of the block chain IntegerHelper.getFourBytes(-2, bigBlockDepot, bbdPos); bbdPos += 4; checkBbdPos(); } /** * Writes the block chains for the additional property sets * * @exception IOException */ private void writeAdditionalPropertySetBlockChains() throws IOException { if (additionalPropertySets == null) { return; } int blockNumber = excelDataStartBlock + excelDataBlocks + 16; /* int numBlocks2 = rootEntryPropertySet.data.length >= SMALL_BLOCK_THRESHOLD ? getBigBlocksRequired(rootEntryPropertySet.data.length) : SMALL_BLOCK_THRESHOLD / BIG_BLOCK_SIZE; String psname2 = rootEntryPropertySet.propertyStorage.name; logger.debug("writing big block chain for " + psname2 + " block " + blockNumber + " numBlocks " + numBlocks2); writeBlockChain(blockNumber, numBlocks2); blockNumber += numBlocks2; */ for (Iterator i = additionalPropertySets.iterator(); i.hasNext() ; ) { ReadPropertyStorage rps = (ReadPropertyStorage) i.next(); int numBlocks = rps.data.length >= SMALL_BLOCK_THRESHOLD ? getBigBlocksRequired(rps.data.length) : SMALL_BLOCK_THRESHOLD / BIG_BLOCK_SIZE; String psname = rps.propertyStorage.name; logger.debug("writing big block chain for " + psname + " block " + blockNumber + " numBlocks " + numBlocks); writeBlockChain(blockNumber, numBlocks); blockNumber += numBlocks; } } /** * Writes out the Big Block Depot * * @exception IOException */ private void writeBigBlockDepot() throws IOException { // This is after the excel data, the summary information, the // big block property sets and the small block depot bigBlockDepot = new byte[BIG_BLOCK_SIZE]; bbdPos = 0; // Write out the extension blocks, indicating them as special blocks for (int i = 0 ; i < numExtensionBlocks; i++) { IntegerHelper.getFourBytes(-3, bigBlockDepot, bbdPos); bbdPos += 4; checkBbdPos(); } writeBlockChain(excelDataStartBlock, excelDataBlocks); // The excel data has been written. Now write out the rest of it // Write the block chain for the summary information int summaryInfoBlock = excelDataStartBlock + excelDataBlocks + additionalPropertyBlocks; for (int i = summaryInfoBlock; i < summaryInfoBlock + 7; i++) { IntegerHelper.getFourBytes(i + 1, bigBlockDepot, bbdPos); bbdPos +=4 ; checkBbdPos(); } // Write the end of the block chain for the summary info block IntegerHelper.getFourBytes(-2, bigBlockDepot, bbdPos); bbdPos += 4; checkBbdPos(); // Write the block chain for the document summary information for (int i = summaryInfoBlock + 8; i < summaryInfoBlock + 15; i++) { IntegerHelper.getFourBytes(i + 1, bigBlockDepot, bbdPos); bbdPos +=4 ; checkBbdPos(); } // Write the end of the block chain for the document summary IntegerHelper.getFourBytes(-2, bigBlockDepot, bbdPos); bbdPos += 4; checkBbdPos(); // Write out the block chain for the copied property sets, if present writeAdditionalPropertySetBlockChains(); // The Big Block Depot immediately follows the document summary. Denote // these as a special block for (int i = 0; i < numBigBlockDepotBlocks; i++) { IntegerHelper.getFourBytes(-3, bigBlockDepot, bbdPos); bbdPos += 4; checkBbdPos(); } // Write the root entry writeBlockChain(rootStartBlock, numRootEntryBlocks); // Pad out the remainder of the block if (bbdPos != 0) { for (int i = bbdPos; i < BIG_BLOCK_SIZE; i++) { bigBlockDepot[i] = (byte) 0xff; } out.write(bigBlockDepot); } } /** * Calculates the number of big blocks required to store data of the * specified length * * @param length the length of the data * @return the number of big blocks required to store the data */ private int getBigBlocksRequired(int length) { int blocks = length / BIG_BLOCK_SIZE; return (length % BIG_BLOCK_SIZE > 0 )? blocks + 1 : blocks; } /** * Calculates the number of small blocks required to store data of the * specified length * * @param length the length of the data * @return the number of small blocks required to store the data */ private int getSmallBlocksRequired(int length) { int blocks = length / SMALL_BLOCK_SIZE; return (length % SMALL_BLOCK_SIZE > 0 )? blocks + 1 : blocks; } /** * Writes out the property sets * * @exception IOException */ private void writePropertySets() throws IOException { byte[] propertySetStorage = new byte[BIG_BLOCK_SIZE * numRootEntryBlocks]; int pos = 0; int[] mappings = null; // Build up the mappings array if (additionalPropertySets != null) { mappings = new int[numPropertySets]; // Map the standard ones to the first four for (int i = 0 ; i < STANDARD_PROPERTY_SETS.length ; i++) { ReadPropertyStorage rps = (ReadPropertyStorage) readPropertySets.get(STANDARD_PROPERTY_SETS[i]); mappings[rps.number] = i; } // Now go through the original ones int newMapping = STANDARD_PROPERTY_SETS.length; for (Iterator i = additionalPropertySets.iterator(); i.hasNext(); ) { ReadPropertyStorage rps = (ReadPropertyStorage) i.next(); mappings[rps.number] = newMapping; newMapping++; } } int dir = 0; int previous = 0; int next = 0; // Set the root entry property set PropertyStorage ps = new PropertyStorage(ROOT_ENTRY_NAME); ps.setType(5); ps.setStartBlock(-2); ps.setSize(0); ps.setPrevious(-1); ps.setNext(-1); ps.setColour(0); dir = 2; if (additionalPropertySets != null) { ReadPropertyStorage rps = (ReadPropertyStorage) readPropertySets.get(ROOT_ENTRY_NAME); dir = mappings[rps.propertyStorage.directory]; } ps.setDirectory(dir); System.arraycopy(ps.data, 0, propertySetStorage, pos, PROPERTY_STORAGE_BLOCK_SIZE); pos += PROPERTY_STORAGE_BLOCK_SIZE; // Set the workbook property set ps = new PropertyStorage(WORKBOOK_NAME); ps.setType(2); ps.setStartBlock(excelDataStartBlock); // start the excel data after immediately after this block ps.setSize(requiredSize); // alway use a big block stream - none of that messing around // with small blocks ps.setColour(1); previous = 3; if (additionalPropertySets != null) { ReadPropertyStorage rps = (ReadPropertyStorage) readPropertySets.get(WORKBOOK_NAME); previous = mappings[rps.propertyStorage.previous]; } ps.setPrevious(previous); ps.setNext(-1); ps.setDirectory(-1); // ps.setColour(1); System.arraycopy(ps.data, 0, propertySetStorage, pos, PROPERTY_STORAGE_BLOCK_SIZE); pos += PROPERTY_STORAGE_BLOCK_SIZE; // Set the summary information ps = new PropertyStorage(SUMMARY_INFORMATION_NAME); ps.setType(2); ps.setStartBlock(excelDataStartBlock + excelDataBlocks); ps.setSize(SMALL_BLOCK_THRESHOLD); // ps.setColour(1); previous = 1; next = 3; if (additionalPropertySets != null) { ReadPropertyStorage rps = (ReadPropertyStorage) readPropertySets.get(SUMMARY_INFORMATION_NAME); previous = rps.propertyStorage.previous != - 1 ? mappings[rps.propertyStorage.previous] : -1 ; next = rps.propertyStorage.next != - 1 ? mappings[rps.propertyStorage.next] : -1 ; } ps.setPrevious(previous); ps.setNext(next); ps.setDirectory(-1); // ps.setColour(1); System.arraycopy(ps.data, 0, propertySetStorage, pos, PROPERTY_STORAGE_BLOCK_SIZE); pos += PROPERTY_STORAGE_BLOCK_SIZE; // Set the document summary information ps = new PropertyStorage(DOCUMENT_SUMMARY_INFORMATION_NAME); ps.setType(2); ps.setStartBlock(excelDataStartBlock + excelDataBlocks + 8); ps.setSize(SMALL_BLOCK_THRESHOLD); ps.setPrevious(-1); ps.setNext(-1); ps.setDirectory(-1); // ps.setColour(1); System.arraycopy(ps.data, 0, propertySetStorage, pos, PROPERTY_STORAGE_BLOCK_SIZE); pos += PROPERTY_STORAGE_BLOCK_SIZE; // Write out the additional property sets if (additionalPropertySets == null) { out.write(propertySetStorage); return; } int bigBlock = excelDataStartBlock + excelDataBlocks + 16 + 18; for (Iterator i = additionalPropertySets.iterator() ; i.hasNext(); ) { ReadPropertyStorage rps = (ReadPropertyStorage) i.next(); ps = new PropertyStorage(rps.propertyStorage.name); ps.setType(rps.propertyStorage.type); ps.setStartBlock(bigBlock); ps.setSize(Math.max(rps.propertyStorage.size, SMALL_BLOCK_THRESHOLD)); previous = rps.propertyStorage.previous != -1 ? mappings[rps.propertyStorage.previous] : -1; next = rps.propertyStorage.next != -1 ? mappings[rps.propertyStorage.next] : -1; dir = rps.propertyStorage.directory != -1 ? mappings[rps.propertyStorage.directory] : -1; ps.setPrevious(previous); ps.setNext(next); ps.setDirectory(dir); // ps.setColour(1); System.arraycopy(ps.data, 0, propertySetStorage, pos, PROPERTY_STORAGE_BLOCK_SIZE); pos += PROPERTY_STORAGE_BLOCK_SIZE; if (rps.data.length >= SMALL_BLOCK_THRESHOLD) { bigBlock += getBigBlocksRequired(rps.data.length); } else { bigBlock += SMALL_BLOCK_THRESHOLD / BIG_BLOCK_SIZE; } } out.write(propertySetStorage); } }