package jxl.write.biff; import common.Assert; import common.Logger; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import jxl.biff.BaseCompoundFile; import jxl.biff.IntegerHelper; import jxl.read.biff.BiffException; final class CompoundFile extends BaseCompoundFile { private static Logger logger = Logger.getLogger(CompoundFile.class); private OutputStream out; private byte[] excelData; private int size; private int requiredSize; private int numBigBlockDepotBlocks; private int numSmallBlockDepotChainBlocks; private int numSmallBlockDepotBlocks; private int numExtensionBlocks; private int extensionBlock; private int excelDataBlocks; private int rootStartBlock; private int excelDataStartBlock; private int bbdStartBlock; private int sbdStartBlockChain; private int sbdStartBlock; private int additionalPropertyBlocks; private int numSmallBlocks; private int numPropertySets; private int numRootEntryBlocks; private ArrayList additionalPropertySets; private HashMap standardPropertySets; private int bbdPos; private byte[] bigBlockDepot; private static final class ReadPropertyStorage { BaseCompoundFile.PropertyStorage propertyStorage; byte[] data; int number; ReadPropertyStorage(BaseCompoundFile.PropertyStorage ps, byte[] d, int n) { this.propertyStorage = ps; this.data = d; this.number = n; } } public CompoundFile(byte[] data, int l, OutputStream os, jxl.read.biff.CompoundFile rcf) throws CopyAdditionalPropertySetsException, IOException { this.size = l; this.excelData = data; readAdditionalPropertySets(rcf); this.numRootEntryBlocks = 1; this.numPropertySets = 4 + ((this.additionalPropertySets != null) ? this.additionalPropertySets.size() : 0); if (this.additionalPropertySets != null) { this.numSmallBlockDepotChainBlocks = getBigBlocksRequired(this.numSmallBlocks * 4); this.numSmallBlockDepotBlocks = getBigBlocksRequired(this.numSmallBlocks * 64); this.numRootEntryBlocks += getBigBlocksRequired(this.additionalPropertySets.size() * 128); } int blocks = getBigBlocksRequired(l); if (l < 4096) { this.requiredSize = 4096; } else { this.requiredSize = blocks * 512; } this.out = os; this.excelDataBlocks = this.requiredSize / 512; this.numBigBlockDepotBlocks = 1; int blockChainLength = 109; int startTotalBlocks = this.excelDataBlocks + 8 + 8 + this.additionalPropertyBlocks + this.numSmallBlockDepotBlocks + this.numSmallBlockDepotChainBlocks + this.numRootEntryBlocks; int totalBlocks = startTotalBlocks + this.numBigBlockDepotBlocks; this.numBigBlockDepotBlocks = (int)Math.ceil(totalBlocks / 128.0D); totalBlocks = startTotalBlocks + this.numBigBlockDepotBlocks; this.numBigBlockDepotBlocks = (int)Math.ceil(totalBlocks / 128.0D); totalBlocks = startTotalBlocks + this.numBigBlockDepotBlocks; if (this.numBigBlockDepotBlocks > blockChainLength - 1) { this.extensionBlock = 0; int bbdBlocksLeft = this.numBigBlockDepotBlocks - blockChainLength + 1; this.numExtensionBlocks = (int)Math.ceil(bbdBlocksLeft / 127.0D); totalBlocks = startTotalBlocks + this.numExtensionBlocks + this.numBigBlockDepotBlocks; this.numBigBlockDepotBlocks = (int)Math.ceil(totalBlocks / 128.0D); totalBlocks = startTotalBlocks + this.numExtensionBlocks + this.numBigBlockDepotBlocks; } else { this.extensionBlock = -2; this.numExtensionBlocks = 0; } this.excelDataStartBlock = this.numExtensionBlocks; this.sbdStartBlock = -2; if (this.additionalPropertySets != null && this.numSmallBlockDepotBlocks != 0) this.sbdStartBlock = this.excelDataStartBlock + this.excelDataBlocks + this.additionalPropertyBlocks + 16; this.sbdStartBlockChain = -2; if (this.sbdStartBlock != -2) this.sbdStartBlockChain = this.sbdStartBlock + this.numSmallBlockDepotBlocks; if (this.sbdStartBlockChain != -2) { this.bbdStartBlock = this.sbdStartBlockChain + this.numSmallBlockDepotChainBlocks; } else { this.bbdStartBlock = this.excelDataStartBlock + this.excelDataBlocks + this.additionalPropertyBlocks + 16; } this.rootStartBlock = this.bbdStartBlock + this.numBigBlockDepotBlocks; if (totalBlocks != this.rootStartBlock + this.numRootEntryBlocks) { logger.warn("Root start block and total blocks are inconsistent generated file may be corrupt"); logger.warn("RootStartBlock " + this.rootStartBlock + " totalBlocks " + totalBlocks); } } private void readAdditionalPropertySets(jxl.read.biff.CompoundFile readCompoundFile) throws CopyAdditionalPropertySetsException, IOException { if (readCompoundFile == null) return; this.additionalPropertySets = new ArrayList(); this.standardPropertySets = new HashMap(); int blocksRequired = 0; int numPropertySets = readCompoundFile.getNumberOfPropertySets(); for (int i = 0; i < numPropertySets; i++) { BaseCompoundFile.PropertyStorage ps = readCompoundFile.getPropertySet(i); boolean standard = false; if (ps.name.equalsIgnoreCase("Root Entry")) { standard = true; ReadPropertyStorage rps = new ReadPropertyStorage(ps, null, i); this.standardPropertySets.put("Root Entry", rps); } for (int j = 0; j < STANDARD_PROPERTY_SETS.length && !standard; j++) { if (ps.name.equalsIgnoreCase(STANDARD_PROPERTY_SETS[j])) { BaseCompoundFile.PropertyStorage ps2 = readCompoundFile.findPropertyStorage(ps.name); Assert.verify((ps2 != null)); if (ps2 == ps) { standard = true; ReadPropertyStorage rps = new ReadPropertyStorage(ps, null, i); this.standardPropertySets.put(STANDARD_PROPERTY_SETS[j], rps); } } } if (!standard) try { byte[] data = null; if (ps.size > 0) { data = readCompoundFile.getStream(i); } else { data = new byte[0]; } ReadPropertyStorage rps = new ReadPropertyStorage(ps, data, i); this.additionalPropertySets.add(rps); if (data.length > 4096) { int blocks = getBigBlocksRequired(data.length); blocksRequired += blocks; } else { int blocks = getSmallBlocksRequired(data.length); this.numSmallBlocks += blocks; } } catch (BiffException e) { logger.error(e); throw new CopyAdditionalPropertySetsException(); } } this.additionalPropertyBlocks = blocksRequired; } public void write() throws IOException { writeHeader(); writeExcelData(); writeDocumentSummaryData(); writeSummaryData(); writeAdditionalPropertySets(); writeSmallBlockDepot(); writeSmallBlockDepotChain(); writeBigBlockDepot(); writePropertySets(); } private void writeAdditionalPropertySets() throws IOException { if (this.additionalPropertySets == null) return; for (Iterator i = this.additionalPropertySets.iterator(); i.hasNext(); ) { ReadPropertyStorage rps = i.next(); byte[] data = rps.data; if (data.length > 4096) { int numBlocks = getBigBlocksRequired(data.length); int requiredSize = numBlocks * 512; this.out.write(data, 0, data.length); byte[] padding = new byte[requiredSize - data.length]; this.out.write(padding, 0, padding.length); } } } private void writeExcelData() throws IOException { this.out.write(this.excelData, 0, this.size); byte[] padding = new byte[this.requiredSize - this.size]; this.out.write(padding); } private void writeDocumentSummaryData() throws IOException { byte[] padding = new byte[4096]; this.out.write(padding); } private void writeSummaryData() throws IOException { byte[] padding = new byte[4096]; this.out.write(padding); } private void writeHeader() throws IOException { byte[] headerBlock = new byte[512]; byte[] extensionBlockData = new byte[512 * this.numExtensionBlocks]; System.arraycopy(IDENTIFIER, 0, headerBlock, 0, IDENTIFIER.length); headerBlock[24] = 62; headerBlock[26] = 3; headerBlock[28] = -2; headerBlock[29] = -1; headerBlock[30] = 9; headerBlock[32] = 6; headerBlock[57] = 16; IntegerHelper.getFourBytes(this.numBigBlockDepotBlocks, headerBlock, 44); IntegerHelper.getFourBytes(this.sbdStartBlockChain, headerBlock, 60); IntegerHelper.getFourBytes(this.numSmallBlockDepotChainBlocks, headerBlock, 64); IntegerHelper.getFourBytes(this.extensionBlock, headerBlock, 68); IntegerHelper.getFourBytes(this.numExtensionBlocks, headerBlock, 72); IntegerHelper.getFourBytes(this.rootStartBlock, headerBlock, 48); int pos = 76; int blocksToWrite = Math.min(this.numBigBlockDepotBlocks, 109); int blocksWritten = 0; int i; for (i = 0; i < blocksToWrite; i++) { IntegerHelper.getFourBytes(this.bbdStartBlock + i, headerBlock, pos); pos += 4; blocksWritten++; } for (i = pos; i < 512; i++) headerBlock[i] = -1; this.out.write(headerBlock); pos = 0; for (int extBlock = 0; extBlock < this.numExtensionBlocks; extBlock++) { blocksToWrite = Math.min(this.numBigBlockDepotBlocks - blocksWritten, 127); for (int j = 0; j < blocksToWrite; j++) { IntegerHelper.getFourBytes(this.bbdStartBlock + blocksWritten + j, extensionBlockData, pos); pos += 4; } blocksWritten += blocksToWrite; int nextBlock = (blocksWritten == this.numBigBlockDepotBlocks) ? -2 : (extBlock + 1); IntegerHelper.getFourBytes(nextBlock, extensionBlockData, pos); pos += 4; } if (this.numExtensionBlocks > 0) { for (int j = pos; j < extensionBlockData.length; j++) extensionBlockData[j] = -1; this.out.write(extensionBlockData); } } private void checkBbdPos() throws IOException { if (this.bbdPos >= 512) { this.out.write(this.bigBlockDepot); this.bigBlockDepot = new byte[512]; this.bbdPos = 0; } } 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, (512 - this.bbdPos) / 4); for (int i = 0; i < bbdBlocks; i++) { IntegerHelper.getFourBytes(blockNumber, this.bigBlockDepot, this.bbdPos); this.bbdPos += 4; blockNumber++; } blocksToWrite -= bbdBlocks; checkBbdPos(); } IntegerHelper.getFourBytes(-2, this.bigBlockDepot, this.bbdPos); this.bbdPos += 4; checkBbdPos(); } private void writeAdditionalPropertySetBlockChains() throws IOException { if (this.additionalPropertySets == null) return; int blockNumber = this.excelDataStartBlock + this.excelDataBlocks + 16; for (Iterator i = this.additionalPropertySets.iterator(); i.hasNext(); ) { ReadPropertyStorage rps = i.next(); if (rps.data.length > 4096) { int numBlocks = getBigBlocksRequired(rps.data.length); writeBlockChain(blockNumber, numBlocks); blockNumber += numBlocks; } } } private void writeSmallBlockDepotChain() throws IOException { if (this.sbdStartBlockChain == -2) return; byte[] smallBlockDepotChain = new byte[this.numSmallBlockDepotChainBlocks * 512]; int pos = 0; int sbdBlockNumber = 1; for (Iterator i = this.additionalPropertySets.iterator(); i.hasNext(); ) { ReadPropertyStorage rps = i.next(); if (rps.data.length <= 4096 && rps.data.length != 0) { int numSmallBlocks = getSmallBlocksRequired(rps.data.length); for (int j = 0; j < numSmallBlocks - 1; j++) { IntegerHelper.getFourBytes(sbdBlockNumber, smallBlockDepotChain, pos); pos += 4; sbdBlockNumber++; } IntegerHelper.getFourBytes(-2, smallBlockDepotChain, pos); pos += 4; sbdBlockNumber++; } } this.out.write(smallBlockDepotChain); } private void writeSmallBlockDepot() throws IOException { if (this.additionalPropertySets == null) return; byte[] smallBlockDepot = new byte[this.numSmallBlockDepotBlocks * 512]; int pos = 0; for (Iterator i = this.additionalPropertySets.iterator(); i.hasNext(); ) { ReadPropertyStorage rps = i.next(); if (rps.data.length <= 4096) { int smallBlocks = getSmallBlocksRequired(rps.data.length); int length = smallBlocks * 64; System.arraycopy(rps.data, 0, smallBlockDepot, pos, rps.data.length); pos += length; } } this.out.write(smallBlockDepot); } private void writeBigBlockDepot() throws IOException { this.bigBlockDepot = new byte[512]; this.bbdPos = 0; for (int i = 0; i < this.numExtensionBlocks; i++) { IntegerHelper.getFourBytes(-3, this.bigBlockDepot, this.bbdPos); this.bbdPos += 4; checkBbdPos(); } writeBlockChain(this.excelDataStartBlock, this.excelDataBlocks); int summaryInfoBlock = this.excelDataStartBlock + this.excelDataBlocks + this.additionalPropertyBlocks; int j; for (j = summaryInfoBlock; j < summaryInfoBlock + 7; j++) { IntegerHelper.getFourBytes(j + 1, this.bigBlockDepot, this.bbdPos); this.bbdPos += 4; checkBbdPos(); } IntegerHelper.getFourBytes(-2, this.bigBlockDepot, this.bbdPos); this.bbdPos += 4; checkBbdPos(); for (j = summaryInfoBlock + 8; j < summaryInfoBlock + 15; j++) { IntegerHelper.getFourBytes(j + 1, this.bigBlockDepot, this.bbdPos); this.bbdPos += 4; checkBbdPos(); } IntegerHelper.getFourBytes(-2, this.bigBlockDepot, this.bbdPos); this.bbdPos += 4; checkBbdPos(); writeAdditionalPropertySetBlockChains(); if (this.sbdStartBlock != -2) { writeBlockChain(this.sbdStartBlock, this.numSmallBlockDepotBlocks); writeBlockChain(this.sbdStartBlockChain, this.numSmallBlockDepotChainBlocks); } for (j = 0; j < this.numBigBlockDepotBlocks; j++) { IntegerHelper.getFourBytes(-3, this.bigBlockDepot, this.bbdPos); this.bbdPos += 4; checkBbdPos(); } writeBlockChain(this.rootStartBlock, this.numRootEntryBlocks); if (this.bbdPos != 0) { for (j = this.bbdPos; j < 512; j++) this.bigBlockDepot[j] = -1; this.out.write(this.bigBlockDepot); } } private int getBigBlocksRequired(int length) { int blocks = length / 512; return (length % 512 > 0) ? (blocks + 1) : blocks; } private int getSmallBlocksRequired(int length) { int blocks = length / 64; return (length % 64 > 0) ? (blocks + 1) : blocks; } private void writePropertySets() throws IOException { byte[] propertySetStorage = new byte[512 * this.numRootEntryBlocks]; int pos = 0; int[] mappings = null; if (this.additionalPropertySets != null) { mappings = new int[this.numPropertySets]; for (int j = 0; j < STANDARD_PROPERTY_SETS.length; j++) { ReadPropertyStorage rps = (ReadPropertyStorage)this.standardPropertySets.get(STANDARD_PROPERTY_SETS[j]); if (rps != null) { mappings[rps.number] = j; } else { logger.warn("Standard property set " + STANDARD_PROPERTY_SETS[j] + " not present in source file"); } } int newMapping = STANDARD_PROPERTY_SETS.length; for (Iterator iterator = this.additionalPropertySets.iterator(); iterator.hasNext(); ) { ReadPropertyStorage rps = iterator.next(); mappings[rps.number] = newMapping; newMapping++; } } int child = 0; int previous = 0; int next = 0; int size = 0; if (this.additionalPropertySets != null) { size += getBigBlocksRequired(this.requiredSize) * 512; size += getBigBlocksRequired(4096) * 512; size += getBigBlocksRequired(4096) * 512; for (Iterator iterator = this.additionalPropertySets.iterator(); iterator.hasNext(); ) { ReadPropertyStorage rps = iterator.next(); if (rps.propertyStorage.type != 1) { if (rps.propertyStorage.size >= 4096) { size += getBigBlocksRequired(rps.propertyStorage.size) * 512; continue; } size += getSmallBlocksRequired(rps.propertyStorage.size) * 64; } } } BaseCompoundFile.PropertyStorage ps = new BaseCompoundFile.PropertyStorage(this, "Root Entry"); ps.setType(5); ps.setStartBlock(this.sbdStartBlock); ps.setSize(size); ps.setPrevious(-1); ps.setNext(-1); ps.setColour(0); child = 1; if (this.additionalPropertySets != null) { ReadPropertyStorage rps = (ReadPropertyStorage)this.standardPropertySets.get("Root Entry"); child = mappings[rps.propertyStorage.child]; } ps.setChild(child); System.arraycopy(ps.data, 0, propertySetStorage, pos, 128); pos += 128; ps = new BaseCompoundFile.PropertyStorage(this, "Workbook"); ps.setType(2); ps.setStartBlock(this.excelDataStartBlock); ps.setSize(this.requiredSize); previous = 3; next = -1; if (this.additionalPropertySets != null) { ReadPropertyStorage rps = (ReadPropertyStorage)this.standardPropertySets.get("Workbook"); 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.setChild(-1); System.arraycopy(ps.data, 0, propertySetStorage, pos, 128); pos += 128; ps = new BaseCompoundFile.PropertyStorage(this, "\005SummaryInformation"); ps.setType(2); ps.setStartBlock(this.excelDataStartBlock + this.excelDataBlocks); ps.setSize(4096); previous = 1; next = 3; if (this.additionalPropertySets != null) { ReadPropertyStorage rps = (ReadPropertyStorage)this.standardPropertySets.get("\005SummaryInformation"); if (rps != null) { 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.setChild(-1); System.arraycopy(ps.data, 0, propertySetStorage, pos, 128); pos += 128; ps = new BaseCompoundFile.PropertyStorage(this, "\005DocumentSummaryInformation"); ps.setType(2); ps.setStartBlock(this.excelDataStartBlock + this.excelDataBlocks + 8); ps.setSize(4096); ps.setPrevious(-1); ps.setNext(-1); ps.setChild(-1); System.arraycopy(ps.data, 0, propertySetStorage, pos, 128); pos += 128; if (this.additionalPropertySets == null) { this.out.write(propertySetStorage); return; } int bigBlock = this.excelDataStartBlock + this.excelDataBlocks + 16; int smallBlock = 0; for (Iterator i = this.additionalPropertySets.iterator(); i.hasNext(); ) { ReadPropertyStorage rps = i.next(); int block = (rps.data.length > 4096) ? bigBlock : smallBlock; ps = new BaseCompoundFile.PropertyStorage(this, rps.propertyStorage.name); ps.setType(rps.propertyStorage.type); ps.setStartBlock(block); ps.setSize(rps.propertyStorage.size); previous = (rps.propertyStorage.previous != -1) ? mappings[rps.propertyStorage.previous] : -1; next = (rps.propertyStorage.next != -1) ? mappings[rps.propertyStorage.next] : -1; child = (rps.propertyStorage.child != -1) ? mappings[rps.propertyStorage.child] : -1; ps.setPrevious(previous); ps.setNext(next); ps.setChild(child); System.arraycopy(ps.data, 0, propertySetStorage, pos, 128); pos += 128; if (rps.data.length > 4096) { bigBlock += getBigBlocksRequired(rps.data.length); continue; } smallBlock += getSmallBlocksRequired(rps.data.length); } this.out.write(propertySetStorage); } }