moving files around

This commit is contained in:
Erlend Hamnaberg
2009-11-06 21:36:46 +01:00
parent ad913b5093
commit b3aa378f16
79 changed files with 0 additions and 0 deletions
@@ -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();
}
}
}
@@ -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;