mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-05-27 00:00:02 -04:00
moving files around
This commit is contained in:
+761
@@ -0,0 +1,761 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io.ole2;
|
||||
|
||||
import com.twelvemonkeys.io.*;
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents a read-only OLE2 compound document.
|
||||
* <p/>
|
||||
* <!-- TODO: Consider really detaching the entries, as this is hard for users to enforce... -->
|
||||
* <em>NOTE: This class is not synchronized. Accessing the document or its
|
||||
* entries from different threads, will need synchronization on the document
|
||||
* instance.</em>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java#4 $
|
||||
*/
|
||||
public final class CompoundDocument {
|
||||
// TODO: Write support...
|
||||
// TODO: Properties: http://support.microsoft.com/kb/186898
|
||||
|
||||
private static final byte[] MAGIC = new byte[]{
|
||||
(byte) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0,
|
||||
(byte) 0xA1, (byte) 0xB1, (byte) 0x1A, (byte) 0xE1,
|
||||
};
|
||||
public static final int HEADER_SIZE = 512;
|
||||
|
||||
private final DataInput mInput;
|
||||
|
||||
private UUID mUID;
|
||||
|
||||
private int mSectorSize;
|
||||
private int mShortSectorSize;
|
||||
|
||||
private int mDirectorySId;
|
||||
|
||||
private int mMinStreamSize;
|
||||
|
||||
private int mShortSATSID;
|
||||
private int mShortSATSize;
|
||||
|
||||
// Master Sector Allocation Table
|
||||
private int[] mMasterSAT;
|
||||
private int[] mSAT;
|
||||
private int[] mShortSAT;
|
||||
|
||||
private Entry mRootEntry;
|
||||
private SIdChain mShortStreamSIdChain;
|
||||
private SIdChain mDirectorySIdChain;
|
||||
|
||||
private static final int END_OF_CHAIN_SID = -2;
|
||||
private static final int FREE_SID = -1;
|
||||
|
||||
/** The epoch offset of CompoundDocument time stamps */
|
||||
public final static long EPOCH_OFFSET = -11644477200000L;
|
||||
|
||||
/**
|
||||
* Creates a (for now) read only {@code CompoundDocument}.
|
||||
*
|
||||
* @param pFile the file to read from
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs while reading the header
|
||||
*/
|
||||
public CompoundDocument(final File pFile) throws IOException {
|
||||
mInput = new LittleEndianRandomAccessFile(FileUtil.resolve(pFile), "r");
|
||||
|
||||
// TODO: Might be better to read header on first read operation?!
|
||||
// OTOH: It's also good to be fail-fast, so at least we should make
|
||||
// sure we're reading a valid document
|
||||
readHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a read only {@code CompoundDocument}.
|
||||
*
|
||||
* @param pInput the input to read from
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs while reading the header
|
||||
*/
|
||||
public CompoundDocument(final InputStream pInput) throws IOException {
|
||||
this(new FileCacheSeekableStream(pInput));
|
||||
}
|
||||
|
||||
// For testing only, consider exposing later
|
||||
CompoundDocument(final SeekableInputStream pInput) throws IOException {
|
||||
mInput = new SeekableLittleEndianDataInputStream(pInput);
|
||||
|
||||
// TODO: Might be better to read header on first read operation?!
|
||||
// OTOH: It's also good to be fail-fast, so at least we should make
|
||||
// sure we're reading a valid document
|
||||
readHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a read only {@code CompoundDocument}.
|
||||
*
|
||||
* @param pInput the input to read from
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs while reading the header
|
||||
*/
|
||||
public CompoundDocument(final ImageInputStream pInput) throws IOException {
|
||||
mInput = pInput;
|
||||
|
||||
// TODO: Might be better to read header on first read operation?!
|
||||
// OTOH: It's also good to be fail-fast, so at least we should make
|
||||
// sure we're reading a valid document
|
||||
readHeader();
|
||||
}
|
||||
|
||||
public static boolean canRead(final DataInput pInput) {
|
||||
return canRead(pInput, true);
|
||||
}
|
||||
|
||||
// TODO: Refactor.. Figure out what we really need to expose to ImageIO for
|
||||
// easy reading of the Thumbs.db file
|
||||
// It's probably safer to create one version for InputStream and one for File
|
||||
private static boolean canRead(final DataInput pInput, final boolean pReset) {
|
||||
long pos = FREE_SID;
|
||||
if (pReset) {
|
||||
try {
|
||||
if (pInput instanceof InputStream && ((InputStream) pInput).markSupported()) {
|
||||
((InputStream) pInput).mark(8);
|
||||
}
|
||||
else if (pInput instanceof ImageInputStream) {
|
||||
((ImageInputStream) pInput).mark();
|
||||
}
|
||||
else if (pInput instanceof RandomAccessFile) {
|
||||
pos = ((RandomAccessFile) pInput).getFilePointer();
|
||||
}
|
||||
else if (pInput instanceof LittleEndianRandomAccessFile) {
|
||||
pos = ((LittleEndianRandomAccessFile) pInput).getFilePointer();
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] magic = new byte[8];
|
||||
pInput.readFully(magic);
|
||||
return Arrays.equals(magic, MAGIC);
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
// Ignore
|
||||
}
|
||||
finally {
|
||||
if (pReset) {
|
||||
try {
|
||||
if (pInput instanceof InputStream && ((InputStream) pInput).markSupported()) {
|
||||
((InputStream) pInput).reset();
|
||||
}
|
||||
else if (pInput instanceof ImageInputStream) {
|
||||
((ImageInputStream) pInput).reset();
|
||||
}
|
||||
else if (pInput instanceof RandomAccessFile) {
|
||||
((RandomAccessFile) pInput).seek(pos);
|
||||
}
|
||||
else if (pInput instanceof LittleEndianRandomAccessFile) {
|
||||
((LittleEndianRandomAccessFile) pInput).seek(pos);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
// TODO: This isn't actually good enough...
|
||||
// Means something fucked up, and will fail...
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void readHeader() throws IOException {
|
||||
if (mMasterSAT != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!canRead(mInput, false)) {
|
||||
throw new CorruptDocumentException("Not an OLE 2 Compound Document");
|
||||
}
|
||||
|
||||
// UID (seems to be all 0s)
|
||||
mUID = new UUID(mInput.readLong(), mInput.readLong());
|
||||
|
||||
/*int version = */mInput.readUnsignedShort();
|
||||
//System.out.println("version: " + version);
|
||||
/*int revision = */mInput.readUnsignedShort();
|
||||
//System.out.println("revision: " + revision);
|
||||
|
||||
int byteOrder = mInput.readUnsignedShort();
|
||||
if (byteOrder != 0xfffe) {
|
||||
// Reversed, as I'm allready reading little-endian
|
||||
throw new CorruptDocumentException("Cannot read big endian OLE 2 Compound Documents");
|
||||
}
|
||||
|
||||
mSectorSize = 1 << mInput.readUnsignedShort();
|
||||
//System.out.println("sectorSize: " + mSectorSize + " bytes");
|
||||
mShortSectorSize = 1 << mInput.readUnsignedShort();
|
||||
//System.out.println("shortSectorSize: " + mShortSectorSize + " bytes");
|
||||
|
||||
// Reserved
|
||||
if (mInput.skipBytes(10) != 10) {
|
||||
throw new CorruptDocumentException();
|
||||
}
|
||||
|
||||
int SATSize = mInput.readInt();
|
||||
//System.out.println("normalSATSize: " + mSATSize);
|
||||
|
||||
mDirectorySId = mInput.readInt();
|
||||
//System.out.println("directorySId: " + mDirectorySId);
|
||||
|
||||
// Reserved
|
||||
if (mInput.skipBytes(4) != 4) {
|
||||
throw new CorruptDocumentException();
|
||||
}
|
||||
|
||||
mMinStreamSize = mInput.readInt();
|
||||
//System.out.println("minStreamSize: " + mMinStreamSize + " bytes");
|
||||
|
||||
mShortSATSID = mInput.readInt();
|
||||
//System.out.println("shortSATSID: " + mShortSATSID);
|
||||
mShortSATSize = mInput.readInt();
|
||||
//System.out.println("shortSATSize: " + mShortSATSize);
|
||||
int masterSATSId = mInput.readInt();
|
||||
//System.out.println("masterSATSId: " + mMasterSATSID);
|
||||
int masterSATSize = mInput.readInt();
|
||||
//System.out.println("masterSATSize: " + mMasterSATSize);
|
||||
|
||||
// Read masterSAT: 436 bytes, containing up to 109 SIDs
|
||||
//System.out.println("MSAT:");
|
||||
mMasterSAT = new int[SATSize];
|
||||
final int headerSIds = Math.min(SATSize, 109);
|
||||
for (int i = 0; i < headerSIds; i++) {
|
||||
mMasterSAT[i] = mInput.readInt();
|
||||
//System.out.println("\tSID(" + i + "): " + mMasterSAT[i]);
|
||||
}
|
||||
|
||||
if (masterSATSId == END_OF_CHAIN_SID) {
|
||||
// End of chain
|
||||
int freeSIdLength = 436 - (SATSize * 4);
|
||||
if (mInput.skipBytes(freeSIdLength) != freeSIdLength) {
|
||||
throw new CorruptDocumentException();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Parse the SIDs in the extended MasterSAT sectors...
|
||||
seekToSId(masterSATSId, FREE_SID);
|
||||
|
||||
int index = headerSIds;
|
||||
for (int i = 0; i < masterSATSize; i++) {
|
||||
for (int j = 0; j < 127; j++) {
|
||||
int sid = mInput.readInt();
|
||||
switch (sid) {
|
||||
case FREE_SID:// Free
|
||||
break;
|
||||
default:
|
||||
mMasterSAT[index++] = sid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int next = mInput.readInt();
|
||||
if (next == END_OF_CHAIN_SID) {// End of chain
|
||||
break;
|
||||
}
|
||||
|
||||
seekToSId(next, FREE_SID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readSAT() throws IOException {
|
||||
if (mSAT != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final int intsPerSector = mSectorSize / 4;
|
||||
|
||||
// Read the Sector Allocation Table
|
||||
mSAT = new int[mMasterSAT.length * intsPerSector];
|
||||
|
||||
for (int i = 0; i < mMasterSAT.length; i++) {
|
||||
seekToSId(mMasterSAT[i], FREE_SID);
|
||||
|
||||
for (int j = 0; j < intsPerSector; j++) {
|
||||
int nextSID = mInput.readInt();
|
||||
int index = (j + (i * intsPerSector));
|
||||
|
||||
mSAT[index] = nextSID;
|
||||
}
|
||||
}
|
||||
|
||||
// Read the short-stream Sector Allocation Table
|
||||
SIdChain chain = getSIdChain(mShortSATSID, FREE_SID);
|
||||
mShortSAT = new int[mShortSATSize * intsPerSector];
|
||||
for (int i = 0; i < mShortSATSize; i++) {
|
||||
seekToSId(chain.get(i), FREE_SID);
|
||||
|
||||
for (int j = 0; j < intsPerSector; j++) {
|
||||
int nextSID = mInput.readInt();
|
||||
int index = (j + (i * intsPerSector));
|
||||
|
||||
mShortSAT[index] = nextSID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SIdChain for the given stream Id
|
||||
*
|
||||
* @param pSId the stream Id
|
||||
* @param pStreamSize the size of the stream, or -1 for system control streams
|
||||
* @return the SIdChain for the given stream Id
|
||||
* @throws IOException if an I/O exception occurs
|
||||
*/
|
||||
private SIdChain getSIdChain(final int pSId, final long pStreamSize) throws IOException {
|
||||
SIdChain chain = new SIdChain();
|
||||
|
||||
int[] sat = isShortStream(pStreamSize) ? mShortSAT : mSAT;
|
||||
|
||||
int sid = pSId;
|
||||
while (sid != END_OF_CHAIN_SID && sid != FREE_SID) {
|
||||
chain.addSID(sid);
|
||||
sid = sat[sid];
|
||||
}
|
||||
|
||||
return chain;
|
||||
}
|
||||
|
||||
private boolean isShortStream(final long pStreamSize) {
|
||||
return pStreamSize != FREE_SID && pStreamSize < mMinStreamSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks to the start pos for the given stream Id
|
||||
*
|
||||
* @param pSId the stream Id
|
||||
* @param pStreamSize the size of the stream, or -1 for system control streams
|
||||
* @throws IOException if an I/O exception occurs
|
||||
*/
|
||||
private void seekToSId(final int pSId, final long pStreamSize) throws IOException {
|
||||
long pos;
|
||||
|
||||
if (isShortStream(pStreamSize)) {
|
||||
// The short-stream is not continouos...
|
||||
Entry root = getRootEntry();
|
||||
if (mShortStreamSIdChain == null) {
|
||||
mShortStreamSIdChain = getSIdChain(root.startSId, root.streamSize);
|
||||
}
|
||||
|
||||
int shortPerStd = mSectorSize / mShortSectorSize;
|
||||
int offset = pSId / shortPerStd;
|
||||
int shortOffset = pSId - (offset * shortPerStd);
|
||||
|
||||
pos = HEADER_SIZE
|
||||
+ (mShortStreamSIdChain.get(offset) * (long) mSectorSize)
|
||||
+ (shortOffset * (long) mShortSectorSize);
|
||||
}
|
||||
else {
|
||||
pos = HEADER_SIZE + pSId * (long) mSectorSize;
|
||||
}
|
||||
|
||||
if (mInput instanceof LittleEndianRandomAccessFile) {
|
||||
((LittleEndianRandomAccessFile) mInput).seek(pos);
|
||||
}
|
||||
else if (mInput instanceof ImageInputStream) {
|
||||
((ImageInputStream) mInput).seek(pos);
|
||||
}
|
||||
else {
|
||||
((SeekableLittleEndianDataInputStream) mInput).seek(pos);
|
||||
}
|
||||
}
|
||||
|
||||
private void seekToDId(final int pDId) throws IOException {
|
||||
if (mDirectorySIdChain == null) {
|
||||
mDirectorySIdChain = getSIdChain(mDirectorySId, FREE_SID);
|
||||
}
|
||||
|
||||
int dIdsPerSId = mSectorSize / Entry.LENGTH;
|
||||
|
||||
int sIdOffset = pDId / dIdsPerSId;
|
||||
int dIdOffset = pDId - (sIdOffset * dIdsPerSId);
|
||||
|
||||
int sId = mDirectorySIdChain.get(sIdOffset);
|
||||
|
||||
seekToSId(sId, FREE_SID);
|
||||
if (mInput instanceof LittleEndianRandomAccessFile) {
|
||||
LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) mInput;
|
||||
input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH);
|
||||
}
|
||||
else if (mInput instanceof ImageInputStream) {
|
||||
ImageInputStream input = (ImageInputStream) mInput;
|
||||
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
|
||||
}
|
||||
else {
|
||||
SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) mInput;
|
||||
input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
SeekableInputStream getInputStreamForSId(final int pStreamId, final int pStreamSize) throws IOException {
|
||||
SIdChain chain = getSIdChain(pStreamId, pStreamSize);
|
||||
|
||||
// TODO: Detach? Means, we have to copy to a byte buffer, or keep track of
|
||||
// positions, and seek back and forth (would be cool, but difficult)..
|
||||
int sectorSize = pStreamSize < mMinStreamSize ? mShortSectorSize : mSectorSize;
|
||||
|
||||
return new Stream(chain, pStreamSize, sectorSize, this);
|
||||
}
|
||||
|
||||
private InputStream getDirectoryStreamForDId(final int pDirectoryId) throws IOException {
|
||||
// This is always exactly 128 bytes, so we'll just read it all,
|
||||
// and buffer (we might want to optimize this later).
|
||||
byte[] bytes = new byte[Entry.LENGTH];
|
||||
|
||||
seekToDId(pDirectoryId);
|
||||
mInput.readFully(bytes);
|
||||
|
||||
return new ByteArrayInputStream(bytes);
|
||||
}
|
||||
|
||||
Entry getEntry(final int pDirectoryId, Entry pParent) throws IOException {
|
||||
Entry entry = Entry.readEntry(new LittleEndianDataInputStream(
|
||||
getDirectoryStreamForDId(pDirectoryId)
|
||||
));
|
||||
entry.mParent = pParent;
|
||||
entry.mDocument = this;
|
||||
return entry;
|
||||
}
|
||||
|
||||
SortedSet<Entry> getEntries(final int pDirectoryId, final Entry pParent)
|
||||
throws IOException {
|
||||
return getEntriesRecursive(pDirectoryId, pParent, new TreeSet<Entry>());
|
||||
}
|
||||
|
||||
private SortedSet<Entry> getEntriesRecursive(final int pDirectoryId, final Entry pParent, final SortedSet<Entry> pEntries)
|
||||
throws IOException {
|
||||
|
||||
//System.out.println("pDirectoryId: " + pDirectoryId);
|
||||
|
||||
Entry entry = getEntry(pDirectoryId, pParent);
|
||||
|
||||
//System.out.println("entry: " + entry);
|
||||
|
||||
if (!pEntries.add(entry)) {
|
||||
// TODO: This occurs in some Thumbs.db files, and Windows will
|
||||
// still parse the file gracefully somehow...
|
||||
// Deleting and regenerating the file will remove the cyclic
|
||||
// references, but... How can Windows parse this file?
|
||||
throw new CorruptDocumentException("Cyclic chain reference for entry: " + pDirectoryId);
|
||||
}
|
||||
|
||||
if (entry.prevDId != FREE_SID) {
|
||||
//System.out.println("prevDId: " + entry.prevDId);
|
||||
getEntriesRecursive(entry.prevDId, pParent, pEntries);
|
||||
}
|
||||
if (entry.nextDId != FREE_SID) {
|
||||
//System.out.println("nextDId: " + entry.nextDId);
|
||||
getEntriesRecursive(entry.nextDId, pParent, pEntries);
|
||||
}
|
||||
|
||||
return pEntries;
|
||||
}
|
||||
|
||||
/*public*/ Entry getEntry(String pPath) throws IOException {
|
||||
if (StringUtil.isEmpty(pPath) || !pPath.startsWith("/")) {
|
||||
throw new IllegalArgumentException("Path must be absolute, and contain a valid path: " + pPath);
|
||||
}
|
||||
|
||||
Entry entry = getRootEntry();
|
||||
if (pPath.equals("/")) {
|
||||
// '/' means root entry
|
||||
return entry;
|
||||
}
|
||||
else {
|
||||
// Otherwise get children recursively:
|
||||
String[] pathElements = StringUtil.toStringArray(pPath, "/");
|
||||
for (String pathElement : pathElements) {
|
||||
entry = entry.getChildEntry(pathElement);
|
||||
|
||||
// No such child...
|
||||
if (entry == null) {
|
||||
break;// TODO: FileNotFoundException? Should behave like Entry.getChildEntry!!
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
public Entry getRootEntry() throws IOException {
|
||||
if (mRootEntry == null) {
|
||||
readSAT();
|
||||
|
||||
mRootEntry = getEntry(0, null);
|
||||
|
||||
if (mRootEntry.type != Entry.ROOT_STORAGE) {
|
||||
throw new CorruptDocumentException("Invalid root storage type: " + mRootEntry.type);
|
||||
}
|
||||
}
|
||||
return mRootEntry;
|
||||
}
|
||||
|
||||
// @Override
|
||||
// public int hashCode() {
|
||||
// return mUID.hashCode();
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public boolean equals(final Object pOther) {
|
||||
// if (pOther == this) {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// if (pOther == null) {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// if (pOther.getClass() == getClass()) {
|
||||
// return mUID.equals(((CompoundDocument) pOther).mUID);
|
||||
// }
|
||||
//
|
||||
// return false;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"%s[uuid: %s, sector size: %d/%d bytes, directory SID: %d, master SAT: %s entries]",
|
||||
getClass().getSimpleName(), mUID, mSectorSize, mShortSectorSize, mDirectorySId, mMasterSAT.length
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the given time stamp to standard Java time representation,
|
||||
* milliseconds since January 1, 1970.
|
||||
* The time stamp parameter is assumed to be in units of
|
||||
* 100 nano seconds since January 1, 1601.
|
||||
* <p/>
|
||||
* If the timestamp is {@code 0L} (meaning not specified), no conversion
|
||||
* is done, to behave like {@code java.io.File}.
|
||||
*
|
||||
* @param pMSTime an unsigned long value representing the time stamp (in
|
||||
* units of 100 nano seconds since January 1, 1601).
|
||||
*
|
||||
* @return the time stamp converted to Java time stamp in milliseconds,
|
||||
* or {@code 0L} if {@code pMSTime == 0L}
|
||||
*/
|
||||
public static long toJavaTimeInMillis(final long pMSTime) {
|
||||
// NOTE: The time stamp field is an unsigned 64-bit integer value that
|
||||
// contains the time elapsed since 1601-Jan-01 00:00:00 (Gregorian
|
||||
// calendar).
|
||||
// One unit of this value is equal to 100 nanoseconds).
|
||||
// That means, each second the time stamp value will be increased by
|
||||
// 10 million units.
|
||||
|
||||
if (pMSTime == 0L) {
|
||||
return 0L; // This is just less confusing...
|
||||
}
|
||||
|
||||
// Convert to milliseconds (signed),
|
||||
// then convert to Java std epoch (1970-Jan-01 00:00:00)
|
||||
return ((pMSTime >> 1) / 5000) + EPOCH_OFFSET;
|
||||
}
|
||||
|
||||
// TODO: Enforce stream length!
|
||||
static class Stream extends SeekableInputStream {
|
||||
private SIdChain mChain;
|
||||
int mNextSectorPos;
|
||||
byte[] mBuffer;
|
||||
int mBufferPos;
|
||||
|
||||
private final CompoundDocument mDocument;
|
||||
private final long mLength;
|
||||
|
||||
public Stream(final SIdChain pChain, final long pLength, final int pSectorSize, final CompoundDocument pDocument) {
|
||||
mChain = pChain;
|
||||
mLength = pLength;
|
||||
|
||||
mBuffer = new byte[pSectorSize];
|
||||
mBufferPos = mBuffer.length;
|
||||
|
||||
mDocument = pDocument;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return (int) Math.min(mBuffer.length - mBufferPos, mLength - getStreamPosition());
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (available() <= 0) {
|
||||
if (!fillBuffer()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return mBuffer[mBufferPos++] & 0xff;
|
||||
}
|
||||
|
||||
private boolean fillBuffer() throws IOException {
|
||||
if (mNextSectorPos < mChain.length()) {
|
||||
// TODO: Sync on mDocument.mInput here, and we are completely detached... :-)
|
||||
// TODO: We also need to sync other places...
|
||||
synchronized (mDocument) {
|
||||
mDocument.seekToSId(mChain.get(mNextSectorPos), mLength);
|
||||
mDocument.mInput.readFully(mBuffer);
|
||||
}
|
||||
|
||||
mNextSectorPos++;
|
||||
mBufferPos = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte b[], int off, int len) throws IOException {
|
||||
if (available() <= 0) {
|
||||
if (!fillBuffer()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
int toRead = Math.min(len, available());
|
||||
|
||||
System.arraycopy(mBuffer, mBufferPos, b, off, toRead);
|
||||
mBufferPos += toRead;
|
||||
|
||||
return toRead;
|
||||
}
|
||||
|
||||
public boolean isCached() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isCachedMemory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCachedFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void closeImpl() throws IOException {
|
||||
mBuffer = null;
|
||||
mChain = null;
|
||||
}
|
||||
|
||||
protected void seekImpl(final long pPosition) throws IOException {
|
||||
long pos = getStreamPosition();
|
||||
|
||||
if (pos - mBufferPos >= pPosition && pPosition <= pos + available()) {
|
||||
// Skip inside buffer only
|
||||
mBufferPos += (pPosition - pos);
|
||||
}
|
||||
else {
|
||||
// Skip outside buffer
|
||||
mNextSectorPos = (int) (pPosition / mBuffer.length);
|
||||
if (!fillBuffer()) {
|
||||
throw new EOFException();
|
||||
}
|
||||
mBufferPos = (int) (pPosition % mBuffer.length);
|
||||
}
|
||||
}
|
||||
|
||||
protected void flushBeforeImpl(long pPosition) throws IOException {
|
||||
// No need to do anything here
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add test case for this class!!!
|
||||
static class SeekableLittleEndianDataInputStream extends LittleEndianDataInputStream implements Seekable {
|
||||
private final SeekableInputStream mSeekable;
|
||||
|
||||
public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) {
|
||||
super(pInput);
|
||||
mSeekable = pInput;
|
||||
}
|
||||
|
||||
public void seek(final long pPosition) throws IOException {
|
||||
mSeekable.seek(pPosition);
|
||||
}
|
||||
|
||||
public boolean isCachedFile() {
|
||||
return mSeekable.isCachedFile();
|
||||
}
|
||||
|
||||
public boolean isCachedMemory() {
|
||||
return mSeekable.isCachedMemory();
|
||||
}
|
||||
|
||||
public boolean isCached() {
|
||||
return mSeekable.isCached();
|
||||
}
|
||||
|
||||
public long getStreamPosition() throws IOException {
|
||||
return mSeekable.getStreamPosition();
|
||||
}
|
||||
|
||||
public long getFlushedPosition() throws IOException {
|
||||
return mSeekable.getFlushedPosition();
|
||||
}
|
||||
|
||||
public void flushBefore(final long pPosition) throws IOException {
|
||||
mSeekable.flushBefore(pPosition);
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
mSeekable.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
mSeekable.reset();
|
||||
}
|
||||
|
||||
public void mark() {
|
||||
mSeekable.mark();
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io.ole2;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Thrown when an OLE 2 compound document is considered corrupt.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/CorruptDocumentException.java#3 $
|
||||
* @see com.twelvemonkeys.io.ole2.CompoundDocument
|
||||
*/
|
||||
public class CorruptDocumentException extends IOException {
|
||||
public CorruptDocumentException() {
|
||||
this("Corrupt OLE 2 Compound Document");
|
||||
}
|
||||
|
||||
public CorruptDocumentException(final String pMessage) {
|
||||
super(pMessage);
|
||||
}
|
||||
|
||||
public CorruptDocumentException(final Throwable pCause) {
|
||||
super(pCause.getMessage());
|
||||
initCause(pCause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io.ole2;
|
||||
|
||||
import com.twelvemonkeys.io.SeekableInputStream;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
* Represents an OLE 2 compound document entry.
|
||||
* This is similar to a file in a file system, or an entry in a ZIP or JAR file.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/Entry.java#4 $
|
||||
* @see com.twelvemonkeys.io.ole2.CompoundDocument
|
||||
*/
|
||||
// TODO: Consider extending java.io.File...
|
||||
public final class Entry implements Comparable<Entry> {
|
||||
String name;
|
||||
byte type;
|
||||
byte nodeColor;
|
||||
|
||||
int prevDId;
|
||||
int nextDId;
|
||||
int rootNodeDId;
|
||||
|
||||
long createdTimestamp;
|
||||
long modifiedTimestamp;
|
||||
|
||||
int startSId;
|
||||
int streamSize;
|
||||
|
||||
CompoundDocument mDocument;
|
||||
Entry mParent;
|
||||
SortedSet<Entry> mChildren;
|
||||
|
||||
public final static int LENGTH = 128;
|
||||
|
||||
static final int EMPTY = 0;
|
||||
static final int USER_STORAGE = 1;
|
||||
static final int USER_STREAM = 2;
|
||||
static final int LOCK_BYTES = 3;
|
||||
static final int PROPERTY = 4;
|
||||
static final int ROOT_STORAGE = 5;
|
||||
|
||||
private static final SortedSet<Entry> NO_CHILDREN = Collections.unmodifiableSortedSet(new TreeSet<Entry>());
|
||||
|
||||
private Entry() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an entry from the input.
|
||||
*
|
||||
* @param pInput the input data
|
||||
* @return the {@code Entry} read from the input data
|
||||
* @throws IOException if an i/o exception occurs during reading
|
||||
*/
|
||||
static Entry readEntry(final DataInput pInput) throws IOException {
|
||||
Entry p = new Entry();
|
||||
p.read(pInput);
|
||||
return p;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads this entry
|
||||
*
|
||||
* @param pInput the input data
|
||||
* @throws IOException if an i/o exception occurs during reading
|
||||
*/
|
||||
private void read(final DataInput pInput) throws IOException {
|
||||
char[] chars = new char[32];
|
||||
for (int i = 0; i < chars.length; i++) {
|
||||
chars[i] = pInput.readChar();
|
||||
}
|
||||
|
||||
// NOTE: Length is in bytes, including the null-terminator...
|
||||
int nameLength = pInput.readShort();
|
||||
name = new String(chars, 0, (nameLength - 1) / 2);
|
||||
//System.out.println("name: " + name);
|
||||
|
||||
type = pInput.readByte();
|
||||
//System.out.println("type: " + type);
|
||||
|
||||
nodeColor = pInput.readByte();
|
||||
//System.out.println("nodeColor: " + nodeColor);
|
||||
|
||||
prevDId = pInput.readInt();
|
||||
//System.out.println("prevDID: " + prevDID);
|
||||
nextDId = pInput.readInt();
|
||||
//System.out.println("nextDID: " + nextDID);
|
||||
rootNodeDId = pInput.readInt();
|
||||
//System.out.println("rootNodeDID: " + rootNodeDID);
|
||||
|
||||
// UID (16) + user flags (4), ignored
|
||||
if (pInput.skipBytes(20) != 20) {
|
||||
throw new CorruptDocumentException();
|
||||
}
|
||||
|
||||
createdTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong());
|
||||
modifiedTimestamp = CompoundDocument.toJavaTimeInMillis(pInput.readLong());
|
||||
|
||||
startSId = pInput.readInt();
|
||||
//System.out.println("startSID: " + startSID);
|
||||
streamSize = pInput.readInt();
|
||||
//System.out.println("streamSize: " + streamSize);
|
||||
|
||||
// Reserved
|
||||
pInput.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code true} this {@code Entry} is the root {@code Entry}.
|
||||
*
|
||||
* @return {@code true} if this is the root {@code Entry}
|
||||
*/
|
||||
public boolean isRoot() {
|
||||
return type == ROOT_STORAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code true} this {@code Entry} is a directory
|
||||
* {@code Entry}.
|
||||
*
|
||||
* @return {@code true} if this is a directory {@code Entry}
|
||||
*/
|
||||
public boolean isDirectory() {
|
||||
return type == USER_STORAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@code true} this {@code Entry} is a file (document)
|
||||
* {@code Entry}.
|
||||
*
|
||||
* @return {@code true} if this is a document {@code Entry}
|
||||
*/
|
||||
public boolean isFile() {
|
||||
return type == USER_STREAM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this {@code Entry}
|
||||
*
|
||||
* @return the name of this {@code Entry}
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code InputStream} for this {@code Entry}
|
||||
*
|
||||
* @return an {@code InputStream} containing the data for this
|
||||
* {@code Entry} or {@code null} if this is a directory {@code Entry}
|
||||
* @throws java.io.IOException if an I/O exception occurs
|
||||
* @see #length()
|
||||
*/
|
||||
public SeekableInputStream getInputStream() throws IOException {
|
||||
if (isDirectory()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return mDocument.getInputStreamForSId(startSId, streamSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of this entry
|
||||
*
|
||||
* @return the length of the stream for this entry, or {@code 0} if this is
|
||||
* a directory {@code Entry}
|
||||
* @see #getInputStream()
|
||||
*/
|
||||
public long length() {
|
||||
if (isDirectory()) {
|
||||
return 0L;
|
||||
}
|
||||
return streamSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time that this entry was created.
|
||||
* The time is converted from its internal representation to standard Java
|
||||
* representation, milliseconds since the epoch
|
||||
* (00:00:00 GMT, January 1, 1970).
|
||||
* <p/>
|
||||
* Note that most applications leaves this value empty ({@code 0L}).
|
||||
*
|
||||
* @return A {@code long} value representing the time this entry was
|
||||
* created, measured in milliseconds since the epoch
|
||||
* (00:00:00 GMT, January 1, 1970), or {@code 0L} if no
|
||||
* creation time stamp exists for this entry.
|
||||
*/
|
||||
public long created() {
|
||||
return createdTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time that this entry was last modified.
|
||||
* The time is converted from its internal representation to standard Java
|
||||
* representation, milliseconds since the epoch
|
||||
* (00:00:00 GMT, January 1, 1970).
|
||||
* <p/>
|
||||
* Note that many applications leaves this value empty ({@code 0L}).
|
||||
*
|
||||
* @return A {@code long} value representing the time this entry was
|
||||
* last modified, measured in milliseconds since the epoch
|
||||
* (00:00:00 GMT, January 1, 1970), or {@code 0L} if no
|
||||
* modification time stamp exists for this entry.
|
||||
*/
|
||||
public long lastModified() {
|
||||
return modifiedTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parent of this {@code Entry}
|
||||
*
|
||||
* @return the parent of this {@code Entry}, or {@code null} if this is
|
||||
* the root {@code Entry}
|
||||
*/
|
||||
public Entry getParentEntry() {
|
||||
return mParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the child of this {@code Entry} with the given name.
|
||||
*
|
||||
* @param pName the name of the child {@code Entry}
|
||||
* @return the child {@code Entry} or {@code null} if thee is no such
|
||||
* child
|
||||
* @throws java.io.IOException if an I/O exception occurs
|
||||
*/
|
||||
public Entry getChildEntry(final String pName) throws IOException {
|
||||
if (isFile() || rootNodeDId == -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Entry dummy = new Entry();
|
||||
dummy.name = pName;
|
||||
dummy.mParent = this;
|
||||
|
||||
SortedSet child = getChildEntries().tailSet(dummy);
|
||||
return (Entry) child.first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the children of this {@code Entry}.
|
||||
*
|
||||
* @return a {@code SortedSet} of {@code Entry} objects
|
||||
* @throws java.io.IOException if an I/O exception occurs
|
||||
*/
|
||||
public SortedSet<Entry> getChildEntries() throws IOException {
|
||||
if (mChildren == null) {
|
||||
if (isFile() || rootNodeDId == -1) {
|
||||
mChildren = NO_CHILDREN;
|
||||
}
|
||||
else {
|
||||
// Start at root node in R/B tree, and raed to the left and right,
|
||||
// re-build tree, according to the docs
|
||||
mChildren = mDocument.getEntries(rootNodeDId, this);
|
||||
}
|
||||
}
|
||||
|
||||
return mChildren;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "\"" + name + "\""
|
||||
+ " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root"))
|
||||
+ (mParent != null ? ", parent: \"" + mParent.getName() + "\"" : "")
|
||||
+ (isFile() ? "" : ", children: " + (mChildren != null ? String.valueOf(mChildren.size()) : "(unknown)"))
|
||||
+ ", SId=" + startSId + ", length=" + streamSize + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object pOther) {
|
||||
if (pOther == this) {
|
||||
return true;
|
||||
}
|
||||
if (!(pOther instanceof Entry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Entry other = (Entry) pOther;
|
||||
return name.equals(other.name) && (mParent == other.mParent
|
||||
|| (mParent != null && mParent.equals(other.mParent)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode() ^ startSId;
|
||||
}
|
||||
|
||||
public int compareTo(final Entry pOther) {
|
||||
if (this == pOther) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: This is the sorting algorthm defined by the Compound Document:
|
||||
// - first sort by name length
|
||||
// - if lengths are equal, sort by comparing strings, case sensitive
|
||||
|
||||
int diff = name.length() - pOther.name.length();
|
||||
if (diff != 0) {
|
||||
return diff;
|
||||
}
|
||||
|
||||
return name.compareTo(pOther.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Copyright (c) 2008, Harald Kuhr
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name "TwelveMonkeys" nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io.ole2;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* SIdChain
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.no">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haku $
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java#1 $
|
||||
*/
|
||||
class SIdChain {
|
||||
int[] chain;
|
||||
int size = 0;
|
||||
int next = 0;
|
||||
|
||||
public SIdChain() {
|
||||
chain = new int[16];
|
||||
}
|
||||
|
||||
void addSID(int pSID) {
|
||||
ensureCapacity();
|
||||
chain[size++] = pSID;
|
||||
}
|
||||
|
||||
private void ensureCapacity() {
|
||||
if (chain.length == size) {
|
||||
int[] temp = new int[size << 1];
|
||||
System.arraycopy(chain, 0, temp, 0, size);
|
||||
chain = temp;
|
||||
}
|
||||
}
|
||||
|
||||
public int[] getChain() {
|
||||
int[] result = new int[size];
|
||||
System.arraycopy(chain, 0, result, 0, size);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
next = 0;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return next < size;
|
||||
}
|
||||
|
||||
public int next() {
|
||||
if (next >= size) {
|
||||
throw new NoSuchElementException("No element");
|
||||
}
|
||||
return chain[next++];
|
||||
}
|
||||
|
||||
public int get(final int pIndex) {
|
||||
return chain[pIndex];
|
||||
}
|
||||
|
||||
public int length() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder(size * 5);
|
||||
buf.append('[');
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (i != 0) {
|
||||
buf.append(',');
|
||||
}
|
||||
buf.append(chain[i]);
|
||||
}
|
||||
buf.append(']');
|
||||
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Contains classes for reading the contents of the
|
||||
* Microsoft OLE 2 compound document format.
|
||||
*
|
||||
* @see com.twelvemonkeys.io.ole2.CompoundDocument
|
||||
* @see <a href="http://sc.openoffice.org/compdocfileformat.pdf">OpenOffice.org's documentation</a>
|
||||
*
|
||||
* @version 2.0
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
package com.twelvemonkeys.io.ole2;
|
||||
Reference in New Issue
Block a user