mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2026-03-20 00:00:03 -04:00
moving files around
This commit is contained in:
@@ -0,0 +1,277 @@
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Represents a cached seekable stream, that reads through a cache.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">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/AbstractCachedSeekableStream.java#2 $
|
||||
*/
|
||||
abstract class AbstractCachedSeekableStream extends SeekableInputStream {
|
||||
/** The backing stream */
|
||||
protected final InputStream mStream;
|
||||
|
||||
/** The stream positon in the backing stream (mStream) */
|
||||
protected long mStreamPosition;
|
||||
|
||||
private StreamCache mCache;
|
||||
|
||||
protected AbstractCachedSeekableStream(final InputStream pStream, final StreamCache pCache) {
|
||||
Validate.notNull(pStream, "stream");
|
||||
Validate.notNull(pCache, "cache");
|
||||
|
||||
mStream = pStream;
|
||||
mCache = pCache;
|
||||
}
|
||||
|
||||
protected final StreamCache getCache() {
|
||||
return mCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
long avail = mStreamPosition - mPosition + mStream.available();
|
||||
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
checkOpen();
|
||||
int read;
|
||||
|
||||
if (mPosition == mStreamPosition) {
|
||||
// TODO: Read more bytes here!
|
||||
// TODO: Use buffer if not in-memory cache? (See FileCacheSeekableStream overrides).
|
||||
// Read a byte from the stream
|
||||
read = mStream.read();
|
||||
|
||||
if (read >= 0) {
|
||||
mStreamPosition++;
|
||||
mCache.write(read);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// ..or read byte from the cache
|
||||
syncPosition();
|
||||
read = mCache.read();
|
||||
}
|
||||
|
||||
// TODO: This field is not REALLY considered accessible.. :-P
|
||||
if (read != -1) {
|
||||
mPosition++;
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] pBytes, int pOffset, int pLength) throws IOException {
|
||||
checkOpen();
|
||||
int length;
|
||||
|
||||
if (mPosition == mStreamPosition) {
|
||||
// Read bytes from the stream
|
||||
length = mStream.read(pBytes, pOffset, pLength);
|
||||
|
||||
if (length > 0) {
|
||||
mStreamPosition += length;
|
||||
mCache.write(pBytes, pOffset, length);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// ...or read bytes from the cache
|
||||
syncPosition();
|
||||
length = mCache.read(pBytes, pOffset, pLength);
|
||||
}
|
||||
|
||||
// TODO: This field is not REALLY considered accessible.. :-P
|
||||
if (length > 0) {
|
||||
mPosition += length;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
protected final void syncPosition() throws IOException {
|
||||
if (mCache.getPosition() != mPosition) {
|
||||
mCache.seek(mPosition); // Assure EOF is correctly thrown
|
||||
}
|
||||
}
|
||||
|
||||
public final boolean isCached() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public abstract boolean isCachedMemory();
|
||||
|
||||
public abstract boolean isCachedFile();
|
||||
|
||||
protected void seekImpl(long pPosition) throws IOException {
|
||||
if (mStreamPosition < pPosition) {
|
||||
// Make sure we append at end of cache
|
||||
if (mCache.getPosition() != mStreamPosition) {
|
||||
mCache.seek(mStreamPosition);
|
||||
}
|
||||
|
||||
// Read diff from stream into cache
|
||||
long left = pPosition - mStreamPosition;
|
||||
|
||||
// TODO: Use fixed buffer, instead of allocating here...
|
||||
int bufferLen = left > 1024 ? 1024 : (int) left;
|
||||
byte[] buffer = new byte[bufferLen];
|
||||
|
||||
while (left > 0) {
|
||||
int length = buffer.length < left ? buffer.length : (int) left;
|
||||
int read = mStream.read(buffer, 0, length);
|
||||
|
||||
if (read > 0) {
|
||||
mCache.write(buffer, 0, read);
|
||||
mStreamPosition += read;
|
||||
left -= read;
|
||||
}
|
||||
else if (read < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (mStreamPosition >= pPosition) {
|
||||
// Seek backwards into the cache
|
||||
mCache.seek(pPosition);
|
||||
}
|
||||
|
||||
// System.out.println("pPosition: " + pPosition);
|
||||
// System.out.println("mPosition: " + mPosition);
|
||||
// System.out.println("mStreamPosition: " + mStreamPosition);
|
||||
// System.out.println("mCache.mPosition: " + mCache.getPosition());
|
||||
|
||||
// NOTE: If mPosition == pPosition then we're good to go
|
||||
}
|
||||
|
||||
protected void flushBeforeImpl(long pPosition) {
|
||||
mCache.flush(pPosition);
|
||||
}
|
||||
|
||||
protected void closeImpl() throws IOException {
|
||||
mCache.flush(mPosition);
|
||||
mCache = null;
|
||||
mStream.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* An abstract stream cache.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">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/AbstractCachedSeekableStream.java#2 $
|
||||
*/
|
||||
public static abstract class StreamCache {
|
||||
|
||||
/**
|
||||
* Creates a {@code StreamCache}.
|
||||
*/
|
||||
protected StreamCache() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a single byte at the current read/write position. The read/write position will be increased by one.
|
||||
*
|
||||
* @param pByte the byte value to write.
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
|
||||
*/
|
||||
abstract void write(int pByte) throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a series of bytes at the current read/write position. The read/write position will be increased by
|
||||
* {@code pLength}.
|
||||
* <p/>
|
||||
* This implementation invokes {@link #write(int)} {@code pLength} times.
|
||||
* Subclasses may override this method for performance.
|
||||
*
|
||||
* @param pBuffer the bytes to write.
|
||||
* @param pOffset the starting offset into the buffer.
|
||||
* @param pLength the number of bytes to write from the buffer.
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
|
||||
*/
|
||||
void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
for (int i = 0; i < pLength; i++) {
|
||||
write(pBuffer[pOffset + i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a single byte a the current read/write position. The read/write position will be increased by one.
|
||||
*
|
||||
* @return the value read, or {@code -1} to indicate EOF.
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
|
||||
*/
|
||||
abstract int read() throws IOException;
|
||||
|
||||
/**
|
||||
* Writes a series of bytes at the current read/write position. The read/write position will be increased by
|
||||
* {@code pLength}.
|
||||
* <p/>
|
||||
* This implementation invokes {@link #read()} {@code pLength} times.
|
||||
* Subclasses may override this method for performance.
|
||||
*
|
||||
* @param pBuffer the bytes to write
|
||||
* @param pOffset the starting offset into the buffer.
|
||||
* @param pLength the number of bytes to write from the buffer.
|
||||
* @return the number of bytes read, or {@code -1} to indicate EOF.
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
|
||||
*/
|
||||
int read(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
int count = 0;
|
||||
for (int i = 0; i < pLength; i++) {
|
||||
int read = read();
|
||||
if (read >= 0) {
|
||||
pBuffer[pOffset + i] = (byte) read;
|
||||
count++;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Repositions the current cache read/write position to the given position.
|
||||
*
|
||||
* @param pPosition the new read/write position
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs in the cache backing mechanism.
|
||||
*/
|
||||
abstract void seek(long pPosition) throws IOException;
|
||||
|
||||
/**
|
||||
* Optionally flushes any data prior to the given position.
|
||||
* <p/>
|
||||
* Attempting to perform a seek operation, and/or a read or write operation to a position equal to or before
|
||||
* the flushed position may result in exceptions or undefined behaviour.
|
||||
* <p/>
|
||||
* Subclasses should override this method for performance reasons, to avoid holding on to unnecessary resources.
|
||||
* This implementation does nothing.
|
||||
*
|
||||
* @param pPosition the last position to flush.
|
||||
*/
|
||||
void flush(final long pPosition) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current cache read/write position.
|
||||
*
|
||||
* @return the current cache read/write postion.
|
||||
*
|
||||
* @throws IOException if the position can't be determined because of a problem in the cache backing mechanism.
|
||||
*/
|
||||
abstract long getPosition() throws IOException;
|
||||
}
|
||||
}
|
||||
218
common/common-io/src/main/java/com/twelvemonkeys/io/CompoundReader.java
Executable file
218
common/common-io/src/main/java/com/twelvemonkeys/io/CompoundReader.java
Executable file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.Iterator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A Reader implementation that can read from multiple sources.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">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/CompoundReader.java#2 $
|
||||
*/
|
||||
public class CompoundReader extends Reader {
|
||||
|
||||
private Reader mCurrent;
|
||||
private List<Reader> mReaders;
|
||||
protected final Object mLock;
|
||||
|
||||
protected final boolean mMarkSupported;
|
||||
|
||||
private int mCurrentReader;
|
||||
private int mMarkedReader;
|
||||
private int mMark;
|
||||
private int mNext;
|
||||
|
||||
/**
|
||||
* Create a new compound reader.
|
||||
*
|
||||
* @param pReaders {@code Iterator} containting {@code Reader}s,
|
||||
* providing the character stream.
|
||||
*
|
||||
* @throws NullPointerException if {@code pReaders} is {@code null}, or
|
||||
* any of the elements in the iterator is {@code null}.
|
||||
* @throws ClassCastException if any element of the iterator is not a
|
||||
* {@code java.io.Reader}
|
||||
*/
|
||||
public CompoundReader(final Iterator<Reader> pReaders) {
|
||||
super(Validate.notNull(pReaders, "readers"));
|
||||
|
||||
mLock = pReaders; // NOTE: It's ok to sync on pReaders, as the
|
||||
// reference can't change, only it's elements
|
||||
|
||||
mReaders = new ArrayList<Reader>();
|
||||
|
||||
boolean markSupported = true;
|
||||
while (pReaders.hasNext()) {
|
||||
Reader reader = pReaders.next();
|
||||
if (reader == null) {
|
||||
throw new NullPointerException("readers cannot contain null-elements");
|
||||
}
|
||||
mReaders.add(reader);
|
||||
markSupported = markSupported && reader.markSupported();
|
||||
}
|
||||
mMarkSupported = markSupported;
|
||||
|
||||
mCurrent = nextReader();
|
||||
}
|
||||
|
||||
protected final Reader nextReader() {
|
||||
if (mCurrentReader >= mReaders.size()) {
|
||||
mCurrent = new EmptyReader();
|
||||
}
|
||||
else {
|
||||
mCurrent = mReaders.get(mCurrentReader++);
|
||||
}
|
||||
|
||||
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
|
||||
mNext = 0;
|
||||
return mCurrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to make sure that the stream has not been closed
|
||||
*
|
||||
* @throws IOException if the stream is closed
|
||||
*/
|
||||
protected final void ensureOpen() throws IOException {
|
||||
if (mReaders == null) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
// Close all readers
|
||||
for (Reader reader : mReaders) {
|
||||
reader.close();
|
||||
}
|
||||
mReaders = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark(int pReadLimit) throws IOException {
|
||||
if (pReadLimit < 0) {
|
||||
throw new IllegalArgumentException("Read limit < 0");
|
||||
}
|
||||
|
||||
// TODO: It would be nice if we could actually close some readers now
|
||||
|
||||
synchronized (mLock) {
|
||||
ensureOpen();
|
||||
mMark = mNext;
|
||||
mMarkedReader = mCurrentReader;
|
||||
|
||||
mCurrent.mark(pReadLimit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
synchronized (mLock) {
|
||||
ensureOpen();
|
||||
|
||||
if (mCurrentReader != mMarkedReader) {
|
||||
// Reset any reader before this
|
||||
for (int i = mCurrentReader; i >= mMarkedReader; i--) {
|
||||
mReaders.get(i).reset();
|
||||
}
|
||||
|
||||
mCurrentReader = mMarkedReader - 1;
|
||||
nextReader();
|
||||
}
|
||||
mCurrent.reset();
|
||||
|
||||
mNext = mMark;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean markSupported() {
|
||||
return mMarkSupported;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
synchronized (mLock) {
|
||||
int read = mCurrent.read();
|
||||
|
||||
if (read < 0 && mCurrentReader < mReaders.size()) {
|
||||
nextReader();
|
||||
return read(); // In case of 0-length readers
|
||||
}
|
||||
|
||||
mNext++;
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
|
||||
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
|
||||
synchronized (mLock) {
|
||||
int read = mCurrent.read(pBuffer, pOffset, pLength);
|
||||
|
||||
if (read < 0 && mCurrentReader < mReaders.size()) {
|
||||
nextReader();
|
||||
return read(pBuffer, pOffset, pLength); // In case of 0-length readers
|
||||
}
|
||||
|
||||
mNext += read;
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean ready() throws IOException {
|
||||
return mCurrent.ready();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long pChars) throws IOException {
|
||||
synchronized (mLock) {
|
||||
long skipped = mCurrent.skip(pChars);
|
||||
|
||||
if (skipped == 0 && mCurrentReader < mReaders.size()) {
|
||||
nextReader();
|
||||
return skip(pChars); // In case of 0-length readers
|
||||
}
|
||||
|
||||
mNext += skipped;
|
||||
|
||||
return skipped;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
common/common-io/src/main/java/com/twelvemonkeys/io/EmptyReader.java
Executable file
45
common/common-io/src/main/java/com/twelvemonkeys/io/EmptyReader.java
Executable file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.StringReader;
|
||||
|
||||
/**
|
||||
* EmptyReader
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">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/EmptyReader.java#1 $
|
||||
*/
|
||||
final class EmptyReader extends StringReader {
|
||||
public EmptyReader() {
|
||||
super("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
/**
|
||||
* An unsynchronized {@code ByteArrayOutputStream} implementation. This version
|
||||
* also has a constructor that lets you create a stream with initial content.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java#2 $
|
||||
*/
|
||||
public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
|
||||
/** Max grow size (unless if writing more than this ammount of bytes) */
|
||||
protected int mMaxGrowSize = 1024 * 1024; // 1 MB
|
||||
|
||||
/**
|
||||
* Creates a {@code ByteArrayOutputStream} with the given initial buffer
|
||||
* size.
|
||||
*
|
||||
* @param pSize initial buffer size
|
||||
*/
|
||||
public FastByteArrayOutputStream(int pSize) {
|
||||
super(pSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code ByteArrayOutputStream} with the given initial content.
|
||||
* <p/>
|
||||
* Note that the buffer is not cloned, for maximum performance.
|
||||
*
|
||||
* @param pBuffer initial buffer
|
||||
*/
|
||||
public FastByteArrayOutputStream(byte[] pBuffer) {
|
||||
super(0); // Don't allocate array
|
||||
buf = pBuffer;
|
||||
count = pBuffer.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(byte pBytes[], int pOffset, int pLength) {
|
||||
if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
|
||||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
else if (pLength == 0) {
|
||||
return;
|
||||
}
|
||||
int newcount = count + pLength;
|
||||
growIfNeeded(newcount);
|
||||
System.arraycopy(pBytes, pOffset, buf, count, pLength);
|
||||
count = newcount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void write(int pByte) {
|
||||
int newcount = count + 1;
|
||||
growIfNeeded(newcount);
|
||||
buf[count] = (byte) pByte;
|
||||
count = newcount;
|
||||
}
|
||||
|
||||
private void growIfNeeded(int pNewcount) {
|
||||
if (pNewcount > buf.length) {
|
||||
int newSize = Math.max(Math.min(buf.length << 1, buf.length + mMaxGrowSize), pNewcount);
|
||||
byte newBuf[] = new byte[newSize];
|
||||
System.arraycopy(buf, 0, newBuf, 0, count);
|
||||
buf = newBuf;
|
||||
}
|
||||
}
|
||||
|
||||
// Non-synchronized version of writeTo
|
||||
@Override
|
||||
public void writeTo(OutputStream pOut) throws IOException {
|
||||
pOut.write(buf, 0, count);
|
||||
}
|
||||
|
||||
// Non-synchronized version of toByteArray
|
||||
@Override
|
||||
public byte[] toByteArray() {
|
||||
byte newbuf[] = new byte[count];
|
||||
System.arraycopy(buf, 0, newbuf, 0, count);
|
||||
return newbuf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code ByteArrayInputStream} that reads directly from this
|
||||
* {@code FastByteArrayOutputStream}'s byte buffer.
|
||||
* The buffer is not cloned, for maximum performance.
|
||||
* <p/>
|
||||
* Note that care needs to be taken to avoid writes to
|
||||
* this output stream after the input stream is created.
|
||||
* Failing to do so, may result in unpredictable behviour.
|
||||
*
|
||||
* @return a new {@code ByteArrayInputStream}, reading from this stream's buffer.
|
||||
*/
|
||||
public ByteArrayInputStream createInputStream() {
|
||||
return new ByteArrayInputStream(buf, 0, count);
|
||||
}
|
||||
}
|
||||
313
common/common-io/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java
Executable file
313
common/common-io/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java
Executable file
@@ -0,0 +1,313 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* A {@code SeekableInputStream} implementation that caches data in a temporary {@code File}.
|
||||
* <p/>
|
||||
* Temporary files are created as specified in {@link File#createTempFile(String, String, java.io.File)}.
|
||||
*
|
||||
* @see MemoryCacheSeekableStream
|
||||
* @see FileSeekableStream
|
||||
*
|
||||
* @see File#createTempFile(String, String)
|
||||
* @see RandomAccessFile
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java#5 $
|
||||
*/
|
||||
public final class FileCacheSeekableStream extends AbstractCachedSeekableStream {
|
||||
|
||||
// private final InputStream mStream;
|
||||
// private final RandomAccessFile mCache;
|
||||
private byte[] mBuffer;
|
||||
|
||||
/** The stream positon in the backing stream (mStream) */
|
||||
// private long mStreamPosition;
|
||||
|
||||
// TODO: getStreamPosition() should always be the same as
|
||||
// mCache.getFilePointer()
|
||||
// otherwise there's some inconsistency here... Enforce this?
|
||||
|
||||
/**
|
||||
* Creates a {@code FileCacheSeekableStream} reading from the given
|
||||
* {@code InputStream}. Data will be cached in a temporary file.
|
||||
*
|
||||
* @param pStream the {@code InputStream} to read from
|
||||
*
|
||||
* @throws IOException if the temporary file cannot be created,
|
||||
* or cannot be opened for random access.
|
||||
*/
|
||||
public FileCacheSeekableStream(final InputStream pStream) throws IOException {
|
||||
this(pStream, "iocache", null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code FileCacheSeekableStream} reading from the given
|
||||
* {@code InputStream}. Data will be cached in a temporary file, with
|
||||
* the given base name.
|
||||
*
|
||||
* @param pStream the {@code InputStream} to read from
|
||||
* @param pTempBaseName optional base name for the temporary file
|
||||
*
|
||||
* @throws IOException if the temporary file cannot be created,
|
||||
* or cannot be opened for random access.
|
||||
*/
|
||||
public FileCacheSeekableStream(final InputStream pStream, final String pTempBaseName) throws IOException {
|
||||
this(pStream, pTempBaseName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code FileCacheSeekableStream} reading from the given
|
||||
* {@code InputStream}. Data will be cached in a temporary file, with
|
||||
* the given base name, in the given directory
|
||||
*
|
||||
* @param pStream the {@code InputStream} to read from
|
||||
* @param pTempBaseName optional base name for the temporary file
|
||||
* @param pTempDir optional temp directory
|
||||
*
|
||||
* @throws IOException if the temporary file cannot be created,
|
||||
* or cannot be opened for random access.
|
||||
*/
|
||||
public FileCacheSeekableStream(final InputStream pStream, final String pTempBaseName, final File pTempDir) throws IOException {
|
||||
// NOTE: We do validation BEFORE we create temp file, to avoid orphan files
|
||||
this(Validate.notNull(pStream, "stream"), createTempFile(pTempBaseName, pTempDir));
|
||||
}
|
||||
|
||||
/*protected*/ static File createTempFile(String pTempBaseName, File pTempDir) throws IOException {
|
||||
Validate.notNull(pTempBaseName, "tempBaseName");
|
||||
|
||||
File file = File.createTempFile(pTempBaseName, null, pTempDir);
|
||||
file.deleteOnExit();
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
// TODO: Consider exposing this for external use
|
||||
/*protected*/ FileCacheSeekableStream(final InputStream pStream, final File pFile) throws FileNotFoundException {
|
||||
super(pStream, new FileCache(pFile));
|
||||
|
||||
// TODO: Allow for custom buffer sizes?
|
||||
mBuffer = new byte[1024];
|
||||
}
|
||||
|
||||
public final boolean isCachedMemory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public final boolean isCachedFile() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void closeImpl() throws IOException {
|
||||
super.closeImpl();
|
||||
mBuffer = null;
|
||||
}
|
||||
/*
|
||||
public final boolean isCached() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// InputStream overrides
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
long avail = mStreamPosition - mPosition + mStream.available();
|
||||
return avail > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) avail;
|
||||
}
|
||||
|
||||
public void closeImpl() throws IOException {
|
||||
mStream.close();
|
||||
mCache.close();
|
||||
|
||||
// TODO: Delete cache file here?
|
||||
// ThreadPool.invokeLater(new DeleteFileAction(mCacheFile));
|
||||
}
|
||||
*/
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
checkOpen();
|
||||
|
||||
int read;
|
||||
if (mPosition == mStreamPosition) {
|
||||
// Read ahead into buffer, for performance
|
||||
read = readAhead(mBuffer, 0, mBuffer.length);
|
||||
if (read >= 0) {
|
||||
read = mBuffer[0] & 0xff;
|
||||
}
|
||||
|
||||
//System.out.println("Read 1 byte from stream: " + Integer.toHexString(read & 0xff));
|
||||
}
|
||||
else {
|
||||
// ..or read byte from the cache
|
||||
syncPosition();
|
||||
read = getCache().read();
|
||||
|
||||
//System.out.println("Read 1 byte from cache: " + Integer.toHexString(read & 0xff));
|
||||
}
|
||||
|
||||
// TODO: This field is not REALLY considered accessible.. :-P
|
||||
if (read != -1) {
|
||||
mPosition++;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] pBytes, int pOffset, int pLength) throws IOException {
|
||||
checkOpen();
|
||||
|
||||
int length;
|
||||
if (mPosition == mStreamPosition) {
|
||||
// Read bytes from the stream
|
||||
length = readAhead(pBytes, pOffset, pLength);
|
||||
|
||||
//System.out.println("Read " + length + " byte from stream");
|
||||
}
|
||||
else {
|
||||
// ...or read bytes from the cache
|
||||
syncPosition();
|
||||
length = getCache().read(pBytes, pOffset, (int) Math.min(pLength, mStreamPosition - mPosition));
|
||||
|
||||
//System.out.println("Read " + length + " byte from cache");
|
||||
}
|
||||
|
||||
// TODO: This field is not REALLY considered accessible.. :-P
|
||||
if (length > 0) {
|
||||
mPosition += length;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
int length;
|
||||
length = mStream.read(pBytes, pOffset, pLength);
|
||||
|
||||
if (length > 0) {
|
||||
mStreamPosition += length;
|
||||
getCache().write(pBytes, pOffset, length);
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
/*
|
||||
private void syncPosition() throws IOException {
|
||||
if (mCache.getFilePointer() != mPosition) {
|
||||
mCache.seek(mPosition); // Assure EOF is correctly thrown
|
||||
}
|
||||
}
|
||||
|
||||
// Seekable overrides
|
||||
|
||||
protected void flushBeforeImpl(long pPosition) {
|
||||
// TODO: Implement
|
||||
// For now, it's probably okay to do nothing, this is just for
|
||||
// performance (as long as people follow spec, not behaviour)
|
||||
}
|
||||
|
||||
protected void seekImpl(long pPosition) throws IOException {
|
||||
if (mStreamPosition < pPosition) {
|
||||
// Make sure we append at end of cache
|
||||
if (mCache.getFilePointer() != mStreamPosition) {
|
||||
mCache.seek(mStreamPosition);
|
||||
}
|
||||
|
||||
// Read diff from stream into cache
|
||||
long left = pPosition - mStreamPosition;
|
||||
int bufferLen = left > 1024 ? 1024 : (int) left;
|
||||
byte[] buffer = new byte[bufferLen];
|
||||
|
||||
while (left > 0) {
|
||||
int length = buffer.length < left ? buffer.length : (int) left;
|
||||
int read = mStream.read(buffer, 0, length);
|
||||
|
||||
if (read > 0) {
|
||||
mCache.write(buffer, 0, read);
|
||||
mStreamPosition += read;
|
||||
left -= read;
|
||||
}
|
||||
else if (read < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (mStreamPosition >= pPosition) {
|
||||
// Seek backwards into the cache
|
||||
mCache.seek(pPosition);
|
||||
}
|
||||
|
||||
// System.out.println("pPosition: " + pPosition);
|
||||
// System.out.println("mStreamPosition: " + mStreamPosition);
|
||||
// System.out.println("mCache.getFilePointer(): " + mCache.getFilePointer());
|
||||
|
||||
// NOTE: If mPosition == pPosition then we're good to go
|
||||
}
|
||||
*/
|
||||
|
||||
final static class FileCache extends StreamCache {
|
||||
private RandomAccessFile mCacheFile;
|
||||
|
||||
public FileCache(final File pFile) throws FileNotFoundException {
|
||||
Validate.notNull(pFile, "file");
|
||||
mCacheFile = new RandomAccessFile(pFile, "rw");
|
||||
}
|
||||
|
||||
public void write(final int pByte) throws IOException {
|
||||
mCacheFile.write(pByte);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
mCacheFile.write(pBuffer, pOffset, pLength);
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
return mCacheFile.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
return mCacheFile.read(pBuffer, pOffset, pLength);
|
||||
}
|
||||
|
||||
public void seek(final long pPosition) throws IOException {
|
||||
mCacheFile.seek(pPosition);
|
||||
}
|
||||
|
||||
public long getPosition() throws IOException {
|
||||
return mCacheFile.getFilePointer();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
130
common/common-io/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java
Executable file
130
common/common-io/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java
Executable file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* A {@code SeekableInputStream} implementation that uses random access directly to a {@code File}.
|
||||
* <p/>
|
||||
* @see FileCacheSeekableStream
|
||||
* @see MemoryCacheSeekableStream
|
||||
* @see RandomAccessFile
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java#4 $
|
||||
*/
|
||||
public final class FileSeekableStream extends SeekableInputStream {
|
||||
|
||||
// TODO: Figure out why this class is SLOWER than FileCacheSeekableStream in
|
||||
// my tests..?
|
||||
|
||||
final RandomAccessFile mRandomAccess;
|
||||
|
||||
/**
|
||||
* Creates a {@code FileSeekableStream} that reads from the given
|
||||
* {@code File}.
|
||||
*
|
||||
* @param pInput file to read from
|
||||
* @throws FileNotFoundException if {@code pInput} does not exist
|
||||
*/
|
||||
public FileSeekableStream(final File pInput) throws FileNotFoundException {
|
||||
this(new RandomAccessFile(pInput, "r"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code FileSeekableStream} that reads from the given file.
|
||||
* The {@code RandomAccessFile} needs only to be open in read
|
||||
* ({@code "r"}) mode.
|
||||
*
|
||||
* @param pInput file to read from
|
||||
*/
|
||||
public FileSeekableStream(final RandomAccessFile pInput) {
|
||||
mRandomAccess = pInput;
|
||||
}
|
||||
|
||||
/// Seekable
|
||||
|
||||
public boolean isCached() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCachedFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isCachedMemory() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// InputStream
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
long length = mRandomAccess.length() - mPosition;
|
||||
return length > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) length;
|
||||
}
|
||||
|
||||
public void closeImpl() throws IOException {
|
||||
mRandomAccess.close();
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
checkOpen();
|
||||
|
||||
int read = mRandomAccess.read();
|
||||
if (read >= 0) {
|
||||
mPosition++;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte pBytes[], int pOffset, int pLength) throws IOException {
|
||||
checkOpen();
|
||||
|
||||
int read = mRandomAccess.read(pBytes, pOffset, pLength);
|
||||
if (read > 0) {
|
||||
mPosition += read;
|
||||
}
|
||||
return read;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing, as we don't really do any caching here.
|
||||
*
|
||||
* @param pPosition the position to flush to
|
||||
*/
|
||||
protected void flushBeforeImpl(long pPosition) {
|
||||
}
|
||||
|
||||
protected void seekImpl(long pPosition) throws IOException {
|
||||
mRandomAccess.seek(pPosition);
|
||||
}
|
||||
}
|
||||
101
common/common-io/src/main/java/com/twelvemonkeys/io/FileSystem.java
Executable file
101
common/common-io/src/main/java/com/twelvemonkeys/io/FileSystem.java
Executable file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* FileSystem
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileSystem.java#1 $
|
||||
*/
|
||||
abstract class FileSystem {
|
||||
abstract long getFreeSpace(File pPath);
|
||||
|
||||
abstract long getTotalSpace(File pPath);
|
||||
|
||||
abstract String getName();
|
||||
|
||||
static BufferedReader exec(String[] pArgs) throws IOException {
|
||||
Process cmd = Runtime.getRuntime().exec(pArgs);
|
||||
return new BufferedReader(new InputStreamReader(cmd.getInputStream()));
|
||||
}
|
||||
|
||||
static FileSystem get() {
|
||||
String os = System.getProperty("os.name");
|
||||
//System.out.println("os = " + os);
|
||||
|
||||
os = os.toLowerCase();
|
||||
if (os.indexOf("windows") != -1) {
|
||||
return new Win32FileSystem();
|
||||
}
|
||||
else if (os.indexOf("linux") != -1 ||
|
||||
os.indexOf("sun os") != -1 ||
|
||||
os.indexOf("sunos") != -1 ||
|
||||
os.indexOf("solaris") != -1 ||
|
||||
os.indexOf("mpe/ix") != -1 ||
|
||||
os.indexOf("hp-ux") != -1 ||
|
||||
os.indexOf("aix") != -1 ||
|
||||
os.indexOf("freebsd") != -1 ||
|
||||
os.indexOf("irix") != -1 ||
|
||||
os.indexOf("digital unix") != -1 ||
|
||||
os.indexOf("unix") != -1 ||
|
||||
os.indexOf("mac os x") != -1) {
|
||||
return new UnixFileSystem();
|
||||
}
|
||||
else {
|
||||
return new UnknownFileSystem(os);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UnknownFileSystem extends FileSystem {
|
||||
private final String mOSName;
|
||||
|
||||
UnknownFileSystem(String pOSName) {
|
||||
mOSName = pOSName;
|
||||
}
|
||||
|
||||
long getFreeSpace(File pPath) {
|
||||
return 0l;
|
||||
}
|
||||
|
||||
long getTotalSpace(File pPath) {
|
||||
return 0l;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return "Unknown (" + mOSName + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
1092
common/common-io/src/main/java/com/twelvemonkeys/io/FileUtil.java
Executable file
1092
common/common-io/src/main/java/com/twelvemonkeys/io/FileUtil.java
Executable file
File diff suppressed because it is too large
Load Diff
239
common/common-io/src/main/java/com/twelvemonkeys/io/FilenameMaskFilter.java
Executable file
239
common/common-io/src/main/java/com/twelvemonkeys/io/FilenameMaskFilter.java
Executable file
@@ -0,0 +1,239 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.twelvemonkeys.util.regex.WildcardStringParser;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
|
||||
/**
|
||||
* A Java Bean used for approving file names which are to be included in a
|
||||
* {@code java.io.File} listing.
|
||||
* The mask is given as a well-known DOS filename format, with '*' and '?' as
|
||||
* wildcards.
|
||||
* All other characters counts as ordinary characters.
|
||||
* <p/>
|
||||
* The file name masks are used as a filter input and is given to the class via
|
||||
* the string array property:<br>
|
||||
* <dd>{@code filenameMasksForInclusion} - Filename mask for exclusion of
|
||||
* files (default if both properties are defined)
|
||||
* <dd>{@code filenameMasksForExclusion} - Filename mask for exclusion of
|
||||
* files.
|
||||
* <p/>
|
||||
* A recommended way of doing this is by referencing to the component which uses
|
||||
* this class for file listing. In this way all properties are set in the same
|
||||
* component and this utility component is kept in the background with only
|
||||
* initial configuration necessary.
|
||||
*
|
||||
* @author <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
|
||||
* @see File#list(java.io.FilenameFilter) java.io.File.list
|
||||
* @see FilenameFilter java.io.FilenameFilter
|
||||
* @see WildcardStringParser
|
||||
*/
|
||||
public class FilenameMaskFilter implements FilenameFilter {
|
||||
|
||||
// Members
|
||||
private String[] mFilenameMasksForInclusion;
|
||||
private String[] mFilenameMasksForExclusion;
|
||||
private boolean mInclusion = true;
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@code FilenameMaskFilter}
|
||||
*/
|
||||
public FilenameMaskFilter() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code FilenameMaskFilter}
|
||||
*
|
||||
* @param pFilenameMask the filename mask
|
||||
*/
|
||||
public FilenameMaskFilter(final String pFilenameMask) {
|
||||
String[] filenameMask = {pFilenameMask};
|
||||
setFilenameMasksForInclusion(filenameMask);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code FilenameMaskFilter}
|
||||
*
|
||||
* @param pFilenameMasks the filename masks
|
||||
*/
|
||||
public FilenameMaskFilter(final String[] pFilenameMasks) {
|
||||
this(pFilenameMasks, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code FilenameMaskFilter}
|
||||
*
|
||||
* @param pFilenameMask the filename masks
|
||||
* @param pExclusion if {@code true}, the masks will be excluded
|
||||
*/
|
||||
public FilenameMaskFilter(final String pFilenameMask, final boolean pExclusion) {
|
||||
String[] filenameMask = {pFilenameMask};
|
||||
|
||||
if (pExclusion) {
|
||||
setFilenameMasksForExclusion(filenameMask);
|
||||
}
|
||||
else {
|
||||
setFilenameMasksForInclusion(filenameMask);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code FilenameMaskFilter}
|
||||
*
|
||||
* @param pFilenameMasks the filename masks
|
||||
* @param pExclusion if {@code true}, the masks will be excluded
|
||||
*/
|
||||
public FilenameMaskFilter(final String[] pFilenameMasks, final boolean pExclusion) {
|
||||
if (pExclusion) {
|
||||
setFilenameMasksForExclusion(pFilenameMasks);
|
||||
}
|
||||
else {
|
||||
setFilenameMasksForInclusion(pFilenameMasks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param pFilenameMasksForInclusion the filename masks to include
|
||||
*/
|
||||
public void setFilenameMasksForInclusion(String[] pFilenameMasksForInclusion) {
|
||||
mFilenameMasksForInclusion = pFilenameMasksForInclusion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current inclusion masks
|
||||
*/
|
||||
public String[] getFilenameMasksForInclusion() {
|
||||
return mFilenameMasksForInclusion.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pFilenameMasksForExclusion the filename masks to exclude
|
||||
*/
|
||||
public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) {
|
||||
mFilenameMasksForExclusion = pFilenameMasksForExclusion;
|
||||
mInclusion = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the current exclusion masks
|
||||
*/
|
||||
public String[] getFilenameMasksForExclusion() {
|
||||
return mFilenameMasksForExclusion.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method implements the {@code java.io.FilenameFilter} interface.
|
||||
*
|
||||
* @param pDir the directory in which the file was found.
|
||||
* @param pName the name of the file.
|
||||
* @return {@code true} if the file {@code pName} should be included in the file
|
||||
* list; {@code false} otherwise.
|
||||
*/
|
||||
public boolean accept(File pDir, String pName) {
|
||||
WildcardStringParser parser;
|
||||
|
||||
// Check each filename string mask whether the file is to be accepted
|
||||
if (mInclusion) { // Inclusion
|
||||
for (String mask : mFilenameMasksForInclusion) {
|
||||
parser = new WildcardStringParser(mask);
|
||||
if (parser.parseString(pName)) {
|
||||
|
||||
// The filename was accepted by the filename masks provided
|
||||
// - include it in filename list
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// The filename not was accepted by any of the filename masks
|
||||
// provided - NOT to be included in the filename list
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// Exclusion
|
||||
for (String mask : mFilenameMasksForExclusion) {
|
||||
parser = new WildcardStringParser(mask);
|
||||
if (parser.parseString(pName)) {
|
||||
|
||||
// The filename was accepted by the filename masks provided
|
||||
// - NOT to be included in the filename list
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The filename was not accepted by any of the filename masks
|
||||
// provided - include it in filename list
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a string representation for debug purposes
|
||||
*/
|
||||
public String toString() {
|
||||
StringBuilder retVal = new StringBuilder();
|
||||
int i;
|
||||
|
||||
if (mInclusion) {
|
||||
// Inclusion
|
||||
if (mFilenameMasksForInclusion == null) {
|
||||
retVal.append("No filename masks set - property mFilenameMasksForInclusion is null!");
|
||||
}
|
||||
else {
|
||||
retVal.append(mFilenameMasksForInclusion.length);
|
||||
retVal.append(" filename mask(s) - ");
|
||||
for (i = 0; i < mFilenameMasksForInclusion.length; i++) {
|
||||
retVal.append("\"");
|
||||
retVal.append(mFilenameMasksForInclusion[i]);
|
||||
retVal.append("\", \"");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Exclusion
|
||||
if (mFilenameMasksForExclusion == null) {
|
||||
retVal.append("No filename masks set - property mFilenameMasksForExclusion is null!");
|
||||
}
|
||||
else {
|
||||
retVal.append(mFilenameMasksForExclusion.length);
|
||||
retVal.append(" exclusion filename mask(s) - ");
|
||||
for (i = 0; i < mFilenameMasksForExclusion.length; i++) {
|
||||
retVal.append("\"");
|
||||
retVal.append(mFilenameMasksForExclusion[i]);
|
||||
retVal.append("\", \"");
|
||||
}
|
||||
}
|
||||
}
|
||||
return retVal.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
|
||||
|
||||
/**
|
||||
* A Java Bean used for approving file names which are to be included in a
|
||||
* {@code java.io.File} listing. The file name suffixes are used as a
|
||||
* filter input and is given to the class via the string array property:<br>
|
||||
* <dd>{@code filenameSuffixesToExclude}
|
||||
* <p>
|
||||
* A recommended way of doing this is by referencing to the component which uses
|
||||
* this class for file listing. In this way all properties are set in the same
|
||||
* component and this utility component is kept in the background with only
|
||||
* initial configuration necessary.
|
||||
*
|
||||
* @author <a href="mailto:eirik.torske@iconmedialab.no">Eirik Torske</a>
|
||||
* @see File#list(java.io.FilenameFilter) java.io.File.list
|
||||
* @see FilenameFilter java.io.FilenameFilter
|
||||
*/
|
||||
public class FilenameSuffixFilter implements FilenameFilter {
|
||||
|
||||
// Members
|
||||
String[] mFilenameSuffixesToExclude;
|
||||
|
||||
/** Creates a {@code FileNameSuffixFilter} */
|
||||
public FilenameSuffixFilter() {
|
||||
}
|
||||
|
||||
public void setFilenameSuffixesToExclude(String[] pFilenameSuffixesToExclude) {
|
||||
mFilenameSuffixesToExclude = pFilenameSuffixesToExclude;
|
||||
}
|
||||
|
||||
public String[] getFilenameSuffixesToExclude() {
|
||||
return mFilenameSuffixesToExclude;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method implements the {@code java.io.FilenameFilter} interface.
|
||||
* <p/>
|
||||
*
|
||||
* @param pDir the directory in which the file was found.
|
||||
* @param pName the pName of the file.
|
||||
* @return {@code true} if the pName should be included in the file list;
|
||||
* {@code false} otherwise.
|
||||
*/
|
||||
public boolean accept(final File pDir, final String pName) {
|
||||
if (StringUtil.isEmpty(mFilenameSuffixesToExclude)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (String aMFilenameSuffixesToExclude : mFilenameSuffixesToExclude) {
|
||||
// -- Edit by haraldK, to make interfaces more consistent
|
||||
// if (StringUtil.filenameSuffixIs(pName, mFilenameSuffixesToExclude[i])) {
|
||||
if (aMFilenameSuffixesToExclude.equals(FileUtil.getExtension(pName))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,427 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* From http://www.cafeaulait.org/books/javaio/ioexamples/index.html:
|
||||
*
|
||||
* Please feel free to use any fragment of this code you need in your own work.
|
||||
* As far as I am concerned, it's in the public domain. No permission is necessary
|
||||
* or required. Credit is always appreciated if you use a large chunk or base a
|
||||
* significant product on one of my examples, but that's not required either.
|
||||
*
|
||||
* Elliotte Rusty Harold
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* A little endian input stream reads two's complement,
|
||||
* little endian integers, floating point numbers, and characters
|
||||
* and returns them as Java primitive types.
|
||||
* <p/>
|
||||
* The standard {@code java.io.DataInputStream} class
|
||||
* which this class imitates reads big endian quantities.
|
||||
* <p/>
|
||||
* <em>Warning:
|
||||
* <!-- Beware of little indians! -->
|
||||
* The {@code DataInput} and {@code DataOutput} interfaces
|
||||
* specifies big endian byte order in their documentation.
|
||||
* This means that this class is, strictly speaking, not a proper
|
||||
* implementation. However, I don't see a reason for the these interfaces to
|
||||
* specify the byte order of their underlying representations.
|
||||
* </em>
|
||||
*
|
||||
* @see com.twelvemonkeys.io.LittleEndianRandomAccessFile
|
||||
* @see java.io.DataInputStream
|
||||
* @see java.io.DataInput
|
||||
* @see java.io.DataOutput
|
||||
*
|
||||
* @author Elliotte Rusty Harold
|
||||
* @version 1.0.3, 28 December 2002
|
||||
*/
|
||||
public class LittleEndianDataInputStream extends FilterInputStream implements DataInput {
|
||||
// TODO: Optimize by reading into a fixed size (8 bytes) buffer instead of individual read operations?
|
||||
/**
|
||||
* Creates a new little endian input stream and chains it to the
|
||||
* input stream specified by the {@code pStream} argument.
|
||||
*
|
||||
* @param pStream the underlying input stream.
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public LittleEndianDataInputStream(final InputStream pStream) {
|
||||
super(pStream);
|
||||
if (pStream == null) {
|
||||
throw new IllegalArgumentException("stream == null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a {@code boolean} from the underlying input stream by
|
||||
* reading a single byte. If the byte is zero, false is returned.
|
||||
* If the byte is positive, true is returned.
|
||||
*
|
||||
* @return the {@code boolean} value read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public boolean readBoolean() throws IOException {
|
||||
int b = in.read();
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return b != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a signed {@code byte} from the underlying input stream
|
||||
* with value between -128 and 127
|
||||
*
|
||||
* @return the {@code byte} value read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public byte readByte() throws IOException {
|
||||
int b = in.read();
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (byte) b;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned {@code byte} from the underlying
|
||||
* input stream with value between 0 and 255
|
||||
*
|
||||
* @return the {@code byte} value read.
|
||||
* @throws EOFException if the end of the underlying input
|
||||
* stream has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public int readUnsignedByte() throws IOException {
|
||||
int b = in.read();
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a two byte signed {@code short} from the underlying
|
||||
* input stream in little endian order, low byte first.
|
||||
*
|
||||
* @return the {@code short} read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public short readShort() throws IOException {
|
||||
int byte1 = in.read();
|
||||
int byte2 = in.read();
|
||||
// only need to test last byte read
|
||||
// if byte1 is -1 so is byte2
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a two byte unsigned {@code short} from the underlying
|
||||
* input stream in little endian order, low byte first.
|
||||
*
|
||||
* @return the int value of the unsigned short read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public int readUnsignedShort() throws IOException {
|
||||
int byte1 = in.read();
|
||||
int byte2 = in.read();
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
|
||||
return (byte2 << 8) + byte1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a two byte Unicode {@code char} from the underlying
|
||||
* input stream in little endian order, low byte first.
|
||||
*
|
||||
* @return the int value of the unsigned short read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public char readChar() throws IOException {
|
||||
int byte1 = in.read();
|
||||
int byte2 = in.read();
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads a four byte signed {@code int} from the underlying
|
||||
* input stream in little endian order, low byte first.
|
||||
*
|
||||
* @return the {@code int} read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public int readInt() throws IOException {
|
||||
int byte1 = in.read();
|
||||
int byte2 = in.read();
|
||||
int byte3 = in.read();
|
||||
int byte4 = in.read();
|
||||
|
||||
if (byte4 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (byte4 << 24) + ((byte3 << 24) >>> 8)
|
||||
+ ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an eight byte signed {@code int} from the underlying
|
||||
* input stream in little endian order, low byte first.
|
||||
*
|
||||
* @return the {@code int} read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public long readLong() throws IOException {
|
||||
long byte1 = in.read();
|
||||
long byte2 = in.read();
|
||||
long byte3 = in.read();
|
||||
long byte4 = in.read();
|
||||
long byte5 = in.read();
|
||||
long byte6 = in.read();
|
||||
long byte7 = in.read();
|
||||
long byte8 = in.read();
|
||||
|
||||
if (byte8 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (byte8 << 56) + ((byte7 << 56) >>> 8)
|
||||
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
|
||||
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
|
||||
+ ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a string of no more than 65,535 characters
|
||||
* from the underlying input stream using UTF-8
|
||||
* encoding. This method first reads a two byte short
|
||||
* in <b>big</b> endian order as required by the
|
||||
* UTF-8 specification. This gives the number of bytes in
|
||||
* the UTF-8 encoded version of the string.
|
||||
* Next this many bytes are read and decoded as UTF-8
|
||||
* encoded characters.
|
||||
*
|
||||
* @return the decoded string
|
||||
* @throws UTFDataFormatException if the string cannot be decoded
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public String readUTF() throws IOException {
|
||||
int byte1 = in.read();
|
||||
int byte2 = in.read();
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
int numbytes = (byte1 << 8) + byte2;
|
||||
char result[] = new char[numbytes];
|
||||
int numread = 0;
|
||||
int numchars = 0;
|
||||
|
||||
while (numread < numbytes) {
|
||||
|
||||
int c1 = readUnsignedByte();
|
||||
int c2, c3;
|
||||
|
||||
// The first four bits of c1 determine how many bytes are in this char
|
||||
int test = c1 >> 4;
|
||||
if (test < 8) { // one byte
|
||||
numread++;
|
||||
result[numchars++] = (char) c1;
|
||||
}
|
||||
else if (test == 12 || test == 13) { // two bytes
|
||||
numread += 2;
|
||||
if (numread > numbytes) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
c2 = readUnsignedByte();
|
||||
if ((c2 & 0xC0) != 0x80) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
|
||||
}
|
||||
else if (test == 14) { // three bytes
|
||||
numread += 3;
|
||||
if (numread > numbytes) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
c2 = readUnsignedByte();
|
||||
c3 = readUnsignedByte();
|
||||
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
result[numchars++] = (char)
|
||||
(((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
|
||||
}
|
||||
else { // malformed
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
|
||||
} // end while
|
||||
|
||||
return new String(result, 0, numchars);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the next eight bytes of this input stream, interpreted as a
|
||||
* little endian {@code double}.
|
||||
* @throws EOFException if end of stream occurs before eight bytes
|
||||
* have been read.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public final double readDouble() throws IOException {
|
||||
return Double.longBitsToDouble(readLong());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the next four bytes of this input stream, interpreted as a
|
||||
* little endian {@code int}.
|
||||
* @throws EOFException if end of stream occurs before four bytes
|
||||
* have been read.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public final float readFloat() throws IOException {
|
||||
return Float.intBitsToFloat(readInt());
|
||||
}
|
||||
|
||||
/**
|
||||
* See the general contract of the {@code skipBytes}
|
||||
* method of {@code DataInput}.
|
||||
* <p>
|
||||
* Bytes for this operation are read from the contained input stream.
|
||||
*
|
||||
* @param pLength the number of bytes to be skipped.
|
||||
* @return the actual number of bytes skipped.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
*/
|
||||
public final int skipBytes(int pLength) throws IOException {
|
||||
// NOTE: There was probably a bug in ERH's original code here, as skip
|
||||
// never returns -1, but returns 0 if no more bytes can be skipped...
|
||||
int total = 0;
|
||||
int skipped;
|
||||
|
||||
while ((total < pLength) && ((skipped = (int) in.skip(pLength - total)) > 0)) {
|
||||
total += skipped;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* See the general contract of the {@code readFully}
|
||||
* method of {@code DataInput}.
|
||||
* <p/>
|
||||
* Bytes
|
||||
* for this operation are read from the contained
|
||||
* input stream.
|
||||
*
|
||||
* @param pBytes the buffer into which the data is read.
|
||||
* @throws EOFException if this input stream reaches the end before
|
||||
* reading all the bytes.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public final void readFully(byte pBytes[]) throws IOException {
|
||||
readFully(pBytes, 0, pBytes.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* See the general contract of the {@code readFully}
|
||||
* method of {@code DataInput}.
|
||||
* <p/>
|
||||
* Bytes
|
||||
* for this operation are read from the contained
|
||||
* input stream.
|
||||
*
|
||||
* @param pBytes the buffer into which the data is read.
|
||||
* @param pOffset the start offset of the data.
|
||||
* @param pLength the number of bytes to read.
|
||||
* @throws EOFException if this input stream reaches the end before
|
||||
* reading all the bytes.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public final void readFully(byte pBytes[], int pOffset, int pLength) throws IOException {
|
||||
if (pLength < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
int count = 0;
|
||||
while (count < pLength) {
|
||||
int read = in.read(pBytes, pOffset + count, pLength - count);
|
||||
if (read < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
count += read;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See the general contract of the {@code readLine}
|
||||
* method of {@code DataInput}.
|
||||
* <p>
|
||||
* Bytes for this operation are read from the contained input stream.
|
||||
*
|
||||
* @deprecated This method does not properly convert bytes to characters.
|
||||
*
|
||||
* @return the next line of text from this input stream.
|
||||
* @exception IOException if an I/O error occurs.
|
||||
* @see java.io.BufferedReader#readLine()
|
||||
* @see java.io.DataInputStream#readLine()
|
||||
* @noinspection deprecation
|
||||
*/
|
||||
public String readLine() throws IOException {
|
||||
DataInputStream ds = new DataInputStream(in);
|
||||
return ds.readLine();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* From http://www.cafeaulait.org/books/javaio/ioexamples/index.html:
|
||||
*
|
||||
* Please feel free to use any fragment of this code you need in your own work.
|
||||
* As far as I am concerned, it's in the public domain. No permission is necessary
|
||||
* or required. Credit is always appreciated if you use a large chunk or base a
|
||||
* significant product on one of my examples, but that's not required either.
|
||||
*
|
||||
* Elliotte Rusty Harold
|
||||
*/
|
||||
|
||||
package com.twelvemonkeys.io;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* A little endian output stream writes primitive Java numbers
|
||||
* and characters to an output stream in a little endian format.
|
||||
* <p/>
|
||||
* The standard {@code java.io.DataOutputStream} class which this class
|
||||
* imitates uses big endian integers.
|
||||
* <p/>
|
||||
* <em>Warning:
|
||||
* <!-- Beware of little indians! -->
|
||||
* The {@code DataInput} and {@code DataOutput} interfaces
|
||||
* specifies big endian byte order in their documentation.
|
||||
* This means that this class is, strictly speaking, not a proper
|
||||
* implementation. However, I don't see a reason for the these interfaces to
|
||||
* specify the byte order of their underlying representations.
|
||||
* </em>
|
||||
*
|
||||
* @see com.twelvemonkeys.io.LittleEndianRandomAccessFile
|
||||
* @see java.io.DataOutputStream
|
||||
* @see java.io.DataInput
|
||||
* @see java.io.DataOutput
|
||||
*
|
||||
* @author Elliotte Rusty Harold
|
||||
* @version 1.0.1, 19 May 1999
|
||||
*/
|
||||
public class LittleEndianDataOutputStream extends FilterOutputStream implements DataOutput {
|
||||
|
||||
/**
|
||||
* The number of bytes written so far to the little endian output stream.
|
||||
*/
|
||||
protected int mWritten;
|
||||
|
||||
/**
|
||||
* Creates a new little endian output stream and chains it to the
|
||||
* output stream specified by the {@code pStream} argument.
|
||||
*
|
||||
* @param pStream the underlying output stream.
|
||||
* @see java.io.FilterOutputStream#out
|
||||
*/
|
||||
public LittleEndianDataOutputStream(OutputStream pStream) {
|
||||
super(pStream);
|
||||
if (pStream == null) {
|
||||
throw new IllegalArgumentException("stream == null");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the specified byte value to the underlying output stream.
|
||||
*
|
||||
* @param pByte the {@code byte} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public synchronized void write(int pByte) throws IOException {
|
||||
out.write(pByte);
|
||||
mWritten++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes {@code pLength} bytes from the specified byte array
|
||||
* starting at {@code pOffset} to the underlying output stream.
|
||||
*
|
||||
* @param pBytes the data.
|
||||
* @param pOffset the start offset in the data.
|
||||
* @param pLength the number of bytes to write.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public synchronized void write(byte[] pBytes, int pOffset, int pLength)
|
||||
throws IOException {
|
||||
out.write(pBytes, pOffset, pLength);
|
||||
mWritten += pLength;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes a {@code boolean} to the underlying output stream as
|
||||
* a single byte. If the argument is true, the byte value 1 is written.
|
||||
* If the argument is false, the byte value {@code 0} in written.
|
||||
*
|
||||
* @param pBoolean the {@code boolean} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeBoolean(boolean pBoolean) throws IOException {
|
||||
if (pBoolean) {
|
||||
write(1);
|
||||
}
|
||||
else {
|
||||
write(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes out a {@code byte} to the underlying output stream
|
||||
*
|
||||
* @param pByte the {@code byte} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeByte(int pByte) throws IOException {
|
||||
out.write(pByte);
|
||||
mWritten++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a two byte {@code short} to the underlying output stream in
|
||||
* little endian order, low byte first.
|
||||
*
|
||||
* @param pShort the {@code short} to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeShort(int pShort) throws IOException {
|
||||
out.write(pShort & 0xFF);
|
||||
out.write((pShort >>> 8) & 0xFF);
|
||||
mWritten += 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a two byte {@code char} to the underlying output stream
|
||||
* in little endian order, low byte first.
|
||||
*
|
||||
* @param pChar the {@code char} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeChar(int pChar) throws IOException {
|
||||
out.write(pChar & 0xFF);
|
||||
out.write((pChar >>> 8) & 0xFF);
|
||||
mWritten += 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a four-byte {@code int} to the underlying output stream
|
||||
* in little endian order, low byte first, high byte last
|
||||
*
|
||||
* @param pInt the {@code int} to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeInt(int pInt) throws IOException {
|
||||
out.write(pInt & 0xFF);
|
||||
out.write((pInt >>> 8) & 0xFF);
|
||||
out.write((pInt >>> 16) & 0xFF);
|
||||
out.write((pInt >>> 24) & 0xFF);
|
||||
mWritten += 4;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an eight-byte {@code long} to the underlying output stream
|
||||
* in little endian order, low byte first, high byte last
|
||||
*
|
||||
* @param pLong the {@code long} to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeLong(long pLong) throws IOException {
|
||||
out.write((int) pLong & 0xFF);
|
||||
out.write((int) (pLong >>> 8) & 0xFF);
|
||||
out.write((int) (pLong >>> 16) & 0xFF);
|
||||
out.write((int) (pLong >>> 24) & 0xFF);
|
||||
out.write((int) (pLong >>> 32) & 0xFF);
|
||||
out.write((int) (pLong >>> 40) & 0xFF);
|
||||
out.write((int) (pLong >>> 48) & 0xFF);
|
||||
out.write((int) (pLong >>> 56) & 0xFF);
|
||||
mWritten += 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a 4 byte Java float to the underlying output stream in
|
||||
* little endian order.
|
||||
*
|
||||
* @param f the {@code float} value to be written.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public final void writeFloat(float f) throws IOException {
|
||||
writeInt(Float.floatToIntBits(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an 8 byte Java double to the underlying output stream in
|
||||
* little endian order.
|
||||
*
|
||||
* @param d the {@code double} value to be written.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public final void writeDouble(double d) throws IOException {
|
||||
writeLong(Double.doubleToLongBits(d));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a string to the underlying output stream as a sequence of
|
||||
* bytes. Each character is written to the data output stream as
|
||||
* if by the {@link #writeByte(int)} method.
|
||||
*
|
||||
* @param pString the {@code String} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
* @see #writeByte(int)
|
||||
* @see #out
|
||||
*/
|
||||
public void writeBytes(String pString) throws IOException {
|
||||
int length = pString.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
out.write((byte) pString.charAt(i));
|
||||
}
|
||||
mWritten += length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a string to the underlying output stream as a sequence of
|
||||
* characters. Each character is written to the data output stream as
|
||||
* if by the {@code writeChar} method.
|
||||
*
|
||||
* @param pString a {@code String} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
* @see #writeChar(int)
|
||||
* @see #out
|
||||
*/
|
||||
public void writeChars(String pString) throws IOException {
|
||||
int length = pString.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int c = pString.charAt(i);
|
||||
out.write(c & 0xFF);
|
||||
out.write((c >>> 8) & 0xFF);
|
||||
}
|
||||
mWritten += length * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a string of no more than 65,535 characters
|
||||
* to the underlying output stream using UTF-8
|
||||
* encoding. This method first writes a two byte short
|
||||
* in <b>big</b> endian order as required by the
|
||||
* UTF-8 specification. This gives the number of bytes in the
|
||||
* UTF-8 encoded version of the string, not the number of characters
|
||||
* in the string. Next each character of the string is written
|
||||
* using the UTF-8 encoding for the character.
|
||||
*
|
||||
* @param pString the string to be written.
|
||||
* @throws UTFDataFormatException if the string is longer than
|
||||
* 65,535 characters.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeUTF(String pString) throws IOException {
|
||||
int numchars = pString.length();
|
||||
int numbytes = 0;
|
||||
|
||||
for (int i = 0; i < numchars; i++) {
|
||||
int c = pString.charAt(i);
|
||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||
numbytes++;
|
||||
}
|
||||
else if (c > 0x07FF) {
|
||||
numbytes += 3;
|
||||
}
|
||||
else {
|
||||
numbytes += 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (numbytes > 65535) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
|
||||
out.write((numbytes >>> 8) & 0xFF);
|
||||
out.write(numbytes & 0xFF);
|
||||
for (int i = 0; i < numchars; i++) {
|
||||
int c = pString.charAt(i);
|
||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||
out.write(c);
|
||||
}
|
||||
else if (c > 0x07FF) {
|
||||
out.write(0xE0 | ((c >> 12) & 0x0F));
|
||||
out.write(0x80 | ((c >> 6) & 0x3F));
|
||||
out.write(0x80 | (c & 0x3F));
|
||||
mWritten += 2;
|
||||
}
|
||||
else {
|
||||
out.write(0xC0 | ((c >> 6) & 0x1F));
|
||||
out.write(0x80 | (c & 0x3F));
|
||||
mWritten += 1;
|
||||
}
|
||||
}
|
||||
|
||||
mWritten += numchars + 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes written to this little endian output stream.
|
||||
* (This class is not thread-safe with respect to this method. It is
|
||||
* possible that this number is temporarily less than the actual
|
||||
* number of bytes written.)
|
||||
* @return the value of the {@code written} field.
|
||||
* @see #mWritten
|
||||
*/
|
||||
public int size() {
|
||||
return mWritten;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
/**
|
||||
* A replacement for {@link java.io.RandomAccessFile} that is capable of reading
|
||||
* and writing data in little endian byte order.
|
||||
* <p/>
|
||||
* <em>Warning:
|
||||
* <!-- Beware of little indians! -->
|
||||
* The {@code DataInput} and {@code DataOutput} interfaces
|
||||
* specifies big endian byte order in their documentation.
|
||||
* This means that this class is, strictly speaking, not a proper
|
||||
* implementation. However, I don't see a reason for the these interfaces to
|
||||
* specify the byte order of their underlying representations.
|
||||
* </em>
|
||||
*
|
||||
* @see com.twelvemonkeys.io.LittleEndianDataInputStream
|
||||
* @see com.twelvemonkeys.io.LittleEndianDataOutputStream
|
||||
* @see java.io.RandomAccessFile
|
||||
* @see java.io.DataInput
|
||||
* @see java.io.DataOutput
|
||||
*
|
||||
* @author Elliotte Rusty Harold
|
||||
* @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/LittleEndianRandomAccessFile.java#1 $
|
||||
*/
|
||||
public class LittleEndianRandomAccessFile implements DataInput, DataOutput {
|
||||
private RandomAccessFile mFile;
|
||||
|
||||
public LittleEndianRandomAccessFile(final String pName, final String pMode) throws FileNotFoundException {
|
||||
this(FileUtil.resolve(pName), pMode);
|
||||
}
|
||||
|
||||
public LittleEndianRandomAccessFile(final File pFile, final String pMode) throws FileNotFoundException {
|
||||
mFile = new RandomAccessFile(pFile, pMode);
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
mFile.close();
|
||||
}
|
||||
|
||||
public FileChannel getChannel() {
|
||||
return mFile.getChannel();
|
||||
}
|
||||
|
||||
public FileDescriptor getFD() throws IOException {
|
||||
return mFile.getFD();
|
||||
}
|
||||
|
||||
public long getFilePointer() throws IOException {
|
||||
return mFile.getFilePointer();
|
||||
}
|
||||
|
||||
public long length() throws IOException {
|
||||
return mFile.length();
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
return mFile.read();
|
||||
}
|
||||
|
||||
public int read(final byte[] b) throws IOException {
|
||||
return mFile.read(b);
|
||||
}
|
||||
|
||||
public int read(final byte[] b, final int off, final int len) throws IOException {
|
||||
return mFile.read(b, off, len);
|
||||
}
|
||||
|
||||
public void readFully(final byte[] b) throws IOException {
|
||||
mFile.readFully(b);
|
||||
}
|
||||
|
||||
public void readFully(final byte[] b, final int off, final int len) throws IOException {
|
||||
mFile.readFully(b, off, len);
|
||||
}
|
||||
|
||||
public String readLine() throws IOException {
|
||||
return mFile.readLine();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a {@code boolean} from the underlying input stream by
|
||||
* reading a single byte. If the byte is zero, false is returned.
|
||||
* If the byte is positive, true is returned.
|
||||
*
|
||||
* @return the {@code boolean} value read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public boolean readBoolean() throws IOException {
|
||||
int b = mFile.read();
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return b != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a signed {@code byte} from the underlying input stream
|
||||
* with value between -128 and 127
|
||||
*
|
||||
* @return the {@code byte} value read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public byte readByte() throws IOException {
|
||||
int b = mFile.read();
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (byte) b;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned {@code byte} from the underlying
|
||||
* input stream with value between 0 and 255
|
||||
*
|
||||
* @return the {@code byte} value read.
|
||||
* @throws EOFException if the end of the underlying input
|
||||
* stream has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public int readUnsignedByte() throws IOException {
|
||||
int b = mFile.read();
|
||||
if (b < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a two byte signed {@code short} from the underlying
|
||||
* input stream in little endian order, low byte first.
|
||||
*
|
||||
* @return the {@code short} read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public short readShort() throws IOException {
|
||||
int byte1 = mFile.read();
|
||||
int byte2 = mFile.read();
|
||||
// only need to test last byte read
|
||||
// if byte1 is -1 so is byte2
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (short) (((byte2 << 24) >>> 16) + (byte1 << 24) >>> 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a two byte unsigned {@code short} from the underlying
|
||||
* input stream in little endian order, low byte first.
|
||||
*
|
||||
* @return the int value of the unsigned short read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public int readUnsignedShort() throws IOException {
|
||||
int byte1 = mFile.read();
|
||||
int byte2 = mFile.read();
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
//return ((byte2 << 24) >> 16) + ((byte1 << 24) >> 24);
|
||||
return (byte2 << 8) + byte1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a two byte Unicode {@code char} from the underlying
|
||||
* input stream in little endian order, low byte first.
|
||||
*
|
||||
* @return the int value of the unsigned short read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public char readChar() throws IOException {
|
||||
int byte1 = mFile.read();
|
||||
int byte2 = mFile.read();
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (char) (((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads a four byte signed {@code int} from the underlying
|
||||
* input stream in little endian order, low byte first.
|
||||
*
|
||||
* @return the {@code int} read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public int readInt() throws IOException {
|
||||
int byte1 = mFile.read();
|
||||
int byte2 = mFile.read();
|
||||
int byte3 = mFile.read();
|
||||
int byte4 = mFile.read();
|
||||
|
||||
if (byte4 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (byte4 << 24) + ((byte3 << 24) >>> 8)
|
||||
+ ((byte2 << 24) >>> 16) + ((byte1 << 24) >>> 24);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an eight byte signed {@code int} from the underlying
|
||||
* input stream in little endian order, low byte first.
|
||||
*
|
||||
* @return the {@code int} read.
|
||||
* @throws EOFException if the end of the underlying input stream
|
||||
* has been reached
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public long readLong() throws IOException {
|
||||
long byte1 = mFile.read();
|
||||
long byte2 = mFile.read();
|
||||
long byte3 = mFile.read();
|
||||
long byte4 = mFile.read();
|
||||
long byte5 = mFile.read();
|
||||
long byte6 = mFile.read();
|
||||
long byte7 = mFile.read();
|
||||
long byte8 = mFile.read();
|
||||
|
||||
if (byte8 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return (byte8 << 56) + ((byte7 << 56) >>> 8)
|
||||
+ ((byte6 << 56) >>> 16) + ((byte5 << 56) >>> 24)
|
||||
+ ((byte4 << 56) >>> 32) + ((byte3 << 56) >>> 40)
|
||||
+ ((byte2 << 56) >>> 48) + ((byte1 << 56) >>> 56);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a string of no more than 65,535 characters
|
||||
* from the underlying input stream using UTF-8
|
||||
* encoding. This method first reads a two byte short
|
||||
* in <b>big</b> endian order as required by the
|
||||
* UTF-8 specification. This gives the number of bytes in
|
||||
* the UTF-8 encoded version of the string.
|
||||
* Next this many bytes are read and decoded as UTF-8
|
||||
* encoded characters.
|
||||
*
|
||||
* @return the decoded string
|
||||
* @throws UTFDataFormatException if the string cannot be decoded
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public String readUTF() throws IOException {
|
||||
int byte1 = mFile.read();
|
||||
int byte2 = mFile.read();
|
||||
if (byte2 < 0) {
|
||||
throw new EOFException();
|
||||
}
|
||||
int numbytes = (byte1 << 8) + byte2;
|
||||
char result[] = new char[numbytes];
|
||||
int numread = 0;
|
||||
int numchars = 0;
|
||||
|
||||
while (numread < numbytes) {
|
||||
|
||||
int c1 = readUnsignedByte();
|
||||
int c2, c3;
|
||||
|
||||
// The first four bits of c1 determine how many bytes are in this char
|
||||
int test = c1 >> 4;
|
||||
if (test < 8) { // one byte
|
||||
numread++;
|
||||
result[numchars++] = (char) c1;
|
||||
}
|
||||
else if (test == 12 || test == 13) { // two bytes
|
||||
numread += 2;
|
||||
if (numread > numbytes) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
c2 = readUnsignedByte();
|
||||
if ((c2 & 0xC0) != 0x80) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
result[numchars++] = (char) (((c1 & 0x1F) << 6) | (c2 & 0x3F));
|
||||
}
|
||||
else if (test == 14) { // three bytes
|
||||
numread += 3;
|
||||
if (numread > numbytes) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
c2 = readUnsignedByte();
|
||||
c3 = readUnsignedByte();
|
||||
if (((c2 & 0xC0) != 0x80) || ((c3 & 0xC0) != 0x80)) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
result[numchars++] = (char)
|
||||
(((c1 & 0x0F) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F));
|
||||
}
|
||||
else { // malformed
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
|
||||
} // end while
|
||||
|
||||
return new String(result, 0, numchars);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the next eight bytes of this input stream, interpreted as a
|
||||
* little endian {@code double}.
|
||||
* @throws EOFException if end of stream occurs before eight bytes
|
||||
* have been read.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public final double readDouble() throws IOException {
|
||||
return Double.longBitsToDouble(readLong());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the next four bytes of this input stream, interpreted as a
|
||||
* little endian {@code int}.
|
||||
* @throws EOFException if end of stream occurs before four bytes
|
||||
* have been read.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public final float readFloat() throws IOException {
|
||||
return Float.intBitsToFloat(readInt());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the file-pointer offset, measured from the beginning of this
|
||||
* file, at which the next read or write occurs. The offset may be
|
||||
* set beyond the end of the file. Setting the offset beyond the end
|
||||
* of the file does not change the file length. The file length will
|
||||
* change only by writing after the offset has been set beyond the end
|
||||
* of the file.
|
||||
*
|
||||
* @param pos the offset position, measured in bytes from the
|
||||
* beginning of the file, at which to set the file
|
||||
* pointer.
|
||||
* @exception IOException if {@code pos} is less than
|
||||
* {@code 0} or if an I/O error occurs.
|
||||
*/
|
||||
public void seek(final long pos) throws IOException {
|
||||
mFile.seek(pos);
|
||||
}
|
||||
|
||||
public void setLength(final long newLength) throws IOException {
|
||||
mFile.setLength(newLength);
|
||||
}
|
||||
|
||||
public int skipBytes(final int n) throws IOException {
|
||||
return mFile.skipBytes(n);
|
||||
}
|
||||
|
||||
public void write(final byte[] b) throws IOException {
|
||||
mFile.write(b);
|
||||
}
|
||||
|
||||
public void write(final byte[] b, final int off, final int len) throws IOException {
|
||||
mFile.write(b, off, len);
|
||||
}
|
||||
|
||||
public void write(final int b) throws IOException {
|
||||
mFile.write(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a {@code boolean} to the underlying output stream as
|
||||
* a single byte. If the argument is true, the byte value 1 is written.
|
||||
* If the argument is false, the byte value {@code 0} in written.
|
||||
*
|
||||
* @param pBoolean the {@code boolean} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeBoolean(boolean pBoolean) throws IOException {
|
||||
if (pBoolean) {
|
||||
write(1);
|
||||
}
|
||||
else {
|
||||
write(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes out a {@code byte} to the underlying output stream
|
||||
*
|
||||
* @param pByte the {@code byte} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeByte(int pByte) throws IOException {
|
||||
mFile.write(pByte);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a two byte {@code short} to the underlying output stream in
|
||||
* little endian order, low byte first.
|
||||
*
|
||||
* @param pShort the {@code short} to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeShort(int pShort) throws IOException {
|
||||
mFile.write(pShort & 0xFF);
|
||||
mFile.write((pShort >>> 8) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a two byte {@code char} to the underlying output stream
|
||||
* in little endian order, low byte first.
|
||||
*
|
||||
* @param pChar the {@code char} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeChar(int pChar) throws IOException {
|
||||
mFile.write(pChar & 0xFF);
|
||||
mFile.write((pChar >>> 8) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a four-byte {@code int} to the underlying output stream
|
||||
* in little endian order, low byte first, high byte last
|
||||
*
|
||||
* @param pInt the {@code int} to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeInt(int pInt) throws IOException {
|
||||
mFile.write(pInt & 0xFF);
|
||||
mFile.write((pInt >>> 8) & 0xFF);
|
||||
mFile.write((pInt >>> 16) & 0xFF);
|
||||
mFile.write((pInt >>> 24) & 0xFF);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an eight-byte {@code long} to the underlying output stream
|
||||
* in little endian order, low byte first, high byte last
|
||||
*
|
||||
* @param pLong the {@code long} to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeLong(long pLong) throws IOException {
|
||||
mFile.write((int) pLong & 0xFF);
|
||||
mFile.write((int) (pLong >>> 8) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 16) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 24) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 32) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 40) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 48) & 0xFF);
|
||||
mFile.write((int) (pLong >>> 56) & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a 4 byte Java float to the underlying output stream in
|
||||
* little endian order.
|
||||
*
|
||||
* @param f the {@code float} value to be written.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public final void writeFloat(float f) throws IOException {
|
||||
writeInt(Float.floatToIntBits(f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an 8 byte Java double to the underlying output stream in
|
||||
* little endian order.
|
||||
*
|
||||
* @param d the {@code double} value to be written.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
public final void writeDouble(double d) throws IOException {
|
||||
writeLong(Double.doubleToLongBits(d));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a string to the underlying output stream as a sequence of
|
||||
* bytes. Each character is written to the data output stream as
|
||||
* if by the {@code writeByte()} method.
|
||||
*
|
||||
* @param pString the {@code String} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
* @see #writeByte(int)
|
||||
* @see #mFile
|
||||
*/
|
||||
public void writeBytes(String pString) throws IOException {
|
||||
int length = pString.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
mFile.write((byte) pString.charAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a string to the underlying output stream as a sequence of
|
||||
* characters. Each character is written to the data output stream as
|
||||
* if by the {@code writeChar} method.
|
||||
*
|
||||
* @param pString a {@code String} value to be written.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
* @see #writeChar(int)
|
||||
* @see #mFile
|
||||
*/
|
||||
public void writeChars(String pString) throws IOException {
|
||||
int length = pString.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int c = pString.charAt(i);
|
||||
mFile.write(c & 0xFF);
|
||||
mFile.write((c >>> 8) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a string of no more than 65,535 characters
|
||||
* to the underlying output stream using UTF-8
|
||||
* encoding. This method first writes a two byte short
|
||||
* in <b>big</b> endian order as required by the
|
||||
* UTF-8 specification. This gives the number of bytes in the
|
||||
* UTF-8 encoded version of the string, not the number of characters
|
||||
* in the string. Next each character of the string is written
|
||||
* using the UTF-8 encoding for the character.
|
||||
*
|
||||
* @param pString the string to be written.
|
||||
* @throws UTFDataFormatException if the string is longer than
|
||||
* 65,535 characters.
|
||||
* @throws IOException if the underlying stream throws an IOException.
|
||||
*/
|
||||
public void writeUTF(String pString) throws IOException {
|
||||
int numchars = pString.length();
|
||||
int numbytes = 0;
|
||||
|
||||
for (int i = 0; i < numchars; i++) {
|
||||
int c = pString.charAt(i);
|
||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||
numbytes++;
|
||||
}
|
||||
else if (c > 0x07FF) {
|
||||
numbytes += 3;
|
||||
}
|
||||
else {
|
||||
numbytes += 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (numbytes > 65535) {
|
||||
throw new UTFDataFormatException();
|
||||
}
|
||||
|
||||
mFile.write((numbytes >>> 8) & 0xFF);
|
||||
mFile.write(numbytes & 0xFF);
|
||||
for (int i = 0; i < numchars; i++) {
|
||||
int c = pString.charAt(i);
|
||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||
mFile.write(c);
|
||||
}
|
||||
else if (c > 0x07FF) {
|
||||
mFile.write(0xE0 | ((c >> 12) & 0x0F));
|
||||
mFile.write(0x80 | ((c >> 6) & 0x3F));
|
||||
mFile.write(0x80 | (c & 0x3F));
|
||||
}
|
||||
else {
|
||||
mFile.write(0xC0 | ((c >> 6) & 0x1F));
|
||||
mFile.write(0x80 | (c & 0x3F));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A {@code SeekableInputStream} implementation that caches data in memory.
|
||||
* <p/>
|
||||
*
|
||||
* @see FileCacheSeekableStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/MemoryCacheSeekableStream.java#3 $
|
||||
*/
|
||||
public final class MemoryCacheSeekableStream extends AbstractCachedSeekableStream {
|
||||
|
||||
/**
|
||||
* Creates a {@code MemoryCacheSeekableStream}, reading from the given
|
||||
* {@code InputStream}. Data will be cached in memory.
|
||||
*
|
||||
* @param pStream the {@code InputStream} to read from.
|
||||
*/
|
||||
public MemoryCacheSeekableStream(final InputStream pStream) {
|
||||
super(pStream, new MemoryCache());
|
||||
}
|
||||
|
||||
public final boolean isCachedMemory() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public final boolean isCachedFile() {
|
||||
return false;
|
||||
}
|
||||
|
||||
final static class MemoryCache extends StreamCache {
|
||||
final static int BLOCK_SIZE = 1 << 13;
|
||||
|
||||
private final List<byte[]> mCache = new ArrayList<byte[]>();
|
||||
private long mLength;
|
||||
private long mPosition;
|
||||
private long mStart;
|
||||
|
||||
private byte[] getBlock() throws IOException {
|
||||
final long currPos = mPosition - mStart;
|
||||
if (currPos < 0) {
|
||||
throw new IOException("StreamCache flushed before read position");
|
||||
}
|
||||
|
||||
long index = currPos / BLOCK_SIZE;
|
||||
|
||||
if (index >= Integer.MAX_VALUE) {
|
||||
throw new IOException("Memory cache max size exceeded");
|
||||
}
|
||||
|
||||
if (index >= mCache.size()) {
|
||||
try {
|
||||
mCache.add(new byte[BLOCK_SIZE]);
|
||||
// System.out.println("Allocating new block, size: " + BLOCK_SIZE);
|
||||
// System.out.println("New total size: " + mCache.size() * BLOCK_SIZE + " (" + mCache.size() + " blocks)");
|
||||
}
|
||||
catch (OutOfMemoryError e) {
|
||||
throw new IOException("No more memory for cache: " + mCache.size() * BLOCK_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
//System.out.println("index: " + index);
|
||||
|
||||
return mCache.get((int) index);
|
||||
}
|
||||
|
||||
public void write(final int pByte) throws IOException {
|
||||
byte[] buffer = getBlock();
|
||||
|
||||
int idx = (int) (mPosition % BLOCK_SIZE);
|
||||
buffer[idx] = (byte) pByte;
|
||||
mPosition++;
|
||||
|
||||
if (mPosition > mLength) {
|
||||
mLength = mPosition;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: OptimizeMe!!!
|
||||
@Override
|
||||
public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
byte[] buffer = getBlock();
|
||||
for (int i = 0; i < pLength; i++) {
|
||||
int index = (int) mPosition % BLOCK_SIZE;
|
||||
if (index == 0) {
|
||||
buffer = getBlock();
|
||||
}
|
||||
buffer[index] = pBuffer[pOffset + i];
|
||||
|
||||
mPosition++;
|
||||
}
|
||||
if (mPosition > mLength) {
|
||||
mLength = mPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (mPosition >= mLength) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
byte[] buffer = getBlock();
|
||||
|
||||
int idx = (int) (mPosition % BLOCK_SIZE);
|
||||
mPosition++;
|
||||
|
||||
return buffer[idx] & 0xff;
|
||||
}
|
||||
|
||||
// TODO: OptimizeMe!!!
|
||||
@Override
|
||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (mPosition >= mLength) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
byte[] buffer = getBlock();
|
||||
|
||||
int bufferPos = (int) (mPosition % BLOCK_SIZE);
|
||||
|
||||
// Find maxIdx and simplify test in for-loop
|
||||
int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), mLength - mPosition);
|
||||
|
||||
int i;
|
||||
//for (i = 0; i < pLength && i < buffer.length - idx && i < mLength - mPosition; i++) {
|
||||
for (i = 0; i < maxLen; i++) {
|
||||
pBytes[pOffset + i] = buffer[bufferPos + i];
|
||||
}
|
||||
|
||||
mPosition += i;
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
public void seek(final long pPosition) throws IOException {
|
||||
if (pPosition < mStart) {
|
||||
throw new IOException("Seek before flush position");
|
||||
}
|
||||
mPosition = pPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush(final long pPosition) {
|
||||
int firstPos = (int) (pPosition / BLOCK_SIZE) - 1;
|
||||
|
||||
for (int i = 0; i < firstPos; i++) {
|
||||
mCache.remove(0);
|
||||
}
|
||||
|
||||
mStart = pPosition;
|
||||
}
|
||||
|
||||
public long getPosition() {
|
||||
return mPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
80
common/common-io/src/main/java/com/twelvemonkeys/io/NullInputStream.java
Executable file
80
common/common-io/src/main/java/com/twelvemonkeys/io/NullInputStream.java
Executable file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* An {@code InputStream} that contains no bytes.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/NullInputStream.java#2 $
|
||||
*/
|
||||
public class NullInputStream extends InputStream {
|
||||
|
||||
/**
|
||||
* Creates a {@code NullInputStream}.
|
||||
*/
|
||||
public NullInputStream() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns {@code -1} (EOF), always.
|
||||
*
|
||||
* @return {@code -1}
|
||||
* @throws IOException
|
||||
*/
|
||||
public int read() throws IOException {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns {@code 0}, always.
|
||||
*
|
||||
* @return {@code 0}
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation returns {@code 0}, always.
|
||||
*
|
||||
* @return {@code 0}
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public long skip(long pOffset) throws IOException {
|
||||
return 0l;
|
||||
}
|
||||
}
|
||||
68
common/common-io/src/main/java/com/twelvemonkeys/io/NullOutputStream.java
Executable file
68
common/common-io/src/main/java/com/twelvemonkeys/io/NullOutputStream.java
Executable file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* An {@code OutputStream} implementation that works as a sink.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/NullOutputStream.java#2 $
|
||||
*/
|
||||
public class NullOutputStream extends OutputStream {
|
||||
|
||||
/**
|
||||
* Creates a {@code NullOutputStream}.
|
||||
*/
|
||||
public NullOutputStream() {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation does nothing.
|
||||
*/
|
||||
public void write(int pByte) throws IOException {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation does nothing.
|
||||
*/
|
||||
@Override
|
||||
public void write(byte pBytes[]) throws IOException {
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation does nothing.
|
||||
*/
|
||||
@Override
|
||||
public void write(byte pBytes[], int pOffset, int pLength) throws IOException {
|
||||
}
|
||||
}
|
||||
240
common/common-io/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java
Executable file
240
common/common-io/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java
Executable file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.DataInput;
|
||||
import java.io.DataOutput;
|
||||
import java.io.IOException;
|
||||
import java.io.EOFException;
|
||||
|
||||
/**
|
||||
* A data stream that is both readable and writable, much like a
|
||||
* {@code RandomAccessFile}, except it may be backed by something other than a file.
|
||||
* <p/>
|
||||
*
|
||||
* @see java.io.RandomAccessFile
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">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/RandomAccessStream.java#3 $
|
||||
*/
|
||||
public abstract class RandomAccessStream implements Seekable, DataInput, DataOutput {
|
||||
// TODO: Use a RandomAcceessFile as backing in impl, probably
|
||||
// TODO: Create an in-memory implementation too?
|
||||
// TODO: Package private SeekableDelegate?
|
||||
|
||||
// TODO: Both read and write must update stream position
|
||||
//private int mPosition = -1;
|
||||
|
||||
/** This random access stream, wrapped in an {@code InputStream} */
|
||||
SeekableInputStream mInputView = null;
|
||||
/** This random access stream, wrapped in an {@code OutputStream} */
|
||||
SeekableOutputStream mOutputView = null;
|
||||
|
||||
|
||||
// TODO: Create an Input and an Output interface matching InputStream and OutputStream?
|
||||
public int read() throws IOException {
|
||||
try {
|
||||
return readByte() & 0xff;
|
||||
}
|
||||
catch (EOFException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (pBytes == null) {
|
||||
throw new NullPointerException("bytes == null");
|
||||
}
|
||||
else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
|
||||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
else if (pLength == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Special case, allready at EOF
|
||||
int c = read();
|
||||
if (c == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Otherwise, read as many as bytes as possible
|
||||
pBytes[pOffset] = (byte) c;
|
||||
|
||||
int i = 1;
|
||||
try {
|
||||
for (; i < pLength; i++) {
|
||||
c = read();
|
||||
if (c == -1) {
|
||||
break;
|
||||
}
|
||||
pBytes[pOffset + i] = (byte) c;
|
||||
}
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
// Ignore exception, just return length
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
public final int read(byte[] pBytes) throws IOException {
|
||||
return read(pBytes, 0, pBytes != null ? pBytes.length : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an input view of this {@code RandomAccessStream}.
|
||||
* Invoking this method several times, will return the same object.
|
||||
* <p/>
|
||||
* <em>Note that read access is NOT synchronized.</em>
|
||||
*
|
||||
* @return a {@code SeekableInputStream} reading from this stream
|
||||
*/
|
||||
public final SeekableInputStream asInputStream() {
|
||||
if (mInputView == null) {
|
||||
mInputView = new InputStreamView(this);
|
||||
}
|
||||
return mInputView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an output view of this {@code RandomAccessStream}.
|
||||
* Invoking this method several times, will return the same object.
|
||||
* <p/>
|
||||
* <em>Note that write access is NOT synchronized.</em>
|
||||
*
|
||||
* @return a {@code SeekableOutputStream} writing to this stream
|
||||
*/
|
||||
public final SeekableOutputStream asOutputStream() {
|
||||
if (mOutputView == null) {
|
||||
mOutputView = new OutputStreamView(this);
|
||||
}
|
||||
return mOutputView;
|
||||
}
|
||||
|
||||
static final class InputStreamView extends SeekableInputStream {
|
||||
// TODO: Consider adding synchonization (on mStream) for all operations
|
||||
// TODO: Is is a good thing that close/flush etc works on mStream?
|
||||
// - Or should it rather just work on the views?
|
||||
// - Allow multiple views?
|
||||
|
||||
final private RandomAccessStream mStream;
|
||||
|
||||
public InputStreamView(RandomAccessStream pStream) {
|
||||
if (pStream == null) {
|
||||
throw new IllegalArgumentException("stream == null");
|
||||
}
|
||||
mStream = pStream;
|
||||
}
|
||||
|
||||
public boolean isCached() {
|
||||
return mStream.isCached();
|
||||
}
|
||||
|
||||
public boolean isCachedFile() {
|
||||
return mStream.isCachedFile();
|
||||
}
|
||||
|
||||
public boolean isCachedMemory() {
|
||||
return mStream.isCachedMemory();
|
||||
}
|
||||
|
||||
protected void closeImpl() throws IOException {
|
||||
mStream.close();
|
||||
}
|
||||
|
||||
protected void flushBeforeImpl(long pPosition) throws IOException {
|
||||
mStream.flushBefore(pPosition);
|
||||
}
|
||||
|
||||
protected void seekImpl(long pPosition) throws IOException {
|
||||
mStream.seek(pPosition);
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
return mStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte pBytes[], int pOffset, int pLength) throws IOException {
|
||||
return mStream.read(pBytes, pOffset, pLength);
|
||||
}
|
||||
}
|
||||
|
||||
static final class OutputStreamView extends SeekableOutputStream {
|
||||
// TODO: Consider adding synchonization (on mStream) for all operations
|
||||
// TODO: Is is a good thing that close/flush etc works on mStream?
|
||||
// - Or should it rather just work on the views?
|
||||
// - Allow multiple views?
|
||||
|
||||
final private RandomAccessStream mStream;
|
||||
|
||||
public OutputStreamView(RandomAccessStream pStream) {
|
||||
if (pStream == null) {
|
||||
throw new IllegalArgumentException("stream == null");
|
||||
}
|
||||
mStream = pStream;
|
||||
}
|
||||
|
||||
public boolean isCached() {
|
||||
return mStream.isCached();
|
||||
}
|
||||
|
||||
public boolean isCachedFile() {
|
||||
return mStream.isCachedFile();
|
||||
}
|
||||
|
||||
public boolean isCachedMemory() {
|
||||
return mStream.isCachedMemory();
|
||||
}
|
||||
|
||||
protected void closeImpl() throws IOException {
|
||||
mStream.close();
|
||||
}
|
||||
|
||||
protected void flushBeforeImpl(long pPosition) throws IOException {
|
||||
mStream.flushBefore(pPosition);
|
||||
}
|
||||
|
||||
protected void seekImpl(long pPosition) throws IOException {
|
||||
mStream.seek(pPosition);
|
||||
}
|
||||
|
||||
public void write(int pByte) throws IOException {
|
||||
mStream.write(pByte);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte pBytes[], int pOffset, int pLength) throws IOException {
|
||||
mStream.write(pBytes, pOffset, pLength);
|
||||
}
|
||||
}
|
||||
}
|
||||
184
common/common-io/src/main/java/com/twelvemonkeys/io/Seekable.java
Executable file
184
common/common-io/src/main/java/com/twelvemonkeys/io/Seekable.java
Executable file
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Interface for seekable streams.
|
||||
* <p/>
|
||||
* @see SeekableInputStream
|
||||
* @see SeekableOutputStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Seekable.java#1 $
|
||||
*/
|
||||
public interface Seekable {
|
||||
|
||||
/**
|
||||
* Returns the current byte position of the stream. The next read will take
|
||||
* place starting at this offset.
|
||||
*
|
||||
* @return a {@code long} containing the position of the stream.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
long getStreamPosition() throws IOException;
|
||||
|
||||
/**
|
||||
* Sets the current stream position to the desired location.
|
||||
* The next read will occur at this location.
|
||||
* <p/>
|
||||
* An {@code IndexOutOfBoundsException} will be thrown if pPosition is smaller
|
||||
* than the flushed position (as returned by {@link #getFlushedPosition()}).
|
||||
* <p/>
|
||||
* It is legal to seek past the end of the file; an {@code EOFException}
|
||||
* will be thrown only if a read is performed.
|
||||
*
|
||||
* @param pPosition a long containing the desired file pointer position.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if {@code pPosition} is smaller than
|
||||
* the flushed position.
|
||||
* @throws IOException if any other I/O error occurs.
|
||||
*/
|
||||
void seek(long pPosition) throws IOException;
|
||||
|
||||
/**
|
||||
* Marks a position in the stream to be returned to by a subsequent call to
|
||||
* reset.
|
||||
* Unlike a standard {@code InputStream}, all {@code Seekable}
|
||||
* streams upport marking. Additionally, calls to {@code mark} and
|
||||
* {@code reset} may be nested arbitrarily.
|
||||
* <p/>
|
||||
* Unlike the {@code mark} methods declared by the {@code Reader} or
|
||||
* {@code InputStream}
|
||||
* interfaces, no {@code readLimit} parameter is used. An arbitrary amount
|
||||
* of data may be read following the call to {@code mark}.
|
||||
*/
|
||||
void mark();
|
||||
|
||||
/**
|
||||
* Returns the file pointer to its previous position,
|
||||
* at the time of the most recent unmatched call to mark.
|
||||
* <p/>
|
||||
* Calls to reset without a corresponding call to mark will either:
|
||||
* <ul>
|
||||
* <li>throw an {@code IOException}</li>
|
||||
* <li>or, reset to the beginning of the stream.</li>
|
||||
* </ul>
|
||||
* An {@code IOException} will be thrown if the previous marked position
|
||||
* lies in the discarded portion of the stream.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs.
|
||||
* @see java.io.InputStream#reset()
|
||||
*/
|
||||
void reset() throws IOException;
|
||||
|
||||
/**
|
||||
* Discards the initial portion of the stream prior to the indicated
|
||||
* postion. Attempting to seek to an offset within the flushed portion of
|
||||
* the stream will result in an {@code IndexOutOfBoundsException}.
|
||||
* <p/>
|
||||
* Calling {@code flushBefore} may allow classes implementing this
|
||||
* interface to free up resources such as memory or disk space that are
|
||||
* being used to store data from the stream.
|
||||
*
|
||||
* @param pPosition a long containing the length of the file prefix that
|
||||
* may be flushed.
|
||||
*
|
||||
* @throws IndexOutOfBoundsException if {@code pPosition} lies in the
|
||||
* flushed portion of the stream or past the current stream position.
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
void flushBefore(long pPosition) throws IOException;
|
||||
|
||||
/**
|
||||
* Discards the initial position of the stream prior to the current stream
|
||||
* position. Equivalent to {@code flushBefore(getStreamPosition())}.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
void flush() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns the earliest position in the stream to which seeking may be
|
||||
* performed. The returned value will be the maximum of all values passed
|
||||
* into previous calls to {@code flushBefore}.
|
||||
*
|
||||
* @return the earliest legal position for seeking, as a {@code long}.
|
||||
*
|
||||
* @throws IOException if an I/O error occurs.
|
||||
*/
|
||||
long getFlushedPosition() throws IOException;
|
||||
|
||||
/**
|
||||
* Returns true if this {@code Seekable} stream caches data itself in order
|
||||
* to allow seeking backwards. Applications may consult this in order to
|
||||
* decide how frequently, or whether, to flush in order to conserve cache
|
||||
* resources.
|
||||
*
|
||||
* @return {@code true} if this {@code Seekable} caches data.
|
||||
* @see #isCachedMemory()
|
||||
* @see #isCachedFile()
|
||||
*/
|
||||
boolean isCached();
|
||||
|
||||
/**
|
||||
* Returns true if this {@code Seekable} stream caches data itself in order
|
||||
* to allow seeking backwards, and the cache is kept in main memory.
|
||||
* Applications may consult this in order to decide how frequently, or
|
||||
* whether, to flush in order to conserve cache resources.
|
||||
*
|
||||
* @return {@code true} if this {@code Seekable} caches data in main
|
||||
* memory.
|
||||
* @see #isCached()
|
||||
* @see #isCachedFile()
|
||||
*/
|
||||
boolean isCachedMemory();
|
||||
|
||||
/**
|
||||
* Returns true if this {@code Seekable} stream caches data itself in
|
||||
* order to allow seeking backwards, and the cache is kept in a
|
||||
* temporary file.
|
||||
* Applications may consult this in order to decide how frequently,
|
||||
* or whether, to flush in order to conserve cache resources.
|
||||
*
|
||||
* @return {@code true} if this {@code Seekable} caches data in a
|
||||
* temporary file.
|
||||
* @see #isCached
|
||||
* @see #isCachedMemory
|
||||
*/
|
||||
boolean isCachedFile();
|
||||
|
||||
/**
|
||||
* Closes the stream.
|
||||
*
|
||||
* @throws java.io.IOException if the stream can't be closed.
|
||||
*/
|
||||
void close() throws IOException;
|
||||
}
|
||||
224
common/common-io/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java
Executable file
224
common/common-io/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java
Executable file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@code InputStream}s implementing the {@code Seekable} interface.
|
||||
* <p/>
|
||||
* @see SeekableOutputStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java#4 $
|
||||
*/
|
||||
public abstract class SeekableInputStream extends InputStream implements Seekable {
|
||||
|
||||
// TODO: It's at the moment not possible to create subclasses outside this
|
||||
// package, as there's no access to mPosition. mPosition needs to be
|
||||
// updated from the read/read/read methods...
|
||||
|
||||
/** The stream position in this stream */
|
||||
long mPosition;
|
||||
long mFlushedPosition;
|
||||
boolean mClosed;
|
||||
|
||||
protected Stack<Long> mMarkedPositions = new Stack<Long>();
|
||||
|
||||
/// InputStream overrides
|
||||
@Override
|
||||
public final int read(byte[] pBytes) throws IOException {
|
||||
return read(pBytes, 0, pBytes != null ? pBytes.length : 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented using {@code seek(currentPos + pLength)}.
|
||||
*
|
||||
* @param pLength the number of bytes to skip
|
||||
* @return the actual number of bytes skipped (may be equal to or less
|
||||
* than {@code pLength})
|
||||
*
|
||||
* @throws IOException if an I/O exception occurs during skip
|
||||
*/
|
||||
@Override
|
||||
public final long skip(long pLength) throws IOException {
|
||||
long pos = mPosition;
|
||||
if (pos + pLength < mFlushedPosition) {
|
||||
throw new IOException("position < flushedPosition");
|
||||
}
|
||||
|
||||
// Stop at stream length for compatibility, even though it's allowed
|
||||
// to seek past end of stream
|
||||
seek(Math.min(pos + pLength, pos + available()));
|
||||
|
||||
return mPosition - pos;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void mark(int pLimit) {
|
||||
mark();
|
||||
|
||||
// TODO: We don't really need to do this.. Is it a good idea?
|
||||
try {
|
||||
flushBefore(Math.max(mPosition - pLimit, mFlushedPosition));
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
// Ignore, as it's not really critical
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true}, as marking is always supported.
|
||||
*
|
||||
* @return {@code true}.
|
||||
*/
|
||||
@Override
|
||||
public final boolean markSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Seekable implementation
|
||||
public final void seek(long pPosition) throws IOException {
|
||||
checkOpen();
|
||||
|
||||
// NOTE: This is correct according to javax.imageio (IndexOutOfBoundsException),
|
||||
// but it's kind of inconsistent with reset that throws IOException...
|
||||
if (pPosition < mFlushedPosition) {
|
||||
throw new IndexOutOfBoundsException("position < flushedPosition");
|
||||
}
|
||||
|
||||
seekImpl(pPosition);
|
||||
mPosition = pPosition;
|
||||
}
|
||||
|
||||
protected abstract void seekImpl(long pPosition) throws IOException;
|
||||
|
||||
public final void mark() {
|
||||
mMarkedPositions.push(mPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void reset() throws IOException {
|
||||
checkOpen();
|
||||
if (!mMarkedPositions.isEmpty()) {
|
||||
long newPos = mMarkedPositions.pop();
|
||||
|
||||
// NOTE: This is correct according to javax.imageio (IOException),
|
||||
// but it's kind of inconsistent with seek that throws IndexOutOfBoundsException...
|
||||
if (newPos < mFlushedPosition) {
|
||||
throw new IOException("Previous marked position has been discarded");
|
||||
}
|
||||
|
||||
seek(newPos);
|
||||
}
|
||||
else {
|
||||
// TODO: To iron out some wrinkles due to conflicting contracts
|
||||
// (InputStream and Seekable both declare reset),
|
||||
// we might need to reset to the last marked position instead..
|
||||
// However, that becomes REALLY confusing if that position is after
|
||||
// the current position...
|
||||
seek(0);
|
||||
}
|
||||
}
|
||||
|
||||
public final void flushBefore(long pPosition) throws IOException {
|
||||
if (pPosition < mFlushedPosition) {
|
||||
throw new IndexOutOfBoundsException("position < flushedPosition");
|
||||
}
|
||||
if (pPosition > getStreamPosition()) {
|
||||
throw new IndexOutOfBoundsException("position > stream position");
|
||||
}
|
||||
checkOpen();
|
||||
flushBeforeImpl(pPosition);
|
||||
mFlushedPosition = pPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discards the initial portion of the stream prior to the indicated postion.
|
||||
*
|
||||
* @param pPosition the position to flush to
|
||||
* @throws IOException if an I/O exception occurs during the flush operation
|
||||
*
|
||||
* @see #flushBefore(long)
|
||||
*/
|
||||
protected abstract void flushBeforeImpl(long pPosition) throws IOException;
|
||||
|
||||
public final void flush() throws IOException {
|
||||
flushBefore(mFlushedPosition);
|
||||
}
|
||||
|
||||
public final long getFlushedPosition() throws IOException {
|
||||
checkOpen();
|
||||
return mFlushedPosition;
|
||||
}
|
||||
|
||||
public final long getStreamPosition() throws IOException {
|
||||
checkOpen();
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
protected final void checkOpen() throws IOException {
|
||||
if (mClosed) {
|
||||
throw new IOException("closed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void close() throws IOException {
|
||||
checkOpen();
|
||||
mClosed = true;
|
||||
closeImpl();
|
||||
}
|
||||
|
||||
protected abstract void closeImpl() throws IOException;
|
||||
|
||||
/**
|
||||
* Finalizes this object prior to garbage collection. The
|
||||
* {@code close} method is called to close any open input
|
||||
* source. This method should not be called from application
|
||||
* code.
|
||||
*
|
||||
* @exception Throwable if an error occurs during superclass
|
||||
* finalization.
|
||||
*/
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
if (!mClosed) {
|
||||
try {
|
||||
close();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
// Ignroe
|
||||
}
|
||||
}
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
138
common/common-io/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java
Executable file
138
common/common-io/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java
Executable file
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* Abstract base class for {@code OutputStream}s implementing the
|
||||
* {@code Seekable} interface.
|
||||
* <p/>
|
||||
* @see SeekableInputStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java#2 $
|
||||
*/
|
||||
public abstract class SeekableOutputStream extends OutputStream implements Seekable {
|
||||
// TODO: Implement
|
||||
long mPosition;
|
||||
long mFlushedPosition;
|
||||
boolean mClosed;
|
||||
|
||||
protected Stack<Long> mMarkedPositions = new Stack<Long>();
|
||||
|
||||
/// Outputstream overrides
|
||||
@Override
|
||||
public final void write(byte pBytes[]) throws IOException {
|
||||
write(pBytes, 0, pBytes != null ? pBytes.length : 1);
|
||||
}
|
||||
|
||||
/// Seekable implementation
|
||||
// TODO: This is common behaviour/implementation with SeekableInputStream,
|
||||
// probably a good idea to extract a delegate..?
|
||||
public final void seek(long pPosition) throws IOException {
|
||||
checkOpen();
|
||||
|
||||
// TODO: This is correct according to javax.imageio (IndexOutOfBoundsException),
|
||||
// but it's inconsistent with reset that throws IOException...
|
||||
if (pPosition < mFlushedPosition) {
|
||||
throw new IndexOutOfBoundsException("position < flushedPosition!");
|
||||
}
|
||||
|
||||
seekImpl(pPosition);
|
||||
mPosition = pPosition;
|
||||
}
|
||||
|
||||
protected abstract void seekImpl(long pPosition) throws IOException;
|
||||
|
||||
public final void mark() {
|
||||
mMarkedPositions.push(mPosition);
|
||||
}
|
||||
|
||||
public final void reset() throws IOException {
|
||||
checkOpen();
|
||||
if (!mMarkedPositions.isEmpty()) {
|
||||
long newPos = mMarkedPositions.pop();
|
||||
|
||||
// TODO: This is correct according to javax.imageio (IOException),
|
||||
// but it's inconsistent with seek that throws IndexOutOfBoundsException...
|
||||
if (newPos < mFlushedPosition) {
|
||||
throw new IOException("Previous marked position has been discarded!");
|
||||
}
|
||||
|
||||
seek(newPos);
|
||||
}
|
||||
}
|
||||
|
||||
public final void flushBefore(long pPosition) throws IOException {
|
||||
if (pPosition < mFlushedPosition) {
|
||||
throw new IndexOutOfBoundsException("position < flushedPosition!");
|
||||
}
|
||||
if (pPosition > getStreamPosition()) {
|
||||
throw new IndexOutOfBoundsException("position > getStreamPosition()!");
|
||||
}
|
||||
checkOpen();
|
||||
flushBeforeImpl(pPosition);
|
||||
mFlushedPosition = pPosition;
|
||||
}
|
||||
|
||||
protected abstract void flushBeforeImpl(long pPosition) throws IOException;
|
||||
|
||||
@Override
|
||||
public final void flush() throws IOException {
|
||||
flushBefore(mFlushedPosition);
|
||||
}
|
||||
|
||||
public final long getFlushedPosition() throws IOException {
|
||||
checkOpen();
|
||||
return mFlushedPosition;
|
||||
}
|
||||
|
||||
public final long getStreamPosition() throws IOException {
|
||||
checkOpen();
|
||||
return mPosition;
|
||||
}
|
||||
|
||||
protected final void checkOpen() throws IOException {
|
||||
if (mClosed) {
|
||||
throw new IOException("closed");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void close() throws IOException {
|
||||
checkOpen();
|
||||
mClosed = true;
|
||||
closeImpl();
|
||||
}
|
||||
|
||||
protected abstract void closeImpl() throws IOException;
|
||||
}
|
||||
185
common/common-io/src/main/java/com/twelvemonkeys/io/StringArrayReader.java
Executable file
185
common/common-io/src/main/java/com/twelvemonkeys/io/StringArrayReader.java
Executable file
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
|
||||
/**
|
||||
* StringArrayReader
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">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/StringArrayReader.java#2 $
|
||||
*/
|
||||
public class StringArrayReader extends StringReader {
|
||||
|
||||
private StringReader mCurrent;
|
||||
private String[] mStrings;
|
||||
protected final Object mLock;
|
||||
private int mCurrentSting;
|
||||
private int mMarkedString;
|
||||
private int mMark;
|
||||
private int mNext;
|
||||
|
||||
/**
|
||||
* Create a new string array reader.
|
||||
*
|
||||
* @param pStrings {@code String}s providing the character stream.
|
||||
*/
|
||||
public StringArrayReader(final String[] pStrings) {
|
||||
super("");
|
||||
if (pStrings == null) {
|
||||
throw new NullPointerException("strings == null");
|
||||
}
|
||||
|
||||
mLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the
|
||||
// reference can't change, only it's elements
|
||||
|
||||
mStrings = pStrings.clone(); // Defensive copy for content
|
||||
nextReader();
|
||||
}
|
||||
|
||||
protected final Reader nextReader() {
|
||||
if (mCurrentSting >= mStrings.length) {
|
||||
mCurrent = new EmptyReader();
|
||||
}
|
||||
else {
|
||||
mCurrent = new StringReader(mStrings[mCurrentSting++]);
|
||||
}
|
||||
// NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
|
||||
mNext = 0;
|
||||
|
||||
return mCurrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to make sure that the stream has not been closed
|
||||
*
|
||||
* @throws IOException if the stream is closed
|
||||
*/
|
||||
protected final void ensureOpen() throws IOException {
|
||||
if (mStrings == null) {
|
||||
throw new IOException("Stream closed");
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
super.close();
|
||||
mStrings = null;
|
||||
mCurrent.close();
|
||||
}
|
||||
|
||||
public void mark(int pReadLimit) throws IOException {
|
||||
if (pReadLimit < 0){
|
||||
throw new IllegalArgumentException("Read limit < 0");
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
ensureOpen();
|
||||
mMark = mNext;
|
||||
mMarkedString = mCurrentSting;
|
||||
|
||||
mCurrent.mark(pReadLimit);
|
||||
}
|
||||
}
|
||||
|
||||
public void reset() throws IOException {
|
||||
synchronized (mLock) {
|
||||
ensureOpen();
|
||||
|
||||
if (mCurrentSting != mMarkedString) {
|
||||
mCurrentSting = mMarkedString - 1;
|
||||
nextReader();
|
||||
mCurrent.skip(mMark);
|
||||
}
|
||||
else {
|
||||
mCurrent.reset();
|
||||
}
|
||||
|
||||
mNext = mMark;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean markSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
synchronized (mLock) {
|
||||
int read = mCurrent.read();
|
||||
|
||||
if (read < 0 && mCurrentSting < mStrings.length) {
|
||||
nextReader();
|
||||
return read(); // In case of empty strings
|
||||
}
|
||||
|
||||
mNext++;
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
|
||||
public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
|
||||
synchronized (mLock) {
|
||||
int read = mCurrent.read(pBuffer, pOffset, pLength);
|
||||
|
||||
if (read < 0 && mCurrentSting < mStrings.length) {
|
||||
nextReader();
|
||||
return read(pBuffer, pOffset, pLength); // In case of empty strings
|
||||
}
|
||||
|
||||
mNext += read;
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean ready() throws IOException {
|
||||
return mCurrent.ready();
|
||||
}
|
||||
|
||||
public long skip(long pChars) throws IOException {
|
||||
synchronized (mLock) {
|
||||
long skipped = mCurrent.skip(pChars);
|
||||
|
||||
if (skipped == 0 && mCurrentSting < mStrings.length) {
|
||||
nextReader();
|
||||
return skip(pChars);
|
||||
}
|
||||
|
||||
mNext += skipped;
|
||||
|
||||
return skipped;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
135
common/common-io/src/main/java/com/twelvemonkeys/io/SubStream.java
Executable file
135
common/common-io/src/main/java/com/twelvemonkeys/io/SubStream.java
Executable file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* An {@code InputStream} reading up to a specified number of bytes from an
|
||||
* underlying stream.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/SubStream.java#2 $
|
||||
*/
|
||||
public final class SubStream extends FilterInputStream {
|
||||
private long mLeft;
|
||||
private int mMarkLimit;
|
||||
|
||||
/**
|
||||
* Creates a {@code SubStream} of the given {@code pStream}.
|
||||
*
|
||||
* @param pStream the underlying input stream
|
||||
* @param pLength maximum number of bytes to read drom this stream
|
||||
*/
|
||||
public SubStream(final InputStream pStream, final long pLength) {
|
||||
super(Validate.notNull(pStream, "stream"));
|
||||
mLeft = pLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks this stream as closed.
|
||||
* This implementation does <em>not</em> close the underlying stream.
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
// NOTE: Do not close the underlying stream
|
||||
while (mLeft > 0) {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
skip(mLeft);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return (int) Math.min(super.available(), mLeft);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mark(int pReadLimit) {
|
||||
super.mark(pReadLimit);// This either succeeds or does nothing...
|
||||
mMarkLimit = pReadLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reset() throws IOException {
|
||||
super.reset();// This either succeeds or throws IOException
|
||||
mLeft += mMarkLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
if (mLeft-- <= 0) {
|
||||
return -1;
|
||||
}
|
||||
return super.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int read(byte[] pBytes) throws IOException {
|
||||
return read(pBytes, 0, pBytes.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (mLeft <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
|
||||
mLeft = read < 0 ? 0 : mLeft - read;
|
||||
return read;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the maximum number of bytes we can read or skip, from this stream.
|
||||
*
|
||||
* @param pLength the requested length
|
||||
* @return the maximum number of bytes to read
|
||||
*/
|
||||
private long findMaxLen(long pLength) {
|
||||
if (mLeft < pLength) {
|
||||
return (int) Math.max(mLeft, 0);
|
||||
}
|
||||
else {
|
||||
return pLength;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long skip(long pLength) throws IOException {
|
||||
long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
|
||||
mLeft -= skipped;
|
||||
return skipped;
|
||||
}
|
||||
}
|
||||
105
common/common-io/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java
Executable file
105
common/common-io/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java
Executable file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.twelvemonkeys.util.StringTokenIterator;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.BufferedReader;
|
||||
|
||||
/**
|
||||
* UnixFileSystem
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java#1 $
|
||||
*/
|
||||
final class UnixFileSystem extends FileSystem {
|
||||
long getFreeSpace(File pPath) {
|
||||
try {
|
||||
return getNumber(pPath, 3);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return 0l;
|
||||
}
|
||||
}
|
||||
|
||||
long getTotalSpace(File pPath) {
|
||||
try {
|
||||
return getNumber(pPath, 5);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return 0l;
|
||||
}
|
||||
}
|
||||
|
||||
private long getNumber(File pPath, int pIndex) throws IOException {
|
||||
// TODO: Test on other platforms
|
||||
// Tested on Mac OS X, CygWin
|
||||
BufferedReader reader = exec(new String[] {"df", "-k", pPath.getAbsolutePath()});
|
||||
|
||||
String last = null;
|
||||
String line;
|
||||
try {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
last = line;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
FileUtil.close(reader);
|
||||
}
|
||||
|
||||
if (last != null) {
|
||||
String blocks = null;
|
||||
StringTokenIterator tokens = new StringTokenIterator(last, " ", StringTokenIterator.REVERSE);
|
||||
int count = 0;
|
||||
// We want the 3rd last token
|
||||
while (count < pIndex && tokens.hasNext()) {
|
||||
blocks = tokens.nextToken();
|
||||
count++;
|
||||
}
|
||||
|
||||
if (blocks != null) {
|
||||
try {
|
||||
return Long.parseLong(blocks) * 1024L;
|
||||
}
|
||||
catch (NumberFormatException ignore) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0l;
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return "Unix";
|
||||
}
|
||||
}
|
||||
190
common/common-io/src/main/java/com/twelvemonkeys/io/Win32File.java
Executable file
190
common/common-io/src/main/java/com/twelvemonkeys/io/Win32File.java
Executable file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* Win32File
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Win32File.java#2 $
|
||||
*/
|
||||
final class Win32File extends File {
|
||||
private final static boolean IS_WINDOWS = isWindows();
|
||||
|
||||
private static boolean isWindows() {
|
||||
try {
|
||||
String os = System.getProperty("os.name");
|
||||
return os.toLowerCase().indexOf("windows") >= 0;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
// Ignore
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private Win32File(File pPath) {
|
||||
super(pPath.getPath());
|
||||
}
|
||||
|
||||
public static void main(String[] pArgs) {
|
||||
int argIdx = 0;
|
||||
boolean recursive = false;
|
||||
while (pArgs.length > argIdx + 1 && pArgs[argIdx].charAt(0) == '-' && pArgs[argIdx].length() > 1) {
|
||||
if (pArgs[argIdx].charAt(1) == 'R' || pArgs[argIdx].equals("--recursive")) {
|
||||
recursive = true;
|
||||
}
|
||||
else {
|
||||
System.err.println("Unknown option: " + pArgs[argIdx]);
|
||||
}
|
||||
argIdx++;
|
||||
}
|
||||
|
||||
File file = wrap(new File(pArgs[argIdx]));
|
||||
System.out.println("file: " + file);
|
||||
System.out.println("file.getClass(): " + file.getClass());
|
||||
|
||||
listFiles(file, 0, recursive);
|
||||
}
|
||||
|
||||
private static void listFiles(File pFile, int pLevel, boolean pRecursive) {
|
||||
if (pFile.isDirectory()) {
|
||||
File[] files = pFile.listFiles();
|
||||
for (int l = 0; l < pLevel; l++) {
|
||||
System.out.print(" ");
|
||||
}
|
||||
System.out.println("Contents of " + pFile + ": ");
|
||||
for (File file : files) {
|
||||
for (int l = 0; l < pLevel; l++) {
|
||||
System.out.print(" ");
|
||||
}
|
||||
System.out.println(" " + file);
|
||||
if (pRecursive) {
|
||||
listFiles(file, pLevel + 1, pLevel < 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a {@code File} object pointing to a Windows symbolic link
|
||||
* ({@code .lnk} file) in a {@code Win32Lnk}.
|
||||
* If the operating system is not Windows, the
|
||||
* {@code pPath} parameter is returned unwrapped.
|
||||
*
|
||||
* @param pPath any path, possibly pointing to a Windows symbolic link file.
|
||||
* May be {@code null}, in which case {@code null} is returned.
|
||||
*
|
||||
* @return a new {@code Win32Lnk} object if the current os is Windows, and
|
||||
* the file is a Windows symbolic link ({@code .lnk} file), otherwise
|
||||
* {@code pPath}
|
||||
*/
|
||||
public static File wrap(final File pPath) {
|
||||
if (pPath == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (IS_WINDOWS) {
|
||||
// Don't wrap if allready wrapped
|
||||
if (pPath instanceof Win32File || pPath instanceof Win32Lnk) {
|
||||
return pPath;
|
||||
}
|
||||
|
||||
if (pPath.exists() && pPath.getName().endsWith(".lnk")) {
|
||||
// If Win32 .lnk, let's wrap
|
||||
try {
|
||||
return new Win32Lnk(pPath);
|
||||
}
|
||||
catch (IOException e) {
|
||||
// TODO: FixMe!
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Wwrap even if not a .lnk, as the listFiles() methods etc,
|
||||
// could potentially return .lnk's, that we want to wrap later...
|
||||
return new Win32File(pPath);
|
||||
}
|
||||
|
||||
return pPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a {@code File} array, possibly pointing to Windows symbolic links
|
||||
* ({@code .lnk} files) in {@code Win32Lnk}s.
|
||||
*
|
||||
* @param pPaths an array of {@code File}s, possibly pointing to Windows
|
||||
* symbolic link files.
|
||||
* May be {@code null}, in which case {@code null} is returned.
|
||||
*
|
||||
* @return {@code pPaths}, with any {@code File} representing a Windows
|
||||
* symbolic link ({@code .lnk} file) wrapped in a {@code Win32Lnk}.
|
||||
*/
|
||||
public static File[] wrap(File[] pPaths) {
|
||||
if (IS_WINDOWS) {
|
||||
for (int i = 0; pPaths != null && i < pPaths.length; i++) {
|
||||
pPaths[i] = wrap(pPaths[i]);
|
||||
}
|
||||
}
|
||||
return pPaths;
|
||||
}
|
||||
|
||||
// File overrides
|
||||
@Override
|
||||
public File getAbsoluteFile() {
|
||||
return wrap(super.getAbsoluteFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getCanonicalFile() throws IOException {
|
||||
return wrap(super.getCanonicalFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getParentFile() {
|
||||
return wrap(super.getParentFile());
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles() {
|
||||
return wrap(super.listFiles());
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles(FileFilter filter) {
|
||||
return wrap(super.listFiles(filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles(FilenameFilter filter) {
|
||||
return wrap(super.listFiles(filter));
|
||||
}
|
||||
}
|
||||
90
common/common-io/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java
Executable file
90
common/common-io/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java
Executable file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* WindowsFileSystem
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java#2 $
|
||||
*/
|
||||
final class Win32FileSystem extends FileSystem {
|
||||
public long getFreeSpace(File pPath) {
|
||||
try {
|
||||
// Windows version
|
||||
// TODO: Test on W2K/95/98/etc... (tested on XP)
|
||||
BufferedReader reader = exec(new String[] {"CMD.EXE", "/C", "DIR", "/-C", pPath.getAbsolutePath()});
|
||||
|
||||
String last = null;
|
||||
String line;
|
||||
try {
|
||||
while ((line = reader.readLine()) != null) {
|
||||
last = line;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
FileUtil.close(reader);
|
||||
}
|
||||
|
||||
if (last != null) {
|
||||
int end = last.lastIndexOf(" bytes free");
|
||||
int start = last.lastIndexOf(' ', end - 1);
|
||||
|
||||
if (start >= 0 && end >= 0) {
|
||||
try {
|
||||
return Long.parseLong(last.substring(start + 1, end));
|
||||
}
|
||||
catch (NumberFormatException ignore) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
return 0l;
|
||||
}
|
||||
|
||||
long getTotalSpace(File pPath) {
|
||||
// TODO: Implement, probably need some JNI stuff...
|
||||
// Distribute df.exe and execute from temp!? ;-)
|
||||
return getFreeSpace(pPath);
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return "Win32";
|
||||
}
|
||||
}
|
||||
472
common/common-io/src/main/java/com/twelvemonkeys/io/Win32Lnk.java
Executable file
472
common/common-io/src/main/java/com/twelvemonkeys/io/Win32Lnk.java
Executable file
@@ -0,0 +1,472 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A {@code File} implementation that resolves the Windows {@code .lnk} files as symbolic links.
|
||||
* <p/>
|
||||
* This class is based on example code from
|
||||
* <a href="http://www.oreilly.com/catalog/swinghks/index.html">Swing Hacks</a>,
|
||||
* By Joshua Marinacci, Chris Adamson (O'Reilly, ISBN: 0-596-00907-0), Hack 30.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/Win32Lnk.java#2 $
|
||||
*/
|
||||
final class Win32Lnk extends File {
|
||||
private final static byte[] LNK_MAGIC = {
|
||||
'L', 0x00, 0x00, 0x00, // Magic
|
||||
};
|
||||
private final static byte[] LNK_GUID = {
|
||||
0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // Shell Link GUID
|
||||
(byte) 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 'F'
|
||||
};
|
||||
|
||||
private final File mTarget;
|
||||
private static final int FLAG_ITEM_ID_LIST = 0x01;
|
||||
private static final int FLAG_FILE_LOC_INFO = 0x02;
|
||||
private static final int FLAG_DESC_STRING = 0x04;
|
||||
private static final int FLAG_REL_PATH_STRING = 0x08;
|
||||
private static final int FLAG_WORKING_DIRECTORY = 0x10;
|
||||
private static final int FLAG_COMMAND_LINE_ARGS = 0x20;
|
||||
private static final int FLAG_ICON_FILENAME = 0x40;
|
||||
private static final int FLAG_ADDITIONAL_INFO = 0x80;
|
||||
|
||||
private Win32Lnk(final String pPath) throws IOException {
|
||||
super(pPath);
|
||||
File target = parse(this);
|
||||
if (target == this) {
|
||||
// NOTE: This is a workaround
|
||||
// mTarget = this causes infinite loops in some methods
|
||||
target = new File(pPath);
|
||||
}
|
||||
mTarget = target;
|
||||
}
|
||||
|
||||
Win32Lnk(final File pPath) throws IOException {
|
||||
this(pPath.getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a {@code .lnk} file to find the real file.
|
||||
*
|
||||
* @param pPath the path to the {@code .lnk} file
|
||||
* @return a new file object that
|
||||
* @throws java.io.IOException if the {@code .lnk} cannot be parsed
|
||||
*/
|
||||
static File parse(final File pPath) throws IOException {
|
||||
if (!pPath.getName().endsWith(".lnk")) {
|
||||
return pPath;
|
||||
}
|
||||
|
||||
File result = pPath;
|
||||
|
||||
LittleEndianDataInputStream in = new LittleEndianDataInputStream(new BufferedInputStream(new FileInputStream(pPath)));
|
||||
try {
|
||||
byte[] magic = new byte[4];
|
||||
in.readFully(magic);
|
||||
|
||||
byte[] guid = new byte[16];
|
||||
in.readFully(guid);
|
||||
|
||||
if (!(Arrays.equals(LNK_MAGIC, magic) && Arrays.equals(LNK_GUID, guid))) {
|
||||
//System.out.println("Not a symlink");
|
||||
// Not a symlink
|
||||
return pPath;
|
||||
}
|
||||
|
||||
// Get the flags
|
||||
int flags = in.readInt();
|
||||
//System.out.println("flags: " + Integer.toBinaryString(flags & 0xff));
|
||||
|
||||
// Get to the file settings
|
||||
/*int attributes = */in.readInt();
|
||||
|
||||
// File attributes
|
||||
// 0 Target is read only.
|
||||
// 1 Target is hidden.
|
||||
// 2 Target is a system file.
|
||||
// 3 Target is a volume label. (Not possible)
|
||||
// 4 Target is a directory.
|
||||
// 5 Target has been modified since last backup. (archive)
|
||||
// 6 Target is encrypted (NTFS EFS)
|
||||
// 7 Target is Normal??
|
||||
// 8 Target is temporary.
|
||||
// 9 Target is a sparse file.
|
||||
// 10 Target has reparse point data.
|
||||
// 11 Target is compressed.
|
||||
// 12 Target is offline.
|
||||
//System.out.println("attributes: " + Integer.toBinaryString(attributes));
|
||||
// NOTE: Cygwin .lnks are not directory links, can't rely on this.. :-/
|
||||
|
||||
in.skipBytes(48); // TODO: Make sense of this data...
|
||||
|
||||
// Skipped data:
|
||||
// long time 1 (creation)
|
||||
// long time 2 (modification)
|
||||
// long time 3 (last access)
|
||||
// int file length
|
||||
// int icon number
|
||||
// int ShowVnd value
|
||||
// int hotkey
|
||||
// int, int - unknown: 0,0
|
||||
|
||||
// If the shell settings are present, skip them
|
||||
if ((flags & FLAG_ITEM_ID_LIST) != 0) {
|
||||
// Shell Item Id List present
|
||||
//System.out.println("Shell Item Id List present");
|
||||
int shellLen = in.readShort(); // Short
|
||||
//System.out.println("shellLen: " + shellLen);
|
||||
|
||||
// TODO: Probably need to parse this data, to determine
|
||||
// Cygwin folders...
|
||||
|
||||
/*
|
||||
int read = 2;
|
||||
int itemLen = in.readShort();
|
||||
while (itemLen > 0) {
|
||||
System.out.println("--> ITEM: " + itemLen);
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(new SubStream(in, itemLen - 2)));
|
||||
//byte[] itemBytes = new byte[itemLen - 2]; // NOTE: Lenght included
|
||||
//in.readFully(itemBytes);
|
||||
|
||||
String item = reader.readLine();
|
||||
System.out.println("item: \"" + item + "\"");
|
||||
|
||||
itemLen = in.readShort();
|
||||
read += itemLen;
|
||||
}
|
||||
|
||||
System.out.println("read: " + read);
|
||||
*/
|
||||
|
||||
in.skipBytes(shellLen);
|
||||
}
|
||||
|
||||
if ((flags & FLAG_FILE_LOC_INFO) != 0) {
|
||||
// File Location Info Table present
|
||||
//System.out.println("File Location Info Table present");
|
||||
|
||||
// 0h 1 dword This is the total length of this structure and all following data
|
||||
// 4h 1 dword This is a pointer to first offset after this structure. 1Ch
|
||||
// 8h 1 dword Flags
|
||||
// Ch 1 dword Offset of local volume info
|
||||
// 10h 1 dword Offset of base pathname on local system
|
||||
// 14h 1 dword Offset of network volume info
|
||||
// 18h 1 dword Offset of remaining pathname
|
||||
|
||||
// Flags:
|
||||
// Bit Meaning
|
||||
// 0 Available on a local volume
|
||||
// 1 Available on a network share
|
||||
// TODO: Make sure the path is on a local disk, etc..
|
||||
|
||||
int tableLen = in.readInt(); // Int
|
||||
//System.out.println("tableLen: " + tableLen);
|
||||
|
||||
in.readInt(); // Skip
|
||||
|
||||
int locFlags = in.readInt();
|
||||
//System.out.println("locFlags: " + Integer.toBinaryString(locFlags));
|
||||
if ((locFlags & 0x01) != 0) {
|
||||
//System.out.println("Available local");
|
||||
}
|
||||
if ((locFlags & 0x02) != 0) {
|
||||
//System.err.println("Available on network path");
|
||||
}
|
||||
|
||||
// Get the local volume and local system values
|
||||
in.skipBytes(4); // TODO: see above for structure
|
||||
|
||||
int localSysOff = in.readInt();
|
||||
//System.out.println("localSysOff: " + localSysOff);
|
||||
in.skipBytes(localSysOff - 20); // Relative to start of chunk
|
||||
|
||||
byte[] pathBytes = new byte[tableLen - localSysOff - 1];
|
||||
in.readFully(pathBytes, 0, pathBytes.length);
|
||||
String path = new String(pathBytes, 0, pathBytes.length - 1);
|
||||
/*
|
||||
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||
byte read;
|
||||
// Read bytes until the null (0) character
|
||||
while (true) {
|
||||
read = in.readByte();
|
||||
if (read == 0) {
|
||||
break;
|
||||
}
|
||||
bytes.write(read & 0xff);
|
||||
}
|
||||
|
||||
String path = new String(bytes.toByteArray(), 0, bytes.size());
|
||||
//*/
|
||||
|
||||
// Recurse to end of link chain
|
||||
// TODO: This may cause endless loop if cyclic chain...
|
||||
//System.out.println("path: \"" + path + "\"");
|
||||
try {
|
||||
result = parse(new File(path));
|
||||
}
|
||||
catch (StackOverflowError e) {
|
||||
throw new IOException("Cannot resolve cyclic link: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & FLAG_DESC_STRING) != 0) {
|
||||
// Description String present, skip it.
|
||||
//System.out.println("Description String present");
|
||||
|
||||
// The string length is the first word which must also be skipped.
|
||||
int descLen = in.readShort();
|
||||
//System.out.println("descLen: " + descLen);
|
||||
|
||||
byte[] descBytes = new byte[descLen];
|
||||
in.readFully(descBytes, 0, descLen);
|
||||
|
||||
//String desc = new String(descBytes, 0, descLen);
|
||||
//System.out.println("desc: " + desc);
|
||||
}
|
||||
|
||||
if ((flags & FLAG_REL_PATH_STRING) != 0) {
|
||||
// Relative Path String present
|
||||
//System.out.println("Relative Path String present");
|
||||
|
||||
// The string length is the first word which must also be skipped.
|
||||
int pathLen = in.readShort();
|
||||
//System.out.println("pathLen: " + pathLen);
|
||||
|
||||
byte[] pathBytes = new byte[pathLen];
|
||||
in.readFully(pathBytes, 0, pathLen);
|
||||
|
||||
String path = new String(pathBytes, 0, pathLen);
|
||||
|
||||
// TODO: This may cause endless loop if cyclic chain...
|
||||
//System.out.println("path: \"" + path + "\"");
|
||||
if (result == pPath) {
|
||||
try {
|
||||
result = parse(new File(pPath.getParentFile(), path));
|
||||
}
|
||||
catch (StackOverflowError e) {
|
||||
throw new IOException("Cannot resolve cyclic link: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & FLAG_WORKING_DIRECTORY) != 0) {
|
||||
//System.out.println("Working Directory present");
|
||||
}
|
||||
if ((flags & FLAG_COMMAND_LINE_ARGS) != 0) {
|
||||
//System.out.println("Command Line Arguments present");
|
||||
// NOTE: This means this .lnk is not a folder, don't follow
|
||||
result = pPath;
|
||||
}
|
||||
if ((flags & FLAG_ICON_FILENAME) != 0) {
|
||||
//System.out.println("Icon Filename present");
|
||||
}
|
||||
if ((flags & FLAG_ADDITIONAL_INFO) != 0) {
|
||||
//System.out.println("Additional Info present");
|
||||
}
|
||||
}
|
||||
finally {
|
||||
in.close();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
private static String getNullDelimitedString(byte[] bytes, int off) {
|
||||
int len = 0;
|
||||
// Count bytes until the null (0) character
|
||||
while (true) {
|
||||
if (bytes[off + len] == 0) {
|
||||
break;
|
||||
}
|
||||
len++;
|
||||
}
|
||||
|
||||
System.err.println("--> " + len);
|
||||
|
||||
return new String(bytes, off, len);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts two bytes into a short.
|
||||
* <p/>
|
||||
* NOTE: this is little endian because it's for an
|
||||
* Intel only OS
|
||||
*
|
||||
* @ param bytes
|
||||
* @ param off
|
||||
* @return the bytes as a short.
|
||||
*/
|
||||
/*
|
||||
private static int bytes2short(byte[] bytes, int off) {
|
||||
return ((bytes[off + 1] & 0xff) << 8) | (bytes[off] & 0xff);
|
||||
}
|
||||
*/
|
||||
|
||||
public File getTarget() {
|
||||
return mTarget;
|
||||
}
|
||||
|
||||
// java.io.File overrides below
|
||||
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return mTarget.isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead() {
|
||||
return mTarget.canRead();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite() {
|
||||
return mTarget.canWrite();
|
||||
}
|
||||
|
||||
// NOTE: equals is implemented using compareto == 0
|
||||
/*
|
||||
public int compareTo(File pathname) {
|
||||
// TODO: Verify this
|
||||
// Probably not a good idea, as it IS NOT THE SAME file
|
||||
// It's probably better to not override
|
||||
return mTarget.compareTo(pathname);
|
||||
}
|
||||
*/
|
||||
|
||||
// Should probably never allow creating a new .lnk
|
||||
// public boolean createNewFile() throws IOException
|
||||
|
||||
// Deletes only the .lnk
|
||||
// public boolean delete() {
|
||||
//public void deleteOnExit() {
|
||||
|
||||
@Override
|
||||
public boolean exists() {
|
||||
return mTarget.exists();
|
||||
}
|
||||
|
||||
// A .lnk may be absolute
|
||||
//public File getAbsoluteFile() {
|
||||
//public String getAbsolutePath() {
|
||||
|
||||
// Theses should be resolved according to the API (for Unix).
|
||||
@Override
|
||||
public File getCanonicalFile() throws IOException {
|
||||
return mTarget.getCanonicalFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCanonicalPath() throws IOException {
|
||||
return mTarget.getCanonicalPath();
|
||||
}
|
||||
|
||||
//public String getName() {
|
||||
|
||||
// I guess the parent should be the parent of the .lnk, not the target
|
||||
//public String getParent() {
|
||||
//public File getParentFile() {
|
||||
|
||||
// public boolean isAbsolute() {
|
||||
@Override
|
||||
public boolean isFile() {
|
||||
return mTarget.isFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHidden() {
|
||||
return mTarget.isHidden();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long lastModified() {
|
||||
return mTarget.lastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long length() {
|
||||
return mTarget.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list() {
|
||||
return mTarget.list();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] list(final FilenameFilter filter) {
|
||||
return mTarget.list(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles() {
|
||||
return Win32File.wrap(mTarget.listFiles());
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles(final FileFilter filter) {
|
||||
return Win32File.wrap(mTarget.listFiles(filter));
|
||||
}
|
||||
|
||||
@Override
|
||||
public File[] listFiles(final FilenameFilter filter) {
|
||||
return Win32File.wrap(mTarget.listFiles(filter));
|
||||
}
|
||||
|
||||
// Makes no sense, does it?
|
||||
//public boolean mkdir() {
|
||||
//public boolean mkdirs() {
|
||||
|
||||
// Only rename the lnk
|
||||
//public boolean renameTo(File dest) {
|
||||
|
||||
@Override
|
||||
public boolean setLastModified(long time) {
|
||||
return mTarget.setLastModified(time);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setReadOnly() {
|
||||
return mTarget.setReadOnly();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (mTarget.equals(this)) {
|
||||
return super.toString();
|
||||
}
|
||||
return super.toString() + " -> " + mTarget.toString();
|
||||
}
|
||||
}
|
||||
236
common/common-io/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java
Executable file
236
common/common-io/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java
Executable file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.twelvemonkeys.lang.DateUtil;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
|
||||
/**
|
||||
* Wraps a {@code Writer} in an {@code OutputStream}.
|
||||
* <p/>
|
||||
* <em>Instances of this class are not thread-safe.</em>
|
||||
* <p/>
|
||||
* <em>NOTE: This class is probably not the right way of solving your problem,
|
||||
* however it might prove useful in JSPs etc.
|
||||
* If possible, it's always better to use the {@code Writer}'s underlying
|
||||
* {@code OutputStream}, or wrap it's native backing.
|
||||
* </em>
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java#2 $
|
||||
*/
|
||||
public class WriterOutputStream extends OutputStream {
|
||||
protected Writer mWriter;
|
||||
final protected Decoder mDecoder;
|
||||
final ByteArrayOutputStream mBufferStream = new FastByteArrayOutputStream(1024);
|
||||
private volatile boolean mIsFlushing = false; // Ugly but critical...
|
||||
|
||||
private static final boolean NIO_AVAILABLE = isNIOAvailable();
|
||||
|
||||
private static boolean isNIOAvailable() {
|
||||
try {
|
||||
Class.forName("java.nio.charset.Charset");
|
||||
return true;
|
||||
}
|
||||
catch (Throwable t) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public WriterOutputStream(final Writer pWriter, final String pCharset) {
|
||||
mWriter = pWriter;
|
||||
mDecoder = getDecoder(pCharset);
|
||||
}
|
||||
|
||||
public WriterOutputStream(final Writer pWriter) {
|
||||
this(pWriter, null);
|
||||
}
|
||||
|
||||
private static Decoder getDecoder(final String pCharset) {
|
||||
// NOTE: The CharsetDecoder is typically 10-20% faster than
|
||||
// StringDecoder according to my tests
|
||||
// StringEncoder is horribly slow on 1.2 systems, but there's no
|
||||
// alternative...
|
||||
if (NIO_AVAILABLE) {
|
||||
return new CharsetDecoder(pCharset);
|
||||
}
|
||||
|
||||
return new StringDecoder(pCharset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
flush();
|
||||
mWriter.close();
|
||||
mWriter = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
flushBuffer();
|
||||
mWriter.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(byte[] pBytes) throws IOException {
|
||||
if (pBytes == null) {
|
||||
throw new NullPointerException("bytes == null");
|
||||
}
|
||||
write(pBytes, 0, pBytes.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(byte[] pBytes, int pOffset, int pLength) throws IOException {
|
||||
flushBuffer();
|
||||
mDecoder.decodeTo(mWriter, pBytes, pOffset, pLength);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void write(int pByte) {
|
||||
// TODO: Is it possible to know if this is a good place in the stream to
|
||||
// flush? It might be in the middle of a multi-byte encoded character..
|
||||
mBufferStream.write(pByte);
|
||||
}
|
||||
|
||||
private void flushBuffer() throws IOException {
|
||||
if (!mIsFlushing && mBufferStream.size() > 0) {
|
||||
mIsFlushing = true;
|
||||
mBufferStream.writeTo(this); // NOTE: Avoids cloning buffer array
|
||||
mBufferStream.reset();
|
||||
mIsFlushing = false;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
public static void main(String[] pArgs) throws IOException {
|
||||
int iterations = 1000000;
|
||||
|
||||
byte[] bytes = "åøæÅØÆ klashf lkash ljah lhaaklhghdfgu ksd".getBytes("UTF-8");
|
||||
|
||||
Decoder d;
|
||||
long start;
|
||||
long time;
|
||||
Writer sink = new PrintWriter(new NullOutputStream());
|
||||
StringWriter writer;
|
||||
String str;
|
||||
|
||||
d = new StringDecoder("UTF-8");
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
d.decodeTo(sink, bytes, 0, bytes.length);
|
||||
}
|
||||
start = System.currentTimeMillis();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
d.decodeTo(sink, bytes, 0, bytes.length);
|
||||
}
|
||||
time = DateUtil.delta(start);
|
||||
System.out.println("StringDecoder");
|
||||
System.out.println("time: " + time);
|
||||
|
||||
writer = new StringWriter();
|
||||
d.decodeTo(writer, bytes, 0, bytes.length);
|
||||
str = writer.toString();
|
||||
System.out.println("str: \"" + str + "\"");
|
||||
System.out.println("chars.length: " + str.length());
|
||||
System.out.println();
|
||||
|
||||
if (NIO_AVAILABLE) {
|
||||
d = new CharsetDecoder("UTF-8");
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
d.decodeTo(sink, bytes, 0, bytes.length);
|
||||
}
|
||||
start = System.currentTimeMillis();
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
d.decodeTo(sink, bytes, 0, bytes.length);
|
||||
}
|
||||
time = DateUtil.delta(start);
|
||||
System.out.println("CharsetDecoder");
|
||||
System.out.println("time: " + time);
|
||||
writer = new StringWriter();
|
||||
d.decodeTo(writer, bytes, 0, bytes.length);
|
||||
str = writer.toString();
|
||||
System.out.println("str: \"" + str + "\"");
|
||||
System.out.println("chars.length: " + str.length());
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
OutputStream os = new WriterOutputStream(new PrintWriter(System.out), "UTF-8");
|
||||
os.write(bytes);
|
||||
os.flush();
|
||||
System.out.println();
|
||||
|
||||
for (byte b : bytes) {
|
||||
os.write(b & 0xff);
|
||||
}
|
||||
os.flush();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
private static interface Decoder {
|
||||
void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException;
|
||||
}
|
||||
|
||||
private static final class CharsetDecoder implements Decoder {
|
||||
final Charset mCharset;
|
||||
|
||||
CharsetDecoder(String pCharset) {
|
||||
// Handle null-case, to get default charset
|
||||
String charset = pCharset != null ? pCharset :
|
||||
System.getProperty("file.encoding", "ISO-8859-1");
|
||||
mCharset = Charset.forName(charset);
|
||||
}
|
||||
|
||||
public void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException {
|
||||
CharBuffer cb = mCharset.decode(ByteBuffer.wrap(pBytes, pOffset, pLength));
|
||||
pWriter.write(cb.array(), 0, cb.length());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StringDecoder implements Decoder {
|
||||
final String mCharset;
|
||||
|
||||
StringDecoder(String pCharset) {
|
||||
mCharset = pCharset;
|
||||
}
|
||||
|
||||
public void decodeTo(Writer pWriter, byte[] pBytes, int pOffset, int pLength) throws IOException {
|
||||
String str = mCharset == null ?
|
||||
new String(pBytes, pOffset, pLength) :
|
||||
new String(pBytes, pOffset, pLength, mCharset);
|
||||
|
||||
pWriter.write(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Abstract base class for RLE decoding as specified by in the Windows BMP (aka DIB) file format.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/AbstractRLEDecoder.java#1 $
|
||||
*/
|
||||
// TODO: Move to other package or make public
|
||||
abstract class AbstractRLEDecoder implements Decoder {
|
||||
protected final byte[] mRow;
|
||||
protected final int mWidth;
|
||||
protected int mSrcX;
|
||||
protected int mSrcY;
|
||||
protected int mDstX;
|
||||
protected int mDstY;
|
||||
|
||||
/**
|
||||
* Creates an RLEDecoder. As RLE encoded BMP's may contain x and y deltas,
|
||||
* etc, we need to know height and width of the image.
|
||||
*
|
||||
* @param pWidth width of the image
|
||||
* @param pHeight heigth of the image
|
||||
*/
|
||||
AbstractRLEDecoder(int pWidth, int pHeight) {
|
||||
mWidth = pWidth;
|
||||
int bytesPerRow = mWidth;
|
||||
int mod = bytesPerRow % 4;
|
||||
|
||||
if (mod != 0) {
|
||||
bytesPerRow += 4 - mod;
|
||||
}
|
||||
|
||||
mRow = new byte[bytesPerRow];
|
||||
|
||||
mSrcX = 0;
|
||||
mSrcY = pHeight - 1;
|
||||
|
||||
mDstX = mSrcX;
|
||||
mDstY = mSrcY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes one full row of image data.
|
||||
*
|
||||
* @param pStream the input stream containint RLE data
|
||||
*
|
||||
* @throws IOException if an I/O related exception ocurs while reading
|
||||
*/
|
||||
protected abstract void decodeRow(InputStream pStream) throws IOException;
|
||||
|
||||
/**
|
||||
* Decodes as much data as possible, from the stream into the buffer.
|
||||
*
|
||||
* @param pStream the input stream containing RLE data
|
||||
* @param pBuffer tge buffer to decode the data to
|
||||
*
|
||||
* @return the number of bytes decoded from the stream, to the buffer
|
||||
*
|
||||
* @throws IOException if an I/O related exception ocurs while reading
|
||||
*/
|
||||
public final int decode(InputStream pStream, byte[] pBuffer) throws IOException {
|
||||
int decoded = 0;
|
||||
|
||||
while (decoded < pBuffer.length && mDstY >= 0) {
|
||||
// NOTE: Decode only full rows, don't decode if y delta
|
||||
if (mDstX == 0 && mSrcY == mDstY) {
|
||||
decodeRow(pStream);
|
||||
}
|
||||
|
||||
int length = Math.min(mRow.length - mDstX, pBuffer.length - decoded);
|
||||
System.arraycopy(mRow, mDstX, pBuffer, decoded, length);
|
||||
mDstX += length;
|
||||
decoded += length;
|
||||
|
||||
if (mDstX == mRow.length) {
|
||||
mDstX = 0;
|
||||
mDstY--;
|
||||
|
||||
// NOTE: If src Y is < dst Y, we have a delta, and have to fill the
|
||||
// gap with zero-bytes
|
||||
if (mDstY > mSrcY) {
|
||||
for (int i = 0; i < mRow.length; i++) {
|
||||
mRow[i] = 0x00;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a read byte for EOF marker.
|
||||
*
|
||||
* @param pByte the byte to check
|
||||
* @return the value of {@code pByte} if positive.
|
||||
*
|
||||
* @throws EOFException if {@code pByte} is negative
|
||||
*/
|
||||
protected static int checkEOF(int pByte) throws EOFException {
|
||||
if (pByte < 0) {
|
||||
throw new EOFException("Premature end of file");
|
||||
}
|
||||
|
||||
return pByte;
|
||||
}
|
||||
}
|
||||
671
common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64.java
Executable file
671
common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64.java
Executable file
@@ -0,0 +1,671 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/*
|
||||
* Copyright (c) 2004, Mikael Grev, MiG InfoCom AB. (base64 @ miginfocom . com)
|
||||
* All rights reserved.
|
||||
* <p/>
|
||||
* 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 of the MiG InfoCom AB nor the names of its contributors may be
|
||||
* used to endorse or promote products derived from this software without specific
|
||||
* prior written permission.
|
||||
* <p/>
|
||||
* 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.enc;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* A very fast and memory efficient class to encode and decode to and from
|
||||
* BASE64 in full accordance with RFC 2045.
|
||||
* <p/>
|
||||
* On Windows XP sp1 with 1.4.2_04 and later ;), this encoder and decoder is
|
||||
* about 10 times faster on small arrays (10 - 1000 bytes) and 2-3 times as fast
|
||||
* on larger arrays (10000 - 1000000 bytes) compared to
|
||||
* {@code sun.misc.Encoder()/Decoder()}.
|
||||
* <p/>
|
||||
* On byte arrays the encoder is about 20% faster than
|
||||
* <a href="http://jakarta.apache.org/commons/codec/">Jakarta Commons Base64 Codec</a>
|
||||
* for encode and about 50% faster for decoding large arrays. This
|
||||
* implementation is about twice as fast on very small arrays (< 30 bytes).
|
||||
* If source/destination is a {@code String} this version is about three times
|
||||
* as fast due to the fact that the Commons Codec result has to be recoded
|
||||
* to a {@code String} from {@code byte[]}, which is very expensive.
|
||||
* <p/>
|
||||
* This encode/decode algorithm doesn't create any temporary arrays as many
|
||||
* other codecs do, it only allocates the resulting array. This produces less
|
||||
* garbage and it is possible to handle arrays twice as large as algorithms that
|
||||
* create a temporary array. (E.g. Jakarta Commons Codec). It is unknown
|
||||
* whether Sun's {@code sun.misc.Encoder()/Decoder()} produce temporary arrays
|
||||
* but since performance is quite low it probably does.
|
||||
* <p/>
|
||||
* The encoder produces the same output as the Sun one except that Sun's encoder
|
||||
* appends a trailing line separator if the last character isn't a pad.
|
||||
* Unclear why but it only adds to the length and is probably a side effect.
|
||||
* Both are in conformance with RFC 2045 though.<br>
|
||||
* Commons codec seem to always add a trailing line separator.
|
||||
* <p/>
|
||||
* <b>Note!</b>
|
||||
* The encode/decode method pairs (types) come in three versions with the
|
||||
* <b>exact</b> same algorithm and thus a lot of code redundancy. This is to not
|
||||
* create any temporary arrays for transcoding to/from different
|
||||
* format types. The methods not used can simply be commented out.
|
||||
* <p/>
|
||||
* There is also a "fast" version of all decode methods that works the same way
|
||||
* as the normal ones, but har a few demands on the decoded input. Normally
|
||||
* though, these fast verions should be used if the source if
|
||||
* the input is known and it hasn't bee tampered with.
|
||||
* <p/>
|
||||
* If you find the code useful or you find a bug, please send me a note at
|
||||
* base64 @ miginfocom . com.
|
||||
* <p/>
|
||||
*
|
||||
* @author Mikael Grev, 2004-aug-02 11:31:11
|
||||
* @version 2.2
|
||||
*/
|
||||
final class Base64 {
|
||||
private static final char[] CA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
|
||||
private static final int[] IA = new int[256];
|
||||
|
||||
static {
|
||||
Arrays.fill(IA, -1);
|
||||
for (int i = 0, iS = CA.length; i < iS; i++) {
|
||||
IA[CA[i]] = i;
|
||||
}
|
||||
IA['='] = 0;
|
||||
}
|
||||
|
||||
// ****************************************************************************************
|
||||
// * char[] version
|
||||
// ****************************************************************************************
|
||||
|
||||
/**
|
||||
* Encodes a raw byte array into a BASE64 {@code char[]} representation im
|
||||
* accordance with RFC 2045.
|
||||
*
|
||||
* @param sArr The bytes to convert. If {@code null} or length 0 an
|
||||
* empty array will be returned.
|
||||
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.
|
||||
* <br>
|
||||
* No line separator will be in breach of RFC 2045 which
|
||||
* specifies max 76 per line but will be a little faster.
|
||||
* @return A BASE64 encoded array. Never {@code null}.
|
||||
*/
|
||||
public static char[] encodeToChar(byte[] sArr, boolean lineSep) {
|
||||
// Check special case
|
||||
int sLen = sArr != null ? sArr.length : 0;
|
||||
if (sLen == 0) {
|
||||
return new char[0];
|
||||
}
|
||||
|
||||
int eLen = (sLen / 3) * 3;// Length of even 24-bits.
|
||||
int cCnt = ((sLen - 1) / 3 + 1) << 2;// Returned character count
|
||||
int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0);// Length of returned array
|
||||
char[] dArr = new char[dLen];
|
||||
|
||||
// Encode even 24-bits
|
||||
for (int s = 0, d = 0, cc = 0; s < eLen;) {
|
||||
// Copy next three bytes into lower 24 bits of int, paying attension to sign.
|
||||
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
|
||||
|
||||
// Encode the int into four chars
|
||||
dArr[d++] = CA[(i >>> 18) & 0x3f];
|
||||
dArr[d++] = CA[(i >>> 12) & 0x3f];
|
||||
dArr[d++] = CA[(i >>> 6) & 0x3f];
|
||||
dArr[d++] = CA[i & 0x3f];
|
||||
|
||||
// Add optional line separator
|
||||
if (lineSep && ++cc == 19 && d < dLen - 2) {
|
||||
dArr[d++] = '\r';
|
||||
dArr[d++] = '\n';
|
||||
cc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Pad and encode last bits if source isn't even 24 bits.
|
||||
int left = sLen - eLen;// 0 - 2.
|
||||
if (left > 0) {
|
||||
// Prepare the int
|
||||
int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
|
||||
|
||||
// Set last four chars
|
||||
dArr[dLen - 4] = CA[i >> 12];
|
||||
dArr[dLen - 3] = CA[(i >>> 6) & 0x3f];
|
||||
dArr[dLen - 2] = left == 2 ? CA[i & 0x3f] : '=';
|
||||
dArr[dLen - 1] = '=';
|
||||
}
|
||||
return dArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a BASE64 encoded char array. All illegal characters will be
|
||||
* ignored and can handle both arrays with and without line separators.
|
||||
*
|
||||
* @param sArr The source array. {@code null} or length 0 will return
|
||||
* an empty array.
|
||||
* @return The decoded array of bytes. May be of length 0. Will be
|
||||
* {@code null} if the legal characters (including '=') isn't
|
||||
* divideable by 4. (I.e. definitely corrupted).
|
||||
*/
|
||||
public static byte[] decode(char[] sArr) {
|
||||
// Check special case
|
||||
int sLen = sArr != null ? sArr.length : 0;
|
||||
if (sLen == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
|
||||
// so we don't have to reallocate & copy it later.
|
||||
int sepCnt = 0;// Number of separator characters. (Actually illegal characters, but that's a bonus...)
|
||||
for (int i = 0; i < sLen; i++)// If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
|
||||
{
|
||||
if (IA[sArr[i]] < 0) {
|
||||
sepCnt++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
|
||||
if ((sLen - sepCnt) % 4 != 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int pad = 0;
|
||||
for (int i = sLen; i > 1 && IA[sArr[--i]] <= 0;) {
|
||||
if (sArr[i] == '=') {
|
||||
pad++;
|
||||
}
|
||||
}
|
||||
|
||||
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
|
||||
|
||||
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
|
||||
|
||||
for (int s = 0, d = 0; d < len;) {
|
||||
// Assemble three bytes into an int from four "valid" characters.
|
||||
int i = 0;
|
||||
for (int j = 0; j < 4; j++)
|
||||
{// j only increased if a valid char was found.
|
||||
int c = IA[sArr[s++]];
|
||||
if (c >= 0) {
|
||||
i |= c << (18 - j * 6);
|
||||
}
|
||||
else {
|
||||
j--;
|
||||
}
|
||||
}
|
||||
// Add the bytes
|
||||
dArr[d++] = (byte) (i >> 16);
|
||||
if (d < len) {
|
||||
dArr[d++] = (byte) (i >> 8);
|
||||
if (d < len) {
|
||||
dArr[d++] = (byte) i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a BASE64 encoded char array that is known to be resonably well formatted. The method is about twice as
|
||||
* fast as {@link #decode(char[])}. The preconditions are:<br>
|
||||
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
|
||||
* + Line separator must be "\r\n", as specified in RFC 2045
|
||||
* + The array must not contain illegal characters within the encoded string<br>
|
||||
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
|
||||
*
|
||||
* @param sArr The source array. Length 0 will return an empty array. {@code null} will throw an exception.
|
||||
* @return The decoded array of bytes. May be of length 0.
|
||||
*/
|
||||
public static byte[] decodeFast(char[] sArr) {
|
||||
// Check special case
|
||||
int sLen = sArr.length;
|
||||
if (sLen == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
int sIx = 0, eIx = sLen - 1;// Start and end index after trimming.
|
||||
|
||||
// Trim illegal chars from start
|
||||
while (sIx < eIx && IA[sArr[sIx]] < 0) {
|
||||
sIx++;
|
||||
}
|
||||
|
||||
// Trim illegal chars from end
|
||||
while (eIx > 0 && IA[sArr[eIx]] < 0) {
|
||||
eIx--;
|
||||
}
|
||||
|
||||
// get the padding count (=) (0, 1 or 2)
|
||||
int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0;// Count '=' at end.
|
||||
int cCnt = eIx - sIx + 1;// Content count including possible separators
|
||||
int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
|
||||
|
||||
int len = ((cCnt - sepCnt) * 6 >> 3) - pad;// The number of decoded bytes
|
||||
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
|
||||
|
||||
// Decode all but the last 0 - 2 bytes.
|
||||
int d = 0;
|
||||
for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
|
||||
// Assemble three bytes into an int from four "valid" characters.
|
||||
int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];
|
||||
|
||||
// Add the bytes
|
||||
dArr[d++] = (byte) (i >> 16);
|
||||
dArr[d++] = (byte) (i >> 8);
|
||||
dArr[d++] = (byte) i;
|
||||
|
||||
// If line separator, jump over it.
|
||||
if (sepCnt > 0 && ++cc == 19) {
|
||||
sIx += 2;
|
||||
cc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (d < len) {
|
||||
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
|
||||
int i = 0;
|
||||
for (int j = 0; sIx <= eIx - pad; j++) {
|
||||
i |= IA[sArr[sIx++]] << (18 - j * 6);
|
||||
}
|
||||
|
||||
for (int r = 16; d < len; r -= 8) {
|
||||
dArr[d++] = (byte) (i >> r);
|
||||
}
|
||||
}
|
||||
|
||||
return dArr;
|
||||
}
|
||||
|
||||
// ****************************************************************************************
|
||||
// * byte[] version
|
||||
// ****************************************************************************************
|
||||
|
||||
/**
|
||||
* Encodes a raw byte array into a BASE64 {@code byte[]} representation i accordance with RFC 2045.
|
||||
*
|
||||
* @param sArr The bytes to convert. If {@code null} or length 0 an empty array will be returned.
|
||||
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
|
||||
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
|
||||
* little faster.
|
||||
* @return A BASE64 encoded array. Never {@code null}.
|
||||
*/
|
||||
public static byte[] encodeToByte(byte[] sArr, boolean lineSep) {
|
||||
// Check special case
|
||||
int sLen = sArr != null ? sArr.length : 0;
|
||||
if (sLen == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
int eLen = (sLen / 3) * 3;// Length of even 24-bits.
|
||||
int cCnt = ((sLen - 1) / 3 + 1) << 2;// Returned character count
|
||||
int dLen = cCnt + (lineSep ? (cCnt - 1) / 76 << 1 : 0);// Length of returned array
|
||||
byte[] dArr = new byte[dLen];
|
||||
|
||||
// Encode even 24-bits
|
||||
for (int s = 0, d = 0, cc = 0; s < eLen;) {
|
||||
// Copy next three bytes into lower 24 bits of int, paying attension to sign.
|
||||
int i = (sArr[s++] & 0xff) << 16 | (sArr[s++] & 0xff) << 8 | (sArr[s++] & 0xff);
|
||||
|
||||
// Encode the int into four chars
|
||||
dArr[d++] = (byte) CA[(i >>> 18) & 0x3f];
|
||||
dArr[d++] = (byte) CA[(i >>> 12) & 0x3f];
|
||||
dArr[d++] = (byte) CA[(i >>> 6) & 0x3f];
|
||||
dArr[d++] = (byte) CA[i & 0x3f];
|
||||
|
||||
// Add optional line separator
|
||||
if (lineSep && ++cc == 19 && d < dLen - 2) {
|
||||
dArr[d++] = '\r';
|
||||
dArr[d++] = '\n';
|
||||
cc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Pad and encode last bits if source isn't an even 24 bits.
|
||||
int left = sLen - eLen;// 0 - 2.
|
||||
if (left > 0) {
|
||||
// Prepare the int
|
||||
int i = ((sArr[eLen] & 0xff) << 10) | (left == 2 ? ((sArr[sLen - 1] & 0xff) << 2) : 0);
|
||||
|
||||
// Set last four chars
|
||||
dArr[dLen - 4] = (byte) CA[i >> 12];
|
||||
dArr[dLen - 3] = (byte) CA[(i >>> 6) & 0x3f];
|
||||
dArr[dLen - 2] = left == 2 ? (byte) CA[i & 0x3f] : (byte) '=';
|
||||
dArr[dLen - 1] = '=';
|
||||
}
|
||||
return dArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a BASE64 encoded byte array. All illegal characters will be ignored and can handle both arrays with
|
||||
* and without line separators.
|
||||
*
|
||||
* @param sArr The source array. Length 0 will return an empty array. {@code null} will throw an exception.
|
||||
* @return The decoded array of bytes. May be of length 0. Will be {@code null} if the legal characters
|
||||
* (including '=') isn't divideable by 4. (I.e. definitely corrupted).
|
||||
*/
|
||||
public static byte[] decode(byte[] sArr) {
|
||||
// Check special case
|
||||
int sLen = sArr.length;
|
||||
|
||||
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
|
||||
// so we don't have to reallocate & copy it later.
|
||||
int sepCnt = 0;// Number of separator characters. (Actually illegal characters, but that's a bonus...)
|
||||
for (int i = 0; i < sLen; i++)// If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
|
||||
{
|
||||
if (IA[sArr[i] & 0xff] < 0) {
|
||||
sepCnt++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
|
||||
if ((sLen - sepCnt) % 4 != 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int pad = 0;
|
||||
for (int i = sLen; i > 1 && IA[sArr[--i] & 0xff] <= 0;) {
|
||||
if (sArr[i] == '=') {
|
||||
pad++;
|
||||
}
|
||||
}
|
||||
|
||||
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
|
||||
|
||||
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
|
||||
|
||||
for (int s = 0, d = 0; d < len;) {
|
||||
// Assemble three bytes into an int from four "valid" characters.
|
||||
int i = 0;
|
||||
for (int j = 0; j < 4; j++)
|
||||
{// j only increased if a valid char was found.
|
||||
int c = IA[sArr[s++] & 0xff];
|
||||
if (c >= 0) {
|
||||
i |= c << (18 - j * 6);
|
||||
}
|
||||
else {
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the bytes
|
||||
dArr[d++] = (byte) (i >> 16);
|
||||
if (d < len) {
|
||||
dArr[d++] = (byte) (i >> 8);
|
||||
if (d < len) {
|
||||
dArr[d++] = (byte) i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a BASE64 encoded byte array that is known to be resonably well formatted. The method is about twice as
|
||||
* fast as {@link #decode(byte[])}. The preconditions are:<br>
|
||||
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
|
||||
* + Line separator must be "\r\n", as specified in RFC 2045
|
||||
* + The array must not contain illegal characters within the encoded string<br>
|
||||
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
|
||||
*
|
||||
* @param sArr The source array. Length 0 will return an empty array. {@code null} will throw an exception.
|
||||
* @return The decoded array of bytes. May be of length 0.
|
||||
*/
|
||||
public static byte[] decodeFast(byte[] sArr) {
|
||||
// Check special case
|
||||
int sLen = sArr.length;
|
||||
if (sLen == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
int sIx = 0, eIx = sLen - 1;// Start and end index after trimming.
|
||||
|
||||
// Trim illegal chars from start
|
||||
while (sIx < eIx && IA[sArr[sIx] & 0xff] < 0) {
|
||||
sIx++;
|
||||
}
|
||||
|
||||
// Trim illegal chars from end
|
||||
while (eIx > 0 && IA[sArr[eIx] & 0xff] < 0) {
|
||||
eIx--;
|
||||
}
|
||||
|
||||
// get the padding count (=) (0, 1 or 2)
|
||||
int pad = sArr[eIx] == '=' ? (sArr[eIx - 1] == '=' ? 2 : 1) : 0;// Count '=' at end.
|
||||
int cCnt = eIx - sIx + 1;// Content count including possible separators
|
||||
int sepCnt = sLen > 76 ? (sArr[76] == '\r' ? cCnt / 78 : 0) << 1 : 0;
|
||||
|
||||
int len = ((cCnt - sepCnt) * 6 >> 3) - pad;// The number of decoded bytes
|
||||
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
|
||||
|
||||
// Decode all but the last 0 - 2 bytes.
|
||||
int d = 0;
|
||||
for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
|
||||
// Assemble three bytes into an int from four "valid" characters.
|
||||
int i = IA[sArr[sIx++]] << 18 | IA[sArr[sIx++]] << 12 | IA[sArr[sIx++]] << 6 | IA[sArr[sIx++]];
|
||||
|
||||
// Add the bytes
|
||||
dArr[d++] = (byte) (i >> 16);
|
||||
dArr[d++] = (byte) (i >> 8);
|
||||
dArr[d++] = (byte) i;
|
||||
|
||||
// If line separator, jump over it.
|
||||
if (sepCnt > 0 && ++cc == 19) {
|
||||
sIx += 2;
|
||||
cc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (d < len) {
|
||||
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
|
||||
int i = 0;
|
||||
for (int j = 0; sIx <= eIx - pad; j++) {
|
||||
i |= IA[sArr[sIx++]] << (18 - j * 6);
|
||||
}
|
||||
|
||||
for (int r = 16; d < len; r -= 8) {
|
||||
dArr[d++] = (byte) (i >> r);
|
||||
}
|
||||
}
|
||||
|
||||
return dArr;
|
||||
}
|
||||
|
||||
// ****************************************************************************************
|
||||
// * String version
|
||||
// ****************************************************************************************
|
||||
|
||||
/**
|
||||
* Encodes a raw byte array into a BASE64 {@code String} representation i accordance with RFC 2045.
|
||||
*
|
||||
* @param sArr The bytes to convert. If {@code null} or length 0 an empty array will be returned.
|
||||
* @param lineSep Optional "\r\n" after 76 characters, unless end of file.<br>
|
||||
* No line separator will be in breach of RFC 2045 which specifies max 76 per line but will be a
|
||||
* little faster.
|
||||
* @return A BASE64 encoded array. Never {@code null}.
|
||||
*/
|
||||
public static String encodeToString(byte[] sArr, boolean lineSep) {
|
||||
// Reuse char[] since we can't create a String incrementally anyway and StringBuffer/Builder would be slower.
|
||||
return new String(encodeToChar(sArr, lineSep));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a BASE64 encoded {@code String}. All illegal characters will be ignored and can handle both strings with
|
||||
* and without line separators.<br>
|
||||
* <b>Note!</b> It can be up to about 2x the speed to call {@code decode(str.toCharArray())} instead. That
|
||||
* will create a temporary array though. This version will use {@code str.charAt(i)} to iterate the string.
|
||||
*
|
||||
* @param str The source string. {@code null} or length 0 will return an empty array.
|
||||
* @return The decoded array of bytes. May be of length 0. Will be {@code null} if the legal characters
|
||||
* (including '=') isn't divideable by 4. (I.e. definitely corrupted).
|
||||
*/
|
||||
public static byte[] decode(String str) {
|
||||
// Check special case
|
||||
int sLen = str != null ? str.length() : 0;
|
||||
if (sLen == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
// Count illegal characters (including '\r', '\n') to know what size the returned array will be,
|
||||
// so we don't have to reallocate & copy it later.
|
||||
int sepCnt = 0;// Number of separator characters. (Actually illegal characters, but that's a bonus...)
|
||||
for (int i = 0; i < sLen; i++)// If input is "pure" (I.e. no line separators or illegal chars) base64 this loop can be commented out.
|
||||
{
|
||||
if (IA[str.charAt(i)] < 0) {
|
||||
sepCnt++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check so that legal chars (including '=') are evenly divideable by 4 as specified in RFC 2045.
|
||||
if ((sLen - sepCnt) % 4 != 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Count '=' at end
|
||||
int pad = 0;
|
||||
for (int i = sLen; i > 1 && IA[str.charAt(--i)] <= 0;) {
|
||||
if (str.charAt(i) == '=') {
|
||||
pad++;
|
||||
}
|
||||
}
|
||||
|
||||
int len = ((sLen - sepCnt) * 6 >> 3) - pad;
|
||||
|
||||
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
|
||||
|
||||
for (int s = 0, d = 0; d < len;) {
|
||||
// Assemble three bytes into an int from four "valid" characters.
|
||||
int i = 0;
|
||||
for (int j = 0; j < 4; j++)
|
||||
{// j only increased if a valid char was found.
|
||||
int c = IA[str.charAt(s++)];
|
||||
if (c >= 0) {
|
||||
i |= c << (18 - j * 6);
|
||||
}
|
||||
else {
|
||||
j--;
|
||||
}
|
||||
}
|
||||
// Add the bytes
|
||||
dArr[d++] = (byte) (i >> 16);
|
||||
if (d < len) {
|
||||
dArr[d++] = (byte) (i >> 8);
|
||||
if (d < len) {
|
||||
dArr[d++] = (byte) i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return dArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a BASE64 encoded string that is known to be resonably well formatted. The method is about twice as
|
||||
* fast as {@link #decode(String)}. The preconditions are:<br>
|
||||
* + The array must have a line length of 76 chars OR no line separators at all (one line).<br>
|
||||
* + Line separator must be "\r\n", as specified in RFC 2045
|
||||
* + The array must not contain illegal characters within the encoded string<br>
|
||||
* + The array CAN have illegal characters at the beginning and end, those will be dealt with appropriately.<br>
|
||||
*
|
||||
* @param s The source string. Length 0 will return an empty array. {@code null} will throw an exception.
|
||||
* @return The decoded array of bytes. May be of length 0.
|
||||
*/
|
||||
public static byte[] decodeFast(String s) {
|
||||
// Check special case
|
||||
int sLen = s.length();
|
||||
if (sLen == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
int sIx = 0, eIx = sLen - 1;// Start and end index after trimming.
|
||||
|
||||
// Trim illegal chars from start
|
||||
while (sIx < eIx && IA[s.charAt(sIx) & 0xff] < 0) {
|
||||
sIx++;
|
||||
}
|
||||
|
||||
// Trim illegal chars from end
|
||||
while (eIx > 0 && IA[s.charAt(eIx) & 0xff] < 0) {
|
||||
eIx--;
|
||||
}
|
||||
|
||||
// get the padding count (=) (0, 1 or 2)
|
||||
int pad = s.charAt(eIx) == '=' ? (s.charAt(eIx - 1) == '=' ? 2 : 1) : 0;// Count '=' at end.
|
||||
int cCnt = eIx - sIx + 1;// Content count including possible separators
|
||||
int sepCnt = sLen > 76 ? (s.charAt(76) == '\r' ? cCnt / 78 : 0) << 1 : 0;
|
||||
|
||||
int len = ((cCnt - sepCnt) * 6 >> 3) - pad;// The number of decoded bytes
|
||||
byte[] dArr = new byte[len];// Preallocate byte[] of exact length
|
||||
|
||||
// Decode all but the last 0 - 2 bytes.
|
||||
int d = 0;
|
||||
for (int cc = 0, eLen = (len / 3) * 3; d < eLen;) {
|
||||
// Assemble three bytes into an int from four "valid" characters.
|
||||
int i = IA[s.charAt(sIx++)] << 18 | IA[s.charAt(sIx++)] << 12 | IA[s.charAt(sIx++)] << 6 | IA[s.charAt(sIx++)];
|
||||
|
||||
// Add the bytes
|
||||
dArr[d++] = (byte) (i >> 16);
|
||||
dArr[d++] = (byte) (i >> 8);
|
||||
dArr[d++] = (byte) i;
|
||||
|
||||
// If line separator, jump over it.
|
||||
if (sepCnt > 0 && ++cc == 19) {
|
||||
sIx += 2;
|
||||
cc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (d < len) {
|
||||
// Decode last 1-3 bytes (incl '=') into 1-3 bytes
|
||||
int i = 0;
|
||||
for (int j = 0; sIx <= eIx - pad; j++) {
|
||||
i |= IA[s.charAt(sIx++)] << (18 - j * 6);
|
||||
}
|
||||
|
||||
for (int r = 16; d < len; r -= 8) {
|
||||
dArr[d++] = (byte) (i >> r);
|
||||
}
|
||||
}
|
||||
|
||||
return dArr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
|
||||
import java.io.*;
|
||||
|
||||
/**
|
||||
* {@code Decoder} implementation for standard base64 encoding.
|
||||
* <p/>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2045"RFC 2045</a>
|
||||
*
|
||||
* @see Base64Encoder
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java#2 $
|
||||
*/
|
||||
public final class Base64Decoder implements Decoder {
|
||||
/**
|
||||
* This array maps the characters to their 6 bit values
|
||||
*/
|
||||
final static char[] PEM_ARRAY = {
|
||||
//0 1 2 3 4 5 6 7
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 0
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 1
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 2
|
||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', // 3
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // 4
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // 5
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3', // 6
|
||||
'4', '5', '6', '7', '8', '9', '+', '/' // 7
|
||||
};
|
||||
|
||||
final static byte[] PEM_CONVERT_ARRAY;
|
||||
|
||||
private byte[] mDecodeBuffer = new byte[4];
|
||||
private ByteArrayOutputStream mWrapped;
|
||||
private Object mWrappedObject;
|
||||
|
||||
static {
|
||||
PEM_CONVERT_ARRAY = new byte[256];
|
||||
|
||||
for (int i = 0; i < 255; i++) {
|
||||
PEM_CONVERT_ARRAY[i] = -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < PEM_ARRAY.length; i++) {
|
||||
PEM_CONVERT_ARRAY[PEM_ARRAY[i]] = (byte) i;
|
||||
}
|
||||
}
|
||||
|
||||
protected static int readFully(final InputStream pStream, final byte pBytes[], final int pOffset, final int pLength)
|
||||
throws IOException
|
||||
{
|
||||
for (int i = 0; i < pLength; i++) {
|
||||
int read = pStream.read();
|
||||
|
||||
if (read == -1) {
|
||||
return i != 0 ? i : -1;
|
||||
}
|
||||
|
||||
pBytes[i + pOffset] = (byte) read;
|
||||
}
|
||||
|
||||
return pLength;
|
||||
}
|
||||
|
||||
protected boolean decodeAtom(final InputStream pInput, final OutputStream pOutput, final int pLength)
|
||||
throws IOException {
|
||||
|
||||
byte byte0 = -1;
|
||||
byte byte1 = -1;
|
||||
byte byte2 = -1;
|
||||
byte byte3 = -1;
|
||||
|
||||
if (pLength < 2) {
|
||||
throw new IOException("BASE64Decoder: Not enough bytes for an atom.");
|
||||
}
|
||||
|
||||
int read;
|
||||
|
||||
// Skip line feeds
|
||||
do {
|
||||
read = pInput.read();
|
||||
|
||||
if (read == -1) {
|
||||
return false;
|
||||
}
|
||||
} while (read == 10 || read == 13);
|
||||
|
||||
mDecodeBuffer[0] = (byte) read;
|
||||
read = readFully(pInput, mDecodeBuffer, 1, pLength - 1);
|
||||
|
||||
if (read == -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int length = pLength;
|
||||
|
||||
if (length > 3 && mDecodeBuffer[3] == 61) {
|
||||
length = 3;
|
||||
}
|
||||
|
||||
if (length > 2 && mDecodeBuffer[2] == 61) {
|
||||
length = 2;
|
||||
}
|
||||
|
||||
switch (length) {
|
||||
case 4:
|
||||
byte3 = PEM_CONVERT_ARRAY[mDecodeBuffer[3] & 255];
|
||||
// fall through
|
||||
case 3:
|
||||
byte2 = PEM_CONVERT_ARRAY[mDecodeBuffer[2] & 255];
|
||||
// fall through
|
||||
case 2:
|
||||
byte1 = PEM_CONVERT_ARRAY[mDecodeBuffer[1] & 255];
|
||||
byte0 = PEM_CONVERT_ARRAY[mDecodeBuffer[0] & 255];
|
||||
// fall through
|
||||
default:
|
||||
switch (length) {
|
||||
case 2:
|
||||
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||
break;
|
||||
case 3:
|
||||
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||
pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
|
||||
break;
|
||||
case 4:
|
||||
pOutput.write((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
|
||||
pOutput.write((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
|
||||
pOutput.write((byte) (byte2 << 6 & 192 | byte3 & 63));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void decodeBuffer(final InputStream pInput, final ByteArrayOutputStream pOutput, final int pLength) throws IOException {
|
||||
do {
|
||||
int k = 72;
|
||||
int i;
|
||||
|
||||
for (i = 0; i + 4 < k; i += 4) {
|
||||
if(!decodeAtom(pInput, pOutput, 4)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!decodeAtom(pInput, pOutput, k - i)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
while (pOutput.size() + 54 < pLength); // 72 char lines should produce no more than 54 bytes
|
||||
}
|
||||
|
||||
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
|
||||
if (mWrappedObject != pBuffer) {
|
||||
// NOTE: Array not cloned in FastByteArrayOutputStream
|
||||
mWrapped = new FastByteArrayOutputStream(pBuffer);
|
||||
mWrappedObject = pBuffer;
|
||||
}
|
||||
|
||||
mWrapped.reset(); // NOTE: This only resets count to 0
|
||||
decodeBuffer(pStream, mWrapped, pBuffer.length);
|
||||
|
||||
return mWrapped.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* {@code Encoder} implementation for standard base64 encoding.
|
||||
* <p/>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421</a>
|
||||
* @see <a href="http://tools.ietf.org/html/rfc2045"RFC 2045</a>
|
||||
*
|
||||
* @see Base64Decoder
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Base64Encoder.java#2 $
|
||||
*/
|
||||
public class Base64Encoder implements Encoder {
|
||||
|
||||
public void encode(final OutputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength)
|
||||
throws IOException
|
||||
{
|
||||
if (pOffset < 0 || pOffset > pLength || pOffset > pBuffer.length) {
|
||||
throw new IndexOutOfBoundsException("offset outside [0...length]");
|
||||
}
|
||||
else if (pLength > pBuffer.length) {
|
||||
throw new IndexOutOfBoundsException("length > buffer length");
|
||||
}
|
||||
|
||||
// TODO: Implement
|
||||
// NOTE: This is impossible, given the current spec, as we need to either:
|
||||
// - buffer all data in the EncoderStream
|
||||
// - or have flush/end method(s) in the Encoder
|
||||
// to ensure proper end of stream handling
|
||||
|
||||
int length;
|
||||
int offset = pOffset;
|
||||
|
||||
// TODO: Temp impl, will only work for single writes
|
||||
while ((pBuffer.length - offset) > 0) {
|
||||
byte a, b, c;
|
||||
|
||||
if ((pBuffer.length - offset) > 2) {
|
||||
length = 3;
|
||||
}
|
||||
else {
|
||||
length = pBuffer.length - offset;
|
||||
}
|
||||
|
||||
switch (length) {
|
||||
case 1:
|
||||
a = pBuffer[offset];
|
||||
b = 0;
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
pStream.write('=');
|
||||
pStream.write('=');
|
||||
offset++;
|
||||
break;
|
||||
case 2:
|
||||
a = pBuffer[offset];
|
||||
b = pBuffer[offset + 1];
|
||||
c = 0;
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
||||
pStream.write('=');
|
||||
offset += offset + 2; // ???
|
||||
break;
|
||||
default:
|
||||
a = pBuffer[offset];
|
||||
b = pBuffer[offset + 1];
|
||||
c = pBuffer[offset + 2];
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
|
||||
pStream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]);
|
||||
offset = offset + 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.enc;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Thrown by {@code Decoder}s when encoded data can not be decocded.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecodeException.java#2 $
|
||||
*/
|
||||
public class DecodeException extends IOException {
|
||||
|
||||
public DecodeException(final String pMessage) {
|
||||
super(pMessage);
|
||||
}
|
||||
|
||||
public DecodeException(final String pMessage, final Throwable pCause) {
|
||||
super(pMessage);
|
||||
initCause(pCause);
|
||||
}
|
||||
|
||||
public DecodeException(final Throwable pCause) {
|
||||
this(pCause.getMessage(), pCause);
|
||||
}
|
||||
}
|
||||
64
common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java
Executable file
64
common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java
Executable file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Interface for decoders.
|
||||
* A {@code Decoder} may be used with a {@code DecoderStream}, to perform
|
||||
* on-the-fly decoding from an {@code InputStream}.
|
||||
* <p/>
|
||||
* Important note: Decoder implementations are typically not synchronized.
|
||||
* <p/>
|
||||
* @see Encoder
|
||||
* @see DecoderStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Decoder.java#2 $
|
||||
*/
|
||||
public interface Decoder {
|
||||
|
||||
/**
|
||||
* Decodes up to {@code pBuffer.length} bytes from the given inputstream,
|
||||
* into the given buffer.
|
||||
*
|
||||
* @param pStream the inputstream to decode data from
|
||||
* @param pBuffer buffer to store the read data
|
||||
*
|
||||
* @return the total number of bytes read into the buffer, or {@code -1}
|
||||
* if there is no more data because the end of the stream has been reached.
|
||||
*
|
||||
* @throws DecodeException if encoded data is corrupt
|
||||
* @throws IOException if an I/O error occurs
|
||||
* @throws java.io.EOFException if a premature end-of-file is encountered
|
||||
*/
|
||||
int decode(InputStream pStream, byte[] pBuffer) throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.FilterInputStream;
|
||||
|
||||
/**
|
||||
* An {@code InputStream} that provides on-the-fly decoding from an underlying
|
||||
* stream.
|
||||
* <p/>
|
||||
* @see EncoderStream
|
||||
* @see Decoder
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java#2 $
|
||||
*/
|
||||
public final class DecoderStream extends FilterInputStream {
|
||||
|
||||
protected int mBufferPos;
|
||||
protected int mBufferLimit;
|
||||
protected final byte[] mBuffer;
|
||||
protected final Decoder mDecoder;
|
||||
|
||||
/**
|
||||
* Creates a new decoder stream and chains it to the
|
||||
* input stream specified by the {@code pStream} argument.
|
||||
* The stream will use a default decode buffer size.
|
||||
*
|
||||
* @param pStream the underlying input stream.
|
||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
||||
*
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder) {
|
||||
this(pStream, pDecoder, 1024);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new decoder stream and chains it to the
|
||||
* input stream specified by the {@code pStream} argument.
|
||||
*
|
||||
* @param pStream the underlying input stream.
|
||||
* @param pDecoder the decoder that will be used to decode the underlying stream
|
||||
* @param pBufferSize the size of the decode buffer
|
||||
*
|
||||
* @see java.io.FilterInputStream#in
|
||||
*/
|
||||
public DecoderStream(final InputStream pStream, final Decoder pDecoder, final int pBufferSize) {
|
||||
super(pStream);
|
||||
mDecoder = pDecoder;
|
||||
mBuffer = new byte[pBufferSize];
|
||||
mBufferPos = 0;
|
||||
mBufferLimit = 0;
|
||||
}
|
||||
|
||||
public int available() throws IOException {
|
||||
return mBufferLimit - mBufferPos + super.available();
|
||||
}
|
||||
|
||||
public int read() throws IOException {
|
||||
if (mBufferPos == mBufferLimit) {
|
||||
mBufferLimit = fill();
|
||||
}
|
||||
|
||||
if (mBufferLimit < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return mBuffer[mBufferPos++] & 0xff;
|
||||
}
|
||||
|
||||
public int read(final byte pBytes[]) throws IOException {
|
||||
return read(pBytes, 0, pBytes.length);
|
||||
}
|
||||
|
||||
public int read(final byte pBytes[], final int pOffset, final int pLength) throws IOException {
|
||||
if (pBytes == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
else if ((pOffset < 0) || (pOffset > pBytes.length) || (pLength < 0) ||
|
||||
((pOffset + pLength) > pBytes.length) || ((pOffset + pLength) < 0)) {
|
||||
throw new IndexOutOfBoundsException("bytes.length=" + pBytes.length + " offset=" + pOffset + " length=" + pLength);
|
||||
}
|
||||
else if (pLength == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// End of file?
|
||||
if ((mBufferLimit - mBufferPos) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read until we have read pLength bytes, or have reached EOF
|
||||
int count = 0;
|
||||
int off = pOffset;
|
||||
|
||||
while (pLength > count) {
|
||||
int avail = mBufferLimit - mBufferPos;
|
||||
|
||||
if (avail <= 0) {
|
||||
mBufferLimit = fill();
|
||||
|
||||
if (mBufferLimit < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Copy as many bytes as possible
|
||||
int dstLen = Math.min(pLength - count, avail);
|
||||
System.arraycopy(mBuffer, mBufferPos, pBytes, off, dstLen);
|
||||
|
||||
mBufferPos += dstLen;
|
||||
|
||||
// Update offset (rest)
|
||||
off += dstLen;
|
||||
|
||||
// Inrease count
|
||||
count += dstLen;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public long skip(final long pLength) throws IOException {
|
||||
// End of file?
|
||||
if (mBufferLimit - mBufferPos < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip until we have skipped pLength bytes, or have reached EOF
|
||||
long total = 0;
|
||||
|
||||
while (total < pLength) {
|
||||
int avail = mBufferLimit - mBufferPos;
|
||||
|
||||
if (avail == 0) {
|
||||
mBufferLimit = fill();
|
||||
|
||||
if (mBufferLimit < 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: Skipped can never be more than avail, which is
|
||||
// an int, so the cast is safe
|
||||
int skipped = (int) Math.min(pLength - total, avail);
|
||||
|
||||
mBufferPos += skipped; // Just skip these bytes
|
||||
total += skipped;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills the buffer, by decoding data from the underlying input stream.
|
||||
*
|
||||
* @return the number of bytes decoded, or {@code -1} if the end of the
|
||||
* file is reached
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*/
|
||||
protected int fill() throws IOException {
|
||||
int read = mDecoder.decode(in, mBuffer);
|
||||
|
||||
// TODO: Enforce this in test case, leave here to aid debugging
|
||||
if (read > mBuffer.length) {
|
||||
throw new AssertionError(
|
||||
String.format(
|
||||
"Decode beyond buffer (%d): %d (using %s decoder)",
|
||||
mBuffer.length, read, mDecoder.getClass().getName()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
mBufferPos = 0;
|
||||
|
||||
if (read == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Interface for endcoders.
|
||||
* An {@code Encoder} may be used with an {@code EncoderStream}, to perform
|
||||
* on-the-fly enoding to an {@code OutputStream}.
|
||||
* <p/>
|
||||
* Important note: Encoder implementations are typically not synchronized.
|
||||
*
|
||||
* @see Decoder
|
||||
* @see EncoderStream
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/Encoder.java#2 $
|
||||
*/
|
||||
public interface Encoder {
|
||||
|
||||
/**
|
||||
* Encodes up to {@code pBuffer.length} bytes into the given input stream,
|
||||
* from the given buffer.
|
||||
*
|
||||
* @param pStream the outputstream to encode data to
|
||||
* @param pBuffer buffer to read data from
|
||||
* @param pOffset offset into the buffer array
|
||||
* @param pLength length of data in the buffer
|
||||
*
|
||||
* @throws java.io.IOException if an I/O error occurs
|
||||
*/
|
||||
void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength)
|
||||
throws IOException;
|
||||
|
||||
//TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size
|
||||
// void flush()?
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.FilterOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* An {@code OutputStream} that provides on-the-fly encoding to an underlying
|
||||
* stream.
|
||||
* <p/>
|
||||
* @see DecoderStream
|
||||
* @see Encoder
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java#2 $
|
||||
*/
|
||||
public final class EncoderStream extends FilterOutputStream {
|
||||
|
||||
protected final Encoder mEncoder;
|
||||
private final boolean mFlushOnWrite;
|
||||
|
||||
protected int mBufferPos;
|
||||
protected final byte[] mBuffer;
|
||||
|
||||
/**
|
||||
* Creates an output stream filter built on top of the specified
|
||||
* underlying output stream.
|
||||
*
|
||||
* @param pStream the underlying output stream
|
||||
* @param pEncoder the encoder to use
|
||||
*/
|
||||
public EncoderStream(final OutputStream pStream, final Encoder pEncoder) {
|
||||
this(pStream, pEncoder, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an output stream filter built on top of the specified
|
||||
* underlying output stream.
|
||||
*
|
||||
* @param pStream the underlying output stream
|
||||
* @param pEncoder the encoder to use
|
||||
* @param pFlushOnWrite if {@code true}, calls to the byte-array
|
||||
* {@code write} methods will automatically flush the buffer.
|
||||
*/
|
||||
public EncoderStream(final OutputStream pStream, final Encoder pEncoder, final boolean pFlushOnWrite) {
|
||||
super(pStream);
|
||||
|
||||
mEncoder = pEncoder;
|
||||
mFlushOnWrite = pFlushOnWrite;
|
||||
|
||||
mBuffer = new byte[1024];
|
||||
mBufferPos = 0;
|
||||
}
|
||||
|
||||
public void close() throws IOException {
|
||||
flush();
|
||||
super.close();
|
||||
}
|
||||
|
||||
public void flush() throws IOException {
|
||||
encodeBuffer();
|
||||
super.flush();
|
||||
}
|
||||
|
||||
private void encodeBuffer() throws IOException {
|
||||
if (mBufferPos != 0) {
|
||||
// Make sure all remaining data in buffer is written to the stream
|
||||
mEncoder.encode(out, mBuffer, 0, mBufferPos);
|
||||
|
||||
// Reset buffer
|
||||
mBufferPos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public final void write(final byte[] pBytes) throws IOException {
|
||||
write(pBytes, 0, pBytes.length);
|
||||
}
|
||||
|
||||
// TODO: Verify that this works for the general case (it probably won't)...
|
||||
// TODO: We might need a way to explicitly flush the encoder, or specify
|
||||
// that the encoder can't buffer. In that case, the encoder should probably
|
||||
// tell the EncoderStream how large buffer it prefers...
|
||||
public void write(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
|
||||
if (!mFlushOnWrite && mBufferPos + pLength < mBuffer.length) {
|
||||
// Buffer data
|
||||
System.arraycopy(pBytes, pOffset, mBuffer, mBufferPos, pLength);
|
||||
mBufferPos += pLength;
|
||||
}
|
||||
else {
|
||||
// Encode data already in the buffer
|
||||
if (mBufferPos != 0) {
|
||||
encodeBuffer();
|
||||
}
|
||||
|
||||
// Encode rest without buffering
|
||||
mEncoder.encode(out, pBytes, pOffset, pLength);
|
||||
}
|
||||
}
|
||||
|
||||
public void write(final int pByte) throws IOException {
|
||||
if (mBufferPos >= mBuffer.length - 1) {
|
||||
encodeBuffer(); // Resets mBufferPos to 0
|
||||
}
|
||||
|
||||
mBuffer[mBufferPos++] = (byte) pByte;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.EOFException;
|
||||
|
||||
/**
|
||||
* Decoder implementation for 16 bit-chunked Apple PackBits-like run-length
|
||||
* encoding.
|
||||
* <p/>
|
||||
* This version of the decoder decodes chunk of 16 bit, instead of 8 bit.
|
||||
* This format is used in certain PICT files.
|
||||
*
|
||||
* @see PackBitsDecoder
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBits16Decoder.java#2 $
|
||||
*/
|
||||
public final class PackBits16Decoder implements Decoder {
|
||||
// TODO: Refactor this into an option for the PackBitsDecoder?
|
||||
private final boolean mDisableNoop;
|
||||
|
||||
private int mLeftOfRun;
|
||||
private boolean mSplitRun;
|
||||
private boolean mEOF;
|
||||
|
||||
/**
|
||||
* Creates a {@code PackBitsDecoder}.
|
||||
*/
|
||||
public PackBits16Decoder() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code PackBitsDecoder}.
|
||||
* <p/>
|
||||
* As some implementations of PackBits-like encoders treat {@code -128} as length of
|
||||
* a compressed run, instead of a no-op, it's possible to disable no-ops
|
||||
* for compatibility.
|
||||
* Should be used with caution, even though, most known encoders never write
|
||||
* no-ops in the compressed streams.
|
||||
*
|
||||
* @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
|
||||
*/
|
||||
public PackBits16Decoder(final boolean pDisableNoop) {
|
||||
mDisableNoop = pDisableNoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes bytes from the given input stream, to the given buffer.
|
||||
*
|
||||
* @param pStream the stream to decode from
|
||||
* @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled)
|
||||
* bytes long
|
||||
* @return The number of bytes decoded
|
||||
*
|
||||
* @throws java.io.IOException
|
||||
*/
|
||||
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
|
||||
if (mEOF) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
final int max = pBuffer.length;
|
||||
|
||||
while (read < max) {
|
||||
int n;
|
||||
|
||||
if (mSplitRun) {
|
||||
// Continue run
|
||||
n = mLeftOfRun;
|
||||
mSplitRun = false;
|
||||
}
|
||||
else {
|
||||
// Start new run
|
||||
int b = pStream.read();
|
||||
if (b < 0) {
|
||||
mEOF = true;
|
||||
break;
|
||||
}
|
||||
n = (byte) b;
|
||||
}
|
||||
|
||||
// Split run at or before max
|
||||
if (n >= 0 && 2 * (n + 1) + read > max) {
|
||||
mLeftOfRun = n;
|
||||
mSplitRun = true;
|
||||
break;
|
||||
}
|
||||
else if (n < 0 && 2 * (-n + 1) + read > max) {
|
||||
mLeftOfRun = n;
|
||||
mSplitRun = true;
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
if (n >= 0) {
|
||||
// Copy next n + 1 shorts literally
|
||||
int len = 2 * (n + 1);
|
||||
readFully(pStream, pBuffer, read, len);
|
||||
read += len;
|
||||
}
|
||||
// Allow -128 for compatibility, see above
|
||||
else if (mDisableNoop || n != -128) {
|
||||
// Replicate the next short -n + 1 times
|
||||
byte value1 = readByte(pStream);
|
||||
byte value2 = readByte(pStream);
|
||||
|
||||
for (int i = -n + 1; i > 0; i--) {
|
||||
pBuffer[read++] = value1;
|
||||
pBuffer[read++] = value2;
|
||||
}
|
||||
}
|
||||
// else NOOP (-128)
|
||||
}
|
||||
catch (IndexOutOfBoundsException e) {
|
||||
throw new DecodeException("Error in PackBits decompression, data seems corrupt", e);
|
||||
}
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
private static byte readByte(final InputStream pStream) throws IOException {
|
||||
int read = pStream.read();
|
||||
|
||||
if (read < 0) {
|
||||
throw new EOFException("Unexpected end of PackBits stream");
|
||||
}
|
||||
|
||||
return (byte) read;
|
||||
}
|
||||
|
||||
private static void readFully(final InputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
if (pLength < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
|
||||
while (read < pLength) {
|
||||
int count = pStream.read(pBuffer, pOffset + read, pLength - read);
|
||||
|
||||
if (count < 0) {
|
||||
throw new EOFException("Unexpected end of PackBits stream");
|
||||
}
|
||||
|
||||
read += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.EOFException;
|
||||
|
||||
/**
|
||||
* Decoder implementation for Apple PackBits run-length encoding.
|
||||
* <p/>
|
||||
* <small>From Wikipedia, the free encyclopedia</small><br/>
|
||||
* PackBits is a fast, simple compression scheme for run-length encoding of
|
||||
* data.
|
||||
* <p/>
|
||||
* Apple introduced the PackBits format with the release of MacPaint on the
|
||||
* Macintosh computer. This compression scheme is one of the types of
|
||||
* compression that can be used in TIFF-files.
|
||||
* <p/>
|
||||
* A PackBits data stream consists of packets of one byte of header followed by
|
||||
* data. The header is a signed byte; the data can be signed, unsigned, or
|
||||
* packed (such as MacPaint pixels).
|
||||
* <p/>
|
||||
* <table><tr><th>Header byte</th><th>Data</th></tr>
|
||||
* <tr><td>0 to 127</td> <td>1 + <i>n</i> literal bytes of data</td></tr>
|
||||
* <tr><td>0 to -127</td> <td>One byte of data, repeated 1 - <i>n</i> times in
|
||||
* the decompressed output</td></tr>
|
||||
* <tr><td>-128</td> <td>No operation</td></tr></table>
|
||||
* <p/>
|
||||
* Note that interpreting 0 as positive or negative makes no difference in the
|
||||
* output. Runs of two bytes adjacent to non-runs are typically written as
|
||||
* literal data.
|
||||
* <p/>
|
||||
* See <a href="http://developer.apple.com/technotes/tn/tn1023.html">Understanding PackBits</a>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java#1 $
|
||||
*/
|
||||
public final class PackBitsDecoder implements Decoder {
|
||||
private final boolean mDisableNoop;
|
||||
|
||||
private int mLeftOfRun;
|
||||
private boolean mSplitRun;
|
||||
private boolean mEOF;
|
||||
|
||||
/** Creates a {@code PackBitsDecoder}. */
|
||||
public PackBitsDecoder() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@code PackBitsDecoder}, with optional compatibility mode.
|
||||
* <p/>
|
||||
* As some implementations of PackBits-like encoders treat {@code -128} as length of
|
||||
* a compressed run, instead of a no-op, it's possible to disable no-ops
|
||||
* for compatibility.
|
||||
* Should be used with caution, even though, most known encoders never write
|
||||
* no-ops in the compressed streams.
|
||||
*
|
||||
* @param pDisableNoop {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
|
||||
*/
|
||||
public PackBitsDecoder(final boolean pDisableNoop) {
|
||||
mDisableNoop = pDisableNoop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes bytes from the given input stream, to the given buffer.
|
||||
*
|
||||
* @param pStream the stream to decode from
|
||||
* @param pBuffer a byte array, minimum 128 (or 129 if no-op is disabled)
|
||||
* bytes long
|
||||
* @return The number of bytes decoded
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
public int decode(final InputStream pStream, final byte[] pBuffer) throws IOException {
|
||||
if (mEOF) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
final int max = pBuffer.length;
|
||||
|
||||
while (read < max) {
|
||||
int n;
|
||||
|
||||
if (mSplitRun) {
|
||||
// Continue run
|
||||
n = mLeftOfRun;
|
||||
mSplitRun = false;
|
||||
}
|
||||
else {
|
||||
// Start new run
|
||||
int b = pStream.read();
|
||||
if (b < 0) {
|
||||
mEOF = true;
|
||||
break;
|
||||
}
|
||||
n = (byte) b;
|
||||
}
|
||||
|
||||
// Split run at or before max
|
||||
if (n >= 0 && n + 1 + read > max) {
|
||||
mLeftOfRun = n;
|
||||
mSplitRun = true;
|
||||
break;
|
||||
}
|
||||
else if (n < 0 && -n + 1 + read > max) {
|
||||
mLeftOfRun = n;
|
||||
mSplitRun = true;
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
if (n >= 0) {
|
||||
// Copy next n + 1 bytes literally
|
||||
readFully(pStream, pBuffer, read, n + 1);
|
||||
|
||||
read += n + 1;
|
||||
}
|
||||
// Allow -128 for compatibility, see above
|
||||
else if (mDisableNoop || n != -128) {
|
||||
// Replicate the next byte -n + 1 times
|
||||
byte value = readByte(pStream);
|
||||
|
||||
for (int i = -n + 1; i > 0; i--) {
|
||||
pBuffer[read++] = value;
|
||||
}
|
||||
}
|
||||
// else NOOP (-128)
|
||||
}
|
||||
catch (IndexOutOfBoundsException e) {
|
||||
throw new DecodeException("Error in PackBits decompression, data seems corrupt", e);
|
||||
}
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
private static byte readByte(final InputStream pStream) throws IOException {
|
||||
int read = pStream.read();
|
||||
|
||||
if (read < 0) {
|
||||
throw new EOFException("Unexpected end of PackBits stream");
|
||||
}
|
||||
|
||||
return (byte) read;
|
||||
}
|
||||
|
||||
private static void readFully(final InputStream pStream, final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
|
||||
if (pLength < 0) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
int read = 0;
|
||||
|
||||
while (read < pLength) {
|
||||
int count = pStream.read(pBuffer, pOffset + read, pLength - read);
|
||||
|
||||
if (count < 0) {
|
||||
throw new EOFException("Unexpected end of PackBits stream");
|
||||
}
|
||||
|
||||
read += count;
|
||||
}
|
||||
}
|
||||
}
|
||||
124
common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java
Executable file
124
common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java
Executable file
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Encoder implementation for Apple PackBits run-length encoding.
|
||||
* <p/>
|
||||
* From Wikipedia, the free encyclopedia<br/>
|
||||
* PackBits is a fast, simple compression scheme for run-length encoding of
|
||||
* data.
|
||||
* <p/>
|
||||
* Apple introduced the PackBits format with the release of MacPaint on the
|
||||
* Macintosh computer. This compression scheme is one of the types of
|
||||
* compression that can be used in TIFF-files.
|
||||
* <p/>
|
||||
* A PackBits data stream consists of packets of one byte of header followed by
|
||||
* data. The header is a signed byte; the data can be signed, unsigned, or
|
||||
* packed (such as MacPaint pixels).
|
||||
* <p/>
|
||||
* <table><tr><th>Header byte</th><th>Data</th></tr>
|
||||
* <tr><td>0 to 127</td> <td>1 + <i>n</i> literal bytes of data</td></tr>
|
||||
* <tr><td>0 to -127</td> <td>One byte of data, repeated 1 - <i>n</i> times in
|
||||
* the decompressed output</td></tr>
|
||||
* <tr><td>-128</td> <td>No operation</td></tr></table>
|
||||
* <p/>
|
||||
* Note that interpreting 0 as positive or negative makes no difference in the
|
||||
* output. Runs of two bytes adjacent to non-runs are typically written as
|
||||
* literal data.
|
||||
* <p/>
|
||||
* See <a href="http://developer.apple.com/technotes/tn/tn1023.html">Understanding PackBits</a>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java#1 $
|
||||
*/
|
||||
public final class PackBitsEncoder implements Encoder {
|
||||
|
||||
final private byte[] mBuffer = new byte[128];
|
||||
|
||||
/**
|
||||
* Creates a {@code PackBitsEncoder}.
|
||||
*/
|
||||
public PackBitsEncoder() {
|
||||
}
|
||||
|
||||
public void encode(OutputStream pStream, byte[] pBuffer, int pOffset, int pLength) throws IOException {
|
||||
// NOTE: It's best to encode a 2 byte repeat
|
||||
// run as a replicate run except when preceded and followed by a
|
||||
// literal run, in which case it's best to merge the three into one
|
||||
// literal run. Always encode 3 byte repeats as replicate runs.
|
||||
// NOTE: Worst case: output = input + (input + 127) / 128
|
||||
|
||||
int offset = pOffset;
|
||||
final int max = pOffset + pLength - 1;
|
||||
final int maxMinus1 = max - 1;
|
||||
|
||||
while (offset <= max) {
|
||||
// Compressed run
|
||||
int run = 1;
|
||||
byte replicate = pBuffer[offset];
|
||||
while(run < 127 && offset < max && pBuffer[offset] == pBuffer[offset + 1]) {
|
||||
offset++;
|
||||
run++;
|
||||
}
|
||||
|
||||
if (run > 1) {
|
||||
offset++;
|
||||
pStream.write(-(run - 1));
|
||||
pStream.write(replicate);
|
||||
}
|
||||
|
||||
// Literal run
|
||||
run = 0;
|
||||
while ((run < 128 && ((offset < max && pBuffer[offset] != pBuffer[offset + 1])
|
||||
|| (offset < maxMinus1 && pBuffer[offset] != pBuffer[offset + 2])))) {
|
||||
mBuffer[run++] = pBuffer[offset++];
|
||||
}
|
||||
|
||||
// If last byte, include it in literal run, if space
|
||||
if (offset == max && run > 0 && run < 128) {
|
||||
mBuffer[run++] = pBuffer[offset++];
|
||||
}
|
||||
|
||||
if (run > 0) {
|
||||
pStream.write(run - 1);
|
||||
pStream.write(mBuffer, 0, run);
|
||||
}
|
||||
|
||||
// If last byte, and not space, start new literal run
|
||||
if (offset == max && (run <= 0 || run >= 128)) {
|
||||
pStream.write(0);
|
||||
pStream.write(pBuffer[offset++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Implements 4 bit RLE decoding as specifed by in the Windows BMP (aka DIB) file format.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/RLE4Decoder.java#1 $
|
||||
*/
|
||||
// TODO: Move to other package or make public
|
||||
final class RLE4Decoder extends AbstractRLEDecoder {
|
||||
|
||||
public RLE4Decoder(int pWidth, int pHeight) {
|
||||
super((pWidth + 1) / 2, pHeight);
|
||||
}
|
||||
|
||||
protected void decodeRow(final InputStream pInput) throws IOException {
|
||||
int deltaX = 0;
|
||||
int deltaY = 0;
|
||||
|
||||
while (mSrcY >= 0) {
|
||||
int byte1 = pInput.read();
|
||||
int byte2 = checkEOF(pInput.read());
|
||||
|
||||
if (byte1 == 0x00) {
|
||||
switch (byte2) {
|
||||
case 0x00:
|
||||
// End of line
|
||||
// NOTE: Some BMPs have double EOLs..
|
||||
if (mSrcX != 0) {
|
||||
mSrcX = mRow.length;
|
||||
}
|
||||
break;
|
||||
case 0x01:
|
||||
// End of bitmap
|
||||
mSrcX = mRow.length;
|
||||
mSrcY = 0;
|
||||
break;
|
||||
case 0x02:
|
||||
// Delta
|
||||
deltaX = mSrcX + pInput.read();
|
||||
deltaY = mSrcY - checkEOF(pInput.read());
|
||||
mSrcX = mRow.length;
|
||||
break;
|
||||
default:
|
||||
// Absolute mode
|
||||
// Copy the next byte2 (3..255) bytes from file to output
|
||||
// Two samples are packed into one byte
|
||||
// If the number of bytes used to pack is not a mulitple of 2,
|
||||
// an additional padding byte is in the stream and must be skipped
|
||||
boolean paddingByte = (((byte2 + 1) / 2) % 2) != 0;
|
||||
while (byte2 > 1) {
|
||||
int packed = checkEOF(pInput.read());
|
||||
mRow[mSrcX++] = (byte) packed;
|
||||
byte2 -= 2;
|
||||
}
|
||||
if (byte2 == 1) {
|
||||
// TODO: Half byte alignment? Seems to be ok...
|
||||
int packed = checkEOF(pInput.read());
|
||||
mRow[mSrcX++] = (byte) (packed & 0xf0);
|
||||
}
|
||||
if (paddingByte) {
|
||||
checkEOF(pInput.read());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Encoded mode
|
||||
// Replicate the two samples in byte2 as many times as byte1 says
|
||||
while (byte1 > 1) {
|
||||
mRow[mSrcX++] = (byte) byte2;
|
||||
byte1 -= 2;
|
||||
}
|
||||
|
||||
if (byte1 == 1) {
|
||||
// TODO: Half byte alignment? Seems to be ok...
|
||||
mRow[mSrcX++] = (byte) (byte2 & 0xf0);
|
||||
}
|
||||
}
|
||||
|
||||
// If we're done with a complete row, copy the data
|
||||
if (mSrcX == mRow.length) {
|
||||
// Move to new position, either absolute (delta) or next line
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
mSrcX = (deltaX + 1) / 2;
|
||||
|
||||
if (deltaY > mSrcY) {
|
||||
mSrcY = deltaY;
|
||||
break;
|
||||
}
|
||||
|
||||
deltaX = 0;
|
||||
deltaY = 0;
|
||||
}
|
||||
else {
|
||||
mSrcX = 0;
|
||||
mSrcY--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.enc;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Implements 8 bit RLE decoding as specifed by in the Windows BMP (aka DIB) file format.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/RLE8Decoder.java#1 $
|
||||
*/
|
||||
// TODO: Move to other package or make public
|
||||
final class RLE8Decoder extends AbstractRLEDecoder {
|
||||
|
||||
public RLE8Decoder(int pWidth, int pHeight) {
|
||||
super(pWidth, pHeight);
|
||||
}
|
||||
|
||||
protected void decodeRow(final InputStream pInput) throws IOException {
|
||||
int deltaX = 0;
|
||||
int deltaY = 0;
|
||||
|
||||
while (mSrcY >= 0) {
|
||||
int byte1 = pInput.read();
|
||||
int byte2 = checkEOF(pInput.read());
|
||||
|
||||
if (byte1 == 0x00) {
|
||||
switch (byte2) {
|
||||
case 0x00:
|
||||
// End of line
|
||||
// NOTE: Some BMPs have double EOLs..
|
||||
if (mSrcX != 0) {
|
||||
mSrcX = mRow.length;
|
||||
}
|
||||
break;
|
||||
case 0x01:
|
||||
// End of bitmap
|
||||
mSrcX = mRow.length;
|
||||
mSrcY = 0;
|
||||
break;
|
||||
case 0x02:
|
||||
// Delta
|
||||
deltaX = mSrcX + pInput.read();
|
||||
deltaY = mSrcY - checkEOF(pInput.read());
|
||||
mSrcX = mRow.length;
|
||||
break;
|
||||
default:
|
||||
// Absolute mode
|
||||
// Copy the next byte2 (3..255) bytes from file to output
|
||||
boolean paddingByte = (byte2 % 2) != 0;
|
||||
while (byte2-- > 0) {
|
||||
mRow[mSrcX++] = (byte) checkEOF(pInput.read());
|
||||
}
|
||||
if (paddingByte) {
|
||||
checkEOF(pInput.read());
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Encoded mode
|
||||
// Replicate byte2 as many times as byte1 says
|
||||
byte value = (byte) byte2;
|
||||
while (byte1-- > 0) {
|
||||
mRow[mSrcX++] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// If we're done with a complete row, copy the data
|
||||
if (mSrcX == mRow.length) {
|
||||
|
||||
// Move to new position, either absolute (delta) or next line
|
||||
if (deltaX != 0 || deltaY != 0) {
|
||||
mSrcX = deltaX;
|
||||
if (deltaY != mSrcY) {
|
||||
mSrcY = deltaY;
|
||||
break;
|
||||
}
|
||||
deltaX = 0;
|
||||
deltaY = 0;
|
||||
}
|
||||
else {
|
||||
mSrcX = 0;
|
||||
mSrcY--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Contains customized stream classes, that can read or write compressed data on the fly,
|
||||
* along with encoders and decoders for popular stream formats, such as Base64, ZIP (deflate), LZW, PackBits etc..
|
||||
*
|
||||
* @see com.twelvemonkeys.io.enc.DecoderStream
|
||||
* @see com.twelvemonkeys.io.enc.EncoderStream
|
||||
* @see com.twelvemonkeys.io.enc.Decoder
|
||||
* @see com.twelvemonkeys.io.enc.Encoder
|
||||
* @see com.twelvemonkeys.io.enc.DecodeException
|
||||
*
|
||||
* @version 2.0
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
*/
|
||||
package com.twelvemonkeys.io.enc;
|
||||
761
common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java
Executable file
761
common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java
Executable file
@@ -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);
|
||||
}
|
||||
}
|
||||
340
common/common-io/src/main/java/com/twelvemonkeys/io/ole2/Entry.java
Executable file
340
common/common-io/src/main/java/com/twelvemonkeys/io/ole2/Entry.java
Executable file
@@ -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);
|
||||
}
|
||||
}
|
||||
104
common/common-io/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java
Executable file
104
common/common-io/src/main/java/com/twelvemonkeys/io/ole2/SIdChain.java
Executable file
@@ -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();
|
||||
}
|
||||
}
|
||||
11
common/common-io/src/main/java/com/twelvemonkeys/io/ole2/package-info.java
Executable file
11
common/common-io/src/main/java/com/twelvemonkeys/io/ole2/package-info.java
Executable file
@@ -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;
|
||||
4
common/common-io/src/main/java/com/twelvemonkeys/io/package_info.java
Executable file
4
common/common-io/src/main/java/com/twelvemonkeys/io/package_info.java
Executable file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides for system input and output through data streams, serialization and the file system.
|
||||
*/
|
||||
package com.twelvemonkeys.io;
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.net;
|
||||
|
||||
import java.net.*;
|
||||
|
||||
/**
|
||||
* Interface for filtering Authenticator requests, used by the
|
||||
* SimpleAuthenticator.
|
||||
*
|
||||
* @see SimpleAuthenticator
|
||||
* @see java.net.Authenticator
|
||||
*
|
||||
* @author Harald Kuhr (haraldk@iconmedialab.no),
|
||||
* @version 1.0
|
||||
*/
|
||||
public interface AuthenticatorFilter {
|
||||
public boolean accept(InetAddress pAddress, int pPort, String pProtocol,
|
||||
String pPrompt, String pScheme);
|
||||
|
||||
}
|
||||
1103
common/common-io/src/main/java/com/twelvemonkeys/net/HttpURLConnection.java
Executable file
1103
common/common-io/src/main/java/com/twelvemonkeys/net/HttpURLConnection.java
Executable file
File diff suppressed because it is too large
Load Diff
312
common/common-io/src/main/java/com/twelvemonkeys/net/MIMEUtil.java
Executable file
312
common/common-io/src/main/java/com/twelvemonkeys/net/MIMEUtil.java
Executable file
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* 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.net;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import com.twelvemonkeys.lang.SystemUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Contains mappings from file extension to mime-types and from mime-type to file-types.
|
||||
* <p/>
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/net/MIMEUtil.java#5 $
|
||||
*
|
||||
* @see <A href="http://www.iana.org/assignments/media-types/">MIME Media Types</A>
|
||||
*/
|
||||
public final class MIMEUtil {
|
||||
// TODO: Piggy-back on the mappings form the JRE? (1.6 comes with javax.activation)
|
||||
// TODO: Piggy-back on mappings from javax.activation?
|
||||
// See: http://java.sun.com/j2ee/sdk_1.3/techdocs/api/javax/activation/MimetypesFileTypeMap.html
|
||||
// See: http://java.sun.com/javase/6/docs/api/javax/activation/MimetypesFileTypeMap.html
|
||||
// TODO: Use the format (and lookup) specified by the above URLs
|
||||
// TODO: Allow 3rd party to add mappings? Will need application context support to do it safe.. :-P
|
||||
|
||||
private static Map<String, List<String>> sExtToMIME = new HashMap<String, List<String>>();
|
||||
private static Map<String, List<String>> sUnmodifiableExtToMIME = Collections.unmodifiableMap(sExtToMIME);
|
||||
|
||||
private static Map<String, List<String>> sMIMEToExt = new HashMap<String, List<String>>();
|
||||
private static Map<String, List<String>> sUnmodifiableMIMEToExt = Collections.unmodifiableMap(sMIMEToExt);
|
||||
|
||||
static {
|
||||
// Load mapping for MIMEUtil
|
||||
try {
|
||||
Properties mappings = SystemUtil.loadProperties(MIMEUtil.class);
|
||||
|
||||
for (Map.Entry entry : mappings.entrySet()) {
|
||||
// Convert and break up extensions and mimeTypes
|
||||
String extStr = StringUtil.toLowerCase((String) entry.getKey());
|
||||
List<String> extensions =
|
||||
Collections.unmodifiableList(Arrays.asList(StringUtil.toStringArray(extStr, ";, ")));
|
||||
|
||||
String typeStr = StringUtil.toLowerCase((String) entry.getValue());
|
||||
List<String> mimeTypes =
|
||||
Collections.unmodifiableList(Arrays.asList(StringUtil.toStringArray(typeStr, ";, ")));
|
||||
|
||||
// TODO: Handle duplicates in MIME to extension mapping, like
|
||||
// xhtml=application/xhtml+xml;application/xml
|
||||
// xml=text/xml;application/xml
|
||||
|
||||
// Populate normal and reverse MIME-mappings
|
||||
for (String extension : extensions) {
|
||||
sExtToMIME.put(extension, mimeTypes);
|
||||
}
|
||||
|
||||
for (String mimeType : mimeTypes) {
|
||||
sMIMEToExt.put(mimeType, extensions);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
System.err.println("Could not read properties for MIMEUtil: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// Disallow construction
|
||||
private MIMEUtil() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default MIME type for the given file extension.
|
||||
*
|
||||
* @param pFileExt the file extension
|
||||
*
|
||||
* @return a {@code String} containing the MIME type, or {@code null} if
|
||||
* there are no known MIME types for the given file extension.
|
||||
*/
|
||||
public static String getMIMEType(final String pFileExt) {
|
||||
List<String> types = sExtToMIME.get(StringUtil.toLowerCase(pFileExt));
|
||||
return (types == null || types.isEmpty()) ? null : types.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all MIME types for the given file extension.
|
||||
*
|
||||
* @param pFileExt the file extension
|
||||
*
|
||||
* @return a {@link List} of {@code String}s containing the MIME types, or an empty
|
||||
* list, if there are no known MIME types for the given file extension.
|
||||
*/
|
||||
public static List<String> getMIMETypes(final String pFileExt) {
|
||||
List<String> types = sExtToMIME.get(StringUtil.toLowerCase(pFileExt));
|
||||
return maskNull(types);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiabale {@link Map} view of the extension to
|
||||
* MIME mapping, to use as the default mapping in client applications.
|
||||
*
|
||||
* @return an unmodifiabale {@code Map} view of the extension to
|
||||
* MIME mapping.
|
||||
*/
|
||||
public static Map<String, List<String>> getMIMETypeMappings() {
|
||||
return sUnmodifiableExtToMIME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default file extension for the given MIME type.
|
||||
* Specifying a wildcard type will return {@code null}.
|
||||
*
|
||||
* @param pMIME the MIME type
|
||||
*
|
||||
* @return a {@code String} containing the file extension, or {@code null}
|
||||
* if there are no known file extensions for the given MIME type.
|
||||
*/
|
||||
public static String getExtension(final String pMIME) {
|
||||
String mime = bareMIME(StringUtil.toLowerCase(pMIME));
|
||||
List<String> extensions = sMIMEToExt.get(mime);
|
||||
return (extensions == null || extensions.isEmpty()) ? null : extensions.get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all file extension for the given MIME type.
|
||||
* The default extension will be the first in the list.
|
||||
* Note that no specific order is given for wildcard types (image/*, */* etc).
|
||||
*
|
||||
* @param pMIME the MIME type
|
||||
*
|
||||
* @return a {@link List} of {@code String}s containing the MIME types, or an empty
|
||||
* list, if there are no known file extensions for the given MIME type.
|
||||
*/
|
||||
public static List<String> getExtensions(final String pMIME) {
|
||||
String mime = bareMIME(StringUtil.toLowerCase(pMIME));
|
||||
if (mime.endsWith("/*")) {
|
||||
return getExtensionForWildcard(mime);
|
||||
}
|
||||
List<String> extensions = sMIMEToExt.get(mime);
|
||||
return maskNull(extensions);
|
||||
}
|
||||
|
||||
// Gets all extensions for a wildcard MIME type
|
||||
private static List<String> getExtensionForWildcard(final String pMIME) {
|
||||
final String family = pMIME.substring(0, pMIME.length() - 1);
|
||||
Set<String> extensions = new LinkedHashSet<String>();
|
||||
for (Map.Entry<String, List<String>> mimeToExt : sMIMEToExt.entrySet()) {
|
||||
if ("*/".equals(family) || mimeToExt.getKey().startsWith(family)) {
|
||||
extensions.addAll(mimeToExt.getValue());
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableList(new ArrayList<String>(extensions));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiabale {@link Map} view of the MIME to
|
||||
* extension mapping, to use as the default mapping in client applications.
|
||||
*
|
||||
* @return an unmodifiabale {@code Map} view of the MIME to
|
||||
* extension mapping.
|
||||
*/
|
||||
public static Map<String, List<String>> getExtensionMappings() {
|
||||
return sUnmodifiableMIMEToExt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests wehter the type is a subtype of the type family.
|
||||
*
|
||||
* @param pTypeFamily the MIME type family ({@code image/*, */*}, etc)
|
||||
* @param pType the MIME type
|
||||
* @return {@code true} if {@code pType} is a subtype of {@code pTypeFamily}, otherwise {@code false}
|
||||
*/
|
||||
// TODO: Rename? isSubtype?
|
||||
// TODO: Make public
|
||||
static boolean includes(final String pTypeFamily, final String pType) {
|
||||
// TODO: Handle null in a well-defined way
|
||||
// - Is null family same as */*?
|
||||
// - Is null subtype of any family? Subtype of no family?
|
||||
|
||||
String type = bareMIME(pType);
|
||||
return type.equals(pTypeFamily)
|
||||
|| "*/*".equals(pTypeFamily)
|
||||
|| pTypeFamily.endsWith("/*") && pTypeFamily.startsWith(type.substring(0, type.indexOf('/')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes any charset or extra info from the mime-type string (anything after a semicolon, {@code ;}, inclusive).
|
||||
*
|
||||
* @param pMIME the mime-type string
|
||||
* @return the bare mime-type
|
||||
*/
|
||||
public static String bareMIME(final String pMIME) {
|
||||
int idx;
|
||||
if (pMIME != null && (idx = pMIME.indexOf(';')) >= 0) {
|
||||
return pMIME.substring(0, idx);
|
||||
}
|
||||
return pMIME;
|
||||
}
|
||||
|
||||
// Returns the list or empty list if list is null
|
||||
private static List<String> maskNull(List<String> pTypes) {
|
||||
return (pTypes == null) ? Collections.<String>emptyList() : pTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* For debugging. Prints all known MIME types and file extensions.
|
||||
*
|
||||
* @param pArgs command line arguments
|
||||
*/
|
||||
public static void main(String[] pArgs) {
|
||||
if (pArgs.length > 1) {
|
||||
String type = pArgs[0];
|
||||
String family = pArgs[1];
|
||||
boolean incuded = includes(family, type);
|
||||
System.out.println(
|
||||
"Mime type family " + family
|
||||
+ (incuded ? " includes " : " does not include ")
|
||||
+ "type " + type
|
||||
);
|
||||
}
|
||||
if (pArgs.length > 0) {
|
||||
String str = pArgs[0];
|
||||
|
||||
if (str.indexOf('/') >= 0) {
|
||||
// MIME
|
||||
String extension = getExtension(str);
|
||||
System.out.println("Default extension for MIME type '" + str + "' is "
|
||||
+ (extension != null ? ": '" + extension + "'" : "unknown") + ".");
|
||||
System.out.println("All possible: " + getExtensions(str));
|
||||
}
|
||||
else {
|
||||
// EXT
|
||||
String mimeType = getMIMEType(str);
|
||||
System.out.println("Default MIME type for extension '" + str + "' is "
|
||||
+ (mimeType != null ? ": '" + mimeType + "'" : "unknown") + ".");
|
||||
System.out.println("All possible: " + getMIMETypes(str));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Set set = sMIMEToExt.keySet();
|
||||
String[] mimeTypes = new String[set.size()];
|
||||
int i = 0;
|
||||
for (Iterator iterator = set.iterator(); iterator.hasNext(); i++) {
|
||||
String mime = (String) iterator.next();
|
||||
mimeTypes[i] = mime;
|
||||
}
|
||||
Arrays.sort(mimeTypes);
|
||||
|
||||
System.out.println("Known MIME types (" + mimeTypes.length + "):");
|
||||
for (int j = 0; j < mimeTypes.length; j++) {
|
||||
String mimeType = mimeTypes[j];
|
||||
|
||||
if (j != 0) {
|
||||
System.out.print(", ");
|
||||
}
|
||||
|
||||
System.out.print(mimeType);
|
||||
}
|
||||
|
||||
System.out.println("\n");
|
||||
|
||||
set = sExtToMIME.keySet();
|
||||
String[] extensions = new String[set.size()];
|
||||
i = 0;
|
||||
for (Iterator iterator = set.iterator(); iterator.hasNext(); i++) {
|
||||
String ext = (String) iterator.next();
|
||||
extensions[i] = ext;
|
||||
}
|
||||
Arrays.sort(extensions);
|
||||
|
||||
System.out.println("Known file types (" + extensions.length + "):");
|
||||
for (int j = 0; j < extensions.length; j++) {
|
||||
String extension = extensions[j];
|
||||
|
||||
if (j != 0) {
|
||||
System.out.print(", ");
|
||||
}
|
||||
|
||||
System.out.print(extension);
|
||||
}
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
1423
common/common-io/src/main/java/com/twelvemonkeys/net/NetUtil.java
Executable file
1423
common/common-io/src/main/java/com/twelvemonkeys/net/NetUtil.java
Executable file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.net;
|
||||
|
||||
import java.net.*;
|
||||
|
||||
/**
|
||||
* Interface fro PasswordAuthenticators used by SimpleAuthenticator.
|
||||
*
|
||||
* @see SimpleAuthenticator
|
||||
* @see java.net.Authenticator
|
||||
*
|
||||
* @author Harald Kuhr (haraldk@iconmedialab.no)
|
||||
*
|
||||
* @version 1.0
|
||||
*/
|
||||
public interface PasswordAuthenticator {
|
||||
public PasswordAuthentication requestPasswordAuthentication(InetAddress addr, int port, String protocol, String prompt, String scheme);
|
||||
}
|
||||
326
common/common-io/src/main/java/com/twelvemonkeys/net/SimpleAuthenticator.java
Executable file
326
common/common-io/src/main/java/com/twelvemonkeys/net/SimpleAuthenticator.java
Executable file
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* 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.net;
|
||||
|
||||
import java.net.Authenticator;
|
||||
import java.net.InetAddress;
|
||||
import java.net.PasswordAuthentication;
|
||||
import java.net.URL;
|
||||
import java.util.Hashtable;
|
||||
|
||||
/**
|
||||
* A simple Authenticator implementation.
|
||||
* Singleton class, obtain reference through the static
|
||||
* {@code getInstance} method.
|
||||
* <P>
|
||||
* <EM>After swearing, sweating, pulling my hair, banging my head repeatedly
|
||||
* into the walls and reading the java.net.Authenticator API documentation
|
||||
* once more, an idea came to my mind. This is the result. I hope you find it
|
||||
* useful. -- Harald K.</EM>
|
||||
*
|
||||
* @see java.net.Authenticator
|
||||
*
|
||||
* @author Harald Kuhr (haraldk@iconmedialab.no)
|
||||
* @version 1.0
|
||||
*/
|
||||
public class SimpleAuthenticator extends Authenticator {
|
||||
|
||||
/** The reference to the single instance of this class. */
|
||||
private static SimpleAuthenticator sInstance = null;
|
||||
/** Keeps track of the state of this class. */
|
||||
private static boolean sInitialized = false;
|
||||
|
||||
// These are used for the identification hack.
|
||||
private final static String MAGIC = "magic";
|
||||
private final static int FOURTYTWO = 42;
|
||||
|
||||
/** Basic authentication scheme. */
|
||||
public final static String BASIC = "Basic";
|
||||
|
||||
/**
|
||||
* The hastable that keeps track of the PasswordAuthentications.
|
||||
*/
|
||||
|
||||
protected Hashtable mPasswordAuthentications = null;
|
||||
|
||||
/**
|
||||
* The hastable that keeps track of the Authenticators.
|
||||
*/
|
||||
|
||||
protected Hashtable mAuthenticators = null;
|
||||
|
||||
/**
|
||||
* Creates a SimpleAuthenticator.
|
||||
*/
|
||||
|
||||
private SimpleAuthenticator() {
|
||||
mPasswordAuthentications = new Hashtable();
|
||||
mAuthenticators = new Hashtable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the SimpleAuthenticator instance and registers it through the
|
||||
* Authenticator.setDefault(). If there is no current instance
|
||||
* of the SimpleAuthenticator in the VM, one is created. This method will
|
||||
* try to figure out if the setDefault() succeeded (a hack), and will
|
||||
* return null if it was not able to register the instance as default.
|
||||
*
|
||||
* @return The single instance of this class, or null, if another
|
||||
* Authenticator is allready registered as default.
|
||||
*/
|
||||
|
||||
public static synchronized SimpleAuthenticator getInstance() {
|
||||
if (!sInitialized) {
|
||||
// Create an instance
|
||||
sInstance = new SimpleAuthenticator();
|
||||
|
||||
// Try to set default (this may quietly fail...)
|
||||
Authenticator.setDefault(sInstance);
|
||||
|
||||
// A hack to figure out if we really did set the authenticator
|
||||
PasswordAuthentication pa =
|
||||
Authenticator.requestPasswordAuthentication(null, FOURTYTWO,
|
||||
null, null, MAGIC);
|
||||
|
||||
// If this test returns false, we didn't succeed, so we set the
|
||||
// instance back to null.
|
||||
if (pa == null || !MAGIC.equals(pa.getUserName()) ||
|
||||
!("" + FOURTYTWO).equals(new String(pa.getPassword())))
|
||||
sInstance = null;
|
||||
|
||||
// Done
|
||||
sInitialized = true;
|
||||
}
|
||||
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the PasswordAuthentication for the request. Called when password
|
||||
* authorization is needed.
|
||||
*
|
||||
* @return The PasswordAuthentication collected from the user, or null if
|
||||
* none is provided.
|
||||
*/
|
||||
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
// Don't worry, this is just a hack to figure out if we were able
|
||||
// to set this Authenticator through the setDefault method.
|
||||
if (!sInitialized && MAGIC.equals(getRequestingScheme())
|
||||
&& getRequestingPort() == FOURTYTWO)
|
||||
return new PasswordAuthentication(MAGIC, ("" + FOURTYTWO)
|
||||
.toCharArray());
|
||||
/*
|
||||
System.err.println("getPasswordAuthentication");
|
||||
System.err.println(getRequestingSite());
|
||||
System.err.println(getRequestingPort());
|
||||
System.err.println(getRequestingProtocol());
|
||||
System.err.println(getRequestingPrompt());
|
||||
System.err.println(getRequestingScheme());
|
||||
*/
|
||||
|
||||
// TODO:
|
||||
// Look for a more specific PasswordAuthenticatior before using
|
||||
// Default:
|
||||
//
|
||||
// if (...)
|
||||
// return pa.requestPasswordAuthentication(getRequestingSite(),
|
||||
// getRequestingPort(),
|
||||
// getRequestingProtocol(),
|
||||
// getRequestingPrompt(),
|
||||
// getRequestingScheme());
|
||||
|
||||
return (PasswordAuthentication)
|
||||
mPasswordAuthentications.get(new AuthKey(getRequestingSite(),
|
||||
getRequestingPort(),
|
||||
getRequestingProtocol(),
|
||||
getRequestingPrompt(),
|
||||
getRequestingScheme()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a PasswordAuthentication with a given URL address.
|
||||
*
|
||||
*/
|
||||
|
||||
public PasswordAuthentication registerPasswordAuthentication(URL pURL,
|
||||
PasswordAuthentication pPA) {
|
||||
return registerPasswordAuthentication(NetUtil.createInetAddressFromURL(pURL),
|
||||
pURL.getPort(),
|
||||
pURL.getProtocol(),
|
||||
null, // Prompt/Realm
|
||||
BASIC,
|
||||
pPA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a PasswordAuthentication with a given net address.
|
||||
*
|
||||
*/
|
||||
|
||||
public PasswordAuthentication registerPasswordAuthentication(
|
||||
InetAddress pAddress, int pPort, String pProtocol,
|
||||
String pPrompt, String pScheme, PasswordAuthentication pPA)
|
||||
{
|
||||
/*
|
||||
System.err.println("registerPasswordAuthentication");
|
||||
System.err.println(pAddress);
|
||||
System.err.println(pPort);
|
||||
System.err.println(pProtocol);
|
||||
System.err.println(pPrompt);
|
||||
System.err.println(pScheme);
|
||||
*/
|
||||
|
||||
return (PasswordAuthentication)
|
||||
mPasswordAuthentications.put(new AuthKey(pAddress, pPort,
|
||||
pProtocol, pPrompt,
|
||||
pScheme),
|
||||
pPA);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a PasswordAuthentication with a given URL address.
|
||||
*
|
||||
*/
|
||||
|
||||
public PasswordAuthentication unregisterPasswordAuthentication(URL pURL) {
|
||||
return unregisterPasswordAuthentication(NetUtil.createInetAddressFromURL(pURL),
|
||||
pURL.getPort(),
|
||||
pURL.getProtocol(),
|
||||
null,
|
||||
BASIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a PasswordAuthentication with a given net address.
|
||||
*
|
||||
*/
|
||||
|
||||
public PasswordAuthentication unregisterPasswordAuthentication(
|
||||
InetAddress pAddress, int pPort, String pProtocol,
|
||||
String pPrompt, String pScheme)
|
||||
{
|
||||
return (PasswordAuthentication)
|
||||
mPasswordAuthentications.remove(new AuthKey(pAddress, pPort,
|
||||
pProtocol, pPrompt,
|
||||
pScheme));
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Registers a PasswordAuthenticator that can answer authentication
|
||||
* requests.
|
||||
*
|
||||
* @see PasswordAuthenticator
|
||||
*/
|
||||
|
||||
public void registerPasswordAuthenticator(PasswordAuthenticator pPA,
|
||||
AuthenticatorFilter pFilter) {
|
||||
mAuthenticators.put(pPA, pFilter);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Unregisters a PasswordAuthenticator that can answer authentication
|
||||
* requests.
|
||||
*
|
||||
* @see PasswordAuthenticator
|
||||
*/
|
||||
|
||||
public void unregisterPasswordAuthenticator(PasswordAuthenticator pPA) {
|
||||
mAuthenticators.remove(pPA);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class, used for caching the PasswordAuthentication objects.
|
||||
* Everything but address may be null
|
||||
*/
|
||||
|
||||
class AuthKey {
|
||||
|
||||
InetAddress mAddress = null;
|
||||
int mPort = -1;
|
||||
String mProtocol = null;
|
||||
String mPrompt = null;
|
||||
String mScheme = null;
|
||||
|
||||
AuthKey(InetAddress pAddress, int pPort, String pProtocol,
|
||||
String pPrompt, String pScheme) {
|
||||
if (pAddress == null)
|
||||
throw new IllegalArgumentException("Address argument can't be null!");
|
||||
|
||||
mAddress = pAddress;
|
||||
mPort = pPort;
|
||||
mProtocol = pProtocol;
|
||||
mPrompt = pPrompt;
|
||||
mScheme = pScheme;
|
||||
|
||||
// System.out.println("Created: " + this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a string representation of this object.
|
||||
*/
|
||||
|
||||
public String toString() {
|
||||
return "AuthKey[" + mAddress + ":" + mPort + "/" + mProtocol + " \"" + mPrompt + "\" (" + mScheme + ")]";
|
||||
}
|
||||
|
||||
public boolean equals(Object pObj) {
|
||||
return (pObj instanceof AuthKey ? equals((AuthKey) pObj) : false);
|
||||
}
|
||||
|
||||
// Ahem.. Breaks the rule from Object.equals(Object):
|
||||
// It is transitive: for any reference values x, y, and z, if x.equals(y)
|
||||
// returns true and y.equals(z) returns true, then x.equals(z)
|
||||
// should return true.
|
||||
|
||||
public boolean equals(AuthKey pKey) {
|
||||
// Maybe allow nulls, and still be equal?
|
||||
return (mAddress.equals(pKey.mAddress)
|
||||
&& (mPort == -1
|
||||
|| pKey.mPort == -1
|
||||
|| mPort == pKey.mPort)
|
||||
&& (mProtocol == null
|
||||
|| pKey.mProtocol == null
|
||||
|| mProtocol.equals(pKey.mProtocol))
|
||||
&& (mPrompt == null
|
||||
|| pKey.mPrompt == null
|
||||
|| mPrompt.equals(pKey.mPrompt))
|
||||
&& (mScheme == null
|
||||
|| pKey.mScheme == null
|
||||
|| mScheme.equalsIgnoreCase(pKey.mScheme)));
|
||||
}
|
||||
|
||||
public int hashCode() {
|
||||
// There won't be too many pr address, will it? ;-)
|
||||
return mAddress.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
4
common/common-io/src/main/java/com/twelvemonkeys/net/package_info.java
Executable file
4
common/common-io/src/main/java/com/twelvemonkeys/net/package_info.java
Executable file
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides classes for net access.
|
||||
*/
|
||||
package com.twelvemonkeys.net;
|
||||
182
common/common-io/src/main/java/com/twelvemonkeys/xml/DOMSerializer.java
Executable file
182
common/common-io/src/main/java/com/twelvemonkeys/xml/DOMSerializer.java
Executable file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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.xml;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.DOMConfiguration;
|
||||
import org.w3c.dom.DOMImplementationList;
|
||||
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
|
||||
import org.w3c.dom.ls.DOMImplementationLS;
|
||||
import org.w3c.dom.ls.LSOutput;
|
||||
import org.w3c.dom.ls.LSSerializer;
|
||||
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
/**
|
||||
* {@code DOMImplementationLS} backed implementation.
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">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/xml/DOMSerializer.java#2 $
|
||||
*/
|
||||
public final class DOMSerializer {
|
||||
|
||||
private static final String PARAM_PRETTY_PRINT = "format-pretty-print";
|
||||
private static final String PARAM_XML_DECLARATION = "xml-declaration";
|
||||
|
||||
private final LSSerializer mSerializer;
|
||||
private final LSOutput mOutput;
|
||||
|
||||
private DOMSerializer() {
|
||||
DOMImplementationLS domImpl = Support.getImplementation();
|
||||
mSerializer = domImpl.createLSSerializer();
|
||||
mOutput = domImpl.createLSOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a serializer using the given byte stream and encoding.
|
||||
*
|
||||
* @param pStream the byte stream.
|
||||
* @param pEncoding the encoding.
|
||||
* @throws IllegalStateException if no {@code DOMImplementation} with the right features can be instantiated.
|
||||
*/
|
||||
public DOMSerializer(final OutputStream pStream, final String pEncoding) {
|
||||
this();
|
||||
|
||||
mOutput.setByteStream(pStream);
|
||||
mOutput.setEncoding(pEncoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a serializer using the given character stream and encoding.
|
||||
*
|
||||
* @param pStream the characted stream.
|
||||
* @throws IllegalStateException if no {@code DOMImplementation} with the right features can be instantiated.
|
||||
*/
|
||||
public DOMSerializer(final Writer pStream) {
|
||||
this();
|
||||
|
||||
mOutput.setCharacterStream(pStream);
|
||||
}
|
||||
|
||||
/*
|
||||
// TODO: Is it useful?
|
||||
public void setNewLine(final String pNewLine) {
|
||||
mSerializer.setNewLine(pNewLine);
|
||||
}
|
||||
|
||||
public String getNewLine() {
|
||||
return mSerializer.getNewLine();
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Specifies wether the serializer should use indentation and optimize for
|
||||
* readability.
|
||||
* <p/>
|
||||
* Note: This is a hint, and may be ignored by DOM implemenations.
|
||||
*
|
||||
* @param pPrettyPrint {@code true} to enable pretty printing
|
||||
*/
|
||||
public void setPrettyPrint(final boolean pPrettyPrint) {
|
||||
DOMConfiguration configuration = mSerializer.getDomConfig();
|
||||
if (configuration.canSetParameter(PARAM_PRETTY_PRINT, pPrettyPrint)) {
|
||||
configuration.setParameter(PARAM_PRETTY_PRINT, pPrettyPrint);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getPrettyPrint() {
|
||||
return Boolean.TRUE.equals(mSerializer.getDomConfig().getParameter(PARAM_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
private void setXMLDeclaration(boolean pXMLDeclaration) {
|
||||
mSerializer.getDomConfig().setParameter(PARAM_XML_DECLARATION, pXMLDeclaration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the entire document.
|
||||
*
|
||||
* @param pDocument the document.
|
||||
*/
|
||||
public void serialize(final Document pDocument) {
|
||||
serializeImpl(pDocument, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the given node, along with any subnodes.
|
||||
* Will not emit XML declaration.
|
||||
*
|
||||
* @param pNode the top node.
|
||||
*/
|
||||
public void serialize(final Node pNode) {
|
||||
serializeImpl(pNode, false);
|
||||
}
|
||||
|
||||
private void serializeImpl(final Node pNode, final boolean pOmitDecl) {
|
||||
setXMLDeclaration(pOmitDecl);
|
||||
mSerializer.write(pNode, mOutput);
|
||||
}
|
||||
|
||||
private static class Support {
|
||||
private final static DOMImplementationRegistry DOM_REGISTRY = createDOMRegistry();
|
||||
|
||||
static DOMImplementationLS getImplementation() {
|
||||
DOMImplementationLS implementation = (DOMImplementationLS) DOM_REGISTRY.getDOMImplementation("LS 3.0");
|
||||
if (implementation == null) {
|
||||
|
||||
DOMImplementationList list = DOM_REGISTRY.getDOMImplementationList("");
|
||||
System.err.println("DOM implementations (" + list.getLength() + "):");
|
||||
for (int i = 0; i < list.getLength(); i++) {
|
||||
System.err.println(" " + list.item(i));
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Could not create DOM Implementation (no LS support found)");
|
||||
}
|
||||
return implementation;
|
||||
}
|
||||
|
||||
private static DOMImplementationRegistry createDOMRegistry() {
|
||||
try {
|
||||
return DOMImplementationRegistry.newInstance();
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (InstantiationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
620
common/common-io/src/main/java/com/twelvemonkeys/xml/XMLSerializer.java
Executable file
620
common/common-io/src/main/java/com/twelvemonkeys/xml/XMLSerializer.java
Executable file
@@ -0,0 +1,620 @@
|
||||
/*
|
||||
* 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.xml;
|
||||
|
||||
import com.twelvemonkeys.lang.StringUtil;
|
||||
import org.w3c.dom.*;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* XMLSerializer
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">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/xml/XMLSerializer.java#1 $
|
||||
*/
|
||||
public class XMLSerializer {
|
||||
// TODO: Replace with DOMSerializer? Test performance, pretty printing etc...
|
||||
// Main problem: Sun's Java 5 does not have LS 3.0 support
|
||||
// This class has no dependencies, which probably makes it more useful
|
||||
|
||||
// TODO: Support line breaking (at configurable width)
|
||||
// TODO: Support skipping XML declaration?
|
||||
// TODO: Support standalone?
|
||||
// TODO: Support more than version 1.0?
|
||||
// TODO: Consider using IOException to communicate trouble, rather than RTE,
|
||||
// to be more compatible...
|
||||
// TODO: Support not inserting line-breaks, to preserve space
|
||||
|
||||
// TODO: Idea: Create a SerializationContext that stores attributes on
|
||||
// serialization, to keep the serialization thread-safe
|
||||
// Store preserveSpace attribute in this context, to avoid costly traversals
|
||||
// Store user options here too
|
||||
// TODO: Push/pop?
|
||||
|
||||
private final OutputStream mOutput;
|
||||
private final Charset mEncoding;
|
||||
private final SerializationContext mContext;
|
||||
|
||||
public XMLSerializer(final OutputStream pOutput, final String pEncoding) {
|
||||
mOutput = pOutput;
|
||||
mEncoding = Charset.forName(pEncoding);
|
||||
mContext = new SerializationContext();
|
||||
}
|
||||
|
||||
public final void setIndentation(String pIndent) {
|
||||
mContext.indent = pIndent != null ? pIndent : " ";
|
||||
}
|
||||
|
||||
public final void setStripComments(boolean pStrip) {
|
||||
mContext.stripComments = pStrip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the entire document, along with the XML declaration
|
||||
* ({@code <?xml version="1.0" encoding="..."?>}).
|
||||
*
|
||||
* @param pDocument the document to serialize.
|
||||
*/
|
||||
public void serialize(final Document pDocument) {
|
||||
serialize(pDocument, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes the entire sub tree starting at {@code pRootNode}, along with an optional XML declaration
|
||||
* ({@code <?xml version="1.0" encoding="..."?>}).
|
||||
*
|
||||
* @param pRootNode the root node to serialize.
|
||||
* @param pWriteXMLDeclaration {@code true} if the XML declaration should be included, otherwise {@code false}.
|
||||
*/
|
||||
public void serialize(final Node pRootNode, final boolean pWriteXMLDeclaration) {
|
||||
PrintWriter out = new PrintWriter(new OutputStreamWriter(mOutput, mEncoding));
|
||||
try {
|
||||
if (pWriteXMLDeclaration) {
|
||||
writeXMLDeclaration(out);
|
||||
}
|
||||
writeXML(out, pRootNode, mContext.copy());
|
||||
}
|
||||
finally {
|
||||
out.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeXMLDeclaration(final PrintWriter pOut) {
|
||||
pOut.print("<?xml version=\"1.0\" encoding=\"");
|
||||
pOut.print(mEncoding.name());
|
||||
pOut.println("\"?>");
|
||||
}
|
||||
|
||||
private void writeXML(final PrintWriter pOut, final Node pDocument, final SerializationContext pContext) {
|
||||
writeNodeRecursive(pOut, pDocument, pContext);
|
||||
}
|
||||
|
||||
private void writeNodeRecursive(final PrintWriter pOut, final Node pNode, final SerializationContext pContext) {
|
||||
if (pNode.getNodeType() != Node.TEXT_NODE) {
|
||||
indentToLevel(pOut, pContext);
|
||||
}
|
||||
|
||||
switch (pNode.getNodeType()) {
|
||||
case Node.DOCUMENT_NODE:
|
||||
case Node.DOCUMENT_FRAGMENT_NODE:
|
||||
writeDocument(pOut, pNode, pContext);
|
||||
break;
|
||||
case Node.DOCUMENT_TYPE_NODE:
|
||||
writeDoctype(pOut, (DocumentType) pNode);
|
||||
break;
|
||||
case Node.ELEMENT_NODE:
|
||||
boolean preserveSpace = pContext.preserveSpace;
|
||||
updatePreserveSpace(pNode, pContext);
|
||||
writeElement(pOut, (Element) pNode, pContext);
|
||||
pContext.preserveSpace = preserveSpace;
|
||||
break;
|
||||
case Node.CDATA_SECTION_NODE:
|
||||
writeCData(pOut, pNode);
|
||||
break;
|
||||
case Node.TEXT_NODE:
|
||||
writeText(pOut, pNode, pContext);
|
||||
break;
|
||||
case Node.COMMENT_NODE:
|
||||
writeComment(pOut, pNode, pContext);
|
||||
break;
|
||||
case Node.PROCESSING_INSTRUCTION_NODE:
|
||||
writeProcessingInstruction(pOut, pNode);
|
||||
break;
|
||||
case Node.ATTRIBUTE_NODE:
|
||||
throw new IllegalArgumentException("Malformed input Document: Attribute nodes should only occur inside Element nodes");
|
||||
case Node.ENTITY_NODE:
|
||||
// '<!ENTITY ' + getNodeName + ... + '>'
|
||||
case Node.ENTITY_REFERENCE_NODE:
|
||||
// ( '&' | '%' ) + getNodeName + ';'
|
||||
case Node.NOTATION_NODE:
|
||||
// '<!NOTATION ' + getNodeName + ( ExternalID | PublicID ) + '>'
|
||||
default:
|
||||
throw new InternalError("Lazy programmer never implemented serialization of " + pNode.getClass());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeProcessingInstruction(final PrintWriter pOut, final Node pNode) {
|
||||
pOut.print("\n<?");
|
||||
pOut.print(pNode.getNodeValue());
|
||||
pOut.println("?>");
|
||||
}
|
||||
|
||||
private void writeText(final PrintWriter pOut, final Node pNode, final SerializationContext pContext) {
|
||||
// TODO: Is this really as specified?
|
||||
String value = pNode.getNodeValue();
|
||||
if (pContext.preserveSpace) {
|
||||
pOut.print(maybeEscapeElementValue(value));
|
||||
}
|
||||
else if (!StringUtil.isEmpty(value)) {
|
||||
indentToLevel(pOut, pContext);
|
||||
pOut.println(maybeEscapeElementValue(value.trim()));
|
||||
}
|
||||
}
|
||||
|
||||
private void writeCData(final PrintWriter pOut, final Node pNode) {
|
||||
pOut.print("<![CDATA[");
|
||||
pOut.print(validateCDataValue(pNode.getNodeValue()));
|
||||
pOut.println("]]>");
|
||||
}
|
||||
|
||||
private static void updatePreserveSpace(final Node pNode, final SerializationContext pContext) {
|
||||
NamedNodeMap attributes = pNode.getAttributes();
|
||||
if (attributes != null) {
|
||||
Node space = attributes.getNamedItem("xml:space");
|
||||
if (space != null) {
|
||||
if ("preserve".equals(space.getNodeValue())) {
|
||||
pContext.preserveSpace = true;
|
||||
}
|
||||
else if ("default".equals(space.getNodeValue())) {
|
||||
pContext.preserveSpace = false;
|
||||
}
|
||||
// No other values are allowed per spec, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void indentToLevel(final PrintWriter pOut, final SerializationContext pContext) {
|
||||
for (int i = 0; i < pContext.level; i++) {
|
||||
pOut.print(pContext.indent);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeComment(final PrintWriter pOut, final Node pNode, final SerializationContext pContext) {
|
||||
if (pContext.stripComments) {
|
||||
return;
|
||||
}
|
||||
|
||||
String value = pNode.getNodeValue();
|
||||
validateCommenValue(value);
|
||||
|
||||
if (value.startsWith(" ")) {
|
||||
pOut.print("<!--");
|
||||
}
|
||||
else {
|
||||
pOut.print("<!-- ");
|
||||
}
|
||||
pOut.print(value);
|
||||
if (value.endsWith(" ")) {
|
||||
pOut.println("-->");
|
||||
}
|
||||
else {
|
||||
pOut.println(" -->");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an escaped version of the input string. The string is guaranteed
|
||||
* to not contain illegal XML characters ({@code &<>}).
|
||||
* If no escaping is needed, the input string is returned as is.
|
||||
*
|
||||
* @param pValue the input string that might need escaping.
|
||||
* @return an escaped version of the input string.
|
||||
*/
|
||||
static String maybeEscapeElementValue(final String pValue) {
|
||||
int startEscape = needsEscapeElement(pValue);
|
||||
|
||||
if (startEscape < 0) {
|
||||
// If no escpaing is needed, simply return original
|
||||
return pValue;
|
||||
}
|
||||
else {
|
||||
// Otherwise, start replacing
|
||||
StringBuilder builder = new StringBuilder(pValue.substring(0, startEscape));
|
||||
builder.ensureCapacity(pValue.length() + 30);
|
||||
|
||||
int pos = startEscape;
|
||||
for (int i = pos; i < pValue.length(); i++) {
|
||||
switch (pValue.charAt(i)) {
|
||||
case '&':
|
||||
pos = appendAndEscape(pValue, pos, i, builder, "&");
|
||||
break;
|
||||
case '<':
|
||||
pos = appendAndEscape(pValue, pos, i, builder, "<");
|
||||
break;
|
||||
case '>':
|
||||
pos = appendAndEscape(pValue, pos, i, builder, ">");
|
||||
break;
|
||||
//case '\'':
|
||||
// pos = appendAndEscape(pString, pos, i, builder, "'");
|
||||
// break;
|
||||
//case '"':
|
||||
// pos = appendAndEscape(pString, pos, i, builder, """);
|
||||
// break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
builder.append(pValue.substring(pos));
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static int appendAndEscape(final String pString, int pStart, final int pEnd, final StringBuilder pBuilder, final String pEntity) {
|
||||
pBuilder.append(pString.substring(pStart, pEnd));
|
||||
pBuilder.append(pEntity);
|
||||
return pEnd + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an the first index from the input string that should be escaped
|
||||
* if escaping is needed, otherwise {@code -1}.
|
||||
*
|
||||
* @param pString the input string that might need escaping.
|
||||
* @return the first index from the input string that should be escaped,
|
||||
* or {@code -1}.
|
||||
*/
|
||||
private static int needsEscapeElement(final String pString) {
|
||||
for (int i = 0; i < pString.length(); i++) {
|
||||
switch (pString.charAt(i)) {
|
||||
case '&':
|
||||
case '<':
|
||||
case '>':
|
||||
//case '\'':
|
||||
//case '"':
|
||||
return i;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String maybeEscapeAttributeValue(final String pValue) {
|
||||
int startEscape = needsEscapeAttribute(pValue);
|
||||
|
||||
if (startEscape < 0) {
|
||||
return pValue;
|
||||
}
|
||||
else {
|
||||
StringBuilder builder = new StringBuilder(pValue.substring(0, startEscape));
|
||||
builder.ensureCapacity(pValue.length() + 16);
|
||||
|
||||
int pos = startEscape;
|
||||
for (int i = pos; i < pValue.length(); i++) {
|
||||
switch (pValue.charAt(i)) {
|
||||
case '&':
|
||||
pos = appendAndEscape(pValue, pos, i, builder, "&");
|
||||
break;
|
||||
case '"':
|
||||
pos = appendAndEscape(pValue, pos, i, builder, """);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//StringBuilder builder = new StringBuilder(pValue.length() + 30);
|
||||
//
|
||||
//int start = 0;
|
||||
//while (end >= 0) {
|
||||
// builder.append(pValue.substring(start, end));
|
||||
// builder.append(""");
|
||||
// start = end + 1;
|
||||
// end = pValue.indexOf('"', start);
|
||||
//}
|
||||
//builder.append(pValue.substring(start));
|
||||
|
||||
builder.append(pValue.substring(pos));
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an the first index from the input string that should be escaped
|
||||
* if escaping is needed, otherwise {@code -1}.
|
||||
*
|
||||
* @param pString the input string that might need escaping.
|
||||
* @return the first index from the input string that should be escaped,
|
||||
* or {@code -1}.
|
||||
*/
|
||||
private static int needsEscapeAttribute(final String pString) {
|
||||
for (int i = 0; i < pString.length(); i++) {
|
||||
switch (pString.charAt(i)) {
|
||||
case '&':
|
||||
//case '<':
|
||||
//case '>':
|
||||
//case '\'':
|
||||
case '"':
|
||||
return i;
|
||||
default:
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static String validateCDataValue(final String pValue) {
|
||||
if (pValue.indexOf("]]>") >= 0) {
|
||||
throw new IllegalArgumentException("Malformed input document: CDATA block may not contain the string ']]>'");
|
||||
}
|
||||
return pValue;
|
||||
}
|
||||
|
||||
private static String validateCommenValue(final String pValue) {
|
||||
if (pValue.indexOf("--") >= 0) {
|
||||
throw new IllegalArgumentException("Malformed input document: Comment may not contain the string '--'");
|
||||
}
|
||||
return pValue;
|
||||
}
|
||||
|
||||
private void writeDocument(final PrintWriter pOut, final Node pNode, final SerializationContext pContext) {
|
||||
// Document fragments might not have child nodes...
|
||||
if (pNode.hasChildNodes()) {
|
||||
NodeList nodes = pNode.getChildNodes();
|
||||
for (int i = 0; i < nodes.getLength(); i++) {
|
||||
writeNodeRecursive(pOut, nodes.item(i), pContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeElement(final PrintWriter pOut, final Element pNode, final SerializationContext pContext) {
|
||||
pOut.print("<");
|
||||
pOut.print(pNode.getTagName());
|
||||
|
||||
// TODO: Attributes should probably include namespaces, so that it works
|
||||
// even if the document was created using attributes instead of namespaces...
|
||||
|
||||
// Handle namespace
|
||||
String namespace = pNode.getNamespaceURI();
|
||||
if (namespace != null && !namespace.equals(pContext.defaultNamespace)) {
|
||||
String prefix = pNode.getPrefix();
|
||||
if (prefix == null) {
|
||||
pContext.defaultNamespace = namespace;
|
||||
pOut.print(" xmlns");
|
||||
}
|
||||
else {
|
||||
pOut.print(" xmlns:");
|
||||
pOut.print(prefix);
|
||||
}
|
||||
pOut.print("=\"");
|
||||
pOut.print(namespace);
|
||||
pOut.print("\"");
|
||||
}
|
||||
|
||||
// Iterate attributes if any
|
||||
if (pNode.hasAttributes()) {
|
||||
NamedNodeMap attributes = pNode.getAttributes();
|
||||
for (int i = 0; i < attributes.getLength(); i++) {
|
||||
Attr attribute = (Attr) attributes.item(i);
|
||||
String name = attribute.getName();
|
||||
if (!(name.startsWith("xmlns") && (name.length() == 5 || name.charAt(5) == ':'))) {
|
||||
pOut.print(" ");
|
||||
pOut.print(name);
|
||||
pOut.print("=\"");
|
||||
pOut.print(maybeEscapeAttributeValue(attribute.getValue()));
|
||||
pOut.print("\"");
|
||||
}
|
||||
//else {
|
||||
// System.err.println("attribute.getName(): " + name);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate children if any
|
||||
if (pNode.hasChildNodes()) {
|
||||
pOut.print(">");
|
||||
if (!pContext.preserveSpace) {
|
||||
pOut.println();
|
||||
}
|
||||
|
||||
NodeList children = pNode.getChildNodes();
|
||||
//pContext.level++;
|
||||
for (int i = 0; i < children.getLength(); i++) {
|
||||
writeNodeRecursive(pOut, children.item(i), pContext.push());
|
||||
}
|
||||
//pContext.level--;
|
||||
|
||||
if (!pContext.preserveSpace) {
|
||||
indentToLevel(pOut, pContext);
|
||||
}
|
||||
|
||||
pOut.print("</");
|
||||
pOut.print(pNode.getTagName());
|
||||
pOut.println(">");
|
||||
}
|
||||
else {
|
||||
pOut.println("/>");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void writeDoctype(final PrintWriter pOut, final DocumentType pDoctype) {
|
||||
// NOTE: The DOMImplementationLS LSSerializer actually inserts SYSTEM or
|
||||
// PUBLIC identifiers even if they are empty strings. The result is, it
|
||||
// will create invalid documents.
|
||||
// Testing for empty strings seems to be more compatible.
|
||||
if (pDoctype != null) {
|
||||
pOut.print("<!DOCTYPE ");
|
||||
pOut.print(pDoctype.getName());
|
||||
|
||||
String publicId = pDoctype.getPublicId();
|
||||
if (!StringUtil.isEmpty(publicId)) {
|
||||
pOut.print(" PUBLIC ");
|
||||
pOut.print(publicId);
|
||||
}
|
||||
|
||||
String systemId = pDoctype.getSystemId();
|
||||
if (!StringUtil.isEmpty(systemId)) {
|
||||
if (StringUtil.isEmpty(publicId)) {
|
||||
pOut.print(" SYSTEM \"");
|
||||
}
|
||||
else {
|
||||
pOut.print(" \"");
|
||||
}
|
||||
pOut.print(systemId);
|
||||
pOut.print("\"");
|
||||
}
|
||||
|
||||
String internalSubset = pDoctype.getInternalSubset();
|
||||
if (!StringUtil.isEmpty(internalSubset)) {
|
||||
pOut.print(" [ ");
|
||||
pOut.print(internalSubset);
|
||||
pOut.print(" ]");
|
||||
}
|
||||
pOut.println(">");
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] pArgs) throws IOException, SAXException {
|
||||
// Build XML tree (Document) and write
|
||||
// Find the implementation
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setNamespaceAware(true);
|
||||
DocumentBuilder builder;
|
||||
try {
|
||||
builder = factory.newDocumentBuilder();
|
||||
}
|
||||
catch (ParserConfigurationException e) {
|
||||
throw (IOException) new IOException(e.getMessage()).initCause(e);
|
||||
}
|
||||
DOMImplementation dom = builder.getDOMImplementation();
|
||||
|
||||
Document document = dom.createDocument("http://www.twelvemonkeys.com/xml/test", "test", dom.createDocumentType("test", null, null));
|
||||
|
||||
Element root = document.getDocumentElement();
|
||||
|
||||
// This is probably not the correct way of setting a default namespace
|
||||
//root.setAttribute("xmlns", "http://www.twelvemonkeys.com/xml/test");
|
||||
|
||||
// Create and insert the normal Properties headers as XML comments
|
||||
document.insertBefore(document.createComment(new Date().toString()), root);
|
||||
|
||||
Element test = document.createElement("sub");
|
||||
root.appendChild(test);
|
||||
Element more = document.createElementNS("http://more.com/1999/namespace", "more:more");
|
||||
more.setAttribute("foo", "test");
|
||||
more.setAttribute("bar", "'really' \"legal\" & ok");
|
||||
test.appendChild(more);
|
||||
more.appendChild(document.createTextNode("Simply some text."));
|
||||
more.appendChild(document.createCDATASection("&something escaped;"));
|
||||
more.appendChild(document.createTextNode("More & <more>!"));
|
||||
more.appendChild(document.createTextNode("\"<<'&'>>\""));
|
||||
Element another = document.createElement("another");
|
||||
test.appendChild(another);
|
||||
Element yet = document.createElement("yet-another");
|
||||
yet.setAttribute("this-one", "with-params");
|
||||
test.appendChild(yet);
|
||||
|
||||
Element pre = document.createElementNS("http://www.twelvemonkeys.com/xml/test", "pre");
|
||||
pre.setAttributeNS("http://www.w3.org/XML/1998/namespace", "xml:space", "preserve");
|
||||
pre.appendChild(document.createTextNode(" \t \n\r some text & white ' ' \n "));
|
||||
test.appendChild(pre);
|
||||
|
||||
// Create serializer and output document
|
||||
//XMLSerializer serializer = new XMLSerializer(pOutput, new OutputFormat(document, UTF_8_ENCODING, true));
|
||||
System.out.println("XMLSerializer:");
|
||||
XMLSerializer serializer = new XMLSerializer(System.out, "UTF-8");
|
||||
serializer.serialize(document);
|
||||
System.out.println();
|
||||
|
||||
System.out.println("DOMSerializer:");
|
||||
DOMSerializer serializerD = new DOMSerializer(System.out, "UTF-8");
|
||||
serializerD.setPrettyPrint(true);
|
||||
serializerD.serialize(document);
|
||||
System.out.println();
|
||||
|
||||
System.out.println("\n");
|
||||
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
XMLSerializer serializer2 = new XMLSerializer(out, "UTF-8");
|
||||
serializer2.serialize(document);
|
||||
|
||||
ByteArrayOutputStream outD = new ByteArrayOutputStream();
|
||||
DOMSerializer serializer2D = new DOMSerializer(outD, "UTF-8");
|
||||
serializer2D.serialize(document);
|
||||
|
||||
Document document2 = builder.parse(new ByteArrayInputStream(out.toByteArray()));
|
||||
System.out.println("XMLSerializer reparsed XMLSerializer:");
|
||||
serializer.serialize(document2);
|
||||
System.out.println();
|
||||
System.out.println("DOMSerializer reparsed XMLSerializer:");
|
||||
serializerD.serialize(document2);
|
||||
System.out.println();
|
||||
|
||||
|
||||
Document documentD = builder.parse(new ByteArrayInputStream(outD.toByteArray()));
|
||||
System.out.println("XMLSerializer reparsed DOMSerializer:");
|
||||
serializer.serialize(documentD);
|
||||
System.out.println();
|
||||
System.out.println("DOMSerializer reparsed DOMSerializer:");
|
||||
serializerD.serialize(documentD);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
static class SerializationContext implements Cloneable {
|
||||
String indent = " ";
|
||||
int level = 0;
|
||||
boolean preserveSpace = false;
|
||||
boolean stripComments = false;
|
||||
String defaultNamespace;
|
||||
|
||||
public SerializationContext copy() {
|
||||
try {
|
||||
return (SerializationContext) clone();
|
||||
}
|
||||
catch (CloneNotSupportedException e) {
|
||||
throw new Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public SerializationContext push() {
|
||||
SerializationContext context = copy();
|
||||
context.level++;
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Provides XML support classes.
|
||||
*/
|
||||
package com.twelvemonkeys.xml;
|
||||
Reference in New Issue
Block a user