();
+
+ boolean markSupported = true;
+ while (pReaders.hasNext()) {
+ Reader reader = pReaders.next();
+ if (reader == null) {
+ throw new NullPointerException("readers cannot contain null-elements");
+ }
+ readers.add(reader);
+ markSupported = markSupported && reader.markSupported();
+ }
+ this.markSupported = markSupported;
+
+ current = nextReader();
+ }
+
+ protected final Reader nextReader() {
+ if (currentReader >= readers.size()) {
+ current = new EmptyReader();
+ }
+ else {
+ current = readers.get(currentReader++);
+ }
+
+ // NOTE: Reset mNext for every reader, and record marked reader in mark/reset methods!
+ mNext = 0;
+ return current;
+ }
+
+ /**
+ * 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 (readers == null) {
+ throw new IOException("Stream closed");
+ }
+ }
+
+ public void close() throws IOException {
+ // Close all readers
+ for (Reader reader : readers) {
+ reader.close();
+ }
+
+ readers = 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 (finalLock) {
+ ensureOpen();
+ mark = mNext;
+ markedReader = currentReader;
+
+ current.mark(pReadLimit);
+ }
+ }
+
+ @Override
+ public void reset() throws IOException {
+ synchronized (finalLock) {
+ ensureOpen();
+
+ if (currentReader != markedReader) {
+ // Reset any reader before this
+ for (int i = currentReader; i >= markedReader; i--) {
+ readers.get(i).reset();
+ }
+
+ currentReader = markedReader - 1;
+ nextReader();
+ }
+ current.reset();
+
+ mNext = mark;
+ }
+ }
+
+ @Override
+ public boolean markSupported() {
+ return markSupported;
+ }
+
+ @Override
+ public int read() throws IOException {
+ synchronized (finalLock) {
+ int read = current.read();
+
+ if (read < 0 && currentReader < readers.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 (finalLock) {
+ int read = current.read(pBuffer, pOffset, pLength);
+
+ if (read < 0 && currentReader < readers.size()) {
+ nextReader();
+ return read(pBuffer, pOffset, pLength); // In case of 0-length readers
+ }
+
+ mNext += read;
+
+ return read;
+ }
+ }
+
+ @Override
+ public boolean ready() throws IOException {
+ return current.ready();
+ }
+
+ @Override
+ public long skip(long pChars) throws IOException {
+ synchronized (finalLock) {
+ long skipped = current.skip(pChars);
+
+ if (skipped == 0 && currentReader < readers.size()) {
+ nextReader();
+ return skip(pChars); // In case of 0-length readers
+ }
+
+ mNext += skipped;
+
+ return skipped;
+ }
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/EmptyReader.java b/common/common-io/src/main/java/com/twelvemonkeys/io/EmptyReader.java
index ea1a1abf..6ff1a7a4 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/EmptyReader.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/EmptyReader.java
@@ -1,47 +1,46 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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
- *
- *
- * @author Harald Kuhr
- * @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("");
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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
+ *
+ * @author Harald Kuhr
+ * @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("");
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java
index 4bac2b82..69018a23 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FastByteArrayOutputStream.java
@@ -1,136 +1,137 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * An unsynchronized {@code ByteArrayOutputStream} implementation. This version
- * also has a constructor that lets you create a stream with initial content.
- *
- *
- * @author Harald Kuhr
- * @version $Id: FastByteArrayOutputStream.java#2 $
- */
-// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
-public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
- /** Max grow size (unless if writing more than this amount of bytes) */
- protected int maxGrowSize = 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.
- *
- * 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 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 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 + maxGrowSize), 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.
- *
- * 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 behaviour.
- *
- * @return a new {@code ByteArrayInputStream}, reading from this stream's buffer.
- */
- public ByteArrayInputStream createInputStream() {
- return new ByteArrayInputStream(buf, 0, count);
- }
+/*
+ * 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 of the copyright holder 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 HOLDER 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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An unsynchronized {@code ByteArrayOutputStream} implementation. This version
+ * also has a constructor that lets you create a stream with initial content.
+ *
+ * @author Harald Kuhr
+ * @version $Id: FastByteArrayOutputStream.java#2 $
+ */
+// TODO: Performance test of a stream impl that uses list of fixed size blocks, rather than contiguous block
+public final class FastByteArrayOutputStream extends ByteArrayOutputStream {
+ /** Max grow size (unless if writing more than this amount of bytes) */
+ protected int maxGrowSize = 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.
+ *
+ * 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 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 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 + maxGrowSize), 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.
+ *
+ * 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 behaviour.
+ *
+ *
+ * @return a new {@code ByteArrayInputStream}, reading from this stream's buffer.
+ */
+ public ByteArrayInputStream createInputStream() {
+ return new ByteArrayInputStream(buf, 0, count);
+ }
}
\ No newline at end of file
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java
index a857b817..d02225d9 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FileCacheSeekableStream.java
@@ -1,240 +1,241 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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}.
- *
- * 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 Harald Kuhr
- * @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 byte[] buffer;
-
- /**
- * 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?
- buffer = new byte[1024];
- }
-
- public final boolean isCachedMemory() {
- return false;
- }
-
- public final boolean isCachedFile() {
- return true;
- }
-
- @Override
- protected void closeImpl() throws IOException {
- // TODO: Close cache file
- super.closeImpl();
-
- buffer = null;
- }
-
- @Override
- public int read() throws IOException {
- checkOpen();
-
- int read;
- if (position == streamPosition) {
- // Read ahead into buffer, for performance
- read = readAhead(buffer, 0, buffer.length);
- if (read >= 0) {
- read = buffer[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) {
- position++;
- }
- return read;
- }
-
- @Override
- public int read(byte[] pBytes, int pOffset, int pLength) throws IOException {
- checkOpen();
-
- int length;
- if (position == streamPosition) {
- // 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, streamPosition - position));
-
- //System.out.println("Read " + length + " byte from cache");
- }
-
- // TODO: This field is not REALLY considered accessible.. :-P
- if (length > 0) {
- position += length;
- }
- return length;
- }
-
- private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
- int length;
- length = stream.read(pBytes, pOffset, pLength);
-
- if (length > 0) {
- streamPosition += length;
- getCache().write(pBytes, pOffset, length);
- }
- return length;
- }
-
- // TODO: We need to close the cache file!!! Otherwise we are leaking file descriptors
-
- final static class FileCache extends StreamCache {
- private RandomAccessFile cacheFile;
-
- public FileCache(final File pFile) throws FileNotFoundException {
- Validate.notNull(pFile, "file");
- cacheFile = new RandomAccessFile(pFile, "rw");
- }
-
- public void write(final int pByte) throws IOException {
- cacheFile.write(pByte);
- }
-
- @Override
- public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
- cacheFile.write(pBuffer, pOffset, pLength);
- }
-
- public int read() throws IOException {
- return cacheFile.read();
- }
-
- @Override
- public int read(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
- return cacheFile.read(pBuffer, pOffset, pLength);
- }
-
- public void seek(final long pPosition) throws IOException {
- cacheFile.seek(pPosition);
- }
-
- public long getPosition() throws IOException {
- return cacheFile.getFilePointer();
- }
-
- @Override
- void close() throws IOException {
- cacheFile.close();
- }
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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}.
+ *
+ * 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 Harald Kuhr
+ * @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 byte[] buffer;
+
+ /**
+ * 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?
+ buffer = new byte[1024];
+ }
+
+ public final boolean isCachedMemory() {
+ return false;
+ }
+
+ public final boolean isCachedFile() {
+ return true;
+ }
+
+ @Override
+ protected void closeImpl() throws IOException {
+ // TODO: Close cache file
+ super.closeImpl();
+
+ buffer = null;
+ }
+
+ @Override
+ public int read() throws IOException {
+ checkOpen();
+
+ int read;
+ if (position == streamPosition) {
+ // Read ahead into buffer, for performance
+ read = readAhead(buffer, 0, buffer.length);
+ if (read >= 0) {
+ read = buffer[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) {
+ position++;
+ }
+ return read;
+ }
+
+ @Override
+ public int read(byte[] pBytes, int pOffset, int pLength) throws IOException {
+ checkOpen();
+
+ int length;
+ if (position == streamPosition) {
+ // 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, streamPosition - position));
+
+ //System.out.println("Read " + length + " byte from cache");
+ }
+
+ // TODO: This field is not REALLY considered accessible.. :-P
+ if (length > 0) {
+ position += length;
+ }
+ return length;
+ }
+
+ private int readAhead(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
+ int length;
+ length = stream.read(pBytes, pOffset, pLength);
+
+ if (length > 0) {
+ streamPosition += length;
+ getCache().write(pBytes, pOffset, length);
+ }
+ return length;
+ }
+
+ // TODO: We need to close the cache file!!! Otherwise we are leaking file descriptors
+
+ final static class FileCache extends StreamCache {
+ private RandomAccessFile cacheFile;
+
+ public FileCache(final File pFile) throws FileNotFoundException {
+ Validate.notNull(pFile, "file");
+ cacheFile = new RandomAccessFile(pFile, "rw");
+ }
+
+ public void write(final int pByte) throws IOException {
+ cacheFile.write(pByte);
+ }
+
+ @Override
+ public void write(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
+ cacheFile.write(pBuffer, pOffset, pLength);
+ }
+
+ public int read() throws IOException {
+ return cacheFile.read();
+ }
+
+ @Override
+ public int read(final byte[] pBuffer, final int pOffset, final int pLength) throws IOException {
+ return cacheFile.read(pBuffer, pOffset, pLength);
+ }
+
+ public void seek(final long pPosition) throws IOException {
+ cacheFile.seek(pPosition);
+ }
+
+ public long getPosition() throws IOException {
+ return cacheFile.getFilePointer();
+ }
+
+ @Override
+ void close() throws IOException {
+ cacheFile.close();
+ }
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java
index 86c92e39..c4be9cfd 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FileSeekableStream.java
@@ -1,135 +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 of the copyright holder 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 HOLDER 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.FileNotFoundException;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-
-/**
- * A {@code SeekableInputStream} implementation that uses random access directly to a {@code File}.
- *
- * @see FileCacheSeekableStream
- * @see MemoryCacheSeekableStream
- * @see RandomAccessFile
- *
- * @author Harald Kuhr
- * @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() - position;
- 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) {
- position++;
- }
- 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) {
- position += 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);
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+/**
+ * A {@code SeekableInputStream} implementation that uses random access directly to a {@code File}.
+
+ * @see FileCacheSeekableStream
+ * @see MemoryCacheSeekableStream
+ * @see RandomAccessFile
+ *
+ * @author Harald Kuhr
+ * @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() - position;
+ 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) {
+ position++;
+ }
+ 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) {
+ position += 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);
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FileSystem.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FileSystem.java
index b275e5bf..81668a88 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/FileSystem.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FileSystem.java
@@ -1,103 +1,102 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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
- *
- *
- * @author Harald Kuhr
- * @version $Id: 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.contains("windows")) {
- return new Win32FileSystem();
- }
- else if (os.contains("linux") ||
- os.contains("sun os") ||
- os.contains("sunos") ||
- os.contains("solaris") ||
- os.contains("mpe/ix") ||
- os.contains("hp-ux") ||
- os.contains("aix") ||
- os.contains("freebsd") ||
- os.contains("irix") ||
- os.contains("digital unix") ||
- os.contains("unix") ||
- os.contains("mac os x")) {
- return new UnixFileSystem();
- }
- else {
- return new UnknownFileSystem(os);
- }
- }
-
- private static class UnknownFileSystem extends FileSystem {
- private final String osName;
-
- UnknownFileSystem(String pOSName) {
- osName = pOSName;
- }
-
- long getFreeSpace(File pPath) {
- return 0l;
- }
-
- long getTotalSpace(File pPath) {
- return 0l;
- }
-
- String getName() {
- return "Unknown (" + osName + ")";
- }
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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
+ *
+ * @author Harald Kuhr
+ * @version $Id: 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.contains("windows")) {
+ return new Win32FileSystem();
+ }
+ else if (os.contains("linux") ||
+ os.contains("sun os") ||
+ os.contains("sunos") ||
+ os.contains("solaris") ||
+ os.contains("mpe/ix") ||
+ os.contains("hp-ux") ||
+ os.contains("aix") ||
+ os.contains("freebsd") ||
+ os.contains("irix") ||
+ os.contains("digital unix") ||
+ os.contains("unix") ||
+ os.contains("mac os x")) {
+ return new UnixFileSystem();
+ }
+ else {
+ return new UnknownFileSystem(os);
+ }
+ }
+
+ private static class UnknownFileSystem extends FileSystem {
+ private final String osName;
+
+ UnknownFileSystem(String pOSName) {
+ osName = pOSName;
+ }
+
+ long getFreeSpace(File pPath) {
+ return 0l;
+ }
+
+ long getTotalSpace(File pPath) {
+ return 0l;
+ }
+
+ String getName() {
+ return "Unknown (" + osName + ")";
+ }
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FileUtil.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FileUtil.java
index 58ee8f7f..91a6a7d5 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/FileUtil.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FileUtil.java
@@ -1,1083 +1,1082 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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 com.twelvemonkeys.lang.Validate;
-import com.twelvemonkeys.util.Visitor;
-
-import java.io.*;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.UndeclaredThrowableException;
-import java.net.URL;
-import java.text.NumberFormat;
-
-/**
- * A utility class with some useful file and i/o related methods.
- *
- * Versions exists take Input and OutputStreams as parameters, to
- * allow for copying streams (URL's etc.).
- *
- * @author Harald Kuhr
- * @author Eirik Torske
- * @author last modified by $Author: haku $
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileUtil.java#3 $
- */
-public final class FileUtil {
- // TODO: Be more cosequent using resolve() all places where File objects are involved
- // TODO: Parameter handling (allow null vs IllegalArgument)
- // TODO: Exception handling
-
- /**
- * The size of the buffer used for copying
- */
- public final static int BUF_SIZE = 1024;
- private static String TEMP_DIR = null;
-
- private final static FileSystem FS = FileSystem.get();
-
- public static void main(String[] pArgs) throws IOException {
- File file;
- if (pArgs[0].startsWith("file:")) {
- file = toFile(new URL(pArgs[0]));
- System.out.println(file);
- }
- else {
- file = new File(pArgs[0]);
- System.out.println(file.toURL());
- }
-
- System.out.println("Free space: " + getFreeSpace(file) + "/" + getTotalSpace(file) + " bytes");
- }
-
- /*
- * Method main for test only.
- *
- public static void main0(String[] pArgs) {
- if (pArgs.length != 2) {
- System.out.println("usage: java Copy in out");
- return;
- }
- try {
- if (!copy(pArgs[0], pArgs[1])) {
- System.out.println("Error copying");
- }
- }
- catch (IOException e) {
- System.out.println(e.getMessage());
- }
- }
- //*/
-
- // Avoid instances/constructor showing up in API doc
- private FileUtil() {}
-
- /**
- * Copies the fromFile to the toFile location. If toFile is a directory, a
- * new file is created in that directory, with the name of the fromFile.
- * If the toFile exists, the file will not be copied, unless owerWrite is
- * true.
- *
- * @param pFromFileName The name of the file to copy from
- * @param pToFileName The name of the file to copy to
- * @return true if the file was copied successfully,
- * false if the output file exists. In all other cases, an
- * IOException is thrown, and the method does not return a value.
- * @throws IOException if an i/o error occurs during copy
- */
- public static boolean copy(String pFromFileName, String pToFileName) throws IOException {
- return copy(new File(pFromFileName), new File(pToFileName), false);
- }
-
- /**
- * Copies the fromFile to the toFile location. If toFile is a directory, a
- * new file is created in that directory, with the name of the fromFile.
- * If the toFile exists, the file will not be copied, unless owerWrite is
- * true.
- *
- * @param pFromFileName The name of the file to copy from
- * @param pToFileName The name of the file to copy to
- * @param pOverWrite Specifies if the toFile should be overwritten, if it
- * exists.
- * @return true if the file was copied successfully,
- * false if the output file exists, and the owerWrite parameter is
- * false. In all other cases, an
- * IOException is thrown, and the method does not return a value.
- * @throws IOException if an i/o error occurs during copy
- */
- public static boolean copy(String pFromFileName, String pToFileName, boolean pOverWrite) throws IOException {
- return copy(new File(pFromFileName), new File(pToFileName), pOverWrite);
- }
-
- /**
- * Copies the fromFile to the toFile location. If toFile is a directory, a
- * new file is created in that directory, with the name of the fromFile.
- * If the toFile exists, the file will not be copied, unless owerWrite is
- * true.
- *
- * @param pFromFile The file to copy from
- * @param pToFile The file to copy to
- * @return true if the file was copied successfully,
- * false if the output file exists. In all other cases, an
- * IOException is thrown, and the method does not return a value.
- * @throws IOException if an i/o error occurs during copy
- */
- public static boolean copy(File pFromFile, File pToFile) throws IOException {
- return copy(pFromFile, pToFile, false);
- }
-
- /**
- * Copies the fromFile to the toFile location. If toFile is a directory, a
- * new file is created in that directory, with the name of the fromFile.
- * If the toFile exists, the file will not be copied, unless owerWrite is
- * true.
- *
- * @param pFromFile The file to copy from
- * @param pToFile The file to copy to
- * @param pOverWrite Specifies if the toFile should be overwritten, if it
- * exists.
- * @return {@code true} if the file was copied successfully,
- * {@code false} if the output file exists, and the
- * {@code pOwerWrite} parameter is
- * {@code false}. In all other cases, an
- * {@code IOExceptio}n is thrown, and the method does not return.
- * @throws IOException if an i/o error occurs during copy
- * @todo Test copyDir functionality!
- */
- public static boolean copy(File pFromFile, File pToFile, boolean pOverWrite) throws IOException {
- // Copy all directory structure
- if (pFromFile.isDirectory()) {
- return copyDir(pFromFile, pToFile, pOverWrite);
- }
-
- // Check if destination is a directory
- if (pToFile.isDirectory()) {
- // Create a new file with same name as from
- pToFile = new File(pToFile, pFromFile.getName());
- }
-
- // Check if file exists, and return false if overWrite is false
- if (!pOverWrite && pToFile.exists()) {
- return false;
- }
-
- InputStream in = null;
- OutputStream out = null;
-
- try {
- // Use buffer size two times byte array, to avoid i/o bottleneck
- in = new FileInputStream(pFromFile);
- out = new FileOutputStream(pToFile);
-
- // Copy from inputStream to outputStream
- copy(in, out);
- }
- //Just pass any IOException on up the stack
- finally {
- close(in);
- close(out);
- }
-
- return true; // If we got here, everything is probably okay.. ;-)
- }
-
- /**
- * Tries to close the given stream.
- * NOTE: If the stream cannot be closed, the IOException thrown is silently
- * ignored.
- *
- * @param pInput the stream to close
- */
- public static void close(InputStream pInput) {
- try {
- if (pInput != null) {
- pInput.close();
- }
- }
- catch (IOException ignore) {
- // Non critical error
- }
- }
-
- /**
- * Tries to close the given stream.
- * NOTE: If the stream cannot be closed, the IOException thrown is silently
- * ignored.
- *
- * @param pOutput the stream to close
- */
- public static void close(OutputStream pOutput) {
- try {
- if (pOutput != null) {
- pOutput.close();
- }
- }
- catch (IOException ignore) {
- // Non critical error
- }
- }
-
- static void close(Reader pReader) {
- try {
- if (pReader != null) {
- pReader.close();
- }
- }
- catch (IOException ignore) {
- // Non critical error
- }
- }
-
- static void close(Writer pWriter) {
- try {
- if (pWriter != null) {
- pWriter.close();
- }
- }
- catch (IOException ignore) {
- // Non critical error
- }
- }
-
- /**
- * Copies a directory recursively. If the destination folder does not exist,
- * it is created
- *
- * @param pFrom the source directory
- * @param pTo the destination directory
- * @param pOverWrite {@code true} if we should allow overwrting existing files
- * @return {@code true} if all files were copied sucessfully
- * @throws IOException if {@code pTo} exists, and it not a directory,
- * or if copying of any of the files in the folder fails
- */
- private static boolean copyDir(File pFrom, File pTo, boolean pOverWrite) throws IOException {
- if (pTo.exists() && !pTo.isDirectory()) {
- throw new IOException("A directory may only be copied to another directory, not to a file");
- }
- pTo.mkdirs(); // mkdir?
- boolean allOkay = true;
- File[] files = pFrom.listFiles();
-
- for (File file : files) {
- if (!copy(file, new File(pTo, file.getName()), pOverWrite)) {
- allOkay = false;
- }
- }
- return allOkay;
- }
-
- /**
- * Copies all data from one stream to another.
- * The data is copied from the fromStream to the toStream using buffered
- * streams for efficiency.
- *
- * @param pFrom The input srteam to copy from
- * @param pTo The output stream to copy to
- * @return true. Otherwise, an
- * IOException is thrown, and the method does not return a value.
- * @throws IOException if an i/o error occurs during copy
- * @throws IllegalArgumentException if either {@code pFrom} or {@code pTo} is
- * {@code null}
- */
- public static boolean copy(InputStream pFrom, OutputStream pTo) throws IOException {
- Validate.notNull(pFrom, "from");
- Validate.notNull(pTo, "to");
-
- // TODO: Consider using file channels for faster copy where possible
-
- // Use buffer size two times byte array, to avoid i/o bottleneck
- // TODO: Consider letting the client decide as this is sometimes not a good thing!
- InputStream in = new BufferedInputStream(pFrom, BUF_SIZE * 2);
- OutputStream out = new BufferedOutputStream(pTo, BUF_SIZE * 2);
-
- byte[] buffer = new byte[BUF_SIZE];
- int count;
-
- while ((count = in.read(buffer)) != -1) {
- out.write(buffer, 0, count);
- }
-
- // Flush out stream, to write any remaining buffered data
- out.flush();
-
- return true; // If we got here, everything is probably okay.. ;-)
- }
-
- /**
- * Gets the file (type) extension of the given file.
- * A file extension is the part of the filename, after the last occurence
- * of a period {@code '.'}.
- * If the filename contains no period, {@code null} is returned.
- *
- * @param pFileName the full filename with extension
- * @return the extension (type) of the file, or {@code null}
- */
- public static String getExtension(final String pFileName) {
- return getExtension0(getFilename(pFileName));
- }
-
- /**
- * Gets the file (type) extension of the given file.
- * A file extension is the part of the filename, after the last occurence
- * of a period {@code '.'}.
- * If the filename contains no period, {@code null} is returned.
- *
- * @param pFile the file
- * @return the extension (type) of the file, or {@code null}
- */
- public static String getExtension(final File pFile) {
- return getExtension0(pFile.getName());
- }
-
- // NOTE: Assumes filename and no path
- private static String getExtension0(final String pFileName) {
- int index = pFileName.lastIndexOf('.');
-
- if (index >= 0) {
- return pFileName.substring(index + 1);
- }
-
- // No period found
- return null;
- }
-
-
- /**
- * Gets the file name of the given file, without the extension (type).
- * A file extension is the part of the filename, after the last occurence
- * of a period {@code '.'}.
- * If the filename contains no period, the complete file name is returned
- * (same as {@code pFileName}, if the string contains no path elements).
- *
- * @param pFileName the full filename with extension
- * @return the base name of the file
- */
- public static String getBasename(final String pFileName) {
- return getBasename0(getFilename(pFileName));
- }
-
- /**
- * Gets the file name of the given file, without the extension (type).
- * A file extension is the part of the filename, after the last occurence
- * of a period {@code '.'}.
- * If the filename contains no period, {@code pFile.getName()} is returned.
- *
- * @param pFile the file
- * @return the base name of the file
- */
- public static String getBasename(final File pFile) {
- return getBasename0(pFile.getName());
- }
-
- // NOTE: Assumes filename and no path
- public static String getBasename0(final String pFileName) {
- int index = pFileName.lastIndexOf('.');
-
- if (index >= 0) {
- return pFileName.substring(0, index);
- }
-
- // No period found
- return pFileName;
- }
-
- /**
- * Extracts the directory path without the filename, from a complete
- * filename path.
- *
- * @param pPath The full filename path.
- * @return the path without the filename.
- * @see File#getParent
- * @see #getFilename
- */
- public static String getDirectoryname(final String pPath) {
- return getDirectoryname(pPath, File.separatorChar);
- }
-
- /**
- * Extracts the directory path without the filename, from a complete
- * filename path.
- *
- * @param pPath The full filename path.
- * @param pSeparator the separator char used in {@code pPath}
- * @return the path without the filename.
- * @see File#getParent
- * @see #getFilename
- */
- public static String getDirectoryname(final String pPath, final char pSeparator) {
- int index = pPath.lastIndexOf(pSeparator);
-
- if (index < 0) {
- return ""; // Assume only filename
- }
- return pPath.substring(0, index);
- }
-
- /**
- * Extracts the filename of a complete filename path.
- *
- * @param pPath The full filename path.
- * @return the extracted filename.
- * @see File#getName
- * @see #getDirectoryname
- */
- public static String getFilename(final String pPath) {
- return getFilename(pPath, File.separatorChar);
- }
-
- /**
- * Extracts the filename of a complete filename path.
- *
- * @param pPath The full filename path.
- * @param pSeparator The file separator.
- * @return the extracted filename.
- * @see File#getName
- * @see #getDirectoryname
- */
- public static String getFilename(final String pPath, final char pSeparator) {
- int index = pPath.lastIndexOf(pSeparator);
-
- if (index < 0) {
- return pPath; // Assume only filename
- }
-
- return pPath.substring(index + 1);
- }
-
-
- /**
- * Tests if a file or directory has no content.
- * A file is empty if it has a length of 0L. A non-existing file is also
- * considered empty.
- * A directory is considered empty if it contains no files.
- *
- * @param pFile The file to test
- * @return {@code true} if the file is empty, otherwise
- * {@code false}.
- */
- public static boolean isEmpty(File pFile) {
- if (pFile.isDirectory()) {
- return (pFile.list().length == 0);
- }
- return (pFile.length() == 0);
- }
-
- /**
- * Gets the default temp directory for the system as a File.
- *
- * @return a {@code File}, representing the default temp directory.
- * @see File#createTempFile
- */
- public static File getTempDirFile() {
- return new File(getTempDir());
- }
-
- /**
- * Gets the default temp directory for the system.
- *
- * @return a {@code String}, representing the path to the default temp
- * directory.
- * @see File#createTempFile
- */
- public static String getTempDir() {
- synchronized (FileUtil.class) {
- if (TEMP_DIR == null) {
- // Get the 'java.io.tmpdir' property
- String tmpDir = System.getProperty("java.io.tmpdir");
-
- if (StringUtil.isEmpty(tmpDir)) {
- // Stupid fallback...
- // TODO: Delegate to FileSystem?
- if (new File("/temp").exists()) {
- tmpDir = "/temp"; // Windows
- }
- else {
- tmpDir = "/tmp"; // Unix
- }
- }
- TEMP_DIR = tmpDir;
- }
- }
- return TEMP_DIR;
- }
-
- /**
- * Gets the contents of the given file, as a byte array.
- *
- * @param pFilename the name of the file to get content from
- * @return the content of the file as a byte array.
- * @throws IOException if the read operation fails
- */
- public static byte[] read(String pFilename) throws IOException {
- return read(new File(pFilename));
- }
-
- /**
- * Gets the contents of the given file, as a byte array.
- *
- * @param pFile the file to get content from
- * @return the content of the file as a byte array.
- * @throws IOException if the read operation fails
- */
- public static byte[] read(File pFile) throws IOException {
- // Custom implementation, as we know the size of a file
- if (!pFile.exists()) {
- throw new FileNotFoundException(pFile.toString());
- }
-
- byte[] bytes = new byte[(int) pFile.length()];
- InputStream in = null;
-
- try {
- // Use buffer size two times byte array, to avoid i/o bottleneck
- in = new BufferedInputStream(new FileInputStream(pFile), BUF_SIZE * 2);
-
- int off = 0;
- int len;
- while ((len = in.read(bytes, off, in.available())) != -1 && (off < bytes.length)) {
- off += len;
- // System.out.println("read:" + len);
- }
- }
- // Just pass any IOException on up the stack
- finally {
- close(in);
- }
-
- return bytes;
- }
-
- /**
- * Reads all data from the input stream to a byte array.
- *
- * @param pInput The input stream to read from
- * @return The content of the stream as a byte array.
- * @throws IOException if an i/o error occurs during read.
- */
- public static byte[] read(InputStream pInput) throws IOException {
- // Create byte array
- ByteArrayOutputStream bytes = new FastByteArrayOutputStream(BUF_SIZE);
-
- // Copy from stream to byte array
- copy(pInput, bytes);
-
- return bytes.toByteArray();
- }
-
- /**
- * Writes the contents from a byte array to an output stream.
- *
- * @param pOutput The output stream to write to
- * @param pData The byte array to write
- * @return {@code true}, otherwise an IOException is thrown.
- * @throws IOException if an i/o error occurs during write.
- */
- public static boolean write(OutputStream pOutput, byte[] pData) throws IOException {
- // Write data
- pOutput.write(pData);
-
- // If we got here, all is okay
- return true;
- }
-
- /**
- * Writes the contents from a byte array to a file.
- *
- * @param pFile The file to write to
- * @param pData The byte array to write
- * @return {@code true}, otherwise an IOException is thrown.
- * @throws IOException if an i/o error occurs during write.
- */
- public static boolean write(File pFile, byte[] pData) throws IOException {
- boolean success = false;
- OutputStream out = null;
-
- try {
- out = new BufferedOutputStream(new FileOutputStream(pFile));
- success = write(out, pData);
- }
- finally {
- close(out);
- }
- return success;
- }
-
- /**
- * Writes the contents from a byte array to a file.
- *
- * @param pFilename The name of the file to write to
- * @param pData The byte array to write
- * @return {@code true}, otherwise an IOException is thrown.
- * @throws IOException if an i/o error occurs during write.
- */
- public static boolean write(String pFilename, byte[] pData) throws IOException {
- return write(new File(pFilename), pData);
- }
-
- /**
- * Deletes the specified file.
- *
- * @param pFile The file to delete
- * @param pForce Forces delete, even if the parameter is a directory, and
- * is not empty. Be careful!
- * @return {@code true}, if the file existed and was deleted.
- * @throws IOException if an i/o error occurs during delete.
- */
- public static boolean delete(final File pFile, final boolean pForce) throws IOException {
- if (pForce && pFile.isDirectory()) {
- return deleteDir(pFile);
- }
- return pFile.exists() && pFile.delete();
- }
-
- /**
- * Deletes a directory recursively.
- *
- * @param pFile the file to delete
- * @return {@code true} if the file was deleted sucessfully
- * @throws IOException if an i/o error occurs during delete.
- */
- private static boolean deleteDir(final File pFile) throws IOException {
- // Recusively delete all files/subfolders
- // Deletes the files using visitor pattern, to avoid allocating
- // a file array, which may throw OutOfMemoryExceptions for
- // large directories/in low memory situations
- class DeleteFilesVisitor implements Visitor {
- private int failedCount = 0;
- private IOException exception = null;
-
- public void visit(final File pFile) {
- try {
- if (!delete(pFile, true)) {
- failedCount++;
- }
- }
- catch (IOException e) {
- failedCount++;
- if (exception == null) {
- exception = e;
- }
- }
- }
-
- boolean succeeded() throws IOException {
- if (exception != null) {
- throw exception;
- }
- return failedCount == 0;
- }
- }
- DeleteFilesVisitor fileDeleter = new DeleteFilesVisitor();
- visitFiles(pFile, null, fileDeleter);
-
- // If any of the deletes above failed, this will fail (or return false)
- return fileDeleter.succeeded() && pFile.delete();
- }
-
- /**
- * Deletes the specified file.
- *
- * @param pFilename The name of file to delete
- * @param pForce Forces delete, even if the parameter is a directory, and
- * is not empty. Careful!
- * @return {@code true}, if the file existed and was deleted.
- * @throws java.io.IOException if deletion fails
- */
- public static boolean delete(String pFilename, boolean pForce) throws IOException {
- return delete(new File(pFilename), pForce);
- }
-
- /**
- * Deletes the specified file.
- *
- * @param pFile The file to delete
- * @return {@code true}, if the file existed and was deleted.
- * @throws java.io.IOException if deletion fails
- */
- public static boolean delete(File pFile) throws IOException {
- return delete(pFile, false);
- }
-
- /**
- * Deletes the specified file.
- *
- * @param pFilename The name of file to delete
- * @return {@code true}, if the file existed and was deleted.
- * @throws java.io.IOException if deletion fails
- */
- public static boolean delete(String pFilename) throws IOException {
- return delete(new File(pFilename), false);
- }
-
- /**
- * Renames the specified file.
- * If the destination is a directory (and the source is not), the source
- * file is simply moved to the destination directory.
- *
- * @param pFrom The file to rename
- * @param pTo The new file
- * @param pOverWrite Specifies if the tofile should be overwritten, if it
- * exists
- * @return {@code true}, if the file was renamed.
- *
- * @throws FileNotFoundException if {@code pFrom} does not exist.
- */
- public static boolean rename(File pFrom, File pTo, boolean pOverWrite) throws IOException {
- if (!pFrom.exists()) {
- throw new FileNotFoundException(pFrom.getAbsolutePath());
- }
-
- if (pFrom.isFile() && pTo.isDirectory()) {
- pTo = new File(pTo, pFrom.getName());
- }
- return (pOverWrite || !pTo.exists()) && pFrom.renameTo(pTo);
-
- }
-
- /**
- * Renames the specified file, if the destination does not exist.
- * If the destination is a directory (and the source is not), the source
- * file is simply moved to the destination directory.
- *
- * @param pFrom The file to rename
- * @param pTo The new file
- * @return {@code true}, if the file was renamed.
- * @throws java.io.IOException if rename fails
- */
- public static boolean rename(File pFrom, File pTo) throws IOException {
- return rename(pFrom, pTo, false);
- }
-
- /**
- * Renames the specified file.
- * If the destination is a directory (and the source is not), the source
- * file is simply moved to the destination directory.
- *
- * @param pFrom The file to rename
- * @param pTo The new name of the file
- * @param pOverWrite Specifies if the tofile should be overwritten, if it
- * exists
- * @return {@code true}, if the file was renamed.
- * @throws java.io.IOException if rename fails
- */
- public static boolean rename(File pFrom, String pTo, boolean pOverWrite) throws IOException {
- return rename(pFrom, new File(pTo), pOverWrite);
- }
-
- /**
- * Renames the specified file, if the destination does not exist.
- * If the destination is a directory (and the source is not), the source
- * file is simply moved to the destination directory.
- *
- * @param pFrom The file to rename
- * @param pTo The new name of the file
- * @return {@code true}, if the file was renamed.
- * @throws java.io.IOException if rename fails
- */
- public static boolean rename(File pFrom, String pTo) throws IOException {
- return rename(pFrom, new File(pTo), false);
- }
-
- /**
- * Renames the specified file.
- * If the destination is a directory (and the source is not), the source
- * file is simply moved to the destination directory.
- *
- * @param pFrom The name of the file to rename
- * @param pTo The new name of the file
- * @param pOverWrite Specifies if the tofile should be overwritten, if it
- * exists
- * @return {@code true}, if the file was renamed.
- * @throws java.io.IOException if rename fails
- */
- public static boolean rename(String pFrom, String pTo, boolean pOverWrite) throws IOException {
- return rename(new File(pFrom), new File(pTo), pOverWrite);
- }
-
- /**
- * Renames the specified file, if the destination does not exist.
- * If the destination is a directory (and the source is not), the source
- * file is simply moved to the destination directory.
- *
- * @param pFrom The name of the file to rename
- * @param pTo The new name of the file
- * @return {@code true}, if the file was renamed.
- * @throws java.io.IOException if rename fails
- */
- public static boolean rename(String pFrom, String pTo) throws IOException {
- return rename(new File(pFrom), new File(pTo), false);
- }
-
- /**
- * Lists all files (and directories) in a specific folder.
- *
- * @param pFolder The folder to list
- * @return a list of {@code java.io.File} objects.
- * @throws FileNotFoundException if {@code pFolder} is not a readable file
- */
- public static File[] list(final String pFolder) throws FileNotFoundException {
- return list(pFolder, null);
- }
-
- /**
- * Lists all files (and directories) in a specific folder which are
- * embraced by the wildcard filename mask provided.
- *
- * @param pFolder The folder to list
- * @param pFilenameMask The wildcard filename mask
- * @return a list of {@code java.io.File} objects.
- * @see File#listFiles(FilenameFilter)
- * @throws FileNotFoundException if {@code pFolder} is not a readable file
- */
- public static File[] list(final String pFolder, final String pFilenameMask) throws FileNotFoundException {
- if (StringUtil.isEmpty(pFolder)) {
- return null;
- }
-
- File folder = resolve(pFolder);
- if (!(/*folder.exists() &&*/folder.isDirectory() && folder.canRead())) {
- // NOTE: exists is implicitly called by isDirectory
- throw new FileNotFoundException("\"" + pFolder + "\" is not a directory or is not readable.");
- }
-
- if (StringUtil.isEmpty(pFilenameMask)) {
- return folder.listFiles();
- }
-
- // TODO: Rewrite to use regexp
-
- FilenameFilter filter = new FilenameMaskFilter(pFilenameMask);
- return folder.listFiles(filter);
- }
-
- /**
- * Creates a {@code File} based on the path part of the URL, for
- * file-protocol ({@code file:}) based URLs.
- *
- * @param pURL the {@code file:} URL
- * @return a new {@code File} object representing the URL
- *
- * @throws NullPointerException if {@code pURL} is {@code null}
- * @throws IllegalArgumentException if {@code pURL} is
- * not a file-protocol URL.
- *
- * @see java.io.File#toURI()
- * @see java.io.File#File(java.net.URI)
- */
- public static File toFile(URL pURL) {
- if (pURL == null) {
- throw new NullPointerException("URL == null");
- }
-
- // NOTE: Precondition tests below is based on the File(URI) constructor,
- // and is most likely overkill...
- // NOTE: A URI is absolute iff it has a scheme component
- // As the scheme has to be "file", this is implicitly tested below
- // NOTE: A URI is opaque iff it is absolute and it's shceme-specific
- // part does not begin with a '/', see below
- if (!"file".equals(pURL.getProtocol())) {
- // URL protocol => URI scheme
- throw new IllegalArgumentException("URL scheme is not \"file\"");
- }
- if (pURL.getAuthority() != null) {
- throw new IllegalArgumentException("URL has an authority component");
- }
- if (pURL.getRef() != null) {
- // URL ref (anchor) => URI fragment
- throw new IllegalArgumentException("URI has a fragment component");
- }
- if (pURL.getQuery() != null) {
- throw new IllegalArgumentException("URL has a query component");
- }
- String path = pURL.getPath();
- if (!path.startsWith("/")) {
- // A URL should never be able to represent an opaque URI, test anyway
- throw new IllegalArgumentException("URI is not hierarchical");
- }
- if (path.equals("")) {
- throw new IllegalArgumentException("URI path component is empty");
- }
-
- // Convert separator, doesn't seem to be neccessary on Windows/Unix,
- // but do it anyway to be compatible...
- if (File.separatorChar != '/') {
- path = path.replace('/', File.separatorChar);
- }
-
- return resolve(path);
- }
-
- public static File resolve(String pPath) {
- return Win32File.wrap(new File(pPath));
- }
-
- public static File resolve(File pPath) {
- return Win32File.wrap(pPath);
- }
-
- public static File resolve(File pParent, String pChild) {
- return Win32File.wrap(new File(pParent, pChild));
- }
-
- public static File[] resolve(File[] pPaths) {
- return Win32File.wrap(pPaths);
- }
-
- // TODO: Handle SecurityManagers in a deterministic way
- // TODO: Exception handling
- // TODO: What happens if the file does not exist?
- public static long getFreeSpace(final File pPath) {
- // NOTE: Allow null, to get space in current/system volume
- File path = pPath != null ? pPath : new File(".");
-
- Long space = getSpace16("getFreeSpace", path);
- if (space != null) {
- return space;
- }
-
- return FS.getFreeSpace(path);
- }
-
- public static long getUsableSpace(final File pPath) {
- // NOTE: Allow null, to get space in current/system volume
- File path = pPath != null ? pPath : new File(".");
-
- Long space = getSpace16("getUsableSpace", path);
- if (space != null) {
- return space;
- }
-
- return getTotalSpace(path);
- }
-
- // TODO: FixMe for Windows, before making it public...
- public static long getTotalSpace(final File pPath) {
- // NOTE: Allow null, to get space in current/system volume
- File path = pPath != null ? pPath : new File(".");
-
- Long space = getSpace16("getTotalSpace", path);
- if (space != null) {
- return space;
- }
-
- return FS.getTotalSpace(path);
- }
-
- private static Long getSpace16(final String pMethodName, final File pPath) {
- try {
- Method freeSpace = File.class.getMethod(pMethodName);
- return (Long) freeSpace.invoke(pPath);
- }
- catch (NoSuchMethodException ignore) {}
- catch (IllegalAccessException ignore) {}
- catch (InvocationTargetException e) {
- Throwable throwable = e.getTargetException();
- if (throwable instanceof SecurityException) {
- throw (SecurityException) throwable;
- }
- throw new UndeclaredThrowableException(throwable);
- }
-
- return null;
- }
-
- /**
- * Formats the given number to a human readable format.
- * Kind of like {@code df -h}.
- *
- * @param pSizeInBytes the size in byte
- * @return a human readable string representation
- */
- public static String toHumanReadableSize(final long pSizeInBytes) {
- // TODO: Rewrite to use String.format?
- if (pSizeInBytes < 1024L) {
- return pSizeInBytes + " Bytes";
- }
- else if (pSizeInBytes < (1024L << 10)) {
- return getSizeFormat().format(pSizeInBytes / (double) (1024L)) + " KB";
- }
- else if (pSizeInBytes < (1024L << 20)) {
- return getSizeFormat().format(pSizeInBytes / (double) (1024L << 10)) + " MB";
- }
- else if (pSizeInBytes < (1024L << 30)) {
- return getSizeFormat().format(pSizeInBytes / (double) (1024L << 20)) + " GB";
- }
- else if (pSizeInBytes < (1024L << 40)) {
- return getSizeFormat().format(pSizeInBytes / (double) (1024L << 30)) + " TB";
- }
- else {
- return getSizeFormat().format(pSizeInBytes / (double) (1024L << 40)) + " PB";
- }
- }
-
- // NumberFormat is not thread-safe, so we stick to thread-confined instances
- private static ThreadLocal sNumberFormat = new ThreadLocal() {
- protected NumberFormat initialValue() {
- NumberFormat format = NumberFormat.getNumberInstance();
- // TODO: Consider making this locale/platform specific, OR a method parameter...
-// format.setMaximumFractionDigits(2);
- format.setMaximumFractionDigits(0);
- return format;
- }
- };
-
- private static NumberFormat getSizeFormat() {
- return sNumberFormat.get();
- }
-
- /**
- * Visits all files in {@code pDirectory}. Optionally filtered through a {@link FileFilter}.
- *
- * @param pDirectory the directory to visit files in
- * @param pFilter the filter, may be {@code null}, meaning all files will be visited
- * @param pVisitor the visitor
- *
- * @throws IllegalArgumentException if either {@code pDirectory} or {@code pVisitor} are {@code null}
- *
- * @see com.twelvemonkeys.util.Visitor
- */
- @SuppressWarnings({"ResultOfMethodCallIgnored"})
- public static void visitFiles(final File pDirectory, final FileFilter pFilter, final Visitor pVisitor) {
- Validate.notNull(pDirectory, "directory");
- Validate.notNull(pVisitor, "visitor");
-
- pDirectory.listFiles(new FileFilter() {
- public boolean accept(final File pFile) {
- if (pFilter == null || pFilter.accept(pFile)) {
- pVisitor.visit(pFile);
- }
-
- return false;
- }
- });
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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 com.twelvemonkeys.lang.Validate;
+import com.twelvemonkeys.util.Visitor;
+
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.UndeclaredThrowableException;
+import java.net.URL;
+import java.text.NumberFormat;
+
+/**
+ * A utility class with some useful file and i/o related methods.
+ *
+ * Versions exists take Input and OutputStreams as parameters, to
+ * allow for copying streams (URL's etc.).
+ *
+ * @author Harald Kuhr
+ * @author Eirik Torske
+ * @author last modified by $Author: haku $
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/FileUtil.java#3 $
+ */
+public final class FileUtil {
+ // TODO: Be more cosequent using resolve() all places where File objects are involved
+ // TODO: Parameter handling (allow null vs IllegalArgument)
+ // TODO: Exception handling
+
+ /**
+ * The size of the buffer used for copying
+ */
+ public final static int BUF_SIZE = 1024;
+ private static String TEMP_DIR = null;
+
+ private final static FileSystem FS = FileSystem.get();
+
+ public static void main(String[] pArgs) throws IOException {
+ File file;
+ if (pArgs[0].startsWith("file:")) {
+ file = toFile(new URL(pArgs[0]));
+ System.out.println(file);
+ }
+ else {
+ file = new File(pArgs[0]);
+ System.out.println(file.toURL());
+ }
+
+ System.out.println("Free space: " + getFreeSpace(file) + "/" + getTotalSpace(file) + " bytes");
+ }
+
+ /*
+ * Method main for test only.
+ *
+ public static void main0(String[] pArgs) {
+ if (pArgs.length != 2) {
+ System.out.println("usage: java Copy in out");
+ return;
+ }
+ try {
+ if (!copy(pArgs[0], pArgs[1])) {
+ System.out.println("Error copying");
+ }
+ }
+ catch (IOException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+ //*/
+
+ // Avoid instances/constructor showing up in API doc
+ private FileUtil() {}
+
+ /**
+ * Copies the fromFile to the toFile location. If toFile is a directory, a
+ * new file is created in that directory, with the name of the fromFile.
+ * If the toFile exists, the file will not be copied, unless owerWrite is
+ * true.
+ *
+ * @param pFromFileName The name of the file to copy from
+ * @param pToFileName The name of the file to copy to
+ * @return true if the file was copied successfully,
+ * false if the output file exists. In all other cases, an
+ * IOException is thrown, and the method does not return a value.
+ * @throws IOException if an i/o error occurs during copy
+ */
+ public static boolean copy(String pFromFileName, String pToFileName) throws IOException {
+ return copy(new File(pFromFileName), new File(pToFileName), false);
+ }
+
+ /**
+ * Copies the fromFile to the toFile location. If toFile is a directory, a
+ * new file is created in that directory, with the name of the fromFile.
+ * If the toFile exists, the file will not be copied, unless owerWrite is
+ * true.
+ *
+ * @param pFromFileName The name of the file to copy from
+ * @param pToFileName The name of the file to copy to
+ * @param pOverWrite Specifies if the toFile should be overwritten, if it
+ * exists.
+ * @return true if the file was copied successfully,
+ * false if the output file exists, and the owerWrite parameter is
+ * false. In all other cases, an
+ * IOException is thrown, and the method does not return a value.
+ * @throws IOException if an i/o error occurs during copy
+ */
+ public static boolean copy(String pFromFileName, String pToFileName, boolean pOverWrite) throws IOException {
+ return copy(new File(pFromFileName), new File(pToFileName), pOverWrite);
+ }
+
+ /**
+ * Copies the fromFile to the toFile location. If toFile is a directory, a
+ * new file is created in that directory, with the name of the fromFile.
+ * If the toFile exists, the file will not be copied, unless owerWrite is
+ * true.
+ *
+ * @param pFromFile The file to copy from
+ * @param pToFile The file to copy to
+ * @return true if the file was copied successfully,
+ * false if the output file exists. In all other cases, an
+ * IOException is thrown, and the method does not return a value.
+ * @throws IOException if an i/o error occurs during copy
+ */
+ public static boolean copy(File pFromFile, File pToFile) throws IOException {
+ return copy(pFromFile, pToFile, false);
+ }
+
+ /**
+ * Copies the fromFile to the toFile location. If toFile is a directory, a
+ * new file is created in that directory, with the name of the fromFile.
+ * If the toFile exists, the file will not be copied, unless owerWrite is
+ * true.
+ *
+ * @param pFromFile The file to copy from
+ * @param pToFile The file to copy to
+ * @param pOverWrite Specifies if the toFile should be overwritten, if it
+ * exists.
+ * @return {@code true} if the file was copied successfully,
+ * {@code false} if the output file exists, and the
+ * {@code pOwerWrite} parameter is
+ * {@code false}. In all other cases, an
+ * {@code IOExceptio}n is thrown, and the method does not return.
+ * @throws IOException if an i/o error occurs during copy
+ */
+ public static boolean copy(File pFromFile, File pToFile, boolean pOverWrite) throws IOException {
+ // Copy all directory structure
+ if (pFromFile.isDirectory()) {
+ return copyDir(pFromFile, pToFile, pOverWrite);
+ }
+
+ // Check if destination is a directory
+ if (pToFile.isDirectory()) {
+ // Create a new file with same name as from
+ pToFile = new File(pToFile, pFromFile.getName());
+ }
+
+ // Check if file exists, and return false if overWrite is false
+ if (!pOverWrite && pToFile.exists()) {
+ return false;
+ }
+
+ InputStream in = null;
+ OutputStream out = null;
+
+ try {
+ // Use buffer size two times byte array, to avoid i/o bottleneck
+ in = new FileInputStream(pFromFile);
+ out = new FileOutputStream(pToFile);
+
+ // Copy from inputStream to outputStream
+ copy(in, out);
+ }
+ //Just pass any IOException on up the stack
+ finally {
+ close(in);
+ close(out);
+ }
+
+ return true; // If we got here, everything is probably okay.. ;-)
+ }
+
+ /**
+ * Tries to close the given stream.
+ * NOTE: If the stream cannot be closed, the IOException thrown is silently
+ * ignored.
+ *
+ * @param pInput the stream to close
+ */
+ public static void close(InputStream pInput) {
+ try {
+ if (pInput != null) {
+ pInput.close();
+ }
+ }
+ catch (IOException ignore) {
+ // Non critical error
+ }
+ }
+
+ /**
+ * Tries to close the given stream.
+ * NOTE: If the stream cannot be closed, the IOException thrown is silently
+ * ignored.
+ *
+ * @param pOutput the stream to close
+ */
+ public static void close(OutputStream pOutput) {
+ try {
+ if (pOutput != null) {
+ pOutput.close();
+ }
+ }
+ catch (IOException ignore) {
+ // Non critical error
+ }
+ }
+
+ static void close(Reader pReader) {
+ try {
+ if (pReader != null) {
+ pReader.close();
+ }
+ }
+ catch (IOException ignore) {
+ // Non critical error
+ }
+ }
+
+ static void close(Writer pWriter) {
+ try {
+ if (pWriter != null) {
+ pWriter.close();
+ }
+ }
+ catch (IOException ignore) {
+ // Non critical error
+ }
+ }
+
+ /**
+ * Copies a directory recursively. If the destination folder does not exist,
+ * it is created
+ *
+ * @param pFrom the source directory
+ * @param pTo the destination directory
+ * @param pOverWrite {@code true} if we should allow overwrting existing files
+ * @return {@code true} if all files were copied sucessfully
+ * @throws IOException if {@code pTo} exists, and it not a directory,
+ * or if copying of any of the files in the folder fails
+ */
+ private static boolean copyDir(File pFrom, File pTo, boolean pOverWrite) throws IOException {
+ if (pTo.exists() && !pTo.isDirectory()) {
+ throw new IOException("A directory may only be copied to another directory, not to a file");
+ }
+ pTo.mkdirs(); // mkdir?
+ boolean allOkay = true;
+ File[] files = pFrom.listFiles();
+
+ for (File file : files) {
+ if (!copy(file, new File(pTo, file.getName()), pOverWrite)) {
+ allOkay = false;
+ }
+ }
+ return allOkay;
+ }
+
+ /**
+ * Copies all data from one stream to another.
+ * The data is copied from the fromStream to the toStream using buffered
+ * streams for efficiency.
+ *
+ * @param pFrom The input srteam to copy from
+ * @param pTo The output stream to copy to
+ * @return true. Otherwise, an
+ * IOException is thrown, and the method does not return a value.
+ * @throws IOException if an i/o error occurs during copy
+ * @throws IllegalArgumentException if either {@code pFrom} or {@code pTo} is
+ * {@code null}
+ */
+ public static boolean copy(InputStream pFrom, OutputStream pTo) throws IOException {
+ Validate.notNull(pFrom, "from");
+ Validate.notNull(pTo, "to");
+
+ // TODO: Consider using file channels for faster copy where possible
+
+ // Use buffer size two times byte array, to avoid i/o bottleneck
+ // TODO: Consider letting the client decide as this is sometimes not a good thing!
+ InputStream in = new BufferedInputStream(pFrom, BUF_SIZE * 2);
+ OutputStream out = new BufferedOutputStream(pTo, BUF_SIZE * 2);
+
+ byte[] buffer = new byte[BUF_SIZE];
+ int count;
+
+ while ((count = in.read(buffer)) != -1) {
+ out.write(buffer, 0, count);
+ }
+
+ // Flush out stream, to write any remaining buffered data
+ out.flush();
+
+ return true; // If we got here, everything is probably okay.. ;-)
+ }
+
+ /**
+ * Gets the file (type) extension of the given file.
+ * A file extension is the part of the filename, after the last occurence
+ * of a period {@code '.'}.
+ * If the filename contains no period, {@code null} is returned.
+ *
+ * @param pFileName the full filename with extension
+ * @return the extension (type) of the file, or {@code null}
+ */
+ public static String getExtension(final String pFileName) {
+ return getExtension0(getFilename(pFileName));
+ }
+
+ /**
+ * Gets the file (type) extension of the given file.
+ * A file extension is the part of the filename, after the last occurence
+ * of a period {@code '.'}.
+ * If the filename contains no period, {@code null} is returned.
+ *
+ * @param pFile the file
+ * @return the extension (type) of the file, or {@code null}
+ */
+ public static String getExtension(final File pFile) {
+ return getExtension0(pFile.getName());
+ }
+
+ // NOTE: Assumes filename and no path
+ private static String getExtension0(final String pFileName) {
+ int index = pFileName.lastIndexOf('.');
+
+ if (index >= 0) {
+ return pFileName.substring(index + 1);
+ }
+
+ // No period found
+ return null;
+ }
+
+
+ /**
+ * Gets the file name of the given file, without the extension (type).
+ * A file extension is the part of the filename, after the last occurence
+ * of a period {@code '.'}.
+ * If the filename contains no period, the complete file name is returned
+ * (same as {@code pFileName}, if the string contains no path elements).
+ *
+ * @param pFileName the full filename with extension
+ * @return the base name of the file
+ */
+ public static String getBasename(final String pFileName) {
+ return getBasename0(getFilename(pFileName));
+ }
+
+ /**
+ * Gets the file name of the given file, without the extension (type).
+ * A file extension is the part of the filename, after the last occurence
+ * of a period {@code '.'}.
+ * If the filename contains no period, {@code pFile.getName()} is returned.
+ *
+ * @param pFile the file
+ * @return the base name of the file
+ */
+ public static String getBasename(final File pFile) {
+ return getBasename0(pFile.getName());
+ }
+
+ // NOTE: Assumes filename and no path
+ public static String getBasename0(final String pFileName) {
+ int index = pFileName.lastIndexOf('.');
+
+ if (index >= 0) {
+ return pFileName.substring(0, index);
+ }
+
+ // No period found
+ return pFileName;
+ }
+
+ /**
+ * Extracts the directory path without the filename, from a complete
+ * filename path.
+ *
+ * @param pPath The full filename path.
+ * @return the path without the filename.
+ * @see File#getParent
+ * @see #getFilename
+ */
+ public static String getDirectoryname(final String pPath) {
+ return getDirectoryname(pPath, File.separatorChar);
+ }
+
+ /**
+ * Extracts the directory path without the filename, from a complete
+ * filename path.
+ *
+ * @param pPath The full filename path.
+ * @param pSeparator the separator char used in {@code pPath}
+ * @return the path without the filename.
+ * @see File#getParent
+ * @see #getFilename
+ */
+ public static String getDirectoryname(final String pPath, final char pSeparator) {
+ int index = pPath.lastIndexOf(pSeparator);
+
+ if (index < 0) {
+ return ""; // Assume only filename
+ }
+ return pPath.substring(0, index);
+ }
+
+ /**
+ * Extracts the filename of a complete filename path.
+ *
+ * @param pPath The full filename path.
+ * @return the extracted filename.
+ * @see File#getName
+ * @see #getDirectoryname
+ */
+ public static String getFilename(final String pPath) {
+ return getFilename(pPath, File.separatorChar);
+ }
+
+ /**
+ * Extracts the filename of a complete filename path.
+ *
+ * @param pPath The full filename path.
+ * @param pSeparator The file separator.
+ * @return the extracted filename.
+ * @see File#getName
+ * @see #getDirectoryname
+ */
+ public static String getFilename(final String pPath, final char pSeparator) {
+ int index = pPath.lastIndexOf(pSeparator);
+
+ if (index < 0) {
+ return pPath; // Assume only filename
+ }
+
+ return pPath.substring(index + 1);
+ }
+
+
+ /**
+ * Tests if a file or directory has no content.
+ * A file is empty if it has a length of 0L. A non-existing file is also
+ * considered empty.
+ * A directory is considered empty if it contains no files.
+ *
+ * @param pFile The file to test
+ * @return {@code true} if the file is empty, otherwise
+ * {@code false}.
+ */
+ public static boolean isEmpty(File pFile) {
+ if (pFile.isDirectory()) {
+ return (pFile.list().length == 0);
+ }
+ return (pFile.length() == 0);
+ }
+
+ /**
+ * Gets the default temp directory for the system as a File.
+ *
+ * @return a {@code File}, representing the default temp directory.
+ * @see File#createTempFile
+ */
+ public static File getTempDirFile() {
+ return new File(getTempDir());
+ }
+
+ /**
+ * Gets the default temp directory for the system.
+ *
+ * @return a {@code String}, representing the path to the default temp
+ * directory.
+ * @see File#createTempFile
+ */
+ public static String getTempDir() {
+ synchronized (FileUtil.class) {
+ if (TEMP_DIR == null) {
+ // Get the 'java.io.tmpdir' property
+ String tmpDir = System.getProperty("java.io.tmpdir");
+
+ if (StringUtil.isEmpty(tmpDir)) {
+ // Stupid fallback...
+ // TODO: Delegate to FileSystem?
+ if (new File("/temp").exists()) {
+ tmpDir = "/temp"; // Windows
+ }
+ else {
+ tmpDir = "/tmp"; // Unix
+ }
+ }
+ TEMP_DIR = tmpDir;
+ }
+ }
+ return TEMP_DIR;
+ }
+
+ /**
+ * Gets the contents of the given file, as a byte array.
+ *
+ * @param pFilename the name of the file to get content from
+ * @return the content of the file as a byte array.
+ * @throws IOException if the read operation fails
+ */
+ public static byte[] read(String pFilename) throws IOException {
+ return read(new File(pFilename));
+ }
+
+ /**
+ * Gets the contents of the given file, as a byte array.
+ *
+ * @param pFile the file to get content from
+ * @return the content of the file as a byte array.
+ * @throws IOException if the read operation fails
+ */
+ public static byte[] read(File pFile) throws IOException {
+ // Custom implementation, as we know the size of a file
+ if (!pFile.exists()) {
+ throw new FileNotFoundException(pFile.toString());
+ }
+
+ byte[] bytes = new byte[(int) pFile.length()];
+ InputStream in = null;
+
+ try {
+ // Use buffer size two times byte array, to avoid i/o bottleneck
+ in = new BufferedInputStream(new FileInputStream(pFile), BUF_SIZE * 2);
+
+ int off = 0;
+ int len;
+ while ((len = in.read(bytes, off, in.available())) != -1 && (off < bytes.length)) {
+ off += len;
+ // System.out.println("read:" + len);
+ }
+ }
+ // Just pass any IOException on up the stack
+ finally {
+ close(in);
+ }
+
+ return bytes;
+ }
+
+ /**
+ * Reads all data from the input stream to a byte array.
+ *
+ * @param pInput The input stream to read from
+ * @return The content of the stream as a byte array.
+ * @throws IOException if an i/o error occurs during read.
+ */
+ public static byte[] read(InputStream pInput) throws IOException {
+ // Create byte array
+ ByteArrayOutputStream bytes = new FastByteArrayOutputStream(BUF_SIZE);
+
+ // Copy from stream to byte array
+ copy(pInput, bytes);
+
+ return bytes.toByteArray();
+ }
+
+ /**
+ * Writes the contents from a byte array to an output stream.
+ *
+ * @param pOutput The output stream to write to
+ * @param pData The byte array to write
+ * @return {@code true}, otherwise an IOException is thrown.
+ * @throws IOException if an i/o error occurs during write.
+ */
+ public static boolean write(OutputStream pOutput, byte[] pData) throws IOException {
+ // Write data
+ pOutput.write(pData);
+
+ // If we got here, all is okay
+ return true;
+ }
+
+ /**
+ * Writes the contents from a byte array to a file.
+ *
+ * @param pFile The file to write to
+ * @param pData The byte array to write
+ * @return {@code true}, otherwise an IOException is thrown.
+ * @throws IOException if an i/o error occurs during write.
+ */
+ public static boolean write(File pFile, byte[] pData) throws IOException {
+ boolean success = false;
+ OutputStream out = null;
+
+ try {
+ out = new BufferedOutputStream(new FileOutputStream(pFile));
+ success = write(out, pData);
+ }
+ finally {
+ close(out);
+ }
+ return success;
+ }
+
+ /**
+ * Writes the contents from a byte array to a file.
+ *
+ * @param pFilename The name of the file to write to
+ * @param pData The byte array to write
+ * @return {@code true}, otherwise an IOException is thrown.
+ * @throws IOException if an i/o error occurs during write.
+ */
+ public static boolean write(String pFilename, byte[] pData) throws IOException {
+ return write(new File(pFilename), pData);
+ }
+
+ /**
+ * Deletes the specified file.
+ *
+ * @param pFile The file to delete
+ * @param pForce Forces delete, even if the parameter is a directory, and
+ * is not empty. Be careful!
+ * @return {@code true}, if the file existed and was deleted.
+ * @throws IOException if an i/o error occurs during delete.
+ */
+ public static boolean delete(final File pFile, final boolean pForce) throws IOException {
+ if (pForce && pFile.isDirectory()) {
+ return deleteDir(pFile);
+ }
+ return pFile.exists() && pFile.delete();
+ }
+
+ /**
+ * Deletes a directory recursively.
+ *
+ * @param pFile the file to delete
+ * @return {@code true} if the file was deleted sucessfully
+ * @throws IOException if an i/o error occurs during delete.
+ */
+ private static boolean deleteDir(final File pFile) throws IOException {
+ // Recusively delete all files/subfolders
+ // Deletes the files using visitor pattern, to avoid allocating
+ // a file array, which may throw OutOfMemoryExceptions for
+ // large directories/in low memory situations
+ class DeleteFilesVisitor implements Visitor {
+ private int failedCount = 0;
+ private IOException exception = null;
+
+ public void visit(final File pFile) {
+ try {
+ if (!delete(pFile, true)) {
+ failedCount++;
+ }
+ }
+ catch (IOException e) {
+ failedCount++;
+ if (exception == null) {
+ exception = e;
+ }
+ }
+ }
+
+ boolean succeeded() throws IOException {
+ if (exception != null) {
+ throw exception;
+ }
+ return failedCount == 0;
+ }
+ }
+ DeleteFilesVisitor fileDeleter = new DeleteFilesVisitor();
+ visitFiles(pFile, null, fileDeleter);
+
+ // If any of the deletes above failed, this will fail (or return false)
+ return fileDeleter.succeeded() && pFile.delete();
+ }
+
+ /**
+ * Deletes the specified file.
+ *
+ * @param pFilename The name of file to delete
+ * @param pForce Forces delete, even if the parameter is a directory, and
+ * is not empty. Careful!
+ * @return {@code true}, if the file existed and was deleted.
+ * @throws java.io.IOException if deletion fails
+ */
+ public static boolean delete(String pFilename, boolean pForce) throws IOException {
+ return delete(new File(pFilename), pForce);
+ }
+
+ /**
+ * Deletes the specified file.
+ *
+ * @param pFile The file to delete
+ * @return {@code true}, if the file existed and was deleted.
+ * @throws java.io.IOException if deletion fails
+ */
+ public static boolean delete(File pFile) throws IOException {
+ return delete(pFile, false);
+ }
+
+ /**
+ * Deletes the specified file.
+ *
+ * @param pFilename The name of file to delete
+ * @return {@code true}, if the file existed and was deleted.
+ * @throws java.io.IOException if deletion fails
+ */
+ public static boolean delete(String pFilename) throws IOException {
+ return delete(new File(pFilename), false);
+ }
+
+ /**
+ * Renames the specified file.
+ * If the destination is a directory (and the source is not), the source
+ * file is simply moved to the destination directory.
+ *
+ * @param pFrom The file to rename
+ * @param pTo The new file
+ * @param pOverWrite Specifies if the tofile should be overwritten, if it
+ * exists
+ * @return {@code true}, if the file was renamed.
+ *
+ * @throws FileNotFoundException if {@code pFrom} does not exist.
+ */
+ public static boolean rename(File pFrom, File pTo, boolean pOverWrite) throws IOException {
+ if (!pFrom.exists()) {
+ throw new FileNotFoundException(pFrom.getAbsolutePath());
+ }
+
+ if (pFrom.isFile() && pTo.isDirectory()) {
+ pTo = new File(pTo, pFrom.getName());
+ }
+ return (pOverWrite || !pTo.exists()) && pFrom.renameTo(pTo);
+
+ }
+
+ /**
+ * Renames the specified file, if the destination does not exist.
+ * If the destination is a directory (and the source is not), the source
+ * file is simply moved to the destination directory.
+ *
+ * @param pFrom The file to rename
+ * @param pTo The new file
+ * @return {@code true}, if the file was renamed.
+ * @throws java.io.IOException if rename fails
+ */
+ public static boolean rename(File pFrom, File pTo) throws IOException {
+ return rename(pFrom, pTo, false);
+ }
+
+ /**
+ * Renames the specified file.
+ * If the destination is a directory (and the source is not), the source
+ * file is simply moved to the destination directory.
+ *
+ * @param pFrom The file to rename
+ * @param pTo The new name of the file
+ * @param pOverWrite Specifies if the tofile should be overwritten, if it
+ * exists
+ * @return {@code true}, if the file was renamed.
+ * @throws java.io.IOException if rename fails
+ */
+ public static boolean rename(File pFrom, String pTo, boolean pOverWrite) throws IOException {
+ return rename(pFrom, new File(pTo), pOverWrite);
+ }
+
+ /**
+ * Renames the specified file, if the destination does not exist.
+ * If the destination is a directory (and the source is not), the source
+ * file is simply moved to the destination directory.
+ *
+ * @param pFrom The file to rename
+ * @param pTo The new name of the file
+ * @return {@code true}, if the file was renamed.
+ * @throws java.io.IOException if rename fails
+ */
+ public static boolean rename(File pFrom, String pTo) throws IOException {
+ return rename(pFrom, new File(pTo), false);
+ }
+
+ /**
+ * Renames the specified file.
+ * If the destination is a directory (and the source is not), the source
+ * file is simply moved to the destination directory.
+ *
+ * @param pFrom The name of the file to rename
+ * @param pTo The new name of the file
+ * @param pOverWrite Specifies if the tofile should be overwritten, if it
+ * exists
+ * @return {@code true}, if the file was renamed.
+ * @throws java.io.IOException if rename fails
+ */
+ public static boolean rename(String pFrom, String pTo, boolean pOverWrite) throws IOException {
+ return rename(new File(pFrom), new File(pTo), pOverWrite);
+ }
+
+ /**
+ * Renames the specified file, if the destination does not exist.
+ * If the destination is a directory (and the source is not), the source
+ * file is simply moved to the destination directory.
+ *
+ * @param pFrom The name of the file to rename
+ * @param pTo The new name of the file
+ * @return {@code true}, if the file was renamed.
+ * @throws java.io.IOException if rename fails
+ */
+ public static boolean rename(String pFrom, String pTo) throws IOException {
+ return rename(new File(pFrom), new File(pTo), false);
+ }
+
+ /**
+ * Lists all files (and directories) in a specific folder.
+ *
+ * @param pFolder The folder to list
+ * @return a list of {@code java.io.File} objects.
+ * @throws FileNotFoundException if {@code pFolder} is not a readable file
+ */
+ public static File[] list(final String pFolder) throws FileNotFoundException {
+ return list(pFolder, null);
+ }
+
+ /**
+ * Lists all files (and directories) in a specific folder which are
+ * embraced by the wildcard filename mask provided.
+ *
+ * @param pFolder The folder to list
+ * @param pFilenameMask The wildcard filename mask
+ * @return a list of {@code java.io.File} objects.
+ * @see File#listFiles(FilenameFilter)
+ * @throws FileNotFoundException if {@code pFolder} is not a readable file
+ */
+ public static File[] list(final String pFolder, final String pFilenameMask) throws FileNotFoundException {
+ if (StringUtil.isEmpty(pFolder)) {
+ return null;
+ }
+
+ File folder = resolve(pFolder);
+ if (!(/*folder.exists() &&*/folder.isDirectory() && folder.canRead())) {
+ // NOTE: exists is implicitly called by isDirectory
+ throw new FileNotFoundException("\"" + pFolder + "\" is not a directory or is not readable.");
+ }
+
+ if (StringUtil.isEmpty(pFilenameMask)) {
+ return folder.listFiles();
+ }
+
+ // TODO: Rewrite to use regexp
+
+ FilenameFilter filter = new FilenameMaskFilter(pFilenameMask);
+ return folder.listFiles(filter);
+ }
+
+ /**
+ * Creates a {@code File} based on the path part of the URL, for
+ * file-protocol ({@code file:}) based URLs.
+ *
+ * @param pURL the {@code file:} URL
+ * @return a new {@code File} object representing the URL
+ *
+ * @throws NullPointerException if {@code pURL} is {@code null}
+ * @throws IllegalArgumentException if {@code pURL} is
+ * not a file-protocol URL.
+ *
+ * @see java.io.File#toURI()
+ * @see java.io.File#File(java.net.URI)
+ */
+ public static File toFile(URL pURL) {
+ if (pURL == null) {
+ throw new NullPointerException("URL == null");
+ }
+
+ // NOTE: Precondition tests below is based on the File(URI) constructor,
+ // and is most likely overkill...
+ // NOTE: A URI is absolute iff it has a scheme component
+ // As the scheme has to be "file", this is implicitly tested below
+ // NOTE: A URI is opaque iff it is absolute and it's shceme-specific
+ // part does not begin with a '/', see below
+ if (!"file".equals(pURL.getProtocol())) {
+ // URL protocol => URI scheme
+ throw new IllegalArgumentException("URL scheme is not \"file\"");
+ }
+ if (pURL.getAuthority() != null) {
+ throw new IllegalArgumentException("URL has an authority component");
+ }
+ if (pURL.getRef() != null) {
+ // URL ref (anchor) => URI fragment
+ throw new IllegalArgumentException("URI has a fragment component");
+ }
+ if (pURL.getQuery() != null) {
+ throw new IllegalArgumentException("URL has a query component");
+ }
+ String path = pURL.getPath();
+ if (!path.startsWith("/")) {
+ // A URL should never be able to represent an opaque URI, test anyway
+ throw new IllegalArgumentException("URI is not hierarchical");
+ }
+ if (path.equals("")) {
+ throw new IllegalArgumentException("URI path component is empty");
+ }
+
+ // Convert separator, doesn't seem to be neccessary on Windows/Unix,
+ // but do it anyway to be compatible...
+ if (File.separatorChar != '/') {
+ path = path.replace('/', File.separatorChar);
+ }
+
+ return resolve(path);
+ }
+
+ public static File resolve(String pPath) {
+ return Win32File.wrap(new File(pPath));
+ }
+
+ public static File resolve(File pPath) {
+ return Win32File.wrap(pPath);
+ }
+
+ public static File resolve(File pParent, String pChild) {
+ return Win32File.wrap(new File(pParent, pChild));
+ }
+
+ public static File[] resolve(File[] pPaths) {
+ return Win32File.wrap(pPaths);
+ }
+
+ // TODO: Handle SecurityManagers in a deterministic way
+ // TODO: Exception handling
+ // TODO: What happens if the file does not exist?
+ public static long getFreeSpace(final File pPath) {
+ // NOTE: Allow null, to get space in current/system volume
+ File path = pPath != null ? pPath : new File(".");
+
+ Long space = getSpace16("getFreeSpace", path);
+ if (space != null) {
+ return space;
+ }
+
+ return FS.getFreeSpace(path);
+ }
+
+ public static long getUsableSpace(final File pPath) {
+ // NOTE: Allow null, to get space in current/system volume
+ File path = pPath != null ? pPath : new File(".");
+
+ Long space = getSpace16("getUsableSpace", path);
+ if (space != null) {
+ return space;
+ }
+
+ return getTotalSpace(path);
+ }
+
+ // TODO: FixMe for Windows, before making it public...
+ public static long getTotalSpace(final File pPath) {
+ // NOTE: Allow null, to get space in current/system volume
+ File path = pPath != null ? pPath : new File(".");
+
+ Long space = getSpace16("getTotalSpace", path);
+ if (space != null) {
+ return space;
+ }
+
+ return FS.getTotalSpace(path);
+ }
+
+ private static Long getSpace16(final String pMethodName, final File pPath) {
+ try {
+ Method freeSpace = File.class.getMethod(pMethodName);
+ return (Long) freeSpace.invoke(pPath);
+ }
+ catch (NoSuchMethodException ignore) {}
+ catch (IllegalAccessException ignore) {}
+ catch (InvocationTargetException e) {
+ Throwable throwable = e.getTargetException();
+ if (throwable instanceof SecurityException) {
+ throw (SecurityException) throwable;
+ }
+ throw new UndeclaredThrowableException(throwable);
+ }
+
+ return null;
+ }
+
+ /**
+ * Formats the given number to a human readable format.
+ * Kind of like {@code df -h}.
+ *
+ * @param pSizeInBytes the size in byte
+ * @return a human readable string representation
+ */
+ public static String toHumanReadableSize(final long pSizeInBytes) {
+ // TODO: Rewrite to use String.format?
+ if (pSizeInBytes < 1024L) {
+ return pSizeInBytes + " Bytes";
+ }
+ else if (pSizeInBytes < (1024L << 10)) {
+ return getSizeFormat().format(pSizeInBytes / (double) (1024L)) + " KB";
+ }
+ else if (pSizeInBytes < (1024L << 20)) {
+ return getSizeFormat().format(pSizeInBytes / (double) (1024L << 10)) + " MB";
+ }
+ else if (pSizeInBytes < (1024L << 30)) {
+ return getSizeFormat().format(pSizeInBytes / (double) (1024L << 20)) + " GB";
+ }
+ else if (pSizeInBytes < (1024L << 40)) {
+ return getSizeFormat().format(pSizeInBytes / (double) (1024L << 30)) + " TB";
+ }
+ else {
+ return getSizeFormat().format(pSizeInBytes / (double) (1024L << 40)) + " PB";
+ }
+ }
+
+ // NumberFormat is not thread-safe, so we stick to thread-confined instances
+ private static ThreadLocal sNumberFormat = new ThreadLocal() {
+ protected NumberFormat initialValue() {
+ NumberFormat format = NumberFormat.getNumberInstance();
+ // TODO: Consider making this locale/platform specific, OR a method parameter...
+// format.setMaximumFractionDigits(2);
+ format.setMaximumFractionDigits(0);
+ return format;
+ }
+ };
+
+ private static NumberFormat getSizeFormat() {
+ return sNumberFormat.get();
+ }
+
+ /**
+ * Visits all files in {@code pDirectory}. Optionally filtered through a {@link FileFilter}.
+ *
+ * @param pDirectory the directory to visit files in
+ * @param pFilter the filter, may be {@code null}, meaning all files will be visited
+ * @param pVisitor the visitor
+ *
+ * @throws IllegalArgumentException if either {@code pDirectory} or {@code pVisitor} are {@code null}
+ *
+ * @see com.twelvemonkeys.util.Visitor
+ */
+ @SuppressWarnings({"ResultOfMethodCallIgnored"})
+ public static void visitFiles(final File pDirectory, final FileFilter pFilter, final Visitor pVisitor) {
+ Validate.notNull(pDirectory, "directory");
+ Validate.notNull(pVisitor, "visitor");
+
+ pDirectory.listFiles(new FileFilter() {
+ public boolean accept(final File pFile) {
+ if (pFilter == null || pFilter.accept(pFile)) {
+ pVisitor.visit(pFile);
+ }
+
+ return false;
+ }
+ });
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/FilenameMaskFilter.java b/common/common-io/src/main/java/com/twelvemonkeys/io/FilenameMaskFilter.java
index 26f5e477..65e9e2de 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/FilenameMaskFilter.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/FilenameMaskFilter.java
@@ -1,244 +1,249 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.
- *
- * The file name masks are used as a filter input and is given to the class via
- * the string array property:
- * {@code filenameMasksForInclusion} - Filename mask for exclusion of
- * files (default if both properties are defined)
- * {@code filenameMasksForExclusion} - Filename mask for exclusion of
- * files.
- *
- * 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 Eirik Torske
- * @see File#list(java.io.FilenameFilter) java.io.File.list
- * @see FilenameFilter java.io.FilenameFilter
- * @see WildcardStringParser
- * @deprecated
- */
-public class FilenameMaskFilter implements FilenameFilter {
-
- // TODO: Rewrite to use regexp, or create new class
-
- // Members
- private String[] filenameMasksForInclusion;
- private String[] filenameMasksForExclusion;
- private boolean inclusion = 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) {
- filenameMasksForInclusion = pFilenameMasksForInclusion;
- }
-
- /**
- * @return the current inclusion masks
- */
- public String[] getFilenameMasksForInclusion() {
- return filenameMasksForInclusion.clone();
- }
-
- /**
- * @param pFilenameMasksForExclusion the filename masks to exclude
- */
- public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) {
- filenameMasksForExclusion = pFilenameMasksForExclusion;
- inclusion = false;
- }
-
- /**
- * @return the current exclusion masks
- */
- public String[] getFilenameMasksForExclusion() {
- return filenameMasksForExclusion.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 (inclusion) { // Inclusion
- for (String mask : filenameMasksForInclusion) {
- 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 : filenameMasksForExclusion) {
- 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 (inclusion) {
- // Inclusion
- if (filenameMasksForInclusion == null) {
- retVal.append("No filename masks set - property filenameMasksForInclusion is null!");
- }
- else {
- retVal.append(filenameMasksForInclusion.length);
- retVal.append(" filename mask(s) - ");
- for (i = 0; i < filenameMasksForInclusion.length; i++) {
- retVal.append("\"");
- retVal.append(filenameMasksForInclusion[i]);
- retVal.append("\", \"");
- }
- }
- }
- else {
- // Exclusion
- if (filenameMasksForExclusion == null) {
- retVal.append("No filename masks set - property filenameMasksForExclusion is null!");
- }
- else {
- retVal.append(filenameMasksForExclusion.length);
- retVal.append(" exclusion filename mask(s) - ");
- for (i = 0; i < filenameMasksForExclusion.length; i++) {
- retVal.append("\"");
- retVal.append(filenameMasksForExclusion[i]);
- retVal.append("\", \"");
- }
- }
- }
- return retVal.toString();
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.
+ *
+ * The file name masks are used as a filter input and is given to the class via
+ * the string array property:
+ *
+ *
+ * - {@code filenameMasksForInclusion}
+ * - Filename mask for exclusion of
+ * files (default if both properties are defined).
+ * - {@code filenameMasksForExclusion}
+ * - Filename mask for exclusion of files.
+ *
+ *
+ * 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 Eirik Torske
+ * @see File#list(java.io.FilenameFilter) java.io.File.list
+ * @see FilenameFilter java.io.FilenameFilter
+ * @see WildcardStringParser
+ * @deprecated
+ */
+public class FilenameMaskFilter implements FilenameFilter {
+
+ // TODO: Rewrite to use regexp, or create new class
+
+ // Members
+ private String[] filenameMasksForInclusion;
+ private String[] filenameMasksForExclusion;
+ private boolean inclusion = 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) {
+ filenameMasksForInclusion = pFilenameMasksForInclusion;
+ }
+
+ /**
+ * @return the current inclusion masks
+ */
+ public String[] getFilenameMasksForInclusion() {
+ return filenameMasksForInclusion.clone();
+ }
+
+ /**
+ * @param pFilenameMasksForExclusion the filename masks to exclude
+ */
+ public void setFilenameMasksForExclusion(String[] pFilenameMasksForExclusion) {
+ filenameMasksForExclusion = pFilenameMasksForExclusion;
+ inclusion = false;
+ }
+
+ /**
+ * @return the current exclusion masks
+ */
+ public String[] getFilenameMasksForExclusion() {
+ return filenameMasksForExclusion.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 (inclusion) { // Inclusion
+ for (String mask : filenameMasksForInclusion) {
+ 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 : filenameMasksForExclusion) {
+ 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 (inclusion) {
+ // Inclusion
+ if (filenameMasksForInclusion == null) {
+ retVal.append("No filename masks set - property filenameMasksForInclusion is null!");
+ }
+ else {
+ retVal.append(filenameMasksForInclusion.length);
+ retVal.append(" filename mask(s) - ");
+ for (i = 0; i < filenameMasksForInclusion.length; i++) {
+ retVal.append("\"");
+ retVal.append(filenameMasksForInclusion[i]);
+ retVal.append("\", \"");
+ }
+ }
+ }
+ else {
+ // Exclusion
+ if (filenameMasksForExclusion == null) {
+ retVal.append("No filename masks set - property filenameMasksForExclusion is null!");
+ }
+ else {
+ retVal.append(filenameMasksForExclusion.length);
+ retVal.append(" exclusion filename mask(s) - ");
+ for (i = 0; i < filenameMasksForExclusion.length; i++) {
+ retVal.append("\"");
+ retVal.append(filenameMasksForExclusion[i]);
+ retVal.append("\", \"");
+ }
+ }
+ }
+ return retVal.toString();
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java
index 5f9276f9..2cb94a03 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataInputStream.java
@@ -1,452 +1,449 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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 com.twelvemonkeys.lang.Validate;
-
-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.
- *
- * The standard {@code java.io.DataInputStream} class
- * which this class imitates reads big endian quantities.
- *
- * Warning:
- * 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.
- *
- *
- * @see com.twelvemonkeys.io.LittleEndianRandomAccessFile
- * @see java.io.DataInputStream
- * @see java.io.DataInput
- * @see java.io.DataOutput
- *
- * @author Elliotte Rusty Harold
- * @author Harald Kuhr
- * @version 2
- */
-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(Validate.notNull(pStream, "stream"));
- }
-
- /**
- * 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 << 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 big 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}.
- *
- * 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}.
- *
- * 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}.
- *
- * 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}.
- *
- * 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();
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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 com.twelvemonkeys.lang.Validate;
+
+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.
+ *
+ * The standard {@code java.io.DataInputStream} class
+ * which this class imitates reads big endian quantities.
+ *
+ *
+ * Warning:
+ * 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.
+ *
+ *
+ *
+ * @see com.twelvemonkeys.io.LittleEndianRandomAccessFile
+ * @see java.io.DataInputStream
+ * @see java.io.DataInput
+ * @see java.io.DataOutput
+ *
+ * @author Elliotte Rusty Harold
+ * @author Harald Kuhr
+ * @version 2
+ */
+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(Validate.notNull(pStream, "stream"));
+ }
+
+ /**
+ * 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 << 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 big 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}.
+ *
+ * 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}.
+ *
+ * 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}.
+ *
+ * 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}.
+ *
+ * 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()
+ */
+ public String readLine() throws IOException {
+ DataInputStream ds = new DataInputStream(in);
+ return ds.readLine();
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataOutputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataOutputStream.java
index 23c112f7..68905fa7 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataOutputStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianDataOutputStream.java
@@ -1,340 +1,342 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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 com.twelvemonkeys.lang.Validate;
-
-import java.io.*;
-
-/**
- * A little endian output stream writes primitive Java numbers
- * and characters to an output stream in a little endian format.
- *
- * The standard {@code java.io.DataOutputStream} class which this class
- * imitates uses big endian integers.
- *
- * Warning:
- * 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.
- *
- *
- * @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 bytesWritten;
-
- /**
- * 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(Validate.notNull(pStream, "stream"));
- }
-
- /**
- * 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);
- bytesWritten++;
- }
-
- /**
- * 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);
- bytesWritten += 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);
- bytesWritten++;
- }
-
- /**
- * 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);
- bytesWritten += 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);
- bytesWritten += 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);
- bytesWritten += 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);
- bytesWritten += 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));
- }
-
- bytesWritten += 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);
- }
-
- bytesWritten += 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 big 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));
- bytesWritten += 2;
- }
- else {
- out.write(0xC0 | ((c >> 6) & 0x1F));
- out.write(0x80 | (c & 0x3F));
- bytesWritten += 1;
- }
- }
-
- bytesWritten += 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 #bytesWritten
- */
- public int size() {
- return bytesWritten;
- }
+/*
+ * 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 of the copyright holder 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 HOLDER 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 com.twelvemonkeys.lang.Validate;
+
+import java.io.*;
+
+/**
+ * A little endian output stream writes primitive Java numbers
+ * and characters to an output stream in a little endian format.
+ *
+ * The standard {@code java.io.DataOutputStream} class which this class
+ * imitates uses big endian integers.
+ *
+ *
+ * Warning:
+ * 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.
+ *
+ *
+ *
+ * @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 bytesWritten;
+
+ /**
+ * 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(Validate.notNull(pStream, "stream"));
+ }
+
+ /**
+ * 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);
+ bytesWritten++;
+ }
+
+ /**
+ * 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);
+ bytesWritten += 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);
+ bytesWritten++;
+ }
+
+ /**
+ * 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);
+ bytesWritten += 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);
+ bytesWritten += 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);
+ bytesWritten += 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);
+ bytesWritten += 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));
+ }
+
+ bytesWritten += 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);
+ }
+
+ bytesWritten += 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 big 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));
+ bytesWritten += 2;
+ }
+ else {
+ out.write(0xC0 | ((c >> 6) & 0x1F));
+ out.write(0x80 | (c & 0x3F));
+ bytesWritten += 1;
+ }
+ }
+
+ bytesWritten += 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 #bytesWritten
+ */
+ public int size() {
+ return bytesWritten;
+ }
}
\ No newline at end of file
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java
index de2b32d0..98e54489 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/LittleEndianRandomAccessFile.java
@@ -1,627 +1,628 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.
- *
- * Warning:
- * 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.
- *
- *
- * @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 Harald Kuhr
- * @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 file;
-
- 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 {
- file = new RandomAccessFile(pFile, pMode);
- }
-
- public void close() throws IOException {
- file.close();
- }
-
- public FileChannel getChannel() {
- return file.getChannel();
- }
-
- public FileDescriptor getFD() throws IOException {
- return file.getFD();
- }
-
- public long getFilePointer() throws IOException {
- return file.getFilePointer();
- }
-
- public long length() throws IOException {
- return file.length();
- }
-
- public int read() throws IOException {
- return file.read();
- }
-
- public int read(final byte[] b) throws IOException {
- return file.read(b);
- }
-
- public int read(final byte[] b, final int off, final int len) throws IOException {
- return file.read(b, off, len);
- }
-
- public void readFully(final byte[] b) throws IOException {
- file.readFully(b);
- }
-
- public void readFully(final byte[] b, final int off, final int len) throws IOException {
- file.readFully(b, off, len);
- }
-
- public String readLine() throws IOException {
- return file.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 = file.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 = file.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 = file.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 = file.read();
- int byte2 = file.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 = file.read();
- int byte2 = file.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 = file.read();
- int byte2 = file.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 = file.read();
- int byte2 = file.read();
- int byte3 = file.read();
- int byte4 = file.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 = file.read();
- long byte2 = file.read();
- long byte3 = file.read();
- long byte4 = file.read();
- long byte5 = file.read();
- long byte6 = file.read();
- long byte7 = file.read();
- long byte8 = file.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 big 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 = file.read();
- int byte2 = file.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 {
- file.seek(pos);
- }
-
- public void setLength(final long newLength) throws IOException {
- file.setLength(newLength);
- }
-
- public int skipBytes(final int n) throws IOException {
- return file.skipBytes(n);
- }
-
- public void write(final byte[] b) throws IOException {
- file.write(b);
- }
-
- public void write(final byte[] b, final int off, final int len) throws IOException {
- file.write(b, off, len);
- }
-
- public void write(final int b) throws IOException {
- file.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 {
- file.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 {
- file.write(pShort & 0xFF);
- file.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 {
- file.write(pChar & 0xFF);
- file.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 {
- file.write(pInt & 0xFF);
- file.write((pInt >>> 8) & 0xFF);
- file.write((pInt >>> 16) & 0xFF);
- file.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 {
- file.write((int) pLong & 0xFF);
- file.write((int) (pLong >>> 8) & 0xFF);
- file.write((int) (pLong >>> 16) & 0xFF);
- file.write((int) (pLong >>> 24) & 0xFF);
- file.write((int) (pLong >>> 32) & 0xFF);
- file.write((int) (pLong >>> 40) & 0xFF);
- file.write((int) (pLong >>> 48) & 0xFF);
- file.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 #file
- */
- public void writeBytes(String pString) throws IOException {
- int length = pString.length();
-
- for (int i = 0; i < length; i++) {
- file.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 #file
- */
- public void writeChars(String pString) throws IOException {
- int length = pString.length();
-
- for (int i = 0; i < length; i++) {
- int c = pString.charAt(i);
- file.write(c & 0xFF);
- file.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 big 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();
- }
-
- file.write((numbytes >>> 8) & 0xFF);
- file.write(numbytes & 0xFF);
-
- for (int i = 0; i < numchars; i++) {
- int c = pString.charAt(i);
-
- if ((c >= 0x0001) && (c <= 0x007F)) {
- file.write(c);
- }
- else if (c > 0x07FF) {
- file.write(0xE0 | ((c >> 12) & 0x0F));
- file.write(0x80 | ((c >> 6) & 0x3F));
- file.write(0x80 | (c & 0x3F));
- }
- else {
- file.write(0xC0 | ((c >> 6) & 0x1F));
- file.write(0x80 | (c & 0x3F));
- }
- }
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.
+ *
+ * Warning:
+ * 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.
+ *
+ *
+ *
+ * @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 Harald Kuhr
+ * @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 file;
+
+ 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 {
+ file = new RandomAccessFile(pFile, pMode);
+ }
+
+ public void close() throws IOException {
+ file.close();
+ }
+
+ public FileChannel getChannel() {
+ return file.getChannel();
+ }
+
+ public FileDescriptor getFD() throws IOException {
+ return file.getFD();
+ }
+
+ public long getFilePointer() throws IOException {
+ return file.getFilePointer();
+ }
+
+ public long length() throws IOException {
+ return file.length();
+ }
+
+ public int read() throws IOException {
+ return file.read();
+ }
+
+ public int read(final byte[] b) throws IOException {
+ return file.read(b);
+ }
+
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ return file.read(b, off, len);
+ }
+
+ public void readFully(final byte[] b) throws IOException {
+ file.readFully(b);
+ }
+
+ public void readFully(final byte[] b, final int off, final int len) throws IOException {
+ file.readFully(b, off, len);
+ }
+
+ public String readLine() throws IOException {
+ return file.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 = file.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 = file.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 = file.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 = file.read();
+ int byte2 = file.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 = file.read();
+ int byte2 = file.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 = file.read();
+ int byte2 = file.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 = file.read();
+ int byte2 = file.read();
+ int byte3 = file.read();
+ int byte4 = file.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 = file.read();
+ long byte2 = file.read();
+ long byte3 = file.read();
+ long byte4 = file.read();
+ long byte5 = file.read();
+ long byte6 = file.read();
+ long byte7 = file.read();
+ long byte8 = file.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 big 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 = file.read();
+ int byte2 = file.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 {
+ file.seek(pos);
+ }
+
+ public void setLength(final long newLength) throws IOException {
+ file.setLength(newLength);
+ }
+
+ public int skipBytes(final int n) throws IOException {
+ return file.skipBytes(n);
+ }
+
+ public void write(final byte[] b) throws IOException {
+ file.write(b);
+ }
+
+ public void write(final byte[] b, final int off, final int len) throws IOException {
+ file.write(b, off, len);
+ }
+
+ public void write(final int b) throws IOException {
+ file.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 {
+ file.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 {
+ file.write(pShort & 0xFF);
+ file.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 {
+ file.write(pChar & 0xFF);
+ file.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 {
+ file.write(pInt & 0xFF);
+ file.write((pInt >>> 8) & 0xFF);
+ file.write((pInt >>> 16) & 0xFF);
+ file.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 {
+ file.write((int) pLong & 0xFF);
+ file.write((int) (pLong >>> 8) & 0xFF);
+ file.write((int) (pLong >>> 16) & 0xFF);
+ file.write((int) (pLong >>> 24) & 0xFF);
+ file.write((int) (pLong >>> 32) & 0xFF);
+ file.write((int) (pLong >>> 40) & 0xFF);
+ file.write((int) (pLong >>> 48) & 0xFF);
+ file.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 #file
+ */
+ public void writeBytes(String pString) throws IOException {
+ int length = pString.length();
+
+ for (int i = 0; i < length; i++) {
+ file.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 #file
+ */
+ public void writeChars(String pString) throws IOException {
+ int length = pString.length();
+
+ for (int i = 0; i < length; i++) {
+ int c = pString.charAt(i);
+ file.write(c & 0xFF);
+ file.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 big 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();
+ }
+
+ file.write((numbytes >>> 8) & 0xFF);
+ file.write(numbytes & 0xFF);
+
+ for (int i = 0; i < numchars; i++) {
+ int c = pString.charAt(i);
+
+ if ((c >= 0x0001) && (c <= 0x007F)) {
+ file.write(c);
+ }
+ else if (c > 0x07FF) {
+ file.write(0xE0 | ((c >> 12) & 0x0F));
+ file.write(0x80 | ((c >> 6) & 0x3F));
+ file.write(0x80 | (c & 0x3F));
+ }
+ else {
+ file.write(0xC0 | ((c >> 6) & 0x1F));
+ file.write(0x80 | (c & 0x3F));
+ }
+ }
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/MemoryCacheSeekableStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/MemoryCacheSeekableStream.java
index d17546b6..3a8d89d3 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/MemoryCacheSeekableStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/MemoryCacheSeekableStream.java
@@ -1,198 +1,197 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.
- *
- *
- * @see FileCacheSeekableStream
- *
- * @author Harald Kuhr
- * @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 cache = new ArrayList<>();
- private long length;
- private long position;
- private long start;
-
- private byte[] getBlock() throws IOException {
- final long currPos = position - start;
- 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 >= cache.size()) {
- try {
- cache.add(new byte[BLOCK_SIZE]);
-// System.out.println("Allocating new block, size: " + BLOCK_SIZE);
-// System.out.println("New total size: " + cache.size() * BLOCK_SIZE + " (" + cache.size() + " blocks)");
- }
- catch (OutOfMemoryError e) {
- throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
- }
- }
-
- //System.out.println("index: " + index);
-
- return cache.get((int) index);
- }
-
- public void write(final int pByte) throws IOException {
- byte[] buffer = getBlock();
-
- int idx = (int) (position % BLOCK_SIZE);
- buffer[idx] = (byte) pByte;
- position++;
-
- if (position > length) {
- length = position;
- }
- }
-
- // 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) position % BLOCK_SIZE;
- if (index == 0) {
- buffer = getBlock();
- }
- buffer[index] = pBuffer[pOffset + i];
-
- position++;
- }
- if (position > length) {
- length = position;
- }
- }
-
- public int read() throws IOException {
- if (position >= length) {
- return -1;
- }
-
- byte[] buffer = getBlock();
-
- int idx = (int) (position % BLOCK_SIZE);
- position++;
-
- return buffer[idx] & 0xff;
- }
-
- // TODO: OptimizeMe!!!
- @Override
- public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
- if (position >= length) {
- return -1;
- }
-
- byte[] buffer = getBlock();
-
- int bufferPos = (int) (position % BLOCK_SIZE);
-
- // Find maxIdx and simplify test in for-loop
- int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), length - position);
-
- int i;
- //for (i = 0; i < pLength && i < buffer.length - idx && i < length - position; i++) {
- for (i = 0; i < maxLen; i++) {
- pBytes[pOffset + i] = buffer[bufferPos + i];
- }
-
- position += i;
-
- return i;
- }
-
- public void seek(final long pPosition) throws IOException {
- if (pPosition < start) {
- throw new IOException("Seek before flush position");
- }
- position = pPosition;
- }
-
- @Override
- public void flush(final long pPosition) {
- int firstPos = (int) (pPosition / BLOCK_SIZE) - 1;
-
- for (int i = 0; i < firstPos; i++) {
- cache.remove(0);
- }
-
- start = pPosition;
- }
-
- @Override
- void close() throws IOException {
- cache.clear();
- }
-
- public long getPosition() {
- return position;
- }
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.
+ *
+ * @see FileCacheSeekableStream
+ *
+ * @author Harald Kuhr
+ * @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 cache = new ArrayList<>();
+ private long length;
+ private long position;
+ private long start;
+
+ private byte[] getBlock() throws IOException {
+ final long currPos = position - start;
+ 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 >= cache.size()) {
+ try {
+ cache.add(new byte[BLOCK_SIZE]);
+// System.out.println("Allocating new block, size: " + BLOCK_SIZE);
+// System.out.println("New total size: " + cache.size() * BLOCK_SIZE + " (" + cache.size() + " blocks)");
+ }
+ catch (OutOfMemoryError e) {
+ throw new IOException("No more memory for cache: " + cache.size() * BLOCK_SIZE);
+ }
+ }
+
+ //System.out.println("index: " + index);
+
+ return cache.get((int) index);
+ }
+
+ public void write(final int pByte) throws IOException {
+ byte[] buffer = getBlock();
+
+ int idx = (int) (position % BLOCK_SIZE);
+ buffer[idx] = (byte) pByte;
+ position++;
+
+ if (position > length) {
+ length = position;
+ }
+ }
+
+ // 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) position % BLOCK_SIZE;
+ if (index == 0) {
+ buffer = getBlock();
+ }
+ buffer[index] = pBuffer[pOffset + i];
+
+ position++;
+ }
+ if (position > length) {
+ length = position;
+ }
+ }
+
+ public int read() throws IOException {
+ if (position >= length) {
+ return -1;
+ }
+
+ byte[] buffer = getBlock();
+
+ int idx = (int) (position % BLOCK_SIZE);
+ position++;
+
+ return buffer[idx] & 0xff;
+ }
+
+ // TODO: OptimizeMe!!!
+ @Override
+ public int read(final byte[] pBytes, final int pOffset, final int pLength) throws IOException {
+ if (position >= length) {
+ return -1;
+ }
+
+ byte[] buffer = getBlock();
+
+ int bufferPos = (int) (position % BLOCK_SIZE);
+
+ // Find maxIdx and simplify test in for-loop
+ int maxLen = (int) Math.min(Math.min(pLength, buffer.length - bufferPos), length - position);
+
+ int i;
+ //for (i = 0; i < pLength && i < buffer.length - idx && i < length - position; i++) {
+ for (i = 0; i < maxLen; i++) {
+ pBytes[pOffset + i] = buffer[bufferPos + i];
+ }
+
+ position += i;
+
+ return i;
+ }
+
+ public void seek(final long pPosition) throws IOException {
+ if (pPosition < start) {
+ throw new IOException("Seek before flush position");
+ }
+ position = pPosition;
+ }
+
+ @Override
+ public void flush(final long pPosition) {
+ int firstPos = (int) (pPosition / BLOCK_SIZE) - 1;
+
+ for (int i = 0; i < firstPos; i++) {
+ cache.remove(0);
+ }
+
+ start = pPosition;
+ }
+
+ @Override
+ void close() throws IOException {
+ cache.clear();
+ }
+
+ public long getPosition() {
+ return position;
+ }
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/NullInputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/NullInputStream.java
index 9e7af300..02e45387 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/NullInputStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/NullInputStream.java
@@ -1,82 +1,81 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.
- *
- *
- * @author Harald Kuhr
- * @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;
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.
+ *
+ * @author Harald Kuhr
+ * @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;
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/NullOutputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/NullOutputStream.java
index 2b274a37..b584631a 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/NullOutputStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/NullOutputStream.java
@@ -1,70 +1,69 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.
- *
- *
- * @author Harald Kuhr
- * @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 {
- }
+/*
+ * 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 of the copyright holder 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 HOLDER 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.
+ *
+ * @author Harald Kuhr
+ * @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 {
+ }
}
\ No newline at end of file
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java
index 79e09afe..e3e5051d 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/RandomAccessStream.java
@@ -1,241 +1,242 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.EOFException;
-import java.io.IOException;
-
-/**
- * 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.
- *
- *
- * @see java.io.RandomAccessFile
- *
- * @author Harald Kuhr
- * @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 position = -1;
-
- /** This random access stream, wrapped in an {@code InputStream} */
- SeekableInputStream inputView = null;
- /** This random access stream, wrapped in an {@code OutputStream} */
- SeekableOutputStream outputView = 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.
- *
- * Note that read access is NOT synchronized.
- *
- * @return a {@code SeekableInputStream} reading from this stream
- */
- public final SeekableInputStream asInputStream() {
- if (inputView == null) {
- inputView = new InputStreamView(this);
- }
- return inputView;
- }
-
- /**
- * Returns an output view of this {@code RandomAccessStream}.
- * Invoking this method several times, will return the same object.
- *
- * Note that write access is NOT synchronized.
- *
- * @return a {@code SeekableOutputStream} writing to this stream
- */
- public final SeekableOutputStream asOutputStream() {
- if (outputView == null) {
- outputView = new OutputStreamView(this);
- }
- return outputView;
- }
-
- static final class InputStreamView extends SeekableInputStream {
- // TODO: Consider adding synchonization (on stream) for all operations
- // TODO: Is is a good thing that close/flush etc works on stream?
- // - 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 stream) for all operations
- // TODO: Is is a good thing that close/flush etc works on stream?
- // - 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);
- }
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.EOFException;
+import java.io.IOException;
+
+/**
+ * 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.
+ *
+ * @see java.io.RandomAccessFile
+ *
+ * @author Harald Kuhr
+ * @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 position = -1;
+
+ /** This random access stream, wrapped in an {@code InputStream} */
+ SeekableInputStream inputView = null;
+ /** This random access stream, wrapped in an {@code OutputStream} */
+ SeekableOutputStream outputView = 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.
+ *
+ * Note that read access is NOT synchronized.
+ *
+ *
+ * @return a {@code SeekableInputStream} reading from this stream
+ */
+ public final SeekableInputStream asInputStream() {
+ if (inputView == null) {
+ inputView = new InputStreamView(this);
+ }
+ return inputView;
+ }
+
+ /**
+ * Returns an output view of this {@code RandomAccessStream}.
+ * Invoking this method several times, will return the same object.
+ *
+ * Note that write access is NOT synchronized.
+ *
+ *
+ * @return a {@code SeekableOutputStream} writing to this stream
+ */
+ public final SeekableOutputStream asOutputStream() {
+ if (outputView == null) {
+ outputView = new OutputStreamView(this);
+ }
+ return outputView;
+ }
+
+ static final class InputStreamView extends SeekableInputStream {
+ // TODO: Consider adding synchonization (on stream) for all operations
+ // TODO: Is is a good thing that close/flush etc works on stream?
+ // - 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 stream) for all operations
+ // TODO: Is is a good thing that close/flush etc works on stream?
+ // - 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);
+ }
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/Seekable.java b/common/common-io/src/main/java/com/twelvemonkeys/io/Seekable.java
index 40b00ed9..204d1e10 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/Seekable.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/Seekable.java
@@ -1,186 +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 of the copyright holder 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 HOLDER 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.
- *
- * @see SeekableInputStream
- * @see SeekableOutputStream
- *
- * @author Harald Kuhr
- * @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.
- *
- * An {@code IndexOutOfBoundsException} will be thrown if pPosition is smaller
- * than the flushed position (as returned by {@link #getFlushedPosition()}).
- *
- * 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.
- *
- * 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.
- *
- * Calls to reset without a corresponding call to mark will either:
- *
- * - throw an {@code IOException}
- * - or, reset to the beginning of the stream.
- *
- * 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}.
- *
- * 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;
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.
+ *
+ * @see SeekableInputStream
+ * @see SeekableOutputStream
+ *
+ * @author Harald Kuhr
+ * @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.
+ *
+ * An {@code IndexOutOfBoundsException} will be thrown if pPosition is smaller
+ * than the flushed position (as returned by {@link #getFlushedPosition()}).
+ *
+ *
+ * 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.
+ *
+ * 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.
+ *
+ * Calls to reset without a corresponding call to mark will either:
+ *
+ *
+ * - throw an {@code IOException}
+ * - or, reset to the beginning of the stream.
+ *
+ *
+ * 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}.
+ *
+ * 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;
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java
index 6935bcc3..f585ebea 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableInputStream.java
@@ -1,238 +1,238 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.
- *
- * @see SeekableOutputStream
- *
- * @author Harald Kuhr
- * @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 position. position needs to be
- // updated from the read/read/read methods...
-
- /** The stream position in this stream */
- long position;
- long flushedPosition;
- boolean closed;
-
- protected Stack markedPositions = new Stack();
-
- /// 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(final long pLength) throws IOException {
- long pos = position;
- long wantedPosition = pos + pLength;
- if (wantedPosition < flushedPosition) {
- throw new IOException("position < flushedPosition");
- }
-
- // Stop at stream length for compatibility, even though it might be allowed
- // to seek past end of stream
- int available = available();
- if (available > 0) {
- seek(Math.min(wantedPosition, pos + available));
- }
- // TODO: Add optimization for streams with known length!
- else {
- // Slow mode...
- int toSkip = (int) Math.max(Math.min(pLength, 512), -512);
- while (toSkip > 0 && read() >= 0) {
- toSkip--;
- }
- }
-
- return position - 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(position - pLimit, flushedPosition));
- }
- 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 < flushedPosition) {
- throw new IndexOutOfBoundsException("position < flushedPosition");
- }
-
- seekImpl(pPosition);
- position = pPosition;
- }
-
- protected abstract void seekImpl(long pPosition) throws IOException;
-
- public final void mark() {
- markedPositions.push(position);
- }
-
- @Override
- public final void reset() throws IOException {
- checkOpen();
- if (!markedPositions.isEmpty()) {
- long newPos = markedPositions.pop();
-
- // NOTE: This is correct according to javax.imageio (IOException),
- // but it's kind of inconsistent with seek that throws IndexOutOfBoundsException...
- if (newPos < flushedPosition) {
- 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 < flushedPosition) {
- throw new IndexOutOfBoundsException("position < flushedPosition");
- }
- if (pPosition > getStreamPosition()) {
- throw new IndexOutOfBoundsException("position > stream position");
- }
- checkOpen();
- flushBeforeImpl(pPosition);
- flushedPosition = 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(flushedPosition);
- }
-
- public final long getFlushedPosition() throws IOException {
- checkOpen();
- return flushedPosition;
- }
-
- public final long getStreamPosition() throws IOException {
- checkOpen();
- return position;
- }
-
- protected final void checkOpen() throws IOException {
- if (closed) {
- throw new IOException("closed");
- }
- }
-
- @Override
- public final void close() throws IOException {
- checkOpen();
- closed = 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 (!closed) {
- try {
- close();
- }
- catch (IOException ignore) {
- // Ignroe
- }
- }
- super.finalize();
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.
+ *
+ * @see SeekableOutputStream
+ *
+ * @author Harald Kuhr
+ * @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 position. position needs to be
+ // updated from the read/read/read methods...
+
+ /** The stream position in this stream */
+ long position;
+ long flushedPosition;
+ boolean closed;
+
+ protected Stack markedPositions = new Stack();
+
+ /// 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(final long pLength) throws IOException {
+ long pos = position;
+ long wantedPosition = pos + pLength;
+ if (wantedPosition < flushedPosition) {
+ throw new IOException("position < flushedPosition");
+ }
+
+ // Stop at stream length for compatibility, even though it might be allowed
+ // to seek past end of stream
+ int available = available();
+ if (available > 0) {
+ seek(Math.min(wantedPosition, pos + available));
+ }
+ // TODO: Add optimization for streams with known length!
+ else {
+ // Slow mode...
+ int toSkip = (int) Math.max(Math.min(pLength, 512), -512);
+ while (toSkip > 0 && read() >= 0) {
+ toSkip--;
+ }
+ }
+
+ return position - 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(position - pLimit, flushedPosition));
+ }
+ 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 < flushedPosition) {
+ throw new IndexOutOfBoundsException("position < flushedPosition");
+ }
+
+ seekImpl(pPosition);
+ position = pPosition;
+ }
+
+ protected abstract void seekImpl(long pPosition) throws IOException;
+
+ public final void mark() {
+ markedPositions.push(position);
+ }
+
+ @Override
+ public final void reset() throws IOException {
+ checkOpen();
+ if (!markedPositions.isEmpty()) {
+ long newPos = markedPositions.pop();
+
+ // NOTE: This is correct according to javax.imageio (IOException),
+ // but it's kind of inconsistent with seek that throws IndexOutOfBoundsException...
+ if (newPos < flushedPosition) {
+ 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 < flushedPosition) {
+ throw new IndexOutOfBoundsException("position < flushedPosition");
+ }
+ if (pPosition > getStreamPosition()) {
+ throw new IndexOutOfBoundsException("position > stream position");
+ }
+ checkOpen();
+ flushBeforeImpl(pPosition);
+ flushedPosition = 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(flushedPosition);
+ }
+
+ public final long getFlushedPosition() throws IOException {
+ checkOpen();
+ return flushedPosition;
+ }
+
+ public final long getStreamPosition() throws IOException {
+ checkOpen();
+ return position;
+ }
+
+ protected final void checkOpen() throws IOException {
+ if (closed) {
+ throw new IOException("closed");
+ }
+ }
+
+ @Override
+ public final void close() throws IOException {
+ checkOpen();
+ closed = 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 (!closed) {
+ try {
+ close();
+ }
+ catch (IOException ignore) {
+ // Ignroe
+ }
+ }
+ super.finalize();
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java
index cc99cf73..bdce032b 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/SeekableOutputStream.java
@@ -1,140 +1,140 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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;
-import java.util.Stack;
-
-/**
- * Abstract base class for {@code OutputStream}s implementing the
- * {@code Seekable} interface.
- *
- * @see SeekableInputStream
- *
- * @author Harald Kuhr
- * @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 position;
- long flushedPosition;
- boolean closed;
-
- protected Stack markedPositions = new Stack();
-
- /// 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 < flushedPosition) {
- throw new IndexOutOfBoundsException("position < flushedPosition!");
- }
-
- seekImpl(pPosition);
- position = pPosition;
- }
-
- protected abstract void seekImpl(long pPosition) throws IOException;
-
- public final void mark() {
- markedPositions.push(position);
- }
-
- public final void reset() throws IOException {
- checkOpen();
- if (!markedPositions.isEmpty()) {
- long newPos = markedPositions.pop();
-
- // TODO: This is correct according to javax.imageio (IOException),
- // but it's inconsistent with seek that throws IndexOutOfBoundsException...
- if (newPos < flushedPosition) {
- throw new IOException("Previous marked position has been discarded!");
- }
-
- seek(newPos);
- }
- }
-
- public final void flushBefore(long pPosition) throws IOException {
- if (pPosition < flushedPosition) {
- throw new IndexOutOfBoundsException("position < flushedPosition!");
- }
- if (pPosition > getStreamPosition()) {
- throw new IndexOutOfBoundsException("position > getStreamPosition()!");
- }
- checkOpen();
- flushBeforeImpl(pPosition);
- flushedPosition = pPosition;
- }
-
- protected abstract void flushBeforeImpl(long pPosition) throws IOException;
-
- @Override
- public final void flush() throws IOException {
- flushBefore(flushedPosition);
- }
-
- public final long getFlushedPosition() throws IOException {
- checkOpen();
- return flushedPosition;
- }
-
- public final long getStreamPosition() throws IOException {
- checkOpen();
- return position;
- }
-
- protected final void checkOpen() throws IOException {
- if (closed) {
- throw new IOException("closed");
- }
- }
-
- @Override
- public final void close() throws IOException {
- checkOpen();
- closed = true;
- closeImpl();
- }
-
- protected abstract void closeImpl() throws IOException;
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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;
+import java.util.Stack;
+
+/**
+ * Abstract base class for {@code OutputStream}s implementing the
+ * {@code Seekable} interface.
+ *
+ * @see SeekableInputStream
+ *
+ * @author Harald Kuhr
+ * @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 position;
+ long flushedPosition;
+ boolean closed;
+
+ protected Stack markedPositions = new Stack();
+
+ /// 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 < flushedPosition) {
+ throw new IndexOutOfBoundsException("position < flushedPosition!");
+ }
+
+ seekImpl(pPosition);
+ position = pPosition;
+ }
+
+ protected abstract void seekImpl(long pPosition) throws IOException;
+
+ public final void mark() {
+ markedPositions.push(position);
+ }
+
+ public final void reset() throws IOException {
+ checkOpen();
+ if (!markedPositions.isEmpty()) {
+ long newPos = markedPositions.pop();
+
+ // TODO: This is correct according to javax.imageio (IOException),
+ // but it's inconsistent with seek that throws IndexOutOfBoundsException...
+ if (newPos < flushedPosition) {
+ throw new IOException("Previous marked position has been discarded!");
+ }
+
+ seek(newPos);
+ }
+ }
+
+ public final void flushBefore(long pPosition) throws IOException {
+ if (pPosition < flushedPosition) {
+ throw new IndexOutOfBoundsException("position < flushedPosition!");
+ }
+ if (pPosition > getStreamPosition()) {
+ throw new IndexOutOfBoundsException("position > getStreamPosition()!");
+ }
+ checkOpen();
+ flushBeforeImpl(pPosition);
+ flushedPosition = pPosition;
+ }
+
+ protected abstract void flushBeforeImpl(long pPosition) throws IOException;
+
+ @Override
+ public final void flush() throws IOException {
+ flushBefore(flushedPosition);
+ }
+
+ public final long getFlushedPosition() throws IOException {
+ checkOpen();
+ return flushedPosition;
+ }
+
+ public final long getStreamPosition() throws IOException {
+ checkOpen();
+ return position;
+ }
+
+ protected final void checkOpen() throws IOException {
+ if (closed) {
+ throw new IOException("closed");
+ }
+ }
+
+ @Override
+ public final void close() throws IOException {
+ checkOpen();
+ closed = true;
+ closeImpl();
+ }
+
+ protected abstract void closeImpl() throws IOException;
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/StringArrayReader.java b/common/common-io/src/main/java/com/twelvemonkeys/io/StringArrayReader.java
index 0270ef0a..45ec3f9b 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/StringArrayReader.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/StringArrayReader.java
@@ -1,189 +1,188 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.io.StringReader;
-
-/**
- * StringArrayReader
- *
- *
- * @author Harald Kuhr
- * @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 current;
- private String[] strings;
- protected final Object finalLock;
- private int currentSting;
- private int markedString;
- private int mark;
- private int next;
-
- /**
- * Create a new string array reader.
- *
- * @param pStrings {@code String}s providing the character stream.
- */
- public StringArrayReader(final String[] pStrings) {
- super("");
-
- Validate.notNull(pStrings, "strings");
-
- finalLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the
- // reference can't change, only it's elements
-
- strings = pStrings.clone(); // Defensive copy for content
- nextReader();
- }
-
- protected final Reader nextReader() {
- if (currentSting >= strings.length) {
- current = new EmptyReader();
- }
- else {
- current = new StringReader(strings[currentSting++]);
- }
-
- // NOTE: Reset next for every reader, and record marked reader in mark/reset methods!
- next = 0;
-
- return current;
- }
-
- /**
- * 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 (strings == null) {
- throw new IOException("Stream closed");
- }
- }
-
- public void close() {
- super.close();
- strings = null;
- current.close();
- }
-
- public void mark(int pReadLimit) throws IOException {
- if (pReadLimit < 0){
- throw new IllegalArgumentException("Read limit < 0");
- }
-
- synchronized (finalLock) {
- ensureOpen();
- mark = next;
- markedString = currentSting;
-
- current.mark(pReadLimit);
- }
- }
-
- public void reset() throws IOException {
- synchronized (finalLock) {
- ensureOpen();
-
- if (currentSting != markedString) {
- currentSting = markedString - 1;
- nextReader();
- current.skip(mark);
- }
- else {
- current.reset();
- }
-
- next = mark;
- }
- }
-
- public boolean markSupported() {
- return true;
- }
-
- public int read() throws IOException {
- synchronized (finalLock) {
- int read = current.read();
-
- if (read < 0 && currentSting < strings.length) {
- nextReader();
- return read(); // In case of empty strings
- }
-
- next++;
-
- return read;
- }
- }
-
- public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
- synchronized (finalLock) {
- int read = current.read(pBuffer, pOffset, pLength);
-
- if (read < 0 && currentSting < strings.length) {
- nextReader();
- return read(pBuffer, pOffset, pLength); // In case of empty strings
- }
-
- next += read;
-
- return read;
- }
- }
-
- public boolean ready() throws IOException {
- return current.ready();
- }
-
- public long skip(long pChars) throws IOException {
- synchronized (finalLock) {
- long skipped = current.skip(pChars);
-
- if (skipped == 0 && currentSting < strings.length) {
- nextReader();
- return skip(pChars);
- }
-
- next += skipped;
-
- return skipped;
- }
- }
-
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.io.StringReader;
+
+/**
+ * StringArrayReader
+ *
+ * @author Harald Kuhr
+ * @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 current;
+ private String[] strings;
+ protected final Object finalLock;
+ private int currentSting;
+ private int markedString;
+ private int mark;
+ private int next;
+
+ /**
+ * Create a new string array reader.
+ *
+ * @param pStrings {@code String}s providing the character stream.
+ */
+ public StringArrayReader(final String[] pStrings) {
+ super("");
+
+ Validate.notNull(pStrings, "strings");
+
+ finalLock = lock = pStrings; // NOTE: It's ok to sync on pStrings, as the
+ // reference can't change, only it's elements
+
+ strings = pStrings.clone(); // Defensive copy for content
+ nextReader();
+ }
+
+ protected final Reader nextReader() {
+ if (currentSting >= strings.length) {
+ current = new EmptyReader();
+ }
+ else {
+ current = new StringReader(strings[currentSting++]);
+ }
+
+ // NOTE: Reset next for every reader, and record marked reader in mark/reset methods!
+ next = 0;
+
+ return current;
+ }
+
+ /**
+ * 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 (strings == null) {
+ throw new IOException("Stream closed");
+ }
+ }
+
+ public void close() {
+ super.close();
+ strings = null;
+ current.close();
+ }
+
+ public void mark(int pReadLimit) throws IOException {
+ if (pReadLimit < 0){
+ throw new IllegalArgumentException("Read limit < 0");
+ }
+
+ synchronized (finalLock) {
+ ensureOpen();
+ mark = next;
+ markedString = currentSting;
+
+ current.mark(pReadLimit);
+ }
+ }
+
+ public void reset() throws IOException {
+ synchronized (finalLock) {
+ ensureOpen();
+
+ if (currentSting != markedString) {
+ currentSting = markedString - 1;
+ nextReader();
+ current.skip(mark);
+ }
+ else {
+ current.reset();
+ }
+
+ next = mark;
+ }
+ }
+
+ public boolean markSupported() {
+ return true;
+ }
+
+ public int read() throws IOException {
+ synchronized (finalLock) {
+ int read = current.read();
+
+ if (read < 0 && currentSting < strings.length) {
+ nextReader();
+ return read(); // In case of empty strings
+ }
+
+ next++;
+
+ return read;
+ }
+ }
+
+ public int read(char pBuffer[], int pOffset, int pLength) throws IOException {
+ synchronized (finalLock) {
+ int read = current.read(pBuffer, pOffset, pLength);
+
+ if (read < 0 && currentSting < strings.length) {
+ nextReader();
+ return read(pBuffer, pOffset, pLength); // In case of empty strings
+ }
+
+ next += read;
+
+ return read;
+ }
+ }
+
+ public boolean ready() throws IOException {
+ return current.ready();
+ }
+
+ public long skip(long pChars) throws IOException {
+ synchronized (finalLock) {
+ long skipped = current.skip(pChars);
+
+ if (skipped == 0 && currentSting < strings.length) {
+ nextReader();
+ return skip(pChars);
+ }
+
+ next += skipped;
+
+ return skipped;
+ }
+ }
+
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/SubStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/SubStream.java
index 023d0a1f..76a3c01f 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/SubStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/SubStream.java
@@ -1,137 +1,136 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.
- *
- *
- * @author Harald Kuhr
- * @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 bytesLeft;
- private int markLimit;
-
- /**
- * 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"));
- bytesLeft = pLength;
- }
-
- /**
- * Marks this stream as closed.
- * This implementation does not close the underlying stream.
- */
- @Override
- public void close() throws IOException {
- // NOTE: Do not close the underlying stream
- while (bytesLeft > 0) {
- //noinspection ResultOfMethodCallIgnored
- skip(bytesLeft);
- }
- }
-
- @Override
- public int available() throws IOException {
- return (int) Math.min(super.available(), bytesLeft);
- }
-
- @Override
- public void mark(int pReadLimit) {
- super.mark(pReadLimit);// This either succeeds or does nothing...
- markLimit = pReadLimit;
- }
-
- @Override
- public void reset() throws IOException {
- super.reset();// This either succeeds or throws IOException
- bytesLeft += markLimit;
- }
-
- @Override
- public int read() throws IOException {
- if (bytesLeft-- <= 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 (bytesLeft <= 0) {
- return -1;
- }
-
- int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
- bytesLeft = read < 0 ? 0 : bytesLeft - 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 (bytesLeft < pLength) {
- return (int) Math.max(bytesLeft, 0);
- }
- else {
- return pLength;
- }
- }
-
- @Override
- public long skip(long pLength) throws IOException {
- long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
- bytesLeft -= skipped;
- return skipped;
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.
+ *
+ * @author Harald Kuhr
+ * @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 bytesLeft;
+ private int markLimit;
+
+ /**
+ * 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"));
+ bytesLeft = pLength;
+ }
+
+ /**
+ * Marks this stream as closed.
+ * This implementation does not close the underlying stream.
+ */
+ @Override
+ public void close() throws IOException {
+ // NOTE: Do not close the underlying stream
+ while (bytesLeft > 0) {
+ //noinspection ResultOfMethodCallIgnored
+ skip(bytesLeft);
+ }
+ }
+
+ @Override
+ public int available() throws IOException {
+ return (int) Math.min(super.available(), bytesLeft);
+ }
+
+ @Override
+ public void mark(int pReadLimit) {
+ super.mark(pReadLimit);// This either succeeds or does nothing...
+ markLimit = pReadLimit;
+ }
+
+ @Override
+ public void reset() throws IOException {
+ super.reset();// This either succeeds or throws IOException
+ bytesLeft += markLimit;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (bytesLeft-- <= 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 (bytesLeft <= 0) {
+ return -1;
+ }
+
+ int read = super.read(pBytes, pOffset, (int) findMaxLen(pLength));
+ bytesLeft = read < 0 ? 0 : bytesLeft - 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 (bytesLeft < pLength) {
+ return (int) Math.max(bytesLeft, 0);
+ }
+ else {
+ return pLength;
+ }
+ }
+
+ @Override
+ public long skip(long pLength) throws IOException {
+ long skipped = super.skip(findMaxLen(pLength));// Skips 0 or more, never -1
+ bytesLeft -= skipped;
+ return skipped;
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java b/common/common-io/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java
index 761a2ab8..55caa5e9 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/UnixFileSystem.java
@@ -1,107 +1,106 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.BufferedReader;
-import java.io.File;
-import java.io.IOException;
-
-/**
- * UnixFileSystem
- *
- *
- * @author Harald Kuhr
- * @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";
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * UnixFileSystem
+ *
+ * @author Harald Kuhr
+ * @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";
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32File.java b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32File.java
index ee3e3e1b..fa6c82ef 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32File.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32File.java
@@ -1,195 +1,194 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.FileFilter;
-import java.io.FilenameFilter;
-import java.io.IOException;
-
-/**
- * Win32File
- *
- *
- * @author Harald Kuhr
- * @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));
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.FileFilter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+
+/**
+ * Win32File
+ *
+ * @author Harald Kuhr
+ * @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));
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java
index 400164d1..ae3d3608 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32FileSystem.java
@@ -1,92 +1,91 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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;
-
-/**
- * WindowsFileSystem
- *
- *
- * @author Harald Kuhr
- * @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";
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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;
+
+/**
+ * WindowsFileSystem
+ *
+ * @author Harald Kuhr
+ * @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";
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32Lnk.java b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32Lnk.java
index 7833c0fa..34eb0be6 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/Win32Lnk.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/Win32Lnk.java
@@ -1,475 +1,477 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.
- *
- * This class is based on example code from
- * Swing Hacks,
- * By Joshua Marinacci, Chris Adamson (O'Reilly, ISBN: 0-596-00907-0), Hack 30.
- *
- * @author Harald Kuhr
- * @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 target;
-
- 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
- // target = this causes infinite loops in some methods
- target = new File(pPath);
- }
- this.target = 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.
- *
- * 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 target;
- }
-
- // java.io.File overrides below
-
- @Override
- public boolean isDirectory() {
- return target.isDirectory();
- }
-
- @Override
- public boolean canRead() {
- return target.canRead();
- }
-
- @Override
- public boolean canWrite() {
- return target.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 target.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 target.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 target.getCanonicalFile();
- }
-
- @Override
- public String getCanonicalPath() throws IOException {
- return target.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 target.isFile();
- }
-
- @Override
- public boolean isHidden() {
- return target.isHidden();
- }
-
- @Override
- public long lastModified() {
- return target.lastModified();
- }
-
- @Override
- public long length() {
- return target.length();
- }
-
- @Override
- public String[] list() {
- return target.list();
- }
-
- @Override
- public String[] list(final FilenameFilter filter) {
- return target.list(filter);
- }
-
- @Override
- public File[] listFiles() {
- return Win32File.wrap(target.listFiles());
- }
-
- @Override
- public File[] listFiles(final FileFilter filter) {
- return Win32File.wrap(target.listFiles(filter));
- }
-
- @Override
- public File[] listFiles(final FilenameFilter filter) {
- return Win32File.wrap(target.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 target.setLastModified(time);
- }
-
- @Override
- public boolean setReadOnly() {
- return target.setReadOnly();
- }
-
- @Override
- public String toString() {
- if (target.equals(this)) {
- return super.toString();
- }
- return super.toString() + " -> " + target.toString();
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.
+ *
+ * This class is based on example code from
+ * Swing Hacks,
+ * By Joshua Marinacci, Chris Adamson (O'Reilly, ISBN: 0-596-00907-0), Hack 30.
+ *
+ *
+ * @author Harald Kuhr
+ * @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 target;
+
+ 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
+ // target = this causes infinite loops in some methods
+ target = new File(pPath);
+ }
+ this.target = 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.
+ *
+ * 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 target;
+ }
+
+ // java.io.File overrides below
+
+ @Override
+ public boolean isDirectory() {
+ return target.isDirectory();
+ }
+
+ @Override
+ public boolean canRead() {
+ return target.canRead();
+ }
+
+ @Override
+ public boolean canWrite() {
+ return target.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 target.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 target.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 target.getCanonicalFile();
+ }
+
+ @Override
+ public String getCanonicalPath() throws IOException {
+ return target.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 target.isFile();
+ }
+
+ @Override
+ public boolean isHidden() {
+ return target.isHidden();
+ }
+
+ @Override
+ public long lastModified() {
+ return target.lastModified();
+ }
+
+ @Override
+ public long length() {
+ return target.length();
+ }
+
+ @Override
+ public String[] list() {
+ return target.list();
+ }
+
+ @Override
+ public String[] list(final FilenameFilter filter) {
+ return target.list(filter);
+ }
+
+ @Override
+ public File[] listFiles() {
+ return Win32File.wrap(target.listFiles());
+ }
+
+ @Override
+ public File[] listFiles(final FileFilter filter) {
+ return Win32File.wrap(target.listFiles(filter));
+ }
+
+ @Override
+ public File[] listFiles(final FilenameFilter filter) {
+ return Win32File.wrap(target.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 target.setLastModified(time);
+ }
+
+ @Override
+ public boolean setReadOnly() {
+ return target.setReadOnly();
+ }
+
+ @Override
+ public String toString() {
+ if (target.equals(this)) {
+ return super.toString();
+ }
+ return super.toString() + " -> " + target.toString();
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java
index b9f9f39e..ff0893ad 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/WriterOutputStream.java
@@ -1,239 +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 of the copyright holder 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 HOLDER 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.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.Charset;
-
-/**
- * Wraps a {@code Writer} in an {@code OutputStream}.
- *
- * Instances of this class are not thread-safe.
- *
- * 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.
- *
- *
- *
- * @author Harald Kuhr
- * @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 writer;
- final protected Decoder decoder;
- final ByteArrayOutputStream bufferStream = new FastByteArrayOutputStream(1024);
-
- private volatile boolean isFlushing = 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) {
- writer = pWriter;
- decoder = 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();
- writer.close();
- writer = null;
- }
-
- @Override
- public void flush() throws IOException {
- flushBuffer();
- writer.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();
- decoder.decodeTo(writer, 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..
- bufferStream.write(pByte);
- }
-
- private void flushBuffer() throws IOException {
- if (!isFlushing && bufferStream.size() > 0) {
- isFlushing = true;
- bufferStream.writeTo(this); // NOTE: Avoids cloning buffer array
- bufferStream.reset();
- isFlushing = 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);
- }
- }
+/*
+ * 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 of the copyright holder 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 HOLDER 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.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+
+/**
+ * Wraps a {@code Writer} in an {@code OutputStream}.
+ *
+ * Instances of this class are not thread-safe.
+ *
+ *
+ * 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.
+ *
+ *
+ *
+ * @author Harald Kuhr
+ * @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 writer;
+ final protected Decoder decoder;
+ final ByteArrayOutputStream bufferStream = new FastByteArrayOutputStream(1024);
+
+ private volatile boolean isFlushing = 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) {
+ writer = pWriter;
+ decoder = 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();
+ writer.close();
+ writer = null;
+ }
+
+ @Override
+ public void flush() throws IOException {
+ flushBuffer();
+ writer.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();
+ decoder.decodeTo(writer, 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..
+ bufferStream.write(pByte);
+ }
+
+ private void flushBuffer() throws IOException {
+ if (!isFlushing && bufferStream.size() > 0) {
+ isFlushing = true;
+ bufferStream.writeTo(this); // NOTE: Avoids cloning buffer array
+ bufferStream.reset();
+ isFlushing = 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);
+ }
+ }
}
\ No newline at end of file
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java
index d433e387..2660b2f7 100644
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Decoder.java
@@ -1,188 +1,188 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.nio.ByteBuffer;
-
-/**
- * {@code Decoder} implementation for standard base64 encoding.
- *
- * @see RFC 1421
- * @see
- *
- * @see Base64Encoder
- *
- * @author Harald Kuhr
- * @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 byte[] 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[] decodeBuffer = new byte[4];
-
- 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 ByteBuffer 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);
-
- decodeBuffer[0] = (byte) read;
- read = readFully(pInput, decodeBuffer, 1, pLength - 1);
-
- if (read == -1) {
- return false;
- }
-
- int length = pLength;
-
- if (length > 3 && decodeBuffer[3] == 61) {
- length = 3;
- }
-
- if (length > 2 && decodeBuffer[2] == 61) {
- length = 2;
- }
-
- switch (length) {
- case 4:
- byte3 = PEM_CONVERT_ARRAY[decodeBuffer[3] & 255];
- // fall through
- case 3:
- byte2 = PEM_CONVERT_ARRAY[decodeBuffer[2] & 255];
- // fall through
- case 2:
- byte1 = PEM_CONVERT_ARRAY[decodeBuffer[1] & 255];
- byte0 = PEM_CONVERT_ARRAY[decodeBuffer[0] & 255];
- // fall through
- default:
- switch (length) {
- case 2:
- pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
- break;
- case 3:
- pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
- pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
- break;
- case 4:
- pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
- pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
- pOutput.put((byte) (byte2 << 6 & 192 | byte3 & 63));
- break;
- }
-
- break;
- }
-
- return true;
- }
-
- public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
- do {
- int k = 72;
- int i;
-
- for (i = 0; i + 4 < k; i += 4) {
- if(!decodeAtom(stream, buffer, 4)) {
- break;
- }
- }
-
- if (!decodeAtom(stream, buffer, k - i)) {
- break;
- }
- }
- while (buffer.remaining() > 54); // 72 char lines should produce no more than 54 bytes
-
- return buffer.position();
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.nio.ByteBuffer;
+
+/**
+ * {@code Decoder} implementation for standard base64 encoding.
+ *
+ * @see RFC 1421
+ * @see RFC 2045
+ *
+ * @see Base64Encoder
+ *
+ * @author Harald Kuhr
+ * @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 byte[] 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[] decodeBuffer = new byte[4];
+
+ 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 ByteBuffer 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);
+
+ decodeBuffer[0] = (byte) read;
+ read = readFully(pInput, decodeBuffer, 1, pLength - 1);
+
+ if (read == -1) {
+ return false;
+ }
+
+ int length = pLength;
+
+ if (length > 3 && decodeBuffer[3] == 61) {
+ length = 3;
+ }
+
+ if (length > 2 && decodeBuffer[2] == 61) {
+ length = 2;
+ }
+
+ switch (length) {
+ case 4:
+ byte3 = PEM_CONVERT_ARRAY[decodeBuffer[3] & 255];
+ // fall through
+ case 3:
+ byte2 = PEM_CONVERT_ARRAY[decodeBuffer[2] & 255];
+ // fall through
+ case 2:
+ byte1 = PEM_CONVERT_ARRAY[decodeBuffer[1] & 255];
+ byte0 = PEM_CONVERT_ARRAY[decodeBuffer[0] & 255];
+ // fall through
+ default:
+ switch (length) {
+ case 2:
+ pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
+ break;
+ case 3:
+ pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
+ pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
+ break;
+ case 4:
+ pOutput.put((byte) (byte0 << 2 & 252 | byte1 >>> 4 & 3));
+ pOutput.put((byte) (byte1 << 4 & 240 | byte2 >>> 2 & 15));
+ pOutput.put((byte) (byte2 << 6 & 192 | byte3 & 63));
+ break;
+ }
+
+ break;
+ }
+
+ return true;
+ }
+
+ public int decode(final InputStream stream, final ByteBuffer buffer) throws IOException {
+ do {
+ int k = 72;
+ int i;
+
+ for (i = 0; i + 4 < k; i += 4) {
+ if(!decodeAtom(stream, buffer, 4)) {
+ break;
+ }
+ }
+
+ if (!decodeAtom(stream, buffer, k - i)) {
+ break;
+ }
+ }
+ while (buffer.remaining() > 54); // 72 char lines should produce no more than 54 bytes
+
+ return buffer.position();
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Encoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Encoder.java
index a9317969..8a9eaa5f 100644
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Encoder.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Base64Encoder.java
@@ -1,106 +1,106 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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;
-import java.nio.ByteBuffer;
-
-/**
- * {@code Encoder} implementation for standard base64 encoding.
- *
- * @see RFC 1421
- * @see
- *
- * @see Base64Decoder
- *
- * @author Harald Kuhr
- * @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 stream, final ByteBuffer buffer)
- throws IOException
- {
-
- // 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;
-
- // TODO: Temp impl, will only work for single writes
- while (buffer.hasRemaining()) {
- byte a, b, c;
-
-// if ((buffer.remaining()) > 2) {
-// length = 3;
-// }
-// else {
-// length = buffer.remaining();
-// }
- length = Math.min(3, buffer.remaining());
-
- switch (length) {
- case 1:
- a = buffer.get();
- b = 0;
- stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
- stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
- stream.write('=');
- stream.write('=');
- break;
-
- case 2:
- a = buffer.get();
- b = buffer.get();
- c = 0;
- stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
- stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
- stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
- stream.write('=');
- break;
-
- default:
- a = buffer.get();
- b = buffer.get();
- c = buffer.get();
- stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
- stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
- stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
- stream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]);
- break;
- }
- }
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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;
+import java.nio.ByteBuffer;
+
+/**
+ * {@code Encoder} implementation for standard base64 encoding.
+ *
+ * @see RFC 1421
+ * @see RFC 2045
+ *
+ * @see Base64Decoder
+ *
+ * @author Harald Kuhr
+ * @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 stream, final ByteBuffer buffer)
+ throws IOException
+ {
+
+ // 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;
+
+ // TODO: Temp impl, will only work for single writes
+ while (buffer.hasRemaining()) {
+ byte a, b, c;
+
+// if ((buffer.remaining()) > 2) {
+// length = 3;
+// }
+// else {
+// length = buffer.remaining();
+// }
+ length = Math.min(3, buffer.remaining());
+
+ switch (length) {
+ case 1:
+ a = buffer.get();
+ b = 0;
+ stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
+ stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
+ stream.write('=');
+ stream.write('=');
+ break;
+
+ case 2:
+ a = buffer.get();
+ b = buffer.get();
+ c = 0;
+ stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
+ stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
+ stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
+ stream.write('=');
+ break;
+
+ default:
+ a = buffer.get();
+ b = buffer.get();
+ c = buffer.get();
+ stream.write(Base64Decoder.PEM_ARRAY[(a >>> 2) & 0x3F]);
+ stream.write(Base64Decoder.PEM_ARRAY[((a << 4) & 0x30) + ((b >>> 4) & 0xf)]);
+ stream.write(Base64Decoder.PEM_ARRAY[((b << 2) & 0x3c) + ((c >>> 6) & 0x3)]);
+ stream.write(Base64Decoder.PEM_ARRAY[c & 0x3F]);
+ break;
+ }
+ }
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecodeException.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecodeException.java
index c54e8d09..c377d2d7 100644
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecodeException.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecodeException.java
@@ -1,56 +1,55 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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 decoded.
- *
- *
- * @author Harald Kuhr
- * @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);
- }
+/*
+ * 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 of the copyright holder 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 HOLDER 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 decoded.
+ *
+ * @author Harald Kuhr
+ * @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);
+ }
}
\ No newline at end of file
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java
index 75372bad..eae19df0 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Decoder.java
@@ -1,68 +1,69 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.nio.ByteBuffer;
-
-/**
- * Interface for decoders.
- * A {@code Decoder} may be used with a {@code DecoderStream}, to perform
- * on-the-fly decoding from an {@code InputStream}.
- *
- * Important note: Decoder implementations are typically not synchronized.
- *
- * @see Encoder
- * @see DecoderStream
- *
- * @author Harald Kuhr
- * @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 buffer.length} bytes from the given input stream,
- * into the given buffer.
- *
- * @param stream the input stream to decode data from
- * @param buffer buffer to store the read data
- *
- * @return the total number of bytes read into the buffer, or {@code 0}
- * 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.
- * @throws java.lang.NullPointerException if either argument is {@code null}.
- */
- int decode(InputStream stream, ByteBuffer buffer) throws IOException;
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.nio.ByteBuffer;
+
+/**
+ * Interface for decoders.
+ * A {@code Decoder} may be used with a {@code DecoderStream}, to perform
+ * on-the-fly decoding from an {@code InputStream}.
+ *
+ * Important note: Decoder implementations are typically not synchronized.
+ *
+ *
+ * @see Encoder
+ * @see DecoderStream
+ *
+ * @author Harald Kuhr
+ * @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 buffer.length} bytes from the given input stream,
+ * into the given buffer.
+ *
+ * @param stream the input stream to decode data from
+ * @param buffer buffer to store the read data
+ *
+ * @return the total number of bytes read into the buffer, or {@code 0}
+ * 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.
+ * @throws java.lang.NullPointerException if either argument is {@code null}.
+ */
+ int decode(InputStream stream, ByteBuffer buffer) throws IOException;
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java
index 188f8f0f..ec542a46 100644
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/DecoderStream.java
@@ -1,200 +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 of the copyright holder 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 HOLDER 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.FilterInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
-
-/**
- * An {@code InputStream} that provides on-the-fly decoding from an underlying
- * stream.
- *
- * @see EncoderStream
- * @see Decoder
- *
- * @author Harald Kuhr
- * @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 final ByteBuffer buffer;
- protected final Decoder decoder;
-
- /**
- * 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) {
- // TODO: Let the decoder decide preferred buffer size
- 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);
-
- decoder = pDecoder;
- buffer = ByteBuffer.allocate(pBufferSize);
- buffer.flip();
- }
-
- public int available() throws IOException {
- return buffer.remaining();
- }
-
- public int read() throws IOException {
- if (!buffer.hasRemaining()) {
- if (fill() < 0) {
- return -1;
- }
- }
-
- return buffer.get() & 0xff;
- }
-
- 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 (!buffer.hasRemaining()) {
- if (fill() < 0) {
- return -1;
- }
- }
-
- // Read until we have read pLength bytes, or have reached EOF
- int count = 0;
- int off = pOffset;
-
- while (pLength > count) {
- if (!buffer.hasRemaining()) {
- if (fill() < 0) {
- break;
- }
- }
-
- // Copy as many bytes as possible
- int dstLen = Math.min(pLength - count, buffer.remaining());
- buffer.get(pBytes, off, dstLen);
-
- // Update offset (rest)
- off += dstLen;
-
- // Increase count
- count += dstLen;
- }
-
- return count;
- }
-
- public long skip(final long pLength) throws IOException {
- // End of file?
- if (!buffer.hasRemaining()) {
- if (fill() < 0) {
- return 0; // Yes, 0, not -1
- }
- }
-
- // Skip until we have skipped pLength bytes, or have reached EOF
- long total = 0;
-
- while (total < pLength) {
- if (!buffer.hasRemaining()) {
- if (fill() < 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, buffer.remaining());
- buffer.position(buffer.position() + skipped);
- 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 {
- buffer.clear();
- int read = decoder.decode(in, buffer);
-
- // TODO: Enforce this in test case, leave here to aid debugging
- if (read > buffer.capacity()) {
- throw new AssertionError(
- String.format(
- "Decode beyond buffer (%d): %d (using %s decoder)",
- buffer.capacity(), read, decoder.getClass().getName()
- )
- );
- }
-
- buffer.flip();
-
- if (read == 0) {
- return -1;
- }
-
- return read;
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * An {@code InputStream} that provides on-the-fly decoding from an underlying stream.
+ *
+ * @see EncoderStream
+ * @see Decoder
+ *
+ * @author Harald Kuhr
+ * @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 final ByteBuffer buffer;
+ protected final Decoder decoder;
+
+ /**
+ * 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) {
+ // TODO: Let the decoder decide preferred buffer size
+ 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);
+
+ decoder = pDecoder;
+ buffer = ByteBuffer.allocate(pBufferSize);
+ buffer.flip();
+ }
+
+ public int available() throws IOException {
+ return buffer.remaining();
+ }
+
+ public int read() throws IOException {
+ if (!buffer.hasRemaining()) {
+ if (fill() < 0) {
+ return -1;
+ }
+ }
+
+ return buffer.get() & 0xff;
+ }
+
+ 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 (!buffer.hasRemaining()) {
+ if (fill() < 0) {
+ return -1;
+ }
+ }
+
+ // Read until we have read pLength bytes, or have reached EOF
+ int count = 0;
+ int off = pOffset;
+
+ while (pLength > count) {
+ if (!buffer.hasRemaining()) {
+ if (fill() < 0) {
+ break;
+ }
+ }
+
+ // Copy as many bytes as possible
+ int dstLen = Math.min(pLength - count, buffer.remaining());
+ buffer.get(pBytes, off, dstLen);
+
+ // Update offset (rest)
+ off += dstLen;
+
+ // Increase count
+ count += dstLen;
+ }
+
+ return count;
+ }
+
+ public long skip(final long pLength) throws IOException {
+ // End of file?
+ if (!buffer.hasRemaining()) {
+ if (fill() < 0) {
+ return 0; // Yes, 0, not -1
+ }
+ }
+
+ // Skip until we have skipped pLength bytes, or have reached EOF
+ long total = 0;
+
+ while (total < pLength) {
+ if (!buffer.hasRemaining()) {
+ if (fill() < 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, buffer.remaining());
+ buffer.position(buffer.position() + skipped);
+ 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 {
+ buffer.clear();
+ int read = decoder.decode(in, buffer);
+
+ // TODO: Enforce this in test case, leave here to aid debugging
+ if (read > buffer.capacity()) {
+ throw new AssertionError(
+ String.format(
+ "Decode beyond buffer (%d): %d (using %s decoder)",
+ buffer.capacity(), read, decoder.getClass().getName()
+ )
+ );
+ }
+
+ buffer.flip();
+
+ if (read == 0) {
+ return -1;
+ }
+
+ return read;
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Encoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Encoder.java
index 42e235e4..c5c8278c 100644
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Encoder.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/Encoder.java
@@ -1,65 +1,66 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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;
-import java.nio.ByteBuffer;
-
-/**
- * Interface for encoders.
- * An {@code Encoder} may be used with an {@code EncoderStream}, to perform
- * on-the-fly encoding to an {@code OutputStream}.
- *
- * Important note: Encoder implementations are typically not synchronized.
- *
- * @see Decoder
- * @see EncoderStream
- *
- * @author Harald Kuhr
- * @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 buffer.remaining()} bytes into the given input stream,
- * from the given buffer.
- *
- * @param stream the output stream to encode data to
- * @param buffer buffer to read data from
- *
- * @throws java.io.IOException if an I/O error occurs
- */
- void encode(OutputStream stream, ByteBuffer buffer) throws IOException;
-
- //TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size
- // void flush()?
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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;
+import java.nio.ByteBuffer;
+
+/**
+ * Interface for encoders.
+ * An {@code Encoder} may be used with an {@code EncoderStream}, to perform
+ * on-the-fly encoding to an {@code OutputStream}.
+ *
+ * Important note: Encoder implementations are typically not synchronized.
+ *
+ *
+ * @see Decoder
+ * @see EncoderStream
+ *
+ * @author Harald Kuhr
+ * @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 buffer.remaining()} bytes into the given input stream,
+ * from the given buffer.
+ *
+ * @param stream the output stream to encode data to
+ * @param buffer buffer to read data from
+ *
+ * @throws java.io.IOException if an I/O error occurs
+ */
+ void encode(OutputStream stream, ByteBuffer buffer) throws IOException;
+
+ //TODO: int requiredBufferSize(): -1 == any, otherwise, use this buffer size
+ // void flush()?
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java
index a22ad5f0..dafceb2e 100644
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/EncoderStream.java
@@ -1,137 +1,136 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.IOException;
-import java.io.OutputStream;
-import java.nio.ByteBuffer;
-
-/**
- * An {@code OutputStream} that provides on-the-fly encoding to an underlying
- * stream.
- *
- * @see DecoderStream
- * @see Encoder
- *
- * @author Harald Kuhr
- * @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 {
- // TODO: This class need a test case ASAP!!!
-
- protected final Encoder encoder;
- private final boolean flushOnWrite;
-
- protected final ByteBuffer buffer;
-
- /**
- * 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);
-
- encoder = pEncoder;
- flushOnWrite = pFlushOnWrite;
-
- buffer = ByteBuffer.allocate(1024);
- buffer.flip();
- }
-
- public void close() throws IOException {
- flush();
- super.close();
- }
-
- public void flush() throws IOException {
- encodeBuffer();
- super.flush();
- }
-
- private void encodeBuffer() throws IOException {
- if (buffer.position() != 0) {
- buffer.flip();
-
- // Make sure all remaining data in buffer is written to the stream
- encoder.encode(out, buffer);
-
- // Reset buffer
- buffer.clear();
- }
- }
-
- 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 (!flushOnWrite && pLength < buffer.remaining()) {
- // Buffer data
- buffer.put(pBytes, pOffset, pLength);
- }
- else {
- // Encode data already in the buffer
- encodeBuffer();
-
- // Encode rest without buffering
- encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength));
- }
- }
-
- public void write(final int pByte) throws IOException {
- if (!buffer.hasRemaining()) {
- encodeBuffer(); // Resets bufferPos to 0
- }
-
- buffer.put((byte) pByte);
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * An {@code OutputStream} that provides on-the-fly encoding to an underlying stream.
+ *
+ * @see DecoderStream
+ * @see Encoder
+ *
+ * @author Harald Kuhr
+ * @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 {
+ // TODO: This class need a test case ASAP!!!
+
+ protected final Encoder encoder;
+ private final boolean flushOnWrite;
+
+ protected final ByteBuffer buffer;
+
+ /**
+ * 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);
+
+ encoder = pEncoder;
+ flushOnWrite = pFlushOnWrite;
+
+ buffer = ByteBuffer.allocate(1024);
+ buffer.flip();
+ }
+
+ public void close() throws IOException {
+ flush();
+ super.close();
+ }
+
+ public void flush() throws IOException {
+ encodeBuffer();
+ super.flush();
+ }
+
+ private void encodeBuffer() throws IOException {
+ if (buffer.position() != 0) {
+ buffer.flip();
+
+ // Make sure all remaining data in buffer is written to the stream
+ encoder.encode(out, buffer);
+
+ // Reset buffer
+ buffer.clear();
+ }
+ }
+
+ 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 (!flushOnWrite && pLength < buffer.remaining()) {
+ // Buffer data
+ buffer.put(pBytes, pOffset, pLength);
+ }
+ else {
+ // Encode data already in the buffer
+ encodeBuffer();
+
+ // Encode rest without buffering
+ encoder.encode(out, ByteBuffer.wrap(pBytes, pOffset, pLength));
+ }
+ }
+
+ public void write(final int pByte) throws IOException {
+ if (!buffer.hasRemaining()) {
+ encodeBuffer(); // Resets bufferPos to 0
+ }
+
+ buffer.put((byte) pByte);
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java
index 37d0bae2..69865e39 100644
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java
@@ -37,30 +37,36 @@ import java.nio.ByteBuffer;
/**
* Decoder implementation for Apple PackBits run-length encoding.
- *
- * From Wikipedia, the free encyclopedia
+ *
+ * From Wikipedia, the free encyclopedia
+ *
* PackBits is a fast, simple compression scheme for run-length encoding of
* data.
- *
+ *
+ *
* 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.
- *
+ *
+ *
* 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).
- *
- * Header byte | Data |
- * 0 to 127 | 1 + n literal bytes of data |
- * 0 to -127 | One byte of data, repeated 1 - n times in
- * the decompressed output |
- * -128 | No operation |
- *
+ *
+ *
+ * PackBits
+ * Header byte | Data |
+ * 0 to 127 | 1 + n literal bytes of data |
+ * 0 to -127 | One byte of data, repeated 1 - n times in the decompressed output |
+ * -128 | No operation |
+ *
+ *
* 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.
- *
- * See Understanding PackBits
+ *
+ *
+ * @see Understanding PackBits
*
* @author Harald Kuhr
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/io/enc/PackBitsDecoder.java#1 $
@@ -80,10 +86,11 @@ public final class PackBitsDecoder implements Decoder {
/**
* Creates a {@code PackBitsDecoder}, with optional compatibility mode.
- *
+ *
* 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 disableNoOp {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
*/
@@ -93,10 +100,11 @@ public final class PackBitsDecoder implements Decoder {
/**
* Creates a {@code PackBitsDecoder}, with optional compatibility mode.
- *
+ *
* 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 disableNoOp {@code true} if {@code -128} should be treated as a compressed run, and not a no-op
*/
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java
index 501e7c57..4bbfc15f 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/enc/PackBitsEncoder.java
@@ -1,132 +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 of the copyright holder 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 HOLDER 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;
-import java.nio.ByteBuffer;
-
-/**
- * Encoder implementation for Apple PackBits run-length encoding.
- *
- * From Wikipedia, the free encyclopedia
- * PackBits is a fast, simple compression scheme for run-length encoding of
- * data.
- *
- * 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.
- *
- * 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).
- *
- * Header byte | Data |
- * 0 to 127 | 1 + n literal bytes of data |
- * 0 to -127 | One byte of data, repeated 1 - n times in
- * the decompressed output |
- * -128 | No operation |
- *
- * 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.
- *
- * See Understanding PackBits
- *
- * @author Harald Kuhr
- * @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[] buffer = new byte[128];
-
- /**
- * Creates a {@code PackBitsEncoder}.
- */
- public PackBitsEncoder() {
- }
-
- public void encode(final OutputStream stream, final ByteBuffer buffer) throws IOException {
- encode(stream, buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
- buffer.position(buffer.remaining());
- }
-
- private 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])))) {
- buffer[run++] = pBuffer[offset++];
- }
-
- // If last byte, include it in literal run, if space
- if (offset == max && run > 0 && run < 128) {
- buffer[run++] = pBuffer[offset++];
- }
-
- if (run > 0) {
- pStream.write(run - 1);
- pStream.write(buffer, 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++]);
- }
- }
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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;
+import java.nio.ByteBuffer;
+
+/**
+ * Encoder implementation for Apple PackBits run-length encoding.
+ *
+ * From Wikipedia, the free encyclopedia
+ *
+ * PackBits is a fast, simple compression scheme for run-length encoding of
+ * data.
+ *
+ *
+ * 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.
+ *
+ *
+ * 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).
+ *
+ *
+ * PackBits
+ * Header byte | Data |
+ * 0 to 127 | 1 + n literal bytes of data |
+ * 0 to -127 | One byte of data, repeated 1 - n times in the decompressed output |
+ * -128 | No operation |
+ *
+ *
+ * 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.
+ *
+ *
+ * @see Understanding PackBits
+ *
+ * @author Harald Kuhr
+ * @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[] buffer = new byte[128];
+
+ /**
+ * Creates a {@code PackBitsEncoder}.
+ */
+ public PackBitsEncoder() {
+ }
+
+ public void encode(final OutputStream stream, final ByteBuffer buffer) throws IOException {
+ encode(stream, buffer.array(), buffer.arrayOffset() + buffer.position(), buffer.remaining());
+ buffer.position(buffer.remaining());
+ }
+
+ private 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])))) {
+ buffer[run++] = pBuffer[offset++];
+ }
+
+ // If last byte, include it in literal run, if space
+ if (offset == max && run > 0 && run < 128) {
+ buffer[run++] = pBuffer[offset++];
+ }
+
+ if (run > 0) {
+ pStream.write(run - 1);
+ pStream.write(buffer, 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++]);
+ }
+ }
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java
index 60cdf7ed..75aeb477 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/CompoundDocument.java
@@ -1,798 +1,801 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.nio.ByteOrder;
-import java.util.Arrays;
-import java.util.SortedSet;
-import java.util.TreeSet;
-import java.util.UUID;
-
-import static com.twelvemonkeys.lang.Validate.notNull;
-
-/**
- * Represents a read-only OLE2 compound document.
- *
- *
- * NOTE: This class is not synchronized. Accessing the document or its
- * entries from different threads, will need synchronization on the document
- * instance.
- *
- * @author Harald Kuhr
- * @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 implements AutoCloseable {
- // TODO: Write support...
- // TODO: Properties: http://support.microsoft.com/kb/186898
-
- static final byte[] MAGIC = new byte[]{
- (byte) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0,
- (byte) 0xA1, (byte) 0xB1, (byte) 0x1A, (byte) 0xE1,
- };
-
- private static final int FREE_SID = -1;
- private static final int END_OF_CHAIN_SID = -2;
- private static final int SAT_SECTOR_SID = -3; // Sector used by SAT
- private static final int MSAT_SECTOR_SID = -4; // Sector used my Master SAT
-
- public static final int HEADER_SIZE = 512;
-
- /** The epoch offset of CompoundDocument time stamps */
- public final static long EPOCH_OFFSET = -11644477200000L;
-
- private final DataInput input;
-
- private UUID uUID;
-
- private int sectorSize;
- private int shortSectorSize;
-
- private int directorySId;
-
- private int minStreamSize;
-
- private int shortSATSId;
- private int shortSATSize;
-
- // Master Sector Allocation Table
- private int[] masterSAT;
- private int[] SAT;
- private int[] shortSAT;
-
- private Entry rootEntry;
- private SIdChain shortStreamSIdChain;
- private SIdChain directorySIdChain;
-
- /**
- * Creates a (for now) read only {@code CompoundDocument}.
- *
- * Warning! You must invoke {@link #close()} on the compound document
- * created from this constructor when done, to avoid leaking file
- * descriptors.
- *
- * @param file the file to read from
- *
- * @throws IOException if an I/O exception occurs while reading the header
- */
- public CompoundDocument(final File file) throws IOException {
- // TODO: We need to close this (or it's underlying RAF)! Otherwise we're leaking file descriptors!
- input = new LittleEndianRandomAccessFile(FileUtil.resolve(file), "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 MemoryCacheSeekableStream(pInput));
- }
-
- // For testing only, consider exposing later
- CompoundDocument(final SeekableInputStream stream) throws IOException {
- input = new SeekableLittleEndianDataInputStream(stream);
-
- // 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 input the input to read from
- *
- * @throws IOException if an I/O exception occurs while reading the header
- */
- public CompoundDocument(final ImageInputStream input) throws IOException {
- this.input = notNull(input, "input");
-
- // This implementation only supports little endian (Intel) CompoundDocuments
- input.setByteOrder(ByteOrder.LITTLE_ENDIAN);
-
- // 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();
- }
-
- /**
- * This method will close the underlying {@link RandomAccessFile} if any,
- * but will leave any stream created outside the document open.
- *
- * @see #CompoundDocument(File)
- * @see RandomAccessFile#close()
- *
- * @throws IOException if an I/O error occurs.
- */
- @Override
- public void close() throws IOException {
- if (input instanceof RandomAccessFile) {
- ((RandomAccessFile) input).close();
- }
- else if (input instanceof LittleEndianRandomAccessFile) {
- ((LittleEndianRandomAccessFile) input).close();
- }
-
- // Other streams are left open
- }
-
- 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 (masterSAT != null) {
- return;
- }
-
- if (!canRead(input, false)) {
- throw new CorruptDocumentException("Not an OLE 2 Compound Document");
- }
-
- // UID (seems to be all 0s)
- uUID = new UUID(input.readLong(), input.readLong());
-// System.out.println("uUID: " + uUID);
-
- // int version =
- input.readUnsignedShort();
-// System.out.println("version: " + version);
- // int revision =
- input.readUnsignedShort();
-// System.out.println("revision: " + revision);
-
- int byteOrder = input.readUnsignedShort();
-// System.out.printf("byteOrder: 0x%04x\n", byteOrder);
- if (byteOrder == 0xffff) {
- throw new CorruptDocumentException("Cannot read big endian OLE 2 Compound Documents");
- }
- else if (byteOrder != 0xfffe) {
- // Reversed, as I'm already reading little-endian
- throw new CorruptDocumentException(String.format("Unknown byte order marker: 0x%04x, expected 0xfffe or 0xffff", byteOrder));
- }
-
- sectorSize = 1 << input.readUnsignedShort();
-// System.out.println("sectorSize: " + sectorSize + " bytes");
- shortSectorSize = 1 << input.readUnsignedShort();
-// System.out.println("shortSectorSize: " + shortSectorSize + " bytes");
-
- // Reserved
- if (skipBytesFully(10) != 10) {
- throw new CorruptDocumentException();
- }
-
- int SATSize = input.readInt();
-// System.out.println("normalSATSize: " + SATSize);
-
- directorySId = input.readInt();
-// System.out.println("directorySId: " + directorySId);
-
- // Reserved
- if (skipBytesFully(4) != 4) {
- throw new CorruptDocumentException();
- }
-
- minStreamSize = input.readInt();
-// System.out.println("minStreamSize: " + minStreamSize + " bytes");
-
- shortSATSId = input.readInt();
-// System.out.println("shortSATSId: " + shortSATSId);
- shortSATSize = input.readInt();
-// System.out.println("shortSATSize: " + shortSATSize);
- int masterSATSId = input.readInt();
-// System.out.println("masterSATSId: " + masterSATSId);
- int masterSATSize = input.readInt();
-// System.out.println("masterSATSize: " + masterSATSize);
-
- // Read masterSAT: 436 bytes, containing up to 109 SIDs
- //System.out.println("MSAT:");
- masterSAT = new int[SATSize];
- final int headerSIds = Math.min(SATSize, 109);
- for (int i = 0; i < headerSIds; i++) {
- masterSAT[i] = input.readInt();
- //System.out.println("\tSID(" + i + "): " + masterSAT[i]);
- }
-
- if (masterSATSId == END_OF_CHAIN_SID) {
- // End of chain
- int freeSIdLength = 436 - (SATSize * 4);
- if (skipBytesFully(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 = input.readInt();
- switch (sid) {
- case FREE_SID:// Free
- break;
- default:
- masterSAT[index++] = sid;
- break;
- }
- }
-
- int next = input.readInt();
- if (next == END_OF_CHAIN_SID) {// End of chain
- break;
- }
-
- seekToSId(next, FREE_SID);
- }
- }
- }
-
- private int skipBytesFully(final int n) throws IOException {
- int toSkip = n;
-
- while (toSkip > 0) {
- int skipped = input.skipBytes(n);
- if (skipped <= 0) {
- break;
- }
-
- toSkip -= skipped;
- }
-
- return n - toSkip;
- }
-
- private void readSAT() throws IOException {
- if (SAT != null) {
- return;
- }
-
- final int intsPerSector = sectorSize / 4;
-
- // Read the Sector Allocation Table
- SAT = new int[masterSAT.length * intsPerSector];
-
- for (int i = 0; i < masterSAT.length; i++) {
- seekToSId(masterSAT[i], FREE_SID);
-
- for (int j = 0; j < intsPerSector; j++) {
- int nextSID = input.readInt();
- int index = (j + (i * intsPerSector));
-
- SAT[index] = nextSID;
- }
- }
-
- // Read the short-stream Sector Allocation Table
- SIdChain chain = getSIdChain(shortSATSId, FREE_SID);
- shortSAT = new int[shortSATSize * intsPerSector];
- for (int i = 0; i < shortSATSize; i++) {
- seekToSId(chain.get(i), FREE_SID);
-
- for (int j = 0; j < intsPerSector; j++) {
- int nextSID = input.readInt();
- int index = (j + (i * intsPerSector));
-
- shortSAT[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) ? shortSAT : SAT;
-
- 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 < minStreamSize;
- }
-
- /**
- * 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 continuous...
- Entry root = getRootEntry();
- if (shortStreamSIdChain == null) {
- shortStreamSIdChain = getSIdChain(root.startSId, root.streamSize);
- }
-
-// System.err.println("pSId: " + pSId);
- int shortPerSId = sectorSize / shortSectorSize;
-// System.err.println("shortPerSId: " + shortPerSId);
- int offset = pSId / shortPerSId;
-// System.err.println("offset: " + offset);
- int shortOffset = pSId - (offset * shortPerSId);
-// System.err.println("shortOffset: " + shortOffset);
-// System.err.println("shortStreamSIdChain.offset: " + shortStreamSIdChain.get(offset));
-
- pos = HEADER_SIZE
- + (shortStreamSIdChain.get(offset) * (long) sectorSize)
- + (shortOffset * (long) shortSectorSize);
-// System.err.println("pos: " + pos);
- }
- else {
- pos = HEADER_SIZE + pSId * (long) sectorSize;
- }
-
- if (input instanceof LittleEndianRandomAccessFile) {
- ((LittleEndianRandomAccessFile) input).seek(pos);
- }
- else if (input instanceof ImageInputStream) {
- ((ImageInputStream) input).seek(pos);
- }
- else {
- ((SeekableLittleEndianDataInputStream) input).seek(pos);
- }
- }
-
- private void seekToDId(final int pDId) throws IOException {
- if (directorySIdChain == null) {
- directorySIdChain = getSIdChain(directorySId, FREE_SID);
- }
-
- int dIdsPerSId = sectorSize / Entry.LENGTH;
-
- int sIdOffset = pDId / dIdsPerSId;
- int dIdOffset = pDId - (sIdOffset * dIdsPerSId);
-
- int sId = directorySIdChain.get(sIdOffset);
-
- seekToSId(sId, FREE_SID);
- if (input instanceof LittleEndianRandomAccessFile) {
- LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) this.input;
- input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH);
- }
- else if (input instanceof ImageInputStream) {
- ImageInputStream input = (ImageInputStream) this.input;
- input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
- }
- else {
- SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) this.input;
- 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 < minStreamSize ? shortSectorSize : this.sectorSize;
-
- return new MemoryCacheSeekableStream(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);
- input.readFully(bytes);
-
- return new ByteArrayInputStream(bytes);
- }
-
- Entry getEntry(final int pDirectoryId, Entry pParent) throws IOException {
- Entry entry = Entry.readEntry(new LittleEndianDataInputStream(
- getDirectoryStreamForDId(pDirectoryId)
- ));
- entry.parent = pParent;
- entry.document = this;
- return entry;
- }
-
- SortedSet getEntries(final int pDirectoryId, final Entry pParent)
- throws IOException {
- return getEntriesRecursive(pDirectoryId, pParent, new TreeSet());
- }
-
- private SortedSet getEntriesRecursive(final int pDirectoryId, final Entry pParent, final SortedSet 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 (rootEntry == null) {
- readSAT();
-
- rootEntry = getEntry(0, null);
-
- if (rootEntry.type != Entry.ROOT_STORAGE) {
- throw new CorruptDocumentException("Invalid root storage type: " + rootEntry.type);
- }
- }
-
- return rootEntry;
- }
-
- // This is useless, as most documents on file have all-zero UUIDs...
-// @Override
-// public int hashCode() {
-// return uUID.hashCode();
-// }
-//
-// @Override
-// public boolean equals(final Object pOther) {
-// if (pOther == this) {
-// return true;
-// }
-//
-// if (pOther == null) {
-// return true;
-// }
-//
-// if (pOther.getClass() == getClass()) {
-// return uUID.equals(((CompoundDocument) pOther).uUID);
-// }
-//
-// 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(), uUID, sectorSize, shortSectorSize, directorySId, masterSAT.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.
- *
- * 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;
- }
-
- static class Stream extends InputStream {
- private final SIdChain chain;
- private final CompoundDocument document;
- private final long length;
-
- private long streamPos;
- private int nextSectorPos;
- private byte[] buffer;
- private int bufferPos;
-
- public Stream(SIdChain chain, int streamSize, int sectorSize, CompoundDocument document) {
- this.chain = chain;
- this.length = streamSize;
-
- this.buffer = new byte[sectorSize];
- this.bufferPos = buffer.length;
-
- this.document = document;
- }
-
- @Override
- public int available() throws IOException {
- return (int) Math.min(buffer.length - bufferPos, length - streamPos);
- }
-
- public int read() throws IOException {
- if (available() <= 0) {
- if (!fillBuffer()) {
- return -1;
- }
- }
-
- streamPos++;
-
- return buffer[bufferPos++] & 0xff;
- }
-
- private boolean fillBuffer() throws IOException {
- if (streamPos < length && nextSectorPos < chain.length()) {
- // TODO: Sync on document.input here, and we are completely detached... :-)
- // TODO: Update: We also need to sync other places... :-P
- synchronized (document) {
- document.seekToSId(chain.get(nextSectorPos), length);
- document.input.readFully(buffer);
- }
-
- nextSectorPos++;
- bufferPos = 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(buffer, bufferPos, b, off, toRead);
- bufferPos += toRead;
- streamPos += toRead;
-
- return toRead;
- }
-
- @Override
- public void close() throws IOException {
- buffer = null;
- }
- }
-
- static class SeekableLittleEndianDataInputStream extends LittleEndianDataInputStream implements Seekable {
- private final SeekableInputStream seekable;
-
- public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) {
- super(pInput);
- seekable = pInput;
- }
-
- public void seek(final long pPosition) throws IOException {
- seekable.seek(pPosition);
- }
-
- public boolean isCachedFile() {
- return seekable.isCachedFile();
- }
-
- public boolean isCachedMemory() {
- return seekable.isCachedMemory();
- }
-
- public boolean isCached() {
- return seekable.isCached();
- }
-
- public long getStreamPosition() throws IOException {
- return seekable.getStreamPosition();
- }
-
- public long getFlushedPosition() throws IOException {
- return seekable.getFlushedPosition();
- }
-
- public void flushBefore(final long pPosition) throws IOException {
- seekable.flushBefore(pPosition);
- }
-
- public void flush() throws IOException {
- seekable.flush();
- }
-
- @Override
- public void reset() throws IOException {
- seekable.reset();
- }
-
- public void mark() {
- seekable.mark();
- }
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.UUID;
+
+import static com.twelvemonkeys.lang.Validate.notNull;
+
+/**
+ * Represents a read-only OLE2 compound document.
+ *
+ *
+ * NOTE: This class is not synchronized. Accessing the document or its
+ * entries from different threads, will need synchronization on the document
+ * instance.
+ *
+ *
+ * @author Harald Kuhr
+ * @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 implements AutoCloseable {
+ // TODO: Write support...
+ // TODO: Properties: http://support.microsoft.com/kb/186898
+
+ static final byte[] MAGIC = new byte[]{
+ (byte) 0xD0, (byte) 0xCF, (byte) 0x11, (byte) 0xE0,
+ (byte) 0xA1, (byte) 0xB1, (byte) 0x1A, (byte) 0xE1,
+ };
+
+ private static final int FREE_SID = -1;
+ private static final int END_OF_CHAIN_SID = -2;
+ private static final int SAT_SECTOR_SID = -3; // Sector used by SAT
+ private static final int MSAT_SECTOR_SID = -4; // Sector used my Master SAT
+
+ public static final int HEADER_SIZE = 512;
+
+ /** The epoch offset of CompoundDocument time stamps */
+ public final static long EPOCH_OFFSET = -11644477200000L;
+
+ private final DataInput input;
+
+ private UUID uUID;
+
+ private int sectorSize;
+ private int shortSectorSize;
+
+ private int directorySId;
+
+ private int minStreamSize;
+
+ private int shortSATSId;
+ private int shortSATSize;
+
+ // Master Sector Allocation Table
+ private int[] masterSAT;
+ private int[] SAT;
+ private int[] shortSAT;
+
+ private Entry rootEntry;
+ private SIdChain shortStreamSIdChain;
+ private SIdChain directorySIdChain;
+
+ /**
+ * Creates a (for now) read only {@code CompoundDocument}.
+ *
+ * Warning! You must invoke {@link #close()} on the compound document
+ * created from this constructor when done, to avoid leaking file
+ * descriptors.
+ *
+ *
+ * @param file the file to read from
+ *
+ * @throws IOException if an I/O exception occurs while reading the header
+ */
+ public CompoundDocument(final File file) throws IOException {
+ // TODO: We need to close this (or it's underlying RAF)! Otherwise we're leaking file descriptors!
+ input = new LittleEndianRandomAccessFile(FileUtil.resolve(file), "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 MemoryCacheSeekableStream(pInput));
+ }
+
+ // For testing only, consider exposing later
+ CompoundDocument(final SeekableInputStream stream) throws IOException {
+ input = new SeekableLittleEndianDataInputStream(stream);
+
+ // 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 input the input to read from
+ *
+ * @throws IOException if an I/O exception occurs while reading the header
+ */
+ public CompoundDocument(final ImageInputStream input) throws IOException {
+ this.input = notNull(input, "input");
+
+ // This implementation only supports little endian (Intel) CompoundDocuments
+ input.setByteOrder(ByteOrder.LITTLE_ENDIAN);
+
+ // 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();
+ }
+
+ /**
+ * This method will close the underlying {@link RandomAccessFile} if any,
+ * but will leave any stream created outside the document open.
+ *
+ * @see #CompoundDocument(File)
+ * @see RandomAccessFile#close()
+ *
+ * @throws IOException if an I/O error occurs.
+ */
+ @Override
+ public void close() throws IOException {
+ if (input instanceof RandomAccessFile) {
+ ((RandomAccessFile) input).close();
+ }
+ else if (input instanceof LittleEndianRandomAccessFile) {
+ ((LittleEndianRandomAccessFile) input).close();
+ }
+
+ // Other streams are left open
+ }
+
+ 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 (masterSAT != null) {
+ return;
+ }
+
+ if (!canRead(input, false)) {
+ throw new CorruptDocumentException("Not an OLE 2 Compound Document");
+ }
+
+ // UID (seems to be all 0s)
+ uUID = new UUID(input.readLong(), input.readLong());
+// System.out.println("uUID: " + uUID);
+
+ // int version =
+ input.readUnsignedShort();
+// System.out.println("version: " + version);
+ // int revision =
+ input.readUnsignedShort();
+// System.out.println("revision: " + revision);
+
+ int byteOrder = input.readUnsignedShort();
+// System.out.printf("byteOrder: 0x%04x\n", byteOrder);
+ if (byteOrder == 0xffff) {
+ throw new CorruptDocumentException("Cannot read big endian OLE 2 Compound Documents");
+ }
+ else if (byteOrder != 0xfffe) {
+ // Reversed, as I'm already reading little-endian
+ throw new CorruptDocumentException(String.format("Unknown byte order marker: 0x%04x, expected 0xfffe or 0xffff", byteOrder));
+ }
+
+ sectorSize = 1 << input.readUnsignedShort();
+// System.out.println("sectorSize: " + sectorSize + " bytes");
+ shortSectorSize = 1 << input.readUnsignedShort();
+// System.out.println("shortSectorSize: " + shortSectorSize + " bytes");
+
+ // Reserved
+ if (skipBytesFully(10) != 10) {
+ throw new CorruptDocumentException();
+ }
+
+ int SATSize = input.readInt();
+// System.out.println("normalSATSize: " + SATSize);
+
+ directorySId = input.readInt();
+// System.out.println("directorySId: " + directorySId);
+
+ // Reserved
+ if (skipBytesFully(4) != 4) {
+ throw new CorruptDocumentException();
+ }
+
+ minStreamSize = input.readInt();
+// System.out.println("minStreamSize: " + minStreamSize + " bytes");
+
+ shortSATSId = input.readInt();
+// System.out.println("shortSATSId: " + shortSATSId);
+ shortSATSize = input.readInt();
+// System.out.println("shortSATSize: " + shortSATSize);
+ int masterSATSId = input.readInt();
+// System.out.println("masterSATSId: " + masterSATSId);
+ int masterSATSize = input.readInt();
+// System.out.println("masterSATSize: " + masterSATSize);
+
+ // Read masterSAT: 436 bytes, containing up to 109 SIDs
+ //System.out.println("MSAT:");
+ masterSAT = new int[SATSize];
+ final int headerSIds = Math.min(SATSize, 109);
+ for (int i = 0; i < headerSIds; i++) {
+ masterSAT[i] = input.readInt();
+ //System.out.println("\tSID(" + i + "): " + masterSAT[i]);
+ }
+
+ if (masterSATSId == END_OF_CHAIN_SID) {
+ // End of chain
+ int freeSIdLength = 436 - (SATSize * 4);
+ if (skipBytesFully(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 = input.readInt();
+ switch (sid) {
+ case FREE_SID:// Free
+ break;
+ default:
+ masterSAT[index++] = sid;
+ break;
+ }
+ }
+
+ int next = input.readInt();
+ if (next == END_OF_CHAIN_SID) {// End of chain
+ break;
+ }
+
+ seekToSId(next, FREE_SID);
+ }
+ }
+ }
+
+ private int skipBytesFully(final int n) throws IOException {
+ int toSkip = n;
+
+ while (toSkip > 0) {
+ int skipped = input.skipBytes(n);
+ if (skipped <= 0) {
+ break;
+ }
+
+ toSkip -= skipped;
+ }
+
+ return n - toSkip;
+ }
+
+ private void readSAT() throws IOException {
+ if (SAT != null) {
+ return;
+ }
+
+ final int intsPerSector = sectorSize / 4;
+
+ // Read the Sector Allocation Table
+ SAT = new int[masterSAT.length * intsPerSector];
+
+ for (int i = 0; i < masterSAT.length; i++) {
+ seekToSId(masterSAT[i], FREE_SID);
+
+ for (int j = 0; j < intsPerSector; j++) {
+ int nextSID = input.readInt();
+ int index = (j + (i * intsPerSector));
+
+ SAT[index] = nextSID;
+ }
+ }
+
+ // Read the short-stream Sector Allocation Table
+ SIdChain chain = getSIdChain(shortSATSId, FREE_SID);
+ shortSAT = new int[shortSATSize * intsPerSector];
+ for (int i = 0; i < shortSATSize; i++) {
+ seekToSId(chain.get(i), FREE_SID);
+
+ for (int j = 0; j < intsPerSector; j++) {
+ int nextSID = input.readInt();
+ int index = (j + (i * intsPerSector));
+
+ shortSAT[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) ? shortSAT : SAT;
+
+ 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 < minStreamSize;
+ }
+
+ /**
+ * 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 continuous...
+ Entry root = getRootEntry();
+ if (shortStreamSIdChain == null) {
+ shortStreamSIdChain = getSIdChain(root.startSId, root.streamSize);
+ }
+
+// System.err.println("pSId: " + pSId);
+ int shortPerSId = sectorSize / shortSectorSize;
+// System.err.println("shortPerSId: " + shortPerSId);
+ int offset = pSId / shortPerSId;
+// System.err.println("offset: " + offset);
+ int shortOffset = pSId - (offset * shortPerSId);
+// System.err.println("shortOffset: " + shortOffset);
+// System.err.println("shortStreamSIdChain.offset: " + shortStreamSIdChain.get(offset));
+
+ pos = HEADER_SIZE
+ + (shortStreamSIdChain.get(offset) * (long) sectorSize)
+ + (shortOffset * (long) shortSectorSize);
+// System.err.println("pos: " + pos);
+ }
+ else {
+ pos = HEADER_SIZE + pSId * (long) sectorSize;
+ }
+
+ if (input instanceof LittleEndianRandomAccessFile) {
+ ((LittleEndianRandomAccessFile) input).seek(pos);
+ }
+ else if (input instanceof ImageInputStream) {
+ ((ImageInputStream) input).seek(pos);
+ }
+ else {
+ ((SeekableLittleEndianDataInputStream) input).seek(pos);
+ }
+ }
+
+ private void seekToDId(final int pDId) throws IOException {
+ if (directorySIdChain == null) {
+ directorySIdChain = getSIdChain(directorySId, FREE_SID);
+ }
+
+ int dIdsPerSId = sectorSize / Entry.LENGTH;
+
+ int sIdOffset = pDId / dIdsPerSId;
+ int dIdOffset = pDId - (sIdOffset * dIdsPerSId);
+
+ int sId = directorySIdChain.get(sIdOffset);
+
+ seekToSId(sId, FREE_SID);
+ if (input instanceof LittleEndianRandomAccessFile) {
+ LittleEndianRandomAccessFile input = (LittleEndianRandomAccessFile) this.input;
+ input.seek(input.getFilePointer() + dIdOffset * Entry.LENGTH);
+ }
+ else if (input instanceof ImageInputStream) {
+ ImageInputStream input = (ImageInputStream) this.input;
+ input.seek(input.getStreamPosition() + dIdOffset * Entry.LENGTH);
+ }
+ else {
+ SeekableLittleEndianDataInputStream input = (SeekableLittleEndianDataInputStream) this.input;
+ 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 < minStreamSize ? shortSectorSize : this.sectorSize;
+
+ return new MemoryCacheSeekableStream(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);
+ input.readFully(bytes);
+
+ return new ByteArrayInputStream(bytes);
+ }
+
+ Entry getEntry(final int pDirectoryId, Entry pParent) throws IOException {
+ Entry entry = Entry.readEntry(new LittleEndianDataInputStream(
+ getDirectoryStreamForDId(pDirectoryId)
+ ));
+ entry.parent = pParent;
+ entry.document = this;
+ return entry;
+ }
+
+ SortedSet getEntries(final int pDirectoryId, final Entry pParent)
+ throws IOException {
+ return getEntriesRecursive(pDirectoryId, pParent, new TreeSet());
+ }
+
+ private SortedSet getEntriesRecursive(final int pDirectoryId, final Entry pParent, final SortedSet 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 (rootEntry == null) {
+ readSAT();
+
+ rootEntry = getEntry(0, null);
+
+ if (rootEntry.type != Entry.ROOT_STORAGE) {
+ throw new CorruptDocumentException("Invalid root storage type: " + rootEntry.type);
+ }
+ }
+
+ return rootEntry;
+ }
+
+ // This is useless, as most documents on file have all-zero UUIDs...
+// @Override
+// public int hashCode() {
+// return uUID.hashCode();
+// }
+//
+// @Override
+// public boolean equals(final Object pOther) {
+// if (pOther == this) {
+// return true;
+// }
+//
+// if (pOther == null) {
+// return true;
+// }
+//
+// if (pOther.getClass() == getClass()) {
+// return uUID.equals(((CompoundDocument) pOther).uUID);
+// }
+//
+// 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(), uUID, sectorSize, shortSectorSize, directorySId, masterSAT.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.
+ *
+ * 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;
+ }
+
+ static class Stream extends InputStream {
+ private final SIdChain chain;
+ private final CompoundDocument document;
+ private final long length;
+
+ private long streamPos;
+ private int nextSectorPos;
+ private byte[] buffer;
+ private int bufferPos;
+
+ public Stream(SIdChain chain, int streamSize, int sectorSize, CompoundDocument document) {
+ this.chain = chain;
+ this.length = streamSize;
+
+ this.buffer = new byte[sectorSize];
+ this.bufferPos = buffer.length;
+
+ this.document = document;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return (int) Math.min(buffer.length - bufferPos, length - streamPos);
+ }
+
+ public int read() throws IOException {
+ if (available() <= 0) {
+ if (!fillBuffer()) {
+ return -1;
+ }
+ }
+
+ streamPos++;
+
+ return buffer[bufferPos++] & 0xff;
+ }
+
+ private boolean fillBuffer() throws IOException {
+ if (streamPos < length && nextSectorPos < chain.length()) {
+ // TODO: Sync on document.input here, and we are completely detached... :-)
+ // TODO: Update: We also need to sync other places... :-P
+ synchronized (document) {
+ document.seekToSId(chain.get(nextSectorPos), length);
+ document.input.readFully(buffer);
+ }
+
+ nextSectorPos++;
+ bufferPos = 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(buffer, bufferPos, b, off, toRead);
+ bufferPos += toRead;
+ streamPos += toRead;
+
+ return toRead;
+ }
+
+ @Override
+ public void close() throws IOException {
+ buffer = null;
+ }
+ }
+
+ static class SeekableLittleEndianDataInputStream extends LittleEndianDataInputStream implements Seekable {
+ private final SeekableInputStream seekable;
+
+ public SeekableLittleEndianDataInputStream(final SeekableInputStream pInput) {
+ super(pInput);
+ seekable = pInput;
+ }
+
+ public void seek(final long pPosition) throws IOException {
+ seekable.seek(pPosition);
+ }
+
+ public boolean isCachedFile() {
+ return seekable.isCachedFile();
+ }
+
+ public boolean isCachedMemory() {
+ return seekable.isCachedMemory();
+ }
+
+ public boolean isCached() {
+ return seekable.isCached();
+ }
+
+ public long getStreamPosition() throws IOException {
+ return seekable.getStreamPosition();
+ }
+
+ public long getFlushedPosition() throws IOException {
+ return seekable.getFlushedPosition();
+ }
+
+ public void flushBefore(final long pPosition) throws IOException {
+ seekable.flushBefore(pPosition);
+ }
+
+ public void flush() throws IOException {
+ seekable.flush();
+ }
+
+ @Override
+ public void reset() throws IOException {
+ seekable.reset();
+ }
+
+ public void mark() {
+ seekable.mark();
+ }
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/Entry.java b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/Entry.java
index c7951ff7..1364c7cc 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/Entry.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/io/ole2/Entry.java
@@ -1,342 +1,344 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.nio.charset.Charset;
-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 Harald Kuhr
- * @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 {
- String name;
- byte type;
- byte nodeColor;
-
- int prevDId;
- int nextDId;
- int rootNodeDId;
-
- long createdTimestamp;
- long modifiedTimestamp;
-
- int startSId;
- int streamSize;
-
- CompoundDocument document;
- Entry parent;
- SortedSet children;
-
- 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 NO_CHILDREN = Collections.unmodifiableSortedSet(new TreeSet());
-
- 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 {
- byte[] bytes = new byte[64];
- pInput.readFully(bytes);
-
- // NOTE: Length is in bytes, including the null-terminator...
- int nameLength = pInput.readShort();
- name = new String(bytes, 0, nameLength - 2, Charset.forName("UTF-16LE"));
-// 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 (!isFile()) {
- return null;
- }
-
- return document.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 (!isFile()) {
- 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).
- *
- * 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).
- *
- * 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 parent;
- }
-
- /**
- * 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.parent = 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 getChildEntries() throws IOException {
- if (children == null) {
- if (isFile() || rootNodeDId == -1) {
- children = NO_CHILDREN;
- }
- else {
- // Start at root node in R/B tree, and read to the left and right,
- // re-build tree, according to the docs
- children = Collections.unmodifiableSortedSet(document.getEntries(rootNodeDId, this));
- }
- }
-
- return children;
- }
-
- @Override
- public String toString() {
- return "\"" + name + "\""
- + " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root"))
- + (parent != null ? ", parent: \"" + parent.getName() + "\"" : "")
- + (isFile() ? "" : ", children: " + (children != null ? String.valueOf(children.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) && (parent == other.parent
- || (parent != null && parent.equals(other.parent)));
- }
-
- @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);
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.nio.charset.Charset;
+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 Harald Kuhr
+ * @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 {
+ String name;
+ byte type;
+ byte nodeColor;
+
+ int prevDId;
+ int nextDId;
+ int rootNodeDId;
+
+ long createdTimestamp;
+ long modifiedTimestamp;
+
+ int startSId;
+ int streamSize;
+
+ CompoundDocument document;
+ Entry parent;
+ SortedSet children;
+
+ 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 NO_CHILDREN = Collections.unmodifiableSortedSet(new TreeSet());
+
+ 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 {
+ byte[] bytes = new byte[64];
+ pInput.readFully(bytes);
+
+ // NOTE: Length is in bytes, including the null-terminator...
+ int nameLength = pInput.readShort();
+ name = new String(bytes, 0, nameLength - 2, Charset.forName("UTF-16LE"));
+// 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 (!isFile()) {
+ return null;
+ }
+
+ return document.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 (!isFile()) {
+ 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).
+ *
+ * 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).
+ *
+ * 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 parent;
+ }
+
+ /**
+ * 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.parent = 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 getChildEntries() throws IOException {
+ if (children == null) {
+ if (isFile() || rootNodeDId == -1) {
+ children = NO_CHILDREN;
+ }
+ else {
+ // Start at root node in R/B tree, and read to the left and right,
+ // re-build tree, according to the docs
+ children = Collections.unmodifiableSortedSet(document.getEntries(rootNodeDId, this));
+ }
+ }
+
+ return children;
+ }
+
+ @Override
+ public String toString() {
+ return "\"" + name + "\""
+ + " (" + (isFile() ? "Document" : (isDirectory() ? "Directory" : "Root"))
+ + (parent != null ? ", parent: \"" + parent.getName() + "\"" : "")
+ + (isFile() ? "" : ", children: " + (children != null ? String.valueOf(children.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) && (parent == other.parent
+ || (parent != null && parent.equals(other.parent)));
+ }
+
+ @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);
+ }
+}
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/net/MIMEUtil.java b/common/common-io/src/main/java/com/twelvemonkeys/net/MIMEUtil.java
index a8196395..3b787b3b 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/net/MIMEUtil.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/net/MIMEUtil.java
@@ -1,314 +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 of the copyright holder 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 HOLDER 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.
- *
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/net/MIMEUtil.java#5 $
- *
- * @see MIME Media Types
- */
-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> sExtToMIME = new HashMap>();
- private static Map> sUnmodifiableExtToMIME = Collections.unmodifiableMap(sExtToMIME);
-
- private static Map> sMIMEToExt = new HashMap>();
- private static Map> 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 extensions =
- Collections.unmodifiableList(Arrays.asList(StringUtil.toStringArray(extStr, ";, ")));
-
- String typeStr = StringUtil.toLowerCase((String) entry.getValue());
- List 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 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 getMIMETypes(final String pFileExt) {
- List 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> 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 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 getExtensions(final String pMIME) {
- String mime = bareMIME(StringUtil.toLowerCase(pMIME));
- if (mime.endsWith("/*")) {
- return getExtensionForWildcard(mime);
- }
- List extensions = sMIMEToExt.get(mime);
- return maskNull(extensions);
- }
-
- // Gets all extensions for a wildcard MIME type
- private static List getExtensionForWildcard(final String pMIME) {
- final String family = pMIME.substring(0, pMIME.length() - 1);
- Set extensions = new LinkedHashSet();
- for (Map.Entry> mimeToExt : sMIMEToExt.entrySet()) {
- if ("*/".equals(family) || mimeToExt.getKey().startsWith(family)) {
- extensions.addAll(mimeToExt.getValue());
- }
- }
- return Collections.unmodifiableList(new ArrayList(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> 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 maskNull(List pTypes) {
- return (pTypes == null) ? Collections.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();
- }
+/*
+ * 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 of the copyright holder 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 HOLDER 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.
+ *
+ * @author Harald Kuhr
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/net/MIMEUtil.java#5 $
+ *
+ * @see MIME Media Types
+ */
+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> sExtToMIME = new HashMap>();
+ private static Map> sUnmodifiableExtToMIME = Collections.unmodifiableMap(sExtToMIME);
+
+ private static Map> sMIMEToExt = new HashMap>();
+ private static Map> 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 extensions =
+ Collections.unmodifiableList(Arrays.asList(StringUtil.toStringArray(extStr, ";, ")));
+
+ String typeStr = StringUtil.toLowerCase((String) entry.getValue());
+ List 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 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 getMIMETypes(final String pFileExt) {
+ List 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> 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 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 getExtensions(final String pMIME) {
+ String mime = bareMIME(StringUtil.toLowerCase(pMIME));
+ if (mime.endsWith("/*")) {
+ return getExtensionForWildcard(mime);
+ }
+ List extensions = sMIMEToExt.get(mime);
+ return maskNull(extensions);
+ }
+
+ // Gets all extensions for a wildcard MIME type
+ private static List getExtensionForWildcard(final String pMIME) {
+ final String family = pMIME.substring(0, pMIME.length() - 1);
+ Set extensions = new LinkedHashSet();
+ for (Map.Entry> mimeToExt : sMIMEToExt.entrySet()) {
+ if ("*/".equals(family) || mimeToExt.getKey().startsWith(family)) {
+ extensions.addAll(mimeToExt.getValue());
+ }
+ }
+ return Collections.unmodifiableList(new ArrayList(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> 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 maskNull(List pTypes) {
+ return (pTypes == null) ? Collections.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();
+ }
}
\ No newline at end of file
diff --git a/common/common-io/src/main/java/com/twelvemonkeys/xml/DOMSerializer.java b/common/common-io/src/main/java/com/twelvemonkeys/xml/DOMSerializer.java
index 30f716e9..e2975901 100755
--- a/common/common-io/src/main/java/com/twelvemonkeys/xml/DOMSerializer.java
+++ b/common/common-io/src/main/java/com/twelvemonkeys/xml/DOMSerializer.java
@@ -102,8 +102,9 @@ public final class DOMSerializer {
/**
* Specifies wether the serializer should use indentation and optimize for
* readability.
- *
- * Note: This is a hint, and may be ignored by DOM implemenations.
+ *
+ * Note: This is a hint, and may be ignored by DOM implementations.
+ *
*
* @param pPrettyPrint {@code true} to enable pretty printing
*/
diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java
index dffd0bb3..45077253 100755
--- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java
+++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/BeanUtil.java
@@ -1,605 +1,607 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.lang;
-
-import com.twelvemonkeys.util.convert.ConversionException;
-import com.twelvemonkeys.util.convert.Converter;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.Map;
-
-/**
- * A utility class with some useful bean-related functions.
- *
- * NOTE: This class is not considered part of the public API and may be changed without notice
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/BeanUtil.java#2 $
- */
-public final class BeanUtil {
-
- // Disallow creating objects of this type
- private BeanUtil() {
- }
-
- /**
- * Gets a property value from the given object, using reflection.
- * Now supports getting values from properties of properties
- * (recursive).
- *
- * @param pObject The object to get the property from
- * @param pProperty The name of the property
- *
- * @return A string containing the value of the given property, or {@code null}
- * if it can not be found.
- * @todo Remove System.err's... Create new Exception? Hmm..
- */
- public static Object getPropertyValue(Object pObject, String pProperty) {
- //
- // TODO: Support get(Object) method of Collections!
- // Handle lists and arrays with [] (index) operator
- //
-
- if (pObject == null || pProperty == null || pProperty.length() < 1) {
- return null;
- }
-
- Class> objClass = pObject.getClass();
-
- Object result = pObject;
-
- // Method for method...
- String subProp;
- int begIdx = 0;
- int endIdx = begIdx;
-
- while (begIdx < pProperty.length() && begIdx >= 0) {
-
- endIdx = pProperty.indexOf(".", endIdx + 1);
- if (endIdx > 0) {
- subProp = pProperty.substring(begIdx, endIdx);
- begIdx = endIdx + 1;
- }
- else {
- // The final property!
- // If there's just the first-level property, subProp will be
- // equal to property
- subProp = pProperty.substring(begIdx);
- begIdx = -1;
- }
-
- // Check for "[" and "]"
- Object[] param = null;
- Class[] paramClass = new Class[0];
-
- int begBracket;
- if ((begBracket = subProp.indexOf("[")) > 0) {
- // An error if there is no matching bracket
- if (!subProp.endsWith("]")) {
- return null;
- }
-
- String between = subProp.substring(begBracket + 1,
- subProp.length() - 1);
- subProp = subProp.substring(0, begBracket);
-
- // If brackets exist, check type of argument between brackets
- param = new Object[1];
- paramClass = new Class[1];
-
- //try {
- // TODO: isNumber returns true, even if too big for integer...
- if (StringUtil.isNumber(between)) {
- // We have a number
- // Integer -> array subscript -> getXXX(int i)
- try {
- // Insert param and it's Class
- param[0] = Integer.valueOf(between);
- paramClass[0] = Integer.TYPE; // int.class
- }
- catch (NumberFormatException e) {
- // ??
- // Probably too small or too large value..
- }
- }
- else {
- //catch (NumberFormatException e) {
- // Not a number... Try String
- // String -> Hashtable key -> getXXX(String str)
- // Insert param and it's Class
- param[0] = between.toLowerCase();
- paramClass[0] = String.class;
- }
- }
-
- Method method;
- String methodName = "get" + StringUtil.capitalize(subProp);
- try {
- // Try to get the "get" method for the given property
- method = objClass.getMethod(methodName, paramClass);
- }
- catch (NoSuchMethodException e) {
- System.err.print("No method named \"" + methodName + "()\"");
- // The array might be of size 0...
- if (paramClass.length > 0 && paramClass[0] != null) {
- System.err.print(" with the parameter " + paramClass[0].getName());
- }
-
- System.err.println(" in class " + objClass.getName() + "!");
- return null;
- }
-
- // If method for some reason should be null, give up
- if (method == null) {
- return null;
- }
-
- try {
- // We have a method, try to invoke it
- // The resutling object will be either the property we are
- // Looking for, or the parent
-
- // System.err.println("Trying " + objClass.getName() + "." + method.getName() + "(" + ((param != null && param.length > 0) ? param[0] : "") + ")");
- result = method.invoke(result, param);
- }
- catch (InvocationTargetException e) {
- System.err.println("property=" + pProperty + " & result=" + result + " & param=" + Arrays.toString(param));
- e.getTargetException().printStackTrace();
- e.printStackTrace();
- return null;
- }
- catch (IllegalAccessException e) {
- e.printStackTrace();
- return null;
- }
- catch (NullPointerException e) {
- System.err.println(objClass.getName() + "." + method.getName() + "(" + ((paramClass.length > 0 && paramClass[0] != null) ? paramClass[0].getName() : "") + ")");
- e.printStackTrace();
- return null;
- }
-
- if (result != null) {
- // Get the class of the reulting object
- objClass = result.getClass();
- }
- else {
- return null;
- }
- } // while
-
- return result;
- }
-
- /**
- * Sets the property value to an object using reflection.
- * Supports setting values of properties that are properties of
- * properties (recursive).
- *
- * @param pObject The object to get a property from
- * @param pProperty The name of the property
- * @param pValue The property value
- *
- * @throws NoSuchMethodException if there's no write method for the
- * given property
- * @throws InvocationTargetException if invoking the write method failed
- * @throws IllegalAccessException if the caller class has no access to the
- * write method
- */
- public static void setPropertyValue(Object pObject, String pProperty, Object pValue)
- throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
-
- //
- // TODO: Support set(Object, Object)/put(Object, Object) methods
- // of Collections!
- // Handle lists and arrays with [] (index) operator
-
- Class paramType = pValue != null ? pValue.getClass() : Object.class;
-
- // Preserve references
- Object obj = pObject;
- String property = pProperty;
-
- // Recurse and find real parent if property contains a '.'
- int dotIdx = property.indexOf('.');
- if (dotIdx >= 0) {
- // Get real parent
- obj = getPropertyValue(obj, property.substring(0, dotIdx));
- // Get the property of the parent
- property = property.substring(dotIdx + 1);
- }
-
- // Find method
- Object[] params = {pValue};
- Method method = getMethodMayModifyParams(obj, "set" + StringUtil.capitalize(property),
- new Class[] {paramType}, params);
-
- // Invoke it
- method.invoke(obj, params);
- }
-
- private static Method getMethodMayModifyParams(Object pObject, String pName, Class[] pParams, Object[] pValues)
- throws NoSuchMethodException {
- // NOTE: This method assumes pParams.length == 1 && pValues.length == 1
-
- Method method = null;
- Class paramType = pParams[0];
-
- try {
- method = pObject.getClass().getMethod(pName, pParams);
- }
- catch (NoSuchMethodException e) {
- // No direct match
-
- // 1: If primitive wrapper, try unwrap conversion first
- /*if (paramType.isPrimitive()) { // NOTE: Can't be primitive type
- params[0] = ReflectUtil.wrapType(paramType);
- }
- else*/ if (ReflectUtil.isPrimitiveWrapper(paramType)) {
- pParams[0] = ReflectUtil.unwrapType(paramType);
- }
-
- try {
- // If this does not throw an exception, it works
- method = pObject.getClass().getMethod(pName, pParams);
- }
- catch (Throwable t) {
- // Ignore
- }
-
- // 2: Try any super-types of paramType, to see if we have a match
- if (method == null) {
- while ((paramType = paramType.getSuperclass()) != null) {
- pParams[0] = paramType;
- try {
- // If this does not throw an exception, it works
- method = pObject.getClass().getMethod(pName, pParams);
- }
- catch (Throwable t) {
- // Ignore/Continue
- continue;
- }
-
- break;
- }
- }
-
- // 3: Try to find a different method with the same name, that has
- // a parameter type we can convert to...
- // NOTE: There's no ordering here..
- // TODO: Should we try to do that? What would the ordering be?
- if (method == null) {
- Method[] methods = pObject.getClass().getMethods();
- for (Method candidate : methods) {
- if (Modifier.isPublic(candidate.getModifiers()) && candidate.getName().equals(pName)
- && candidate.getReturnType() == Void.TYPE && candidate.getParameterTypes().length == 1) {
- // NOTE: Assumes paramTypes.length == 1
-
- Class type = candidate.getParameterTypes()[0];
-
- try {
- pValues[0] = convertValueToType(pValues[0], type);
- }
- catch (Throwable t) {
- continue;
- }
-
- // We were able to convert the parameter, let's try
- method = candidate;
- break;
- }
- }
- }
-
- // Give up...
- if (method == null) {
- throw e;
- }
- }
- return method;
- }
-
- private static Object convertValueToType(Object pValue, Class> pType) throws ConversionException {
- if (pType.isPrimitive()) {
- if (pType == Boolean.TYPE && pValue instanceof Boolean) {
- return pValue;
- }
- else if (pType == Byte.TYPE && pValue instanceof Byte) {
- return pValue;
- }
- else if (pType == Character.TYPE && pValue instanceof Character) {
- return pValue;
- }
- else if (pType == Double.TYPE && pValue instanceof Double) {
- return pValue;
- }
- else if (pType == Float.TYPE && pValue instanceof Float) {
- return pValue;
- }
- else if (pType == Integer.TYPE && pValue instanceof Integer) {
- return pValue;
- }
- else if (pType == Long.TYPE && pValue instanceof Long) {
- return pValue;
- }
- else if (pType == Short.TYPE && pValue instanceof Short) {
- return pValue;
- }
- }
-
- // TODO: Convert value to single-value array if needed
- // TODO: Convert CSV String to string array (or potentially any type of array)
-
- // TODO: Convert other types
- if (pValue instanceof String) {
- Converter converter = Converter.getInstance();
- return converter.toObject((String) pValue, pType);
- }
- else if (pType == String.class) {
- Converter converter = Converter.getInstance();
- return converter.toString(pValue);
- }
- else {
- throw new ConversionException("Cannot convert " + pValue.getClass().getName() + " to " + pType.getName());
- }
- }
-
- /**
- * Creates an object from the given class' single argument constructor.
- *
- * @param pClass The class to create instance from
- * @param pParam The parameters to the constructor
- *
- * @return The object created from the constructor.
- * If the constructor could not be invoked for any reason, null is
- * returned.
- *
- * @throws InvocationTargetException if the constructor failed
- */
- // TODO: Move to ReflectUtil
- public static T createInstance(Class pClass, Object pParam)
- throws InvocationTargetException {
- return createInstance(pClass, new Object[] {pParam});
- }
-
- /**
- * Creates an object from the given class' constructor that matches
- * the given paramaters.
- *
- * @param pClass The class to create instance from
- * @param pParams The parameters to the constructor
- *
- * @return The object created from the constructor.
- * If the constructor could not be invoked for any reason, null is
- * returned.
- *
- * @throws InvocationTargetException if the constructor failed
- */
- // TODO: Move to ReflectUtil
- public static T createInstance(Class pClass, Object... pParams)
- throws InvocationTargetException {
- T value;
-
- try {
- // Create param and argument arrays
- Class[] paramTypes = null;
- if (pParams != null && pParams.length > 0) {
- paramTypes = new Class[pParams.length];
- for (int i = 0; i < pParams.length; i++) {
- paramTypes[i] = pParams[i].getClass();
- }
- }
-
- // Get constructor
- Constructor constructor = pClass.getConstructor(paramTypes);
-
- // Invoke and create instance
- value = constructor.newInstance(pParams);
- }
- /* All this to let InvocationTargetException pass on */
- catch (NoSuchMethodException nsme) {
- return null;
- }
- catch (IllegalAccessException iae) {
- return null;
- }
- catch (IllegalArgumentException iarge) {
- return null;
- }
- catch (InstantiationException ie) {
- return null;
- }
- catch (ExceptionInInitializerError err) {
- return null;
- }
-
- return value;
- }
-
- /**
- * Gets an object from any given static method, with the given parameter.
- *
- * @param pClass The class to invoke method on
- * @param pMethod The name of the method to invoke
- * @param pParam The parameter to the method
- *
- * @return The object returned by the static method.
- * If the return type of the method is a primitive type, it is wrapped in
- * the corresponding wrapper object (int is wrapped in an Integer).
- * If the return type of the method is void, null is returned.
- * If the method could not be invoked for any reason, null is returned.
- *
- * @throws InvocationTargetException if the invocation failed
- */
- // TODO: Move to ReflectUtil
- // TODO: Rename to invokeStatic?
- public static Object invokeStaticMethod(Class> pClass, String pMethod, Object pParam)
- throws InvocationTargetException {
-
- return invokeStaticMethod(pClass, pMethod, new Object[] {pParam});
- }
-
- /**
- * Gets an object from any given static method, with the given parameter.
- *
- * @param pClass The class to invoke method on
- * @param pMethod The name of the method to invoke
- * @param pParams The parameters to the method
- *
- * @return The object returned by the static method.
- * If the return type of the method is a primitive type, it is wrapped in
- * the corresponding wrapper object (int is wrapped in an Integer).
- * If the return type of the method is void, null is returned.
- * If the method could not be invoked for any reason, null is returned.
- *
- * @throws InvocationTargetException if the invocation failed
- */
- // TODO: Move to ReflectUtil
- // TODO: Rename to invokeStatic?
- public static Object invokeStaticMethod(Class> pClass, String pMethod, Object... pParams)
- throws InvocationTargetException {
-
- Object value = null;
-
- try {
- // Create param and argument arrays
- Class[] paramTypes = new Class[pParams.length];
- for (int i = 0; i < pParams.length; i++) {
- paramTypes[i] = pParams[i].getClass();
- }
-
- // Get method
- // *** If more than one such method is found in the class, and one
- // of these methods has a RETURN TYPE that is more specific than
- // any of the others, that method is reflected; otherwise one of
- // the methods is chosen ARBITRARILY.
- // java/lang/Class.html#getMethod(java.lang.String, java.lang.Class[])
- Method method = pClass.getMethod(pMethod, paramTypes);
-
- // Invoke public static method
- if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) {
- value = method.invoke(null, pParams);
- }
-
- }
- /* All this to let InvocationTargetException pass on */
- catch (NoSuchMethodException nsme) {
- return null;
- }
- catch (IllegalAccessException iae) {
- return null;
- }
- catch (IllegalArgumentException iarge) {
- return null;
- }
-
- return value;
- }
-
- /**
- * Configures the bean according to the given mapping.
- * For each {@code Map.Entry} in {@code Map.values()},
- * a method named
- * {@code set + capitalize(entry.getKey())} is called on the bean,
- * with {@code entry.getValue()} as its argument.
- *
- * Properties that has no matching set-method in the bean, are simply
- * discarded.
- *
- * @param pBean The bean to configure
- * @param pMapping The mapping for the bean
- *
- * @throws NullPointerException if any of the parameters are null.
- * @throws InvocationTargetException if an error occurs when invoking the
- * setter-method.
- */
- // TODO: Add a version that takes a ConfigurationErrorListener callback interface
- // TODO: ...or a boolean pFailOnError parameter
- // TODO: ...or return Exceptions as an array?!
- // TODO: ...or something whatsoever that makes clients able to determine something's not right
- public static void configure(final Object pBean, final Map pMapping) throws InvocationTargetException {
- configure(pBean, pMapping, false);
- }
-
- /**
- * Configures the bean according to the given mapping.
- * For each {@code Map.Entry} in {@code Map.values()},
- * a method named
- * {@code set + capitalize(entry.getKey())} is called on the bean,
- * with {@code entry.getValue()} as its argument.
- *
- * Optionally, lisp-style names are allowed, and automatically converted
- * to Java-style camel-case names.
- *
- * Properties that has no matching set-method in the bean, are simply
- * discarded.
- *
- * @see StringUtil#lispToCamel(String)
- *
- * @param pBean The bean to configure
- * @param pMapping The mapping for the bean
- * @param pLispToCamel Allow lisp-style names, and automatically convert
- * them to Java-style camel-case.
- *
- * @throws NullPointerException if any of the parameters are null.
- * @throws InvocationTargetException if an error occurs when invoking the
- * setter-method.
- */
- public static void configure(final Object pBean, final Map pMapping, final boolean pLispToCamel) throws InvocationTargetException {
- // Loop over properties in mapping
- for (final Map.Entry entry : pMapping.entrySet()) {
- try {
- // Configure each property in turn
- final String property = StringUtil.valueOf(entry.getKey());
- try {
- setPropertyValue(pBean, property, entry.getValue());
- }
- catch (NoSuchMethodException ignore) {
- // If invocation failed, convert lisp-style and try again
- if (pLispToCamel && property.indexOf('-') > 0) {
- setPropertyValue(pBean, StringUtil.lispToCamel(property, false), entry.getValue());
- }
- }
- }
- catch (NoSuchMethodException nsme) {
- // This property was not configured
- }
- catch (IllegalAccessException iae) {
- // This property was not configured
- }
- }
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.lang;
+
+import com.twelvemonkeys.util.convert.ConversionException;
+import com.twelvemonkeys.util.convert.Converter;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Arrays;
+import java.util.Map;
+
+/**
+ * A utility class with some useful bean-related functions.
+ *
+ * NOTE: This class is not considered part of the public API and may be changed without notice
+ *
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/BeanUtil.java#2 $
+ */
+public final class BeanUtil {
+
+ // Disallow creating objects of this type
+ private BeanUtil() {
+ }
+
+ /**
+ * Gets a property value from the given object, using reflection.
+ * Now supports getting values from properties of properties
+ * (recursive).
+ *
+ * @param pObject The object to get the property from
+ * @param pProperty The name of the property
+ *
+ * @return A string containing the value of the given property, or {@code null}
+ * if it can not be found.
+ */
+ public static Object getPropertyValue(Object pObject, String pProperty) {
+ // TODO: Remove System.err's... Create new Exception? Hmm..
+ // TODO: Support get(Object) method of Collections!
+ // Handle lists and arrays with [] (index) operator
+
+ if (pObject == null || pProperty == null || pProperty.length() < 1) {
+ return null;
+ }
+
+ Class> objClass = pObject.getClass();
+
+ Object result = pObject;
+
+ // Method for method...
+ String subProp;
+ int begIdx = 0;
+ int endIdx = begIdx;
+
+ while (begIdx < pProperty.length() && begIdx >= 0) {
+
+ endIdx = pProperty.indexOf(".", endIdx + 1);
+ if (endIdx > 0) {
+ subProp = pProperty.substring(begIdx, endIdx);
+ begIdx = endIdx + 1;
+ }
+ else {
+ // The final property!
+ // If there's just the first-level property, subProp will be
+ // equal to property
+ subProp = pProperty.substring(begIdx);
+ begIdx = -1;
+ }
+
+ // Check for "[" and "]"
+ Object[] param = null;
+ Class[] paramClass = new Class[0];
+
+ int begBracket;
+ if ((begBracket = subProp.indexOf("[")) > 0) {
+ // An error if there is no matching bracket
+ if (!subProp.endsWith("]")) {
+ return null;
+ }
+
+ String between = subProp.substring(begBracket + 1,
+ subProp.length() - 1);
+ subProp = subProp.substring(0, begBracket);
+
+ // If brackets exist, check type of argument between brackets
+ param = new Object[1];
+ paramClass = new Class[1];
+
+ //try {
+ // TODO: isNumber returns true, even if too big for integer...
+ if (StringUtil.isNumber(between)) {
+ // We have a number
+ // Integer -> array subscript -> getXXX(int i)
+ try {
+ // Insert param and it's Class
+ param[0] = Integer.valueOf(between);
+ paramClass[0] = Integer.TYPE; // int.class
+ }
+ catch (NumberFormatException e) {
+ // ??
+ // Probably too small or too large value..
+ }
+ }
+ else {
+ //catch (NumberFormatException e) {
+ // Not a number... Try String
+ // String -> Hashtable key -> getXXX(String str)
+ // Insert param and it's Class
+ param[0] = between.toLowerCase();
+ paramClass[0] = String.class;
+ }
+ }
+
+ Method method;
+ String methodName = "get" + StringUtil.capitalize(subProp);
+ try {
+ // Try to get the "get" method for the given property
+ method = objClass.getMethod(methodName, paramClass);
+ }
+ catch (NoSuchMethodException e) {
+ System.err.print("No method named \"" + methodName + "()\"");
+ // The array might be of size 0...
+ if (paramClass.length > 0 && paramClass[0] != null) {
+ System.err.print(" with the parameter " + paramClass[0].getName());
+ }
+
+ System.err.println(" in class " + objClass.getName() + "!");
+ return null;
+ }
+
+ // If method for some reason should be null, give up
+ if (method == null) {
+ return null;
+ }
+
+ try {
+ // We have a method, try to invoke it
+ // The resutling object will be either the property we are
+ // Looking for, or the parent
+
+ // System.err.println("Trying " + objClass.getName() + "." + method.getName() + "(" + ((param != null && param.length > 0) ? param[0] : "") + ")");
+ result = method.invoke(result, param);
+ }
+ catch (InvocationTargetException e) {
+ System.err.println("property=" + pProperty + " & result=" + result + " & param=" + Arrays.toString(param));
+ e.getTargetException().printStackTrace();
+ e.printStackTrace();
+ return null;
+ }
+ catch (IllegalAccessException e) {
+ e.printStackTrace();
+ return null;
+ }
+ catch (NullPointerException e) {
+ System.err.println(objClass.getName() + "." + method.getName() + "(" + ((paramClass.length > 0 && paramClass[0] != null) ? paramClass[0].getName() : "") + ")");
+ e.printStackTrace();
+ return null;
+ }
+
+ if (result != null) {
+ // Get the class of the reulting object
+ objClass = result.getClass();
+ }
+ else {
+ return null;
+ }
+ } // while
+
+ return result;
+ }
+
+ /**
+ * Sets the property value to an object using reflection.
+ * Supports setting values of properties that are properties of
+ * properties (recursive).
+ *
+ * @param pObject The object to get a property from
+ * @param pProperty The name of the property
+ * @param pValue The property value
+ *
+ * @throws NoSuchMethodException if there's no write method for the
+ * given property
+ * @throws InvocationTargetException if invoking the write method failed
+ * @throws IllegalAccessException if the caller class has no access to the
+ * write method
+ */
+ public static void setPropertyValue(Object pObject, String pProperty, Object pValue)
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+
+ //
+ // TODO: Support set(Object, Object)/put(Object, Object) methods
+ // of Collections!
+ // Handle lists and arrays with [] (index) operator
+
+ Class paramType = pValue != null ? pValue.getClass() : Object.class;
+
+ // Preserve references
+ Object obj = pObject;
+ String property = pProperty;
+
+ // Recurse and find real parent if property contains a '.'
+ int dotIdx = property.indexOf('.');
+ if (dotIdx >= 0) {
+ // Get real parent
+ obj = getPropertyValue(obj, property.substring(0, dotIdx));
+ // Get the property of the parent
+ property = property.substring(dotIdx + 1);
+ }
+
+ // Find method
+ Object[] params = {pValue};
+ Method method = getMethodMayModifyParams(obj, "set" + StringUtil.capitalize(property),
+ new Class[] {paramType}, params);
+
+ // Invoke it
+ method.invoke(obj, params);
+ }
+
+ private static Method getMethodMayModifyParams(Object pObject, String pName, Class[] pParams, Object[] pValues)
+ throws NoSuchMethodException {
+ // NOTE: This method assumes pParams.length == 1 && pValues.length == 1
+
+ Method method = null;
+ Class paramType = pParams[0];
+
+ try {
+ method = pObject.getClass().getMethod(pName, pParams);
+ }
+ catch (NoSuchMethodException e) {
+ // No direct match
+
+ // 1: If primitive wrapper, try unwrap conversion first
+ /*if (paramType.isPrimitive()) { // NOTE: Can't be primitive type
+ params[0] = ReflectUtil.wrapType(paramType);
+ }
+ else*/ if (ReflectUtil.isPrimitiveWrapper(paramType)) {
+ pParams[0] = ReflectUtil.unwrapType(paramType);
+ }
+
+ try {
+ // If this does not throw an exception, it works
+ method = pObject.getClass().getMethod(pName, pParams);
+ }
+ catch (Throwable t) {
+ // Ignore
+ }
+
+ // 2: Try any super-types of paramType, to see if we have a match
+ if (method == null) {
+ while ((paramType = paramType.getSuperclass()) != null) {
+ pParams[0] = paramType;
+ try {
+ // If this does not throw an exception, it works
+ method = pObject.getClass().getMethod(pName, pParams);
+ }
+ catch (Throwable t) {
+ // Ignore/Continue
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ // 3: Try to find a different method with the same name, that has
+ // a parameter type we can convert to...
+ // NOTE: There's no ordering here..
+ // TODO: Should we try to do that? What would the ordering be?
+ if (method == null) {
+ Method[] methods = pObject.getClass().getMethods();
+ for (Method candidate : methods) {
+ if (Modifier.isPublic(candidate.getModifiers()) && candidate.getName().equals(pName)
+ && candidate.getReturnType() == Void.TYPE && candidate.getParameterTypes().length == 1) {
+ // NOTE: Assumes paramTypes.length == 1
+
+ Class type = candidate.getParameterTypes()[0];
+
+ try {
+ pValues[0] = convertValueToType(pValues[0], type);
+ }
+ catch (Throwable t) {
+ continue;
+ }
+
+ // We were able to convert the parameter, let's try
+ method = candidate;
+ break;
+ }
+ }
+ }
+
+ // Give up...
+ if (method == null) {
+ throw e;
+ }
+ }
+ return method;
+ }
+
+ private static Object convertValueToType(Object pValue, Class> pType) throws ConversionException {
+ if (pType.isPrimitive()) {
+ if (pType == Boolean.TYPE && pValue instanceof Boolean) {
+ return pValue;
+ }
+ else if (pType == Byte.TYPE && pValue instanceof Byte) {
+ return pValue;
+ }
+ else if (pType == Character.TYPE && pValue instanceof Character) {
+ return pValue;
+ }
+ else if (pType == Double.TYPE && pValue instanceof Double) {
+ return pValue;
+ }
+ else if (pType == Float.TYPE && pValue instanceof Float) {
+ return pValue;
+ }
+ else if (pType == Integer.TYPE && pValue instanceof Integer) {
+ return pValue;
+ }
+ else if (pType == Long.TYPE && pValue instanceof Long) {
+ return pValue;
+ }
+ else if (pType == Short.TYPE && pValue instanceof Short) {
+ return pValue;
+ }
+ }
+
+ // TODO: Convert value to single-value array if needed
+ // TODO: Convert CSV String to string array (or potentially any type of array)
+
+ // TODO: Convert other types
+ if (pValue instanceof String) {
+ Converter converter = Converter.getInstance();
+ return converter.toObject((String) pValue, pType);
+ }
+ else if (pType == String.class) {
+ Converter converter = Converter.getInstance();
+ return converter.toString(pValue);
+ }
+ else {
+ throw new ConversionException("Cannot convert " + pValue.getClass().getName() + " to " + pType.getName());
+ }
+ }
+
+ /**
+ * Creates an object from the given class' single argument constructor.
+ *
+ * @param pClass The class to create instance from
+ * @param pParam The parameters to the constructor
+ *
+ * @return The object created from the constructor.
+ * If the constructor could not be invoked for any reason, null is
+ * returned.
+ *
+ * @throws InvocationTargetException if the constructor failed
+ */
+ // TODO: Move to ReflectUtil
+ public static T createInstance(Class pClass, Object pParam)
+ throws InvocationTargetException {
+ return createInstance(pClass, new Object[] {pParam});
+ }
+
+ /**
+ * Creates an object from the given class' constructor that matches
+ * the given paramaters.
+ *
+ * @param pClass The class to create instance from
+ * @param pParams The parameters to the constructor
+ *
+ * @return The object created from the constructor.
+ * If the constructor could not be invoked for any reason, null is
+ * returned.
+ *
+ * @throws InvocationTargetException if the constructor failed
+ */
+ // TODO: Move to ReflectUtil
+ public static T createInstance(Class pClass, Object... pParams)
+ throws InvocationTargetException {
+ T value;
+
+ try {
+ // Create param and argument arrays
+ Class[] paramTypes = null;
+ if (pParams != null && pParams.length > 0) {
+ paramTypes = new Class[pParams.length];
+ for (int i = 0; i < pParams.length; i++) {
+ paramTypes[i] = pParams[i].getClass();
+ }
+ }
+
+ // Get constructor
+ Constructor constructor = pClass.getConstructor(paramTypes);
+
+ // Invoke and create instance
+ value = constructor.newInstance(pParams);
+ }
+ /* All this to let InvocationTargetException pass on */
+ catch (NoSuchMethodException nsme) {
+ return null;
+ }
+ catch (IllegalAccessException iae) {
+ return null;
+ }
+ catch (IllegalArgumentException iarge) {
+ return null;
+ }
+ catch (InstantiationException ie) {
+ return null;
+ }
+ catch (ExceptionInInitializerError err) {
+ return null;
+ }
+
+ return value;
+ }
+
+ /**
+ * Gets an object from any given static method, with the given parameter.
+ *
+ * @param pClass The class to invoke method on
+ * @param pMethod The name of the method to invoke
+ * @param pParam The parameter to the method
+ *
+ * @return The object returned by the static method.
+ * If the return type of the method is a primitive type, it is wrapped in
+ * the corresponding wrapper object (int is wrapped in an Integer).
+ * If the return type of the method is void, null is returned.
+ * If the method could not be invoked for any reason, null is returned.
+ *
+ * @throws InvocationTargetException if the invocation failed
+ */
+ // TODO: Move to ReflectUtil
+ // TODO: Rename to invokeStatic?
+ public static Object invokeStaticMethod(Class> pClass, String pMethod, Object pParam)
+ throws InvocationTargetException {
+
+ return invokeStaticMethod(pClass, pMethod, new Object[] {pParam});
+ }
+
+ /**
+ * Gets an object from any given static method, with the given parameter.
+ *
+ * @param pClass The class to invoke method on
+ * @param pMethod The name of the method to invoke
+ * @param pParams The parameters to the method
+ *
+ * @return The object returned by the static method.
+ * If the return type of the method is a primitive type, it is wrapped in
+ * the corresponding wrapper object (int is wrapped in an Integer).
+ * If the return type of the method is void, null is returned.
+ * If the method could not be invoked for any reason, null is returned.
+ *
+ * @throws InvocationTargetException if the invocation failed
+ */
+ // TODO: Move to ReflectUtil
+ // TODO: Rename to invokeStatic?
+ public static Object invokeStaticMethod(Class> pClass, String pMethod, Object... pParams)
+ throws InvocationTargetException {
+
+ Object value = null;
+
+ try {
+ // Create param and argument arrays
+ Class[] paramTypes = new Class[pParams.length];
+ for (int i = 0; i < pParams.length; i++) {
+ paramTypes[i] = pParams[i].getClass();
+ }
+
+ // Get method
+ // *** If more than one such method is found in the class, and one
+ // of these methods has a RETURN TYPE that is more specific than
+ // any of the others, that method is reflected; otherwise one of
+ // the methods is chosen ARBITRARILY.
+ // java/lang/Class.html#getMethod(java.lang.String, java.lang.Class[])
+ Method method = pClass.getMethod(pMethod, paramTypes);
+
+ // Invoke public static method
+ if (Modifier.isPublic(method.getModifiers()) && Modifier.isStatic(method.getModifiers())) {
+ value = method.invoke(null, pParams);
+ }
+
+ }
+ /* All this to let InvocationTargetException pass on */
+ catch (NoSuchMethodException nsme) {
+ return null;
+ }
+ catch (IllegalAccessException iae) {
+ return null;
+ }
+ catch (IllegalArgumentException iarge) {
+ return null;
+ }
+
+ return value;
+ }
+
+ /**
+ * Configures the bean according to the given mapping.
+ * For each {@code Map.Entry} in {@code Map.values()},
+ * a method named
+ * {@code set + capitalize(entry.getKey())} is called on the bean,
+ * with {@code entry.getValue()} as its argument.
+ *
+ * Properties that has no matching set-method in the bean, are simply
+ * discarded.
+ *
+ *
+ * @param pBean The bean to configure
+ * @param pMapping The mapping for the bean
+ *
+ * @throws NullPointerException if any of the parameters are null.
+ * @throws InvocationTargetException if an error occurs when invoking the
+ * setter-method.
+ */
+ // TODO: Add a version that takes a ConfigurationErrorListener callback interface
+ // TODO: ...or a boolean pFailOnError parameter
+ // TODO: ...or return Exceptions as an array?!
+ // TODO: ...or something whatsoever that makes clients able to determine something's not right
+ public static void configure(final Object pBean, final Map pMapping) throws InvocationTargetException {
+ configure(pBean, pMapping, false);
+ }
+
+ /**
+ * Configures the bean according to the given mapping.
+ * For each {@code Map.Entry} in {@code Map.values()},
+ * a method named
+ * {@code set + capitalize(entry.getKey())} is called on the bean,
+ * with {@code entry.getValue()} as its argument.
+ *
+ * Optionally, lisp-style names are allowed, and automatically converted
+ * to Java-style camel-case names.
+ *
+ *
+ * Properties that has no matching set-method in the bean, are simply
+ * discarded.
+ *
+ *
+ * @see StringUtil#lispToCamel(String)
+ *
+ * @param pBean The bean to configure
+ * @param pMapping The mapping for the bean
+ * @param pLispToCamel Allow lisp-style names, and automatically convert
+ * them to Java-style camel-case.
+ *
+ * @throws NullPointerException if any of the parameters are null.
+ * @throws InvocationTargetException if an error occurs when invoking the
+ * setter-method.
+ */
+ public static void configure(final Object pBean, final Map pMapping, final boolean pLispToCamel) throws InvocationTargetException {
+ // Loop over properties in mapping
+ for (final Map.Entry entry : pMapping.entrySet()) {
+ try {
+ // Configure each property in turn
+ final String property = StringUtil.valueOf(entry.getKey());
+ try {
+ setPropertyValue(pBean, property, entry.getValue());
+ }
+ catch (NoSuchMethodException ignore) {
+ // If invocation failed, convert lisp-style and try again
+ if (pLispToCamel && property.indexOf('-') > 0) {
+ setPropertyValue(pBean, StringUtil.lispToCamel(property, false), entry.getValue());
+ }
+ }
+ }
+ catch (NoSuchMethodException nsme) {
+ // This property was not configured
+ }
+ catch (IllegalAccessException iae) {
+ // This property was not configured
+ }
+ }
+ }
+}
diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/DateUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/DateUtil.java
index 780a68dd..92e262e3 100755
--- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/DateUtil.java
+++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/DateUtil.java
@@ -1,204 +1,203 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.lang;
-
-import java.util.Date;
-import java.util.TimeZone;
-
-/**
- * A utility class with useful date manipulation methods and constants.
- *
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/DateUtil.java#1 $
- */
-public final class DateUtil {
-
- /** One second: 1000 milliseconds. */
- public static final long SECOND = 1000l;
-
- /** One minute: 60 seconds (60 000 milliseconds). */
- public static final long MINUTE = 60l * SECOND;
-
- /**
- * One hour: 60 minutes (3 600 000 milliseconds).
- * 60 minutes = 3 600 seconds = 3 600 000 milliseconds
- */
- public static final long HOUR = 60l * MINUTE;
-
- /**
- * One day: 24 hours (86 400 000 milliseconds).
- * 24 hours = 1 440 minutes = 86 400 seconds = 86 400 000 milliseconds.
- */
- public static final long DAY = 24l * HOUR;
-
- /**
- * One calendar year: 365.2425 days (31556952000 milliseconds).
- * 365.2425 days = 8765.82 hours = 525949.2 minutes = 31556952 seconds
- * = 31556952000 milliseconds.
- */
- public static final long CALENDAR_YEAR = 3652425l * 24l * 60l * 6l;
-
- private DateUtil() {
- }
-
- /**
- * Returns the time between the given start time and now (as defined by
- * {@link System#currentTimeMillis()}).
- *
- * @param pStart the start time
- *
- * @return the time between the given start time and now.
- */
- public static long delta(long pStart) {
- return System.currentTimeMillis() - pStart;
- }
-
- /**
- * Returns the time between the given start time and now (as defined by
- * {@link System#currentTimeMillis()}).
- *
- * @param pStart the start time
- *
- * @return the time between the given start time and now.
- */
- public static long delta(Date pStart) {
- return System.currentTimeMillis() - pStart.getTime();
- }
-
- /**
- * Gets the current time, rounded down to the closest second.
- * Equivalent to invoking
- * {@code roundToSecond(System.currentTimeMillis())}.
- *
- * @return the current time, rounded to the closest second.
- */
- public static long currentTimeSecond() {
- return roundToSecond(System.currentTimeMillis());
- }
-
- /**
- * Gets the current time, rounded down to the closest minute.
- * Equivalent to invoking
- * {@code roundToMinute(System.currentTimeMillis())}.
- *
- * @return the current time, rounded to the closest minute.
- */
- public static long currentTimeMinute() {
- return roundToMinute(System.currentTimeMillis());
- }
-
- /**
- * Gets the current time, rounded down to the closest hour.
- * Equivalent to invoking
- * {@code roundToHour(System.currentTimeMillis())}.
- *
- * @return the current time, rounded to the closest hour.
- */
- public static long currentTimeHour() {
- return roundToHour(System.currentTimeMillis());
- }
-
- /**
- * Gets the current time, rounded down to the closest day.
- * Equivalent to invoking
- * {@code roundToDay(System.currentTimeMillis())}.
- *
- * @return the current time, rounded to the closest day.
- */
- public static long currentTimeDay() {
- return roundToDay(System.currentTimeMillis());
- }
-
- /**
- * Rounds the given time down to the closest second.
- *
- * @param pTime time
- * @return the time rounded to the closest second.
- */
- public static long roundToSecond(final long pTime) {
- return (pTime / SECOND) * SECOND;
- }
-
- /**
- * Rounds the given time down to the closest minute.
- *
- * @param pTime time
- * @return the time rounded to the closest minute.
- */
- public static long roundToMinute(final long pTime) {
- return (pTime / MINUTE) * MINUTE;
- }
-
- /**
- * Rounds the given time down to the closest hour, using the default timezone.
- *
- * @param pTime time
- * @return the time rounded to the closest hour.
- */
- public static long roundToHour(final long pTime) {
- return roundToHour(pTime, TimeZone.getDefault());
- }
-
- /**
- * Rounds the given time down to the closest hour, using the given timezone.
- *
- * @param pTime time
- * @param pTimeZone the timezone to use when rounding
- * @return the time rounded to the closest hour.
- */
- public static long roundToHour(final long pTime, final TimeZone pTimeZone) {
- int offset = pTimeZone.getOffset(pTime);
- return ((pTime / HOUR) * HOUR) - offset;
- }
-
- /**
- * Rounds the given time down to the closest day, using the default timezone.
- *
- * @param pTime time
- * @return the time rounded to the closest day.
- */
- public static long roundToDay(final long pTime) {
- return roundToDay(pTime, TimeZone.getDefault());
- }
-
- /**
- * Rounds the given time down to the closest day, using the given timezone.
- *
- * @param pTime time
- * @param pTimeZone the timezone to use when rounding
- * @return the time rounded to the closest day.
- */
- public static long roundToDay(final long pTime, final TimeZone pTimeZone) {
- int offset = pTimeZone.getOffset(pTime);
- return (((pTime + offset) / DAY) * DAY) - offset;
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.lang;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * A utility class with useful date manipulation methods and constants.
+ *
+ * @author Harald Kuhr
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/DateUtil.java#1 $
+ */
+public final class DateUtil {
+
+ /** One second: 1000 milliseconds. */
+ public static final long SECOND = 1000l;
+
+ /** One minute: 60 seconds (60 000 milliseconds). */
+ public static final long MINUTE = 60l * SECOND;
+
+ /**
+ * One hour: 60 minutes (3 600 000 milliseconds).
+ * 60 minutes = 3 600 seconds = 3 600 000 milliseconds
+ */
+ public static final long HOUR = 60l * MINUTE;
+
+ /**
+ * One day: 24 hours (86 400 000 milliseconds).
+ * 24 hours = 1 440 minutes = 86 400 seconds = 86 400 000 milliseconds.
+ */
+ public static final long DAY = 24l * HOUR;
+
+ /**
+ * One calendar year: 365.2425 days (31556952000 milliseconds).
+ * 365.2425 days = 8765.82 hours = 525949.2 minutes = 31556952 seconds
+ * = 31556952000 milliseconds.
+ */
+ public static final long CALENDAR_YEAR = 3652425l * 24l * 60l * 6l;
+
+ private DateUtil() {
+ }
+
+ /**
+ * Returns the time between the given start time and now (as defined by
+ * {@link System#currentTimeMillis()}).
+ *
+ * @param pStart the start time
+ *
+ * @return the time between the given start time and now.
+ */
+ public static long delta(long pStart) {
+ return System.currentTimeMillis() - pStart;
+ }
+
+ /**
+ * Returns the time between the given start time and now (as defined by
+ * {@link System#currentTimeMillis()}).
+ *
+ * @param pStart the start time
+ *
+ * @return the time between the given start time and now.
+ */
+ public static long delta(Date pStart) {
+ return System.currentTimeMillis() - pStart.getTime();
+ }
+
+ /**
+ * Gets the current time, rounded down to the closest second.
+ * Equivalent to invoking
+ * {@code roundToSecond(System.currentTimeMillis())}.
+ *
+ * @return the current time, rounded to the closest second.
+ */
+ public static long currentTimeSecond() {
+ return roundToSecond(System.currentTimeMillis());
+ }
+
+ /**
+ * Gets the current time, rounded down to the closest minute.
+ * Equivalent to invoking
+ * {@code roundToMinute(System.currentTimeMillis())}.
+ *
+ * @return the current time, rounded to the closest minute.
+ */
+ public static long currentTimeMinute() {
+ return roundToMinute(System.currentTimeMillis());
+ }
+
+ /**
+ * Gets the current time, rounded down to the closest hour.
+ * Equivalent to invoking
+ * {@code roundToHour(System.currentTimeMillis())}.
+ *
+ * @return the current time, rounded to the closest hour.
+ */
+ public static long currentTimeHour() {
+ return roundToHour(System.currentTimeMillis());
+ }
+
+ /**
+ * Gets the current time, rounded down to the closest day.
+ * Equivalent to invoking
+ * {@code roundToDay(System.currentTimeMillis())}.
+ *
+ * @return the current time, rounded to the closest day.
+ */
+ public static long currentTimeDay() {
+ return roundToDay(System.currentTimeMillis());
+ }
+
+ /**
+ * Rounds the given time down to the closest second.
+ *
+ * @param pTime time
+ * @return the time rounded to the closest second.
+ */
+ public static long roundToSecond(final long pTime) {
+ return (pTime / SECOND) * SECOND;
+ }
+
+ /**
+ * Rounds the given time down to the closest minute.
+ *
+ * @param pTime time
+ * @return the time rounded to the closest minute.
+ */
+ public static long roundToMinute(final long pTime) {
+ return (pTime / MINUTE) * MINUTE;
+ }
+
+ /**
+ * Rounds the given time down to the closest hour, using the default timezone.
+ *
+ * @param pTime time
+ * @return the time rounded to the closest hour.
+ */
+ public static long roundToHour(final long pTime) {
+ return roundToHour(pTime, TimeZone.getDefault());
+ }
+
+ /**
+ * Rounds the given time down to the closest hour, using the given timezone.
+ *
+ * @param pTime time
+ * @param pTimeZone the timezone to use when rounding
+ * @return the time rounded to the closest hour.
+ */
+ public static long roundToHour(final long pTime, final TimeZone pTimeZone) {
+ int offset = pTimeZone.getOffset(pTime);
+ return ((pTime / HOUR) * HOUR) - offset;
+ }
+
+ /**
+ * Rounds the given time down to the closest day, using the default timezone.
+ *
+ * @param pTime time
+ * @return the time rounded to the closest day.
+ */
+ public static long roundToDay(final long pTime) {
+ return roundToDay(pTime, TimeZone.getDefault());
+ }
+
+ /**
+ * Rounds the given time down to the closest day, using the given timezone.
+ *
+ * @param pTime time
+ * @param pTimeZone the timezone to use when rounding
+ * @return the time rounded to the closest day.
+ */
+ public static long roundToDay(final long pTime, final TimeZone pTimeZone) {
+ int offset = pTimeZone.getOffset(pTime);
+ return (((pTime + offset) / DAY) * DAY) - offset;
+ }
+}
diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/Platform.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/Platform.java
index f0ab7e1a..8dadc5d2 100755
--- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/Platform.java
+++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/Platform.java
@@ -199,9 +199,10 @@ public final class Platform {
/**
* Enumeration of common System {@code Architecture}s.
- *
+ *
* For {@link #Unknown unknown architectures}, {@code toString()} will return
* the the same value as {@code System.getProperty("os.arch")}.
+ *
*
* @author Harald Kuhr
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/Platform.java#1 $
@@ -228,9 +229,10 @@ public final class Platform {
/**
* Enumeration of common {@code OperatingSystem}s.
- *
+ *
* For {@link #Unknown unknown operating systems}, {@code getName()} will return
* the the same value as {@code System.getProperty("os.name")}.
+ *
*
* @author Harald Kuhr
* @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/Platform.java#1 $
diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java
index 615672ab..aefd92a0 100755
--- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java
+++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java
@@ -1,139 +1,140 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.lang;
-
-/**
- * Util class for various reflection-based operations.
- *
- * NOTE: This class is not considered part of the public API and may be
- * changed without notice
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java#1 $
- */
-public final class ReflectUtil {
-
- /** Don't allow instances */
- private ReflectUtil() {}
-
- /**
- * Returns the primitive type for the given wrapper type.
- *
- * @param pType the wrapper type
- *
- * @return the primitive type
- *
- * @throws IllegalArgumentException if {@code pType} is not a primitive
- * wrapper
- */
- public static Class unwrapType(Class pType) {
- if (pType == Boolean.class) {
- return Boolean.TYPE;
- }
- else if (pType == Byte.class) {
- return Byte.TYPE;
- }
- else if (pType == Character.class) {
- return Character.TYPE;
- }
- else if (pType == Double.class) {
- return Double.TYPE;
- }
- else if (pType == Float.class) {
- return Float.TYPE;
- }
- else if (pType == Integer.class) {
- return Integer.TYPE;
- }
- else if (pType == Long.class) {
- return Long.TYPE;
- }
- else if (pType == Short.class) {
- return Short.TYPE;
- }
-
- throw new IllegalArgumentException("Not a primitive wrapper: " + pType);
- }
-
- /**
- * Returns the wrapper type for the given primitive type.
- *
- * @param pType the primitive tpye
- *
- * @return the wrapper type
- *
- * @throws IllegalArgumentException if {@code pType} is not a primitive
- * type
- */
- public static Class wrapType(Class pType) {
- if (pType == Boolean.TYPE) {
- return Boolean.class;
- }
- else if (pType == Byte.TYPE) {
- return Byte.class;
- }
- else if (pType == Character.TYPE) {
- return Character.class;
- }
- else if (pType == Double.TYPE) {
- return Double.class;
- }
- else if (pType == Float.TYPE) {
- return Float.class;
- }
- else if (pType == Integer.TYPE) {
- return Integer.class;
- }
- else if (pType == Long.TYPE) {
- return Long.class;
- }
- else if (pType == Short.TYPE) {
- return Short.class;
- }
-
- throw new IllegalArgumentException("Not a primitive type: " + pType);
- }
-
- /**
- * Returns {@code true} if the given type is a primitive wrapper.
- *
- * @param pType
- *
- * @return {@code true} if the given type is a primitive wrapper, otherwise
- * {@code false}
- */
- public static boolean isPrimitiveWrapper(Class pType) {
- return pType == Boolean.class || pType == Byte.class
- || pType == Character.class || pType == Double.class
- || pType == Float.class || pType == Integer.class
- || pType == Long.class || pType == Short.class;
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.lang;
+
+/**
+ * Util class for various reflection-based operations.
+ *
+ * NOTE: This class is not considered part of the public API and may be
+ * changed without notice
+ *
+ *
+ * @author Harald Kuhr
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/ReflectUtil.java#1 $
+ */
+public final class ReflectUtil {
+
+ /** Don't allow instances */
+ private ReflectUtil() {}
+
+ /**
+ * Returns the primitive type for the given wrapper type.
+ *
+ * @param pType the wrapper type
+ *
+ * @return the primitive type
+ *
+ * @throws IllegalArgumentException if {@code pType} is not a primitive
+ * wrapper
+ */
+ public static Class unwrapType(Class pType) {
+ if (pType == Boolean.class) {
+ return Boolean.TYPE;
+ }
+ else if (pType == Byte.class) {
+ return Byte.TYPE;
+ }
+ else if (pType == Character.class) {
+ return Character.TYPE;
+ }
+ else if (pType == Double.class) {
+ return Double.TYPE;
+ }
+ else if (pType == Float.class) {
+ return Float.TYPE;
+ }
+ else if (pType == Integer.class) {
+ return Integer.TYPE;
+ }
+ else if (pType == Long.class) {
+ return Long.TYPE;
+ }
+ else if (pType == Short.class) {
+ return Short.TYPE;
+ }
+
+ throw new IllegalArgumentException("Not a primitive wrapper: " + pType);
+ }
+
+ /**
+ * Returns the wrapper type for the given primitive type.
+ *
+ * @param pType the primitive tpye
+ *
+ * @return the wrapper type
+ *
+ * @throws IllegalArgumentException if {@code pType} is not a primitive
+ * type
+ */
+ public static Class wrapType(Class pType) {
+ if (pType == Boolean.TYPE) {
+ return Boolean.class;
+ }
+ else if (pType == Byte.TYPE) {
+ return Byte.class;
+ }
+ else if (pType == Character.TYPE) {
+ return Character.class;
+ }
+ else if (pType == Double.TYPE) {
+ return Double.class;
+ }
+ else if (pType == Float.TYPE) {
+ return Float.class;
+ }
+ else if (pType == Integer.TYPE) {
+ return Integer.class;
+ }
+ else if (pType == Long.TYPE) {
+ return Long.class;
+ }
+ else if (pType == Short.TYPE) {
+ return Short.class;
+ }
+
+ throw new IllegalArgumentException("Not a primitive type: " + pType);
+ }
+
+ /**
+ * Returns {@code true} if the given type is a primitive wrapper.
+ *
+ * @param pType
+ *
+ * @return {@code true} if the given type is a primitive wrapper, otherwise
+ * {@code false}
+ */
+ public static boolean isPrimitiveWrapper(Class pType) {
+ return pType == Boolean.class || pType == Byte.class
+ || pType == Character.class || pType == Double.class
+ || pType == Float.class || pType == Integer.class
+ || pType == Long.class || pType == Short.class;
+ }
+}
diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/StringUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/StringUtil.java
index 5b546646..69979657 100755
--- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/StringUtil.java
+++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/StringUtil.java
@@ -1,2152 +1,2161 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.lang;
-
-import com.twelvemonkeys.util.StringTokenIterator;
-
-import java.awt.*;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.Array;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.lang.reflect.Modifier;
-import java.nio.charset.UnsupportedCharsetException;
-import java.sql.Timestamp;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.List;
-import java.util.regex.Pattern;
-import java.util.regex.PatternSyntaxException;
-
-/**
- * A utility class with some useful string manipulation methods.
- *
- * @author Harald Kuhr
- * @author Eirik Torske
- * @author last modified by $Author: haku $
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/StringUtil.java#2 $
- * @todo Consistency check: Method names, parameter sequence, Exceptions,
- * return values, null-value handling and parameter names (cosmetics).
- */
-public final class StringUtil {
-
- /**
- * The default delimiter string, used by the {@code toXXXArray()}
- * methods.
- * Its value is {@code ", \t\n\r\f"}.
- *
- *
- * @see #toStringArray(String)
- * @see #toIntArray(String)
- * @see #toLongArray(String)
- * @see #toDoubleArray(String)
- */
- public final static String DELIMITER_STRING = ", \t\n\r\f";
-
- // Avoid constructor showing up in API doc
- private StringUtil() {
- }
-
- /**
- * Constructs a new {@link String} by decoding the specified sub array of bytes using the specified charset.
- * Replacement for {@link String#String(byte[], int, int, String) new String(byte[], int, int, String)}, that does
- * not throw the checked {@link UnsupportedEncodingException},
- * but instead the unchecked {@link UnsupportedCharsetException} if the character set is not supported.
- *
- * @param pData the bytes to be decoded to characters
- * @param pOffset the index of the first byte to decode
- * @param pLength the number of bytes to decode
- * @param pCharset the name of a supported character set
- * @return a newly created string.
- * @throws UnsupportedCharsetException
- *
- * @see String#String(byte[], int, int, String)
- */
- public static String decode(final byte[] pData, final int pOffset, final int pLength, final String pCharset) {
- try {
- return new String(pData, pOffset, pLength, pCharset);
- }
- catch (UnsupportedEncodingException e) {
- throw new UnsupportedCharsetException(pCharset);
- }
- }
-
- /**
- * Returns the value of the given {@code Object}, as a {@code String}.
- * Unlike String.valueOf, this method returns {@code null}
- * instead of the {@code String} "null", if {@code null} is given as
- * the argument.
- *
- * @param pObj the Object to find the {@code String} value of.
- * @return the String value of the given object, or {@code null} if the
- * {@code pObj} == {@code null}.
- * @see String#valueOf(Object)
- * @see String#toString()
- */
- public static String valueOf(Object pObj) {
- return ((pObj != null) ? pObj.toString() : null);
- }
-
- /**
- * Converts a string to uppercase.
- *
- * @param pString the string to convert
- * @return the string converted to uppercase, or null if the argument was
- * null.
- */
- public static String toUpperCase(String pString) {
- if (pString != null) {
- return pString.toUpperCase();
- }
- return null;
- }
-
- /**
- * Converts a string to lowercase.
- *
- * @param pString the string to convert
- * @return the string converted to lowercase, or null if the argument was
- * null.
- */
- public static String toLowerCase(String pString) {
- if (pString != null) {
- return pString.toLowerCase();
- }
- return null;
- }
-
- /**
- * Tests if a String is null, or contains nothing but white-space.
- *
- * @param pString The string to test
- * @return true if the string is null or contains only whitespace,
- * otherwise false.
- */
- public static boolean isEmpty(String pString) {
- return ((pString == null) || (pString.trim().length() == 0));
- }
-
- /**
- * Tests a string array, to see if all items are null or an empty string.
- *
- * @param pStringArray The string array to check.
- * @return true if the string array is null or only contains string items
- * that are null or contain only whitespace, otherwise false.
- */
- public static boolean isEmpty(String[] pStringArray) {
- // No elements to test
- if (pStringArray == null) {
- return true;
- }
-
- // Test all the elements
- for (String string : pStringArray) {
- if (!isEmpty(string)) {
- return false;
- }
- }
-
- // All elements are empty
- return true;
- }
-
- /**
- * Tests if a string contains another string.
- *
- * @param pContainer The string to test
- * @param pLookFor The string to look for
- * @return {@code true} if the container string is contains the string, and
- * both parameters are non-{@code null}, otherwise {@code false}.
- */
- public static boolean contains(String pContainer, String pLookFor) {
- return ((pContainer != null) && (pLookFor != null) && (pContainer.indexOf(pLookFor) >= 0));
- }
-
- /**
- * Tests if a string contains another string, ignoring case.
- *
- * @param pContainer The string to test
- * @param pLookFor The string to look for
- * @return {@code true} if the container string is contains the string, and
- * both parameters are non-{@code null}, otherwise {@code false}.
- * @see #contains(String,String)
- */
- public static boolean containsIgnoreCase(String pContainer, String pLookFor) {
- return indexOfIgnoreCase(pContainer, pLookFor, 0) >= 0;
- }
-
- /**
- * Tests if a string contains a specific character.
- *
- * @param pString The string to check.
- * @param pChar The character to search for.
- * @return true if the string contains the specific character.
- */
- public static boolean contains(final String pString, final int pChar) {
- return ((pString != null) && (pString.indexOf(pChar) >= 0));
- }
-
- /**
- * Tests if a string contains a specific character, ignoring case.
- *
- * @param pString The string to check.
- * @param pChar The character to search for.
- * @return true if the string contains the specific character.
- */
- public static boolean containsIgnoreCase(String pString, int pChar) {
- return ((pString != null)
- && ((pString.indexOf(Character.toLowerCase((char) pChar)) >= 0)
- || (pString.indexOf(Character.toUpperCase((char) pChar)) >= 0)));
-
- // NOTE: I don't convert the string to uppercase, but instead test
- // the string (potentially) two times, as this is more efficient for
- // long strings (in most cases).
- }
-
- /**
- * Returns the index within this string of the first occurrence of the
- * specified substring.
- *
- * @param pString The string to test
- * @param pLookFor The string to look for
- * @return if the string argument occurs as a substring within this object,
- * then the index of the first character of the first such substring is
- * returned; if it does not occur as a substring, -1 is returned.
- * @see String#indexOf(String)
- */
- public static int indexOfIgnoreCase(String pString, String pLookFor) {
- return indexOfIgnoreCase(pString, pLookFor, 0);
- }
-
- /**
- * Returns the index within this string of the first occurrence of the
- * specified substring, starting at the specified index.
- *
- * @param pString The string to test
- * @param pLookFor The string to look for
- * @param pPos The first index to test
- * @return if the string argument occurs as a substring within this object,
- * then the index of the first character of the first such substring is
- * returned; if it does not occur as a substring, -1 is returned.
- * @see String#indexOf(String,int)
- */
- public static int indexOfIgnoreCase(String pString, String pLookFor, int pPos) {
- if ((pString == null) || (pLookFor == null)) {
- return -1;
- }
- if (pLookFor.length() == 0) {
- return pPos;// All strings "contains" the empty string
- }
- if (pLookFor.length() > pString.length()) {
- return -1;// Cannot contain string longer than itself
- }
-
- // Get first char
- char firstL = Character.toLowerCase(pLookFor.charAt(0));
- char firstU = Character.toUpperCase(pLookFor.charAt(0));
- int indexLower = 0;
- int indexUpper = 0;
-
- for (int i = pPos; i <= (pString.length() - pLookFor.length()); i++) {
-
- // Peek for first char
- indexLower = ((indexLower >= 0) && (indexLower <= i))
- ? pString.indexOf(firstL, i)
- : indexLower;
- indexUpper = ((indexUpper >= 0) && (indexUpper <= i))
- ? pString.indexOf(firstU, i)
- : indexUpper;
- if (indexLower < 0) {
- if (indexUpper < 0) {
- return -1;// First char not found
- }
- else {
- i = indexUpper;// Only upper
- }
- }
- else if (indexUpper < 0) {
- i = indexLower;// Only lower
- }
- else {
-
- // Both found, select first occurence
- i = (indexLower < indexUpper)
- ? indexLower
- : indexUpper;
- }
-
- // Only one?
- if (pLookFor.length() == 1) {
- return i;// The only char found!
- }
-
- // Test if we still have enough chars
- else if (i > (pString.length() - pLookFor.length())) {
- return -1;
- }
-
- // Test if last char equals! (regionMatches is expensive)
- else if ((pString.charAt(i + pLookFor.length() - 1) != Character.toLowerCase(pLookFor.charAt(pLookFor.length() - 1)))
- && (pString.charAt(i + pLookFor.length() - 1) != Character.toUpperCase(pLookFor.charAt(pLookFor.length() - 1)))) {
- continue;// Nope, try next
- }
-
- // Test from second char, until second-last char
- else if ((pLookFor.length() <= 2) || pString.regionMatches(true, i + 1, pLookFor, 1, pLookFor.length() - 2)) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Returns the index within this string of the rightmost occurrence of the
- * specified substring. The rightmost empty string "" is considered to
- * occur at the index value {@code pString.length() - 1}.
- *
- * @param pString The string to test
- * @param pLookFor The string to look for
- * @return If the string argument occurs one or more times as a substring
- * within this object at a starting index no greater than fromIndex, then
- * the index of the first character of the last such substring is returned.
- * If it does not occur as a substring starting at fromIndex or earlier, -1
- * is returned.
- * @see String#lastIndexOf(String)
- */
- public static int lastIndexOfIgnoreCase(String pString, String pLookFor) {
- return lastIndexOfIgnoreCase(pString, pLookFor, pString != null ? pString.length() - 1 : -1);
- }
-
- /**
- * Returns the index within this string of the rightmost occurrence of the
- * specified substring. The rightmost empty string "" is considered to
- * occur at the index value {@code pPos}
- *
- * @param pString The string to test
- * @param pLookFor The string to look for
- * @param pPos The last index to test
- * @return If the string argument occurs one or more times as a substring
- * within this object at a starting index no greater than fromIndex, then
- * the index of the first character of the last such substring is returned.
- * If it does not occur as a substring starting at fromIndex or earlier, -1
- * is returned.
- * @see String#lastIndexOf(String,int)
- */
- public static int lastIndexOfIgnoreCase(String pString, String pLookFor, int pPos) {
- if ((pString == null) || (pLookFor == null)) {
- return -1;
- }
- if (pLookFor.length() == 0) {
- return pPos;// All strings "contains" the empty string
- }
- if (pLookFor.length() > pString.length()) {
- return -1;// Cannot contain string longer than itself
- }
-
- // Get first char
- char firstL = Character.toLowerCase(pLookFor.charAt(0));
- char firstU = Character.toUpperCase(pLookFor.charAt(0));
- int indexLower = pPos;
- int indexUpper = pPos;
-
- for (int i = pPos; i >= 0; i--) {
-
- // Peek for first char
- indexLower = ((indexLower >= 0) && (indexLower >= i))
- ? pString.lastIndexOf(firstL, i)
- : indexLower;
- indexUpper = ((indexUpper >= 0) && (indexUpper >= i))
- ? pString.lastIndexOf(firstU, i)
- : indexUpper;
- if (indexLower < 0) {
- if (indexUpper < 0) {
- return -1;// First char not found
- }
- else {
- i = indexUpper;// Only upper
- }
- }
- else if (indexUpper < 0) {
- i = indexLower;// Only lower
- }
- else {
-
- // Both found, select last occurence
- i = (indexLower > indexUpper)
- ? indexLower
- : indexUpper;
- }
-
- // Only one?
- if (pLookFor.length() == 1) {
- return i;// The only char found!
- }
-
- // Test if we still have enough chars
- else if (i > (pString.length() - pLookFor.length())) {
- //return -1;
- continue;
- }
-
- // Test if last char equals! (regionMatches is expensive)
- else
- if ((pString.charAt(i + pLookFor.length() - 1) != Character.toLowerCase(pLookFor.charAt(pLookFor.length() - 1)))
- && (pString.charAt(i + pLookFor.length() - 1) != Character.toUpperCase(pLookFor.charAt(pLookFor.length() - 1)))) {
- continue;// Nope, try next
- }
-
- // Test from second char, until second-last char
- else
- if ((pLookFor.length() <= 2) || pString.regionMatches(true, i + 1, pLookFor, 1, pLookFor.length() - 2)) {
- return i;
- }
- }
- return -1;
- }
-
- /**
- * Returns the index within this string of the first occurrence of the
- * specified character.
- *
- * @param pString The string to test
- * @param pChar The character to look for
- * @return if the string argument occurs as a substring within this object,
- * then the index of the first character of the first such substring is
- * returned; if it does not occur as a substring, -1 is returned.
- * @see String#indexOf(int)
- */
- public static int indexOfIgnoreCase(String pString, int pChar) {
- return indexOfIgnoreCase(pString, pChar, 0);
- }
-
- /**
- * Returns the index within this string of the first occurrence of the
- * specified character, starting at the specified index.
- *
- * @param pString The string to test
- * @param pChar The character to look for
- * @param pPos The first index to test
- * @return if the string argument occurs as a substring within this object,
- * then the index of the first character of the first such substring is
- * returned; if it does not occur as a substring, -1 is returned.
- * @see String#indexOf(int,int)
- */
- public static int indexOfIgnoreCase(String pString, int pChar, int pPos) {
- if ((pString == null)) {
- return -1;
- }
-
- // Get first char
- char lower = Character.toLowerCase((char) pChar);
- char upper = Character.toUpperCase((char) pChar);
- int indexLower;
- int indexUpper;
-
- // Test for char
- indexLower = pString.indexOf(lower, pPos);
- indexUpper = pString.indexOf(upper, pPos);
- if (indexLower < 0) {
-
- /* if (indexUpper < 0)
- return -1; // First char not found
- else */
- return indexUpper;// Only upper
- }
- else if (indexUpper < 0) {
- return indexLower;// Only lower
- }
- else {
-
- // Both found, select first occurence
- return (indexLower < indexUpper)
- ? indexLower
- : indexUpper;
- }
- }
-
- /**
- * Returns the index within this string of the last occurrence of the
- * specified character.
- *
- * @param pString The string to test
- * @param pChar The character to look for
- * @return if the string argument occurs as a substring within this object,
- * then the index of the first character of the first such substring is
- * returned; if it does not occur as a substring, -1 is returned.
- * @see String#lastIndexOf(int)
- */
- public static int lastIndexOfIgnoreCase(String pString, int pChar) {
- return lastIndexOfIgnoreCase(pString, pChar, pString != null ? pString.length() : -1);
- }
-
- /**
- * Returns the index within this string of the last occurrence of the
- * specified character, searching backward starting at the specified index.
- *
- * @param pString The string to test
- * @param pChar The character to look for
- * @param pPos The last index to test
- * @return if the string argument occurs as a substring within this object,
- * then the index of the first character of the first such substring is
- * returned; if it does not occur as a substring, -1 is returned.
- * @see String#lastIndexOf(int,int)
- */
- public static int lastIndexOfIgnoreCase(String pString, int pChar, int pPos) {
- if ((pString == null)) {
- return -1;
- }
-
- // Get first char
- char lower = Character.toLowerCase((char) pChar);
- char upper = Character.toUpperCase((char) pChar);
- int indexLower;
- int indexUpper;
-
- // Test for char
- indexLower = pString.lastIndexOf(lower, pPos);
- indexUpper = pString.lastIndexOf(upper, pPos);
- if (indexLower < 0) {
-
- /* if (indexUpper < 0)
- return -1; // First char not found
- else */
- return indexUpper;// Only upper
- }
- else if (indexUpper < 0) {
- return indexLower;// Only lower
- }
- else {
-
- // Both found, select last occurence
- return (indexLower > indexUpper)
- ? indexLower
- : indexUpper;
- }
- }
-
- /**
- * Trims the argument string for whitespace on the left side only.
- *
- * @param pString the string to trim
- * @return the string with no whitespace on the left, or {@code null} if
- * the string argument is {@code null}.
- * @see #rtrim
- * @see String#trim()
- */
- public static String ltrim(String pString) {
- if ((pString == null) || (pString.length() == 0)) {
- return pString;// Null or empty string
- }
- for (int i = 0; i < pString.length(); i++) {
- if (!Character.isWhitespace(pString.charAt(i))) {
- if (i == 0) {
- return pString;// First char is not whitespace
- }
- else {
- return pString.substring(i);// Return rest after whitespace
- }
- }
- }
-
- // If all whitespace, return empty string
- return "";
- }
-
- /**
- * Trims the argument string for whitespace on the right side only.
- *
- * @param pString the string to trim
- * @return the string with no whitespace on the right, or {@code null} if
- * the string argument is {@code null}.
- * @see #ltrim
- * @see String#trim()
- */
- public static String rtrim(String pString) {
- if ((pString == null) || (pString.length() == 0)) {
- return pString;// Null or empty string
- }
- for (int i = pString.length(); i > 0; i--) {
- if (!Character.isWhitespace(pString.charAt(i - 1))) {
- if (i == pString.length()) {
- return pString;// First char is not whitespace
- }
- else {
- return pString.substring(0, i);// Return before whitespace
- }
- }
- }
-
- // If all whitespace, return empty string
- return "";
- }
-
- /**
- * Replaces a substring of a string with another string. All matches are
- * replaced.
- *
- * @param pSource The source String
- * @param pPattern The pattern to replace
- * @param pReplace The new String to be inserted instead of the
- * replace String
- * @return The new String with the pattern replaced
- */
- public static String replace(String pSource, String pPattern, String pReplace) {
- if (pPattern.length() == 0) {
- return pSource;// Special case: No pattern to replace
- }
-
- int match;
- int offset = 0;
- StringBuilder result = new StringBuilder();
-
- // Loop string, until last occurence of pattern, and replace
- while ((match = pSource.indexOf(pPattern, offset)) != -1) {
- // Append everything until pattern
- result.append(pSource.substring(offset, match));
- // Append the replace string
- result.append(pReplace);
- offset = match + pPattern.length();
- }
-
- // Append rest of string and return
- result.append(pSource.substring(offset));
-
- return result.toString();
- }
-
- /**
- * Replaces a substring of a string with another string, ignoring case.
- * All matches are replaced.
- *
- * @param pSource The source String
- * @param pPattern The pattern to replace
- * @param pReplace The new String to be inserted instead of the
- * replace String
- * @return The new String with the pattern replaced
- * @see #replace(String,String,String)
- */
- public static String replaceIgnoreCase(String pSource, String pPattern, String pReplace) {
- if (pPattern.length() == 0) {
- return pSource;// Special case: No pattern to replace
- }
- int match;
- int offset = 0;
- StringBuilder result = new StringBuilder();
-
- while ((match = indexOfIgnoreCase(pSource, pPattern, offset)) != -1) {
- result.append(pSource.substring(offset, match));
- result.append(pReplace);
- offset = match + pPattern.length();
- }
- result.append(pSource.substring(offset));
- return result.toString();
- }
-
- /**
- * Cuts a string between two words, before a sepcified length, if the
- * string is longer than the maxium lenght. The string is optionally padded
- * with the pad argument. The method assumes words to be separated by the
- * space character (" ").
- * Note that the maximum length argument is absolute, and will also include
- * the length of the padding.
- *
- * @param pString The string to cut
- * @param pMaxLen The maximum length before cutting
- * @param pPad The string to append at the end, aftrer cutting
- * @return The cutted string with padding, or the original string, if it
- * was shorter than the max length.
- * @see #pad(String,int,String,boolean)
- */
- public static String cut(String pString, int pMaxLen, String pPad) {
- if (pString == null) {
- return null;
- }
- if (pPad == null) {
- pPad = "";
- }
- int len = pString.length();
-
- if (len > pMaxLen) {
- len = pString.lastIndexOf(' ', pMaxLen - pPad.length());
- }
- else {
- return pString;
- }
- return pString.substring(0, len) + pPad;
- }
-
- /**
- * Makes the Nth letter of a String uppercase. If the index is outside the
- * the length of the argument string, the argument is simply returned.
- *
- * @param pString The string to capitalize
- * @param pIndex The base-0 index of the char to capitalize.
- * @return The capitalized string, or null, if a null argument was given.
- */
- public static String capitalize(String pString, int pIndex) {
- if (pIndex < 0) {
- throw new IndexOutOfBoundsException("Negative index not allowed: " + pIndex);
- }
- if (pString == null || pString.length() <= pIndex) {
- return pString;
- }
-
- // This is the fastest method, according to my tests
-
- // Skip array duplication if allready capitalized
- if (Character.isUpperCase(pString.charAt(pIndex))) {
- return pString;
- }
-
- // Convert to char array, capitalize and create new String
- char[] charArray = pString.toCharArray();
- charArray[pIndex] = Character.toUpperCase(charArray[pIndex]);
- return new String(charArray);
-
- /**
- StringBuilder buf = new StringBuilder(pString);
- buf.setCharAt(pIndex, Character.toUpperCase(buf.charAt(pIndex)));
- return buf.toString();
- //*/
-
- /**
- return pString.substring(0, pIndex)
- + Character.toUpperCase(pString.charAt(pIndex))
- + pString.substring(pIndex + 1);
- //*/
- }
-
- /**
- * Makes the first letter of a String uppercase.
- *
- * @param pString The string to capitalize
- * @return The capitalized string, or null, if a null argument was given.
- */
- public static String capitalize(String pString) {
- return capitalize(pString, 0);
- }
-
- /**
- * Formats a number with leading zeroes, to a specified length.
- *
- * @param pNum The number to format
- * @param pLen The number of digits
- * @return A string containing the formatted number
- * @throws IllegalArgumentException Thrown, if the number contains
- * more digits than allowed by the length argument.
- * @see #pad(String,int,String,boolean)
- * @deprecated Use StringUtil.pad instead!
- */
-
- /*public*/
- static String formatNumber(long pNum, int pLen) throws IllegalArgumentException {
- StringBuilder result = new StringBuilder();
-
- if (pNum >= Math.pow(10, pLen)) {
- throw new IllegalArgumentException("The number to format cannot contain more digits than the length argument specifies!");
- }
- for (int i = pLen; i > 1; i--) {
- if (pNum < Math.pow(10, i - 1)) {
- result.append('0');
- }
- else {
- break;
- }
- }
- result.append(pNum);
- return result.toString();
- }
-
- /**
- * String length check with simple concatenation of selected pad-string.
- * E.g. a zip number from 123 to the correct 0123.
- *
- * @param pSource The source string.
- * @param pRequiredLength The accurate length of the resulting string.
- * @param pPadString The string for concatenation.
- * @param pPrepend The location of fill-ins, prepend (true),
- * or append (false)
- * @return a concatenated string.
- * @todo What if source is allready longer than required length?
- * @todo Consistency with cut
- * @see #cut(String,int,String)
- */
- public static String pad(String pSource, int pRequiredLength, String pPadString, boolean pPrepend) {
- if (pPadString == null || pPadString.length() == 0) {
- throw new IllegalArgumentException("Pad string: \"" + pPadString + "\"");
- }
-
- if (pSource.length() >= pRequiredLength) {
- return pSource;
- }
-
- // TODO: Benchmark the new version against the old one, to see if it's really faster
- // Rewrite to first create pad
- // - pad += pad; - until length is >= gap
- // then append the pad and cut if too long
- int gap = pRequiredLength - pSource.length();
- StringBuilder result = new StringBuilder(pPadString);
- while (result.length() < gap) {
- result.append(result);
- }
-
- if (result.length() > gap) {
- result.delete(gap, result.length());
- }
-
- return pPrepend ? result.append(pSource).toString() : result.insert(0, pSource).toString();
-
- /*
- StringBuilder result = new StringBuilder(pSource);
-
- // Concatenation until proper string length
- while (result.length() < pRequiredLength) {
- // Prepend or append
- if (pPrepend) { // Front
- result.insert(0, pPadString);
- }
- else { // Back
- result.append(pPadString);
- }
- }
-
- // Truncate
- if (result.length() > pRequiredLength) {
- if (pPrepend) {
- result.delete(0, result.length() - pRequiredLength);
- }
- else {
- result.delete(pRequiredLength, result.length());
- }
- }
- return result.toString();
- */
- }
-
- /**
- * Converts the string to a date, using the default date format.
- *
- * @param pString the string to convert
- * @return the date
- * @see DateFormat
- * @see DateFormat#getInstance()
- */
- public static Date toDate(String pString) {
- // Default
- return toDate(pString, DateFormat.getInstance());
- }
-
- /**
- * Converts the string to a date, using the given format.
- *
- * @param pString the string to convert
- * @param pFormat the date format
- * @return the date
- * @todo cache formats?
- * @see java.text.SimpleDateFormat
- * @see java.text.SimpleDateFormat#SimpleDateFormat(String)
- */
- public static Date toDate(String pString, String pFormat) {
- // Get the format from cache, or create new and insert
- // Return new date
- return toDate(pString, new SimpleDateFormat(pFormat));
- }
-
- /**
- * Converts the string to a date, using the given format.
- *
- * @param pString the string to convert
- * @param pFormat the date format
- * @return the date
- * @see SimpleDateFormat
- * @see SimpleDateFormat#SimpleDateFormat(String)
- * @see DateFormat
- */
- public static Date toDate(final String pString, final DateFormat pFormat) {
- try {
- synchronized (pFormat) {
- // Parse date using given format
- return pFormat.parse(pString);
- }
- }
- catch (ParseException pe) {
- // Wrap in RuntimeException
- throw new IllegalArgumentException(pe.getMessage());
- }
- }
-
- /**
- * Converts the string to a jdbc Timestamp, using the standard Timestamp
- * escape format.
- *
- * @param pValue the value
- * @return a new {@code Timestamp}
- * @see java.sql.Timestamp
- * @see java.sql.Timestamp#valueOf(String)
- */
- public static Timestamp toTimestamp(final String pValue) {
- // Parse date using default format
- return Timestamp.valueOf(pValue);
- }
-
- /**
- * Converts a delimiter separated String to an array of Strings.
- *
- * @param pString The comma-separated string
- * @param pDelimiters The delimiter string
- * @return a {@code String} array containing the delimiter separated elements
- */
- public static String[] toStringArray(String pString, String pDelimiters) {
- if (isEmpty(pString)) {
- return new String[0];
- }
-
- StringTokenIterator st = new StringTokenIterator(pString, pDelimiters);
- List v = new ArrayList();
-
- while (st.hasMoreElements()) {
- v.add(st.nextToken());
- }
-
- return v.toArray(new String[v.size()]);
- }
-
- /**
- * Converts a comma-separated String to an array of Strings.
- *
- * @param pString The comma-separated string
- * @return a {@code String} array containing the comma-separated elements
- * @see #toStringArray(String,String)
- */
- public static String[] toStringArray(String pString) {
- return toStringArray(pString, DELIMITER_STRING);
- }
-
- /**
- * Converts a comma-separated String to an array of ints.
- *
- * @param pString The comma-separated string
- * @param pDelimiters The delimiter string
- * @param pBase The radix
- * @return an {@code int} array
- * @throws NumberFormatException if any of the elements are not parseable
- * as an int
- */
- public static int[] toIntArray(String pString, String pDelimiters, int pBase) {
- if (isEmpty(pString)) {
- return new int[0];
- }
-
- // Some room for improvement here...
- String[] temp = toStringArray(pString, pDelimiters);
- int[] array = new int[temp.length];
-
- for (int i = 0; i < array.length; i++) {
- array[i] = Integer.parseInt(temp[i], pBase);
- }
- return array;
- }
-
- /**
- * Converts a comma-separated String to an array of ints.
- *
- * @param pString The comma-separated string
- * @return an {@code int} array
- * @throws NumberFormatException if any of the elements are not parseable
- * as an int
- * @see #toStringArray(String,String)
- * @see #DELIMITER_STRING
- */
- public static int[] toIntArray(String pString) {
- return toIntArray(pString, DELIMITER_STRING, 10);
- }
-
- /**
- * Converts a comma-separated String to an array of ints.
- *
- * @param pString The comma-separated string
- * @param pDelimiters The delimiter string
- * @return an {@code int} array
- * @throws NumberFormatException if any of the elements are not parseable
- * as an int
- * @see #toIntArray(String,String)
- */
- public static int[] toIntArray(String pString, String pDelimiters) {
- return toIntArray(pString, pDelimiters, 10);
- }
-
- /**
- * Converts a comma-separated String to an array of longs.
- *
- * @param pString The comma-separated string
- * @param pDelimiters The delimiter string
- * @return a {@code long} array
- * @throws NumberFormatException if any of the elements are not parseable
- * as a long
- */
- public static long[] toLongArray(String pString, String pDelimiters) {
- if (isEmpty(pString)) {
- return new long[0];
- }
-
- // Some room for improvement here...
- String[] temp = toStringArray(pString, pDelimiters);
- long[] array = new long[temp.length];
-
- for (int i = 0; i < array.length; i++) {
- array[i] = Long.parseLong(temp[i]);
- }
- return array;
- }
-
- /**
- * Converts a comma-separated String to an array of longs.
- *
- * @param pString The comma-separated string
- * @return a {@code long} array
- * @throws NumberFormatException if any of the elements are not parseable
- * as a long
- * @see #toStringArray(String,String)
- * @see #DELIMITER_STRING
- */
- public static long[] toLongArray(String pString) {
- return toLongArray(pString, DELIMITER_STRING);
- }
-
- /**
- * Converts a comma-separated String to an array of doubles.
- *
- * @param pString The comma-separated string
- * @param pDelimiters The delimiter string
- * @return a {@code double} array
- * @throws NumberFormatException if any of the elements are not parseable
- * as a double
- */
- public static double[] toDoubleArray(String pString, String pDelimiters) {
- if (isEmpty(pString)) {
- return new double[0];
- }
-
- // Some room for improvement here...
- String[] temp = toStringArray(pString, pDelimiters);
- double[] array = new double[temp.length];
-
- for (int i = 0; i < array.length; i++) {
- array[i] = Double.valueOf(temp[i]);
-
- // Double.parseDouble() is 1.2...
- }
- return array;
- }
-
- /**
- * Converts a comma-separated String to an array of doubles.
- *
- * @param pString The comma-separated string
- * @return a {@code double} array
- * @throws NumberFormatException if any of the elements are not parseable
- * as a double
- * @see #toDoubleArray(String,String)
- * @see #DELIMITER_STRING
- */
- public static double[] toDoubleArray(String pString) {
- return toDoubleArray(pString, DELIMITER_STRING);
- }
-
- /**
- * Parses a string to a Color.
- * The argument can be a color constant (static constant from
- * {@link java.awt.Color java.awt.Color}), like {@code black} or
- * {@code red}, or it can be HTML/CSS-style, on the format:
- *
- * - {@code #RRGGBB}, where RR, GG and BB means two digit
- * hexadecimal for red, green and blue values respectively.
- * - {@code #AARRGGBB}, as above, with AA as alpha component.
- * - {@code #RGB}, where R, G and B means one digit
- * hexadecimal for red, green and blue values respectively.
- * - {@code #ARGB}, as above, with A as alpha component.
- *
- *
- * @param pString the string representation of the color
- * @return the {@code Color} object, or {@code null} if the argument
- * is {@code null}
- * @throws IllegalArgumentException if the string does not map to a color.
- * @see java.awt.Color
- */
- public static Color toColor(String pString) {
- // No string, no color
- if (pString == null) {
- return null;
- }
-
- // #RRGGBB format
- if (pString.charAt(0) == '#') {
- int r = 0;
- int g = 0;
- int b = 0;
- int a = -1;// alpha
-
- if (pString.length() >= 7) {
- int idx = 1;
-
- // AA
- if (pString.length() >= 9) {
- a = Integer.parseInt(pString.substring(idx, idx + 2), 0x10);
- idx += 2;
- }
- // RR GG BB
- r = Integer.parseInt(pString.substring(idx, idx + 2), 0x10);
- g = Integer.parseInt(pString.substring(idx + 2, idx + 4), 0x10);
- b = Integer.parseInt(pString.substring(idx + 4, idx + 6), 0x10);
-
- }
- else if (pString.length() >= 4) {
- int idx = 1;
-
- // A
- if (pString.length() >= 5) {
- a = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10;
- }
- // R G B
- r = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10;
- g = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10;
- b = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10;
- }
- if (a != -1) {
- // With alpha
- return new Color(r, g, b, a);
- }
-
- // No alpha
- return new Color(r, g, b);
- }
-
- // Get color by name
- try {
- Class colorClass = Color.class;
- Field field = null;
-
- // Workaround for stupidity in Color class constant field names
- try {
- field = colorClass.getField(pString);
- }
- catch (Exception e) {
- // Don't care, this is just a workaround...
- }
- if (field == null) {
- // NOTE: The toLowerCase() on the next line will lose darkGray
- // and lightGray...
- field = colorClass.getField(pString.toLowerCase());
- }
-
- // Only try to get public final fields
- int mod = field.getModifiers();
-
- if (Modifier.isPublic(mod) && Modifier.isStatic(mod)) {
- return (Color) field.get(null);
- }
- }
- catch (NoSuchFieldException nsfe) {
- // No such color, throw illegal argument?
- throw new IllegalArgumentException("No such color: " + pString);
- }
- catch (SecurityException se) {
- // Can't access field, return null
- }
- catch (IllegalAccessException iae) {
- // Can't happen, as the field must be public static
- }
- catch (IllegalArgumentException iar) {
- // Can't happen, as the field must be static
- }
-
- // This should never be reached, but you never know... ;-)
- return null;
- }
-
- /**
- * Creates a HTML/CSS String representation of the given color.
- * The HTML/CSS color format is defined as:
- *
- * - {@code #RRGGBB}, where RR, GG and BB means two digit
- * hexadecimal for red, green and blue values respectively.
- * - {@code #AARRGGBB}, as above, with AA as alpha component.
- *
- *
- * Examlples: {@code toColorString(Color.red) == "#ff0000"},
- * {@code toColorString(new Color(0xcc, 0xcc, 0xcc)) == "#cccccc"}.
- *
- * @param pColor the color
- * @return A String representation of the color on HTML/CSS form
- * @todo Consider moving to ImageUtil?
- */
- public static String toColorString(Color pColor) {
- // Not a color...
- if (pColor == null) {
- return null;
- }
-
- StringBuilder str = new StringBuilder(Integer.toHexString(pColor.getRGB()));
-
- // Make sure string is 8 chars
- for (int i = str.length(); i < 8; i++) {
- str.insert(0, '0');
- }
-
- // All opaque is default
- if (str.charAt(0) == 'f' && str.charAt(1) == 'f') {
- str.delete(0, 2);
- }
-
- // Prepend hash
- return str.insert(0, '#').toString();
- }
-
- /**
- * Tests a string, to see if it is an number (element of Z).
- * Valid integers are positive natural numbers (1, 2, 3, ...),
- * their negatives (?1, ?2, ?3, ...) and the number zero.
- *
- * Note that there is no guarantees made, that this number can be
- * represented as either an int or a long.
- *
- * @param pString The string to check.
- * @return true if the String is a natural number.
- */
- public static boolean isNumber(String pString) {
- if (isEmpty(pString)) {
- return false;
- }
-
- // Special case for first char, may be minus sign ('-')
- char ch = pString.charAt(0);
- if (!(ch == '-' || Character.isDigit(ch))) {
- return false;
- }
-
- // Test every char
- for (int i = 1; i < pString.length(); i++) {
- if (!Character.isDigit(pString.charAt(i))) {
- return false;
- }
- }
-
- // All digits must be a natural number
- return true;
- }
-
- /*
- * This version is benchmarked against toStringArray and found to be
- * increasingly slower, the more elements the string contains.
- * Kept here
- */
-
- /**
- * Removes all occurences of a specific character in a string.
- * This method is not design for efficiency!
- *
- *
- * @param pSource
- * @param pSubstring
- * @param pPosition
- * @return the modified string.
- */
-
- /*
- public static String removeChar(String pSourceString, final char pBadChar) {
-
- char[] sourceCharArray = pSourceString.toCharArray();
- List modifiedCharList = new Vector(sourceCharArray.length, 1);
-
- // Filter the string
- for (int i = 0; i < sourceCharArray.length; i++) {
- if (sourceCharArray[i] != pBadChar) {
- modifiedCharList.add(new Character(sourceCharArray[i]));
- }
- }
-
- // Clean the character list
- modifiedCharList = (List) CollectionUtil.purifyCollection((Collection) modifiedCharList);
-
- // Create new modified String
- char[] modifiedCharArray = new char[modifiedCharList.size()];
- for (int i = 0; i < modifiedCharArray.length; i++) {
- modifiedCharArray[i] = ((Character) modifiedCharList.get(i)).charValue();
- }
-
- return new String(modifiedCharArray);
- }
- */
-
- /**
- *
- * This method is not design for efficiency!
- *
- * @param pSourceString The String for modification.
- * @param pBadChars The char array containing the characters to remove from the source string.
- * @return the modified string.
- * @-deprecated Not tested yet!
- *
- */
-
- /*
- public static String removeChars(String pSourceString, final char[] pBadChars) {
-
- char[] sourceCharArray = pSourceString.toCharArray();
- List modifiedCharList = new Vector(sourceCharArray.length, 1);
-
- Map badCharMap = new Hashtable();
- Character dummyChar = new Character('*');
- for (int i = 0; i < pBadChars.length; i++) {
- badCharMap.put(new Character(pBadChars[i]), dummyChar);
- }
-
- // Filter the string
- for (int i = 0; i < sourceCharArray.length; i++) {
- Character arrayChar = new Character(sourceCharArray[i]);
- if (!badCharMap.containsKey(arrayChar)) {
- modifiedCharList.add(new Character(sourceCharArray[i]));
- }
- }
-
- // Clean the character list
- modifiedCharList = (List) CollectionUtil.purifyCollection((Collection) modifiedCharList);
-
- // Create new modified String
- char[] modifiedCharArray = new char[modifiedCharList.size()];
- for (int i = 0; i < modifiedCharArray.length; i++) {
- modifiedCharArray[i] = ((Character) modifiedCharList.get(i)).charValue();
- }
-
- return new String(modifiedCharArray);
-
- }
- */
-
- /**
- * Ensures that a string includes a given substring at a given position.
- *
- * Extends the string with a given string if it is not already there.
- * E.g an URL "www.vg.no", to "http://www.vg.no".
- *
- * @param pSource The source string.
- * @param pSubstring The substring to include.
- * @param pPosition The location of the fill-in, the index starts with 0.
- * @return the string, with the substring at the given location.
- */
- static String ensureIncludesAt(String pSource, String pSubstring, int pPosition) {
- StringBuilder newString = new StringBuilder(pSource);
-
- try {
- String existingSubstring = pSource.substring(pPosition, pPosition + pSubstring.length());
-
- if (!existingSubstring.equalsIgnoreCase(pSubstring)) {
- newString.insert(pPosition, pSubstring);
- }
- }
- catch (Exception e) {
- // Do something!?
- }
- return newString.toString();
- }
-
- /**
- * Ensures that a string does not include a given substring at a given
- * position.
- *
- * Removes a given substring from a string if it is there.
- * E.g an URL "http://www.vg.no", to "www.vg.no".
- *
- * @param pSource The source string.
- * @param pSubstring The substring to check and possibly remove.
- * @param pPosition The location of possible substring removal, the index starts with 0.
- * @return the string, without the substring at the given location.
- */
- static String ensureExcludesAt(String pSource, String pSubstring, int pPosition) {
- StringBuilder newString = new StringBuilder(pSource);
-
- try {
- String existingString = pSource.substring(pPosition + 1, pPosition + pSubstring.length() + 1);
-
- if (!existingString.equalsIgnoreCase(pSubstring)) {
- newString.delete(pPosition, pPosition + pSubstring.length());
- }
- }
- catch (Exception e) {
- // Do something!?
- }
- return newString.toString();
- }
-
- /**
- * Gets the first substring between the given string boundaries.
- *
- *
- * @param pSource The source string.
- * @param pBeginBoundaryString The string that marks the beginning.
- * @param pEndBoundaryString The string that marks the end.
- * @param pOffset The index to start searching in the source
- * string. If it is less than 0, the index will be set to 0.
- * @return the substring demarcated by the given string boundaries or null
- * if not both string boundaries are found.
- */
- public static String substring(final String pSource, final String pBeginBoundaryString, final String pEndBoundaryString,
- final int pOffset) {
- // Check offset
- int offset = (pOffset < 0)
- ? 0
- : pOffset;
-
- // Find the start index
- int startIndex = pSource.indexOf(pBeginBoundaryString, offset) + pBeginBoundaryString.length();
-
- if (startIndex < 0) {
- return null;
- }
-
- // Find the end index
- int endIndex = pSource.indexOf(pEndBoundaryString, startIndex);
-
- if (endIndex < 0) {
- return null;
- }
- return pSource.substring(startIndex, endIndex);
- }
-
- /**
- * Removes the first substring demarcated by the given string boundaries.
- *
- *
- * @param pSource The source string.
- * @param pBeginBoundaryChar The character that marks the beginning of the
- * unwanted substring.
- * @param pEndBoundaryChar The character that marks the end of the
- * unwanted substring.
- * @param pOffset The index to start searching in the source
- * string. If it is less than 0, the index will be set to 0.
- * @return the source string with all the demarcated substrings removed,
- * included the demarcation characters.
- * @deprecated this method actually removes all demarcated substring.. doesn't it?
- */
-
- /*public*/
- static String removeSubstring(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar, final int pOffset) {
- StringBuilder filteredString = new StringBuilder();
- boolean insideDemarcatedArea = false;
- char[] charArray = pSource.toCharArray();
-
- for (char c : charArray) {
- if (!insideDemarcatedArea) {
- if (c == pBeginBoundaryChar) {
- insideDemarcatedArea = true;
- }
- else {
- filteredString.append(c);
- }
- }
- else {
- if (c == pEndBoundaryChar) {
- insideDemarcatedArea = false;
- }
- }
- }
- return filteredString.toString();
- }
-
- /**
- * Removes all substrings demarcated by the given string boundaries.
- *
- *
- * @param pSource The source string.
- * @param pBeginBoundaryChar The character that marks the beginning of the unwanted substring.
- * @param pEndBoundaryChar The character that marks the end of the unwanted substring.
- * @return the source string with all the demarcated substrings removed, included the demarcation characters.
- */
- /*public*/
- static String removeSubstrings(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar) {
- StringBuilder filteredString = new StringBuilder();
- boolean insideDemarcatedArea = false;
- char[] charArray = pSource.toCharArray();
-
- for (char c : charArray) {
- if (!insideDemarcatedArea) {
- if (c == pBeginBoundaryChar) {
- insideDemarcatedArea = true;
- }
- else {
- filteredString.append(c);
- }
- }
- else {
- if (c == pEndBoundaryChar) {
- insideDemarcatedArea = false;
- }
- }
- }
- return filteredString.toString();
- }
-
- /**
- * Gets the first element of a {@code String} containing string elements delimited by a given delimiter.
- * NB - Straightforward implementation!
- *
- *
- * @param pSource The source string.
- * @param pDelimiter The delimiter used in the source string.
- * @return The last string element.
- * @todo This method should be re-implemented for more efficient execution.
- */
- public static String getFirstElement(final String pSource, final String pDelimiter) {
- if (pDelimiter == null) {
- throw new IllegalArgumentException("delimiter == null");
- }
-
- if (StringUtil.isEmpty(pSource)) {
- return pSource;
- }
-
- int idx = pSource.indexOf(pDelimiter);
- if (idx >= 0) {
- return pSource.substring(0, idx);
- }
- return pSource;
- }
-
- /**
- * Gets the last element of a {@code String} containing string elements
- * delimited by a given delimiter.
- * NB - Straightforward implementation!
- *
- *
- * @param pSource The source string.
- * @param pDelimiter The delimiter used in the source string.
- * @return The last string element.
- */
- public static String getLastElement(final String pSource, final String pDelimiter) {
- if (pDelimiter == null) {
- throw new IllegalArgumentException("delimiter == null");
- }
-
- if (StringUtil.isEmpty(pSource)) {
- return pSource;
- }
- int idx = pSource.lastIndexOf(pDelimiter);
- if (idx >= 0) {
- return pSource.substring(idx + 1);
- }
- return pSource;
- }
-
- /**
- * Converts a string array to a string of comma-separated values.
- *
- * @param pStringArray the string array
- * @return A string of comma-separated values
- */
- public static String toCSVString(Object[] pStringArray) {
- return toCSVString(pStringArray, ", ");
- }
-
- /**
- * Converts a string array to a string separated by the given delimiter.
- *
- * @param pStringArray the string array
- * @param pDelimiterString the delimiter string
- * @return string of delimiter separated values
- * @throws IllegalArgumentException if {@code pDelimiterString == null}
- */
- public static String toCSVString(Object[] pStringArray, String pDelimiterString) {
- if (pStringArray == null) {
- return "";
- }
- if (pDelimiterString == null) {
- throw new IllegalArgumentException("delimiter == null");
- }
-
- StringBuilder buffer = new StringBuilder();
- for (int i = 0; i < pStringArray.length; i++) {
- if (i > 0) {
- buffer.append(pDelimiterString);
- }
-
- buffer.append(pStringArray[i]);
- }
-
- return buffer.toString();
- }
-
- /**
- * @param pObject the object
- * @return a deep string representation of the given object
- */
- public static String deepToString(Object pObject) {
- return deepToString(pObject, false, 1);
- }
-
- /**
- * @param pObject the object
- * @param pDepth the maximum depth
- * @param pForceDeep {@code true} to force deep {@code toString}, even
- * if object overrides toString
- * @return a deep string representation of the given object
- * @todo Array handling (print full type and length)
- * @todo Register handlers for specific toDebugString handling? :-)
- */
- public static String deepToString(Object pObject, boolean pForceDeep, int pDepth) {
- // Null is null
- if (pObject == null) {
- return null;
- }
-
- // Implements toString, use it as-is unless pForceDeep
- if (!pForceDeep && !isIdentityToString(pObject)) {
- return pObject.toString();
- }
-
- StringBuilder buffer = new StringBuilder();
-
- if (pObject.getClass().isArray()) {
- // Special array handling
- Class componentClass = pObject.getClass();
- while (componentClass.isArray()) {
- buffer.append('[');
- buffer.append(Array.getLength(pObject));
- buffer.append(']');
- componentClass = componentClass.getComponentType();
- }
- buffer.insert(0, componentClass);
- buffer.append(" {hashCode=");
- buffer.append(Integer.toHexString(pObject.hashCode()));
- buffer.append("}");
- }
- else {
- // Append toString value only if overridden
- if (isIdentityToString(pObject)) {
- buffer.append(" {");
- }
- else {
- buffer.append(" {toString=");
- buffer.append(pObject.toString());
- buffer.append(", ");
- }
- buffer.append("hashCode=");
- buffer.append(Integer.toHexString(pObject.hashCode()));
- // Loop through, and filter out any getters
- Method[] methods = pObject.getClass().getMethods();
- for (Method method : methods) {
- // Filter only public methods
- if (Modifier.isPublic(method.getModifiers())) {
- String methodName = method.getName();
-
- // Find name of property
- String name = null;
- if (!methodName.equals("getClass")
- && methodName.length() > 3 && methodName.startsWith("get")
- && Character.isUpperCase(methodName.charAt(3))) {
- name = methodName.substring(3);
- }
- else if (methodName.length() > 2 && methodName.startsWith("is")
- && Character.isUpperCase(methodName.charAt(2))) {
- name = methodName.substring(2);
- }
-
- if (name != null) {
- // If lowercase name, convert, else keep case
- if (name.length() > 1 && Character.isLowerCase(name.charAt(1))) {
- name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
- }
-
- Class[] paramTypes = method.getParameterTypes();// involves array copying...
- boolean hasParams = (paramTypes != null && paramTypes.length > 0);
- boolean isVoid = Void.TYPE.equals(method.getReturnType());
-
- // Filter return type & parameters
- if (!isVoid && !hasParams) {
- try {
- Object value = method.invoke(pObject);
- buffer.append(", ");
- buffer.append(name);
- buffer.append('=');
- if (pDepth != 0 && value != null && isIdentityToString(value)) {
- buffer.append(deepToString(value, pForceDeep, pDepth > 0 ? pDepth - 1 : -1));
- }
- else {
- buffer.append(value);
- }
- }
- catch (Exception e) {
- // Next..!
- }
- }
- }
- }
- }
- buffer.append('}');
-
- // Get toString from original object
- buffer.insert(0, pObject.getClass().getName());
- }
-
- return buffer.toString();
- }
-
- /**
- * Tests if the {@code toString} method of the given object is inherited
- * from {@code Object}.
- *
- * @param pObject the object
- * @return {@code true} if toString of class Object
- */
- private static boolean isIdentityToString(Object pObject) {
- try {
- Method toString = pObject.getClass().getMethod("toString");
- if (toString.getDeclaringClass() == Object.class) {
- return true;
- }
- }
- catch (Exception ignore) {
- // Ignore
- }
-
- return false;
- }
-
- /**
- * Returns a string on the same format as {@code Object.toString()}.
- *
- * @param pObject the object
- * @return the object as a {@code String} on the format of
- * {@code Object.toString()}
- */
- public static String identityToString(Object pObject) {
- if (pObject == null) {
- return null;
- }
- else {
- return pObject.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(pObject));
- }
- }
-
- /**
- * Tells whether or not the given string string matches the given regular
- * expression.
- *
- * An invocation of this method of the form
- * matches(str, regex) yields exactly the
- * same result as the expression
- *
- * {@link Pattern}.
- * {@link Pattern#matches(String, CharSequence) matches}
- * (regex, str)
- *
- * @param pString the string
- * @param pRegex the regular expression to which this string is to be matched
- * @return {@code true} if, and only if, this string matches the
- * given regular expression
- * @throws PatternSyntaxException if the regular expression's syntax is invalid
- * @see Pattern
- * @see String#matches(String)
- */
- public boolean matches(String pString, String pRegex) throws PatternSyntaxException {
- return Pattern.matches(pRegex, pString);
- }
-
- /**
- * Replaces the first substring of the given string that matches the given
- * regular expression with the given pReplacement.
- *
- * An invocation of this method of the form
- * replaceFirst(str, regex, repl)
- * yields exactly the same result as the expression
- *
- *
- * {@link Pattern}.{@link Pattern#compile compile}(regex).
- * {@link Pattern#matcher matcher}(str).
- * {@link java.util.regex.Matcher#replaceFirst replaceFirst}(repl)
- *
- * @param pString the string
- * @param pRegex the regular expression to which this string is to be matched
- * @param pReplacement the replacement text
- * @return The resulting {@code String}
- * @throws PatternSyntaxException if the regular expression's syntax is invalid
- * @see Pattern
- * @see java.util.regex.Matcher#replaceFirst(String)
- */
- public String replaceFirst(String pString, String pRegex, String pReplacement) {
- return Pattern.compile(pRegex).matcher(pString).replaceFirst(pReplacement);
- }
-
- /**
- * Replaces each substring of this string that matches the given
- * regular expression with the given pReplacement.
- *
- * An invocation of this method of the form
- * replaceAll(str, pRegex, repl<)
- * yields exactly the same result as the expression
- *
- *
- * {@link Pattern}.{@link Pattern#compile compile}(pRegex).
- * {@link Pattern#matcher matcher}(str{@code ).
- * {@link java.util.regex.Matcher#replaceAll replaceAll}(}repl{@code )}
- *
- * @param pString the string
- * @param pRegex the regular expression to which this string is to be matched
- * @param pReplacement the replacement string
- * @return The resulting {@code String}
- * @throws PatternSyntaxException if the regular expression's syntax is invalid
- * @see Pattern
- * @see String#replaceAll(String,String)
- */
- public String replaceAll(String pString, String pRegex, String pReplacement) {
- return Pattern.compile(pRegex).matcher(pString).replaceAll(pReplacement);
- }
-
- /**
- * Splits this string around matches of the given regular expression.
- *
- * The array returned by this method contains each substring of this
- * string that is terminated by another substring that matches the given
- * expression or is terminated by the end of the string. The substrings in
- * the array are in the order in which they occur in this string. If the
- * expression does not match any part of the input then the resulting array
- * has just one element, namely this string.
- *
- * The {@code pLimit} parameter controls the number of times the
- * pattern is applied and therefore affects the length of the resulting
- * array. If the pLimit n is greater than zero then the pattern
- * will be applied at most n - 1 times, the array's
- * length will be no greater than n, and the array's last entry
- * will contain all input beyond the last matched delimiter. If n
- * is non-positive then the pattern will be applied as many times as
- * possible and the array can have any length. If n is zero then
- * the pattern will be applied as many times as possible, the array can
- * have any length, and trailing empty strings will be discarded.
- *
- * An invocation of this method of the form
- * split(str, regex, n)
- * yields the same result as the expression
- *
- * {@link Pattern}.
- * {@link Pattern#compile compile}(regex).
- * {@link Pattern#split(CharSequence,int) split}(str, n)
- *
- *
- * @param pString the string
- * @param pRegex the delimiting regular expression
- * @param pLimit the result threshold, as described above
- * @return the array of strings computed by splitting this string
- * around matches of the given regular expression
- * @throws PatternSyntaxException
- * if the regular expression's syntax is invalid
- * @see Pattern
- * @see String#split(String,int)
- */
- public String[] split(String pString, String pRegex, int pLimit) {
- return Pattern.compile(pRegex).split(pString, pLimit);
- }
-
- /**
- * Splits this string around matches of the given regular expression.
- *
- * This method works as if by invoking the two-argument
- * {@link #split(String,String,int) split} method with the given
- * expression and a limit argument of zero.
- * Trailing empty strings are therefore not included in the resulting array.
- *
- * @param pString the string
- * @param pRegex the delimiting regular expression
- * @return the array of strings computed by splitting this string
- * around matches of the given regular expression
- * @throws PatternSyntaxException if the regular expression's syntax is invalid
- * @see Pattern
- * @see String#split(String)
- */
- public String[] split(String pString, String pRegex) {
- return split(pString, pRegex, 0);
- }
-
- /**
- * Converts the input string
- * from camel-style (Java in-fix) naming convention
- * to Lisp-style naming convention (hyphen delimitted, all lower case).
- * Other characters in the string are left untouched.
- *
- * Eg.
- * {@code "foo" => "foo"},
- * {@code "fooBar" => "foo-bar"},
- * {@code "myURL" => "my-url"},
- * {@code "HttpRequestWrapper" => "http-request-wrapper"}
- * {@code "HttpURLConnection" => "http-url-connection"}
- * {@code "my45Caliber" => "my-45-caliber"}
- * {@code "allready-lisp" => "allready-lisp"}
- *
- * @param pString the camel-style input string
- * @return the string converted to lisp-style naming convention
- * @throws IllegalArgumentException if {@code pString == null}
- * @see #lispToCamel(String)
- */
- // TODO: RefactorMe!
- public static String camelToLisp(final String pString) {
- if (pString == null) {
- throw new IllegalArgumentException("string == null");
- }
- if (pString.length() == 0) {
- return pString;
- }
-
- StringBuilder buf = null;
- int lastPos = 0;
- boolean inCharSequence = false;
- boolean inNumberSequence = false;
-
- // NOTE: Start at index 1, as first letter should never be hyphen
- for (int i = 1; i < pString.length(); i++) {
- char current = pString.charAt(i);
- if (Character.isUpperCase(current)) {
- // Init buffer if necessary
- if (buf == null) {
- buf = new StringBuilder(pString.length() + 3);// Allow for some growth
- }
-
- if (inNumberSequence) {
- // Sequence end
- inNumberSequence = false;
-
- buf.append(pString.substring(lastPos, i));
- if (current != '-') {
- buf.append('-');
- }
- lastPos = i;
- continue;
- }
-
- // Treat multiple uppercase chars as single word
- char previous = pString.charAt(i - 1);
- if (i == lastPos || Character.isUpperCase(previous)) {
- inCharSequence = true;
- continue;
- }
-
- // Append word
- buf.append(pString.substring(lastPos, i).toLowerCase());
- if (previous != '-') {
- buf.append('-');
- }
- buf.append(Character.toLowerCase(current));
-
- lastPos = i + 1;
- }
- else if (Character.isDigit(current)) {
- // Init buffer if necessary
- if (buf == null) {
- buf = new StringBuilder(pString.length() + 3);// Allow for some growth
- }
-
- if (inCharSequence) {
- // Sequence end
- inCharSequence = false;
-
- buf.append(pString.substring(lastPos, i).toLowerCase());
- if (current != '-') {
- buf.append('-');
- }
- lastPos = i;
- continue;
- }
-
- // Treat multiple digits as single word
- char previous = pString.charAt(i - 1);
- if (i == lastPos || Character.isDigit(previous)) {
- inNumberSequence = true;
- continue;
- }
-
- // Append word
- buf.append(pString.substring(lastPos, i).toLowerCase());
- if (previous != '-') {
- buf.append('-');
- }
- buf.append(Character.toLowerCase(current));
-
- lastPos = i + 1;
- }
- else if (inNumberSequence) {
- // Sequence end
- inNumberSequence = false;
-
- buf.append(pString.substring(lastPos, i));
- if (current != '-') {
- buf.append('-');
- }
- lastPos = i;
- }
- else if (inCharSequence) {
- // Sequence end
- inCharSequence = false;
-
- // NOTE: Special treatment! Last upper case, is first char in
- // next word, not last char in this word
- buf.append(pString.substring(lastPos, i - 1).toLowerCase());
- if (current != '-') {
- buf.append('-');
- }
- lastPos = i - 1;
- }
- }
-
- if (buf != null) {
- // Append the rest
- buf.append(pString.substring(lastPos).toLowerCase());
- return buf.toString();
- }
- else {
- return Character.isUpperCase(pString.charAt(0)) ? pString.toLowerCase() : pString;
- }
- }
-
- /**
- * Converts the input string
- * from Lisp-style naming convention (hyphen delimitted, all lower case)
- * to camel-style (Java in-fix) naming convention.
- * Other characters in the string are left untouched.
- *
- * Eg.
- * {@code "foo" => "foo"},
- * {@code "foo-bar" => "fooBar"},
- * {@code "http-request-wrapper" => "httpRequestWrapper"}
- * {@code "my-45-caliber" => "my45Caliber"}
- * {@code "allreadyCamel" => "allreadyCamel"}
- *
- *
- * @param pString the lisp-style input string
- * @return the string converted to camel-style
- * @throws IllegalArgumentException if {@code pString == null}
- * @see #lispToCamel(String,boolean)
- * @see #camelToLisp(String)
- */
- public static String lispToCamel(final String pString) {
- return lispToCamel(pString, false);
- }
-
- /**
- * Converts the input string
- * from Lisp-style naming convention (hyphen delimitted, all lower case)
- * to camel-style (Java in-fix) naming convention.
- * Other characters in the string are left untouched.
- *
- * To create a string starting with a lower case letter
- * (like Java variable names, etc),
- * specify the {@code pFirstUpperCase} paramter to be {@code false}.
- * Eg.
- * {@code "foo" => "foo"},
- * {@code "foo-bar" => "fooBar"},
- * {@code "allreadyCamel" => "allreadyCamel"}
- *
- * To create a string starting with an upper case letter
- * (like Java class name, etc),
- * specify the {@code pFirstUpperCase} paramter to be {@code true}.
- * Eg.
- * {@code "http-request-wrapper" => "HttpRequestWrapper"}
- * {@code "my-45-caliber" => "My45Caliber"}
- *
- *
- * @param pString the lisp-style input string
- * @param pFirstUpperCase {@code true} if the first char should be
- * upper case
- * @return the string converted to camel-style
- * @throws IllegalArgumentException if {@code pString == null}
- * @see #camelToLisp(String)
- */
- public static String lispToCamel(final String pString, final boolean pFirstUpperCase) {
- if (pString == null) {
- throw new IllegalArgumentException("string == null");
- }
- if (pString.length() == 0) {
- return pString;
- }
-
- StringBuilder buf = null;
- int lastPos = 0;
-
- for (int i = 0; i < pString.length(); i++) {
- char current = pString.charAt(i);
- if (current == '-') {
-
- // Init buffer if necessary
- if (buf == null) {
- buf = new StringBuilder(pString.length() - 1);// Can't be larger
- }
-
- // Append with upper case
- if (lastPos != 0 || pFirstUpperCase) {
- buf.append(Character.toUpperCase(pString.charAt(lastPos)));
- lastPos++;
- }
-
- buf.append(pString.substring(lastPos, i).toLowerCase());
- lastPos = i + 1;
- }
- }
-
- if (buf != null) {
- buf.append(Character.toUpperCase(pString.charAt(lastPos)));
- buf.append(pString.substring(lastPos + 1).toLowerCase());
- return buf.toString();
- }
- else {
- if (pFirstUpperCase && !Character.isUpperCase(pString.charAt(0))) {
- return capitalize(pString, 0);
- }
- else
- if (!pFirstUpperCase && Character.isUpperCase(pString.charAt(0))) {
- return Character.toLowerCase(pString.charAt(0)) + pString.substring(1);
- }
-
- return pString;
- }
- }
-
- public static String reverse(final String pString) {
- final char[] chars = new char[pString.length()];
- pString.getChars(0, chars.length, chars, 0);
-
- for (int i = 0; i < chars.length / 2; i++) {
- char temp = chars[i];
- chars[i] = chars[chars.length - 1 - i];
- chars[chars.length - 1 - i] = temp;
- }
-
- return new String(chars);
- }
+/*
+ * 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 of the copyright holder 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 HOLDER 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.lang;
+
+import com.twelvemonkeys.util.StringTokenIterator;
+
+import java.awt.*;
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.charset.UnsupportedCharsetException;
+import java.sql.Timestamp;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * A utility class with some useful string manipulation methods.
+ *
+ * @author Harald Kuhr
+ * @author Eirik Torske
+ * @author last modified by $Author: haku $
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/StringUtil.java#2 $
+ * return values, null-value handling and parameter names (cosmetics).
+ */
+// TODO: Consistency check: Method names, parameter sequence, Exceptions,
+public final class StringUtil {
+
+ /**
+ * The default delimiter string, used by the {@code toXXXArray()}
+ * methods.
+ * Its value is {@code ", \t\n\r\f"}.
+ *
+ *
+ * @see #toStringArray(String)
+ * @see #toIntArray(String)
+ * @see #toLongArray(String)
+ * @see #toDoubleArray(String)
+ */
+ public final static String DELIMITER_STRING = ", \t\n\r\f";
+
+ // Avoid constructor showing up in API doc
+ private StringUtil() {
+ }
+
+ /**
+ * Constructs a new {@link String} by decoding the specified sub array of bytes using the specified charset.
+ * Replacement for {@link String#String(byte[], int, int, String) new String(byte[], int, int, String)}, that does
+ * not throw the checked {@link UnsupportedEncodingException},
+ * but instead the unchecked {@link UnsupportedCharsetException} if the character set is not supported.
+ *
+ * @param pData the bytes to be decoded to characters
+ * @param pOffset the index of the first byte to decode
+ * @param pLength the number of bytes to decode
+ * @param pCharset the name of a supported character set
+ * @return a newly created string.
+ * @throws UnsupportedCharsetException
+ *
+ * @see String#String(byte[], int, int, String)
+ */
+ public static String decode(final byte[] pData, final int pOffset, final int pLength, final String pCharset) {
+ try {
+ return new String(pData, pOffset, pLength, pCharset);
+ }
+ catch (UnsupportedEncodingException e) {
+ throw new UnsupportedCharsetException(pCharset);
+ }
+ }
+
+ /**
+ * Returns the value of the given {@code Object}, as a {@code String}.
+ * Unlike String.valueOf, this method returns {@code null}
+ * instead of the {@code String} "null", if {@code null} is given as
+ * the argument.
+ *
+ * @param pObj the Object to find the {@code String} value of.
+ * @return the String value of the given object, or {@code null} if the
+ * {@code pObj} == {@code null}.
+ * @see String#valueOf(Object)
+ * @see String#toString()
+ */
+ public static String valueOf(Object pObj) {
+ return ((pObj != null) ? pObj.toString() : null);
+ }
+
+ /**
+ * Converts a string to uppercase.
+ *
+ * @param pString the string to convert
+ * @return the string converted to uppercase, or null if the argument was
+ * null.
+ */
+ public static String toUpperCase(String pString) {
+ if (pString != null) {
+ return pString.toUpperCase();
+ }
+ return null;
+ }
+
+ /**
+ * Converts a string to lowercase.
+ *
+ * @param pString the string to convert
+ * @return the string converted to lowercase, or null if the argument was
+ * null.
+ */
+ public static String toLowerCase(String pString) {
+ if (pString != null) {
+ return pString.toLowerCase();
+ }
+ return null;
+ }
+
+ /**
+ * Tests if a String is null, or contains nothing but white-space.
+ *
+ * @param pString The string to test
+ * @return true if the string is null or contains only whitespace,
+ * otherwise false.
+ */
+ public static boolean isEmpty(String pString) {
+ return ((pString == null) || (pString.trim().length() == 0));
+ }
+
+ /**
+ * Tests a string array, to see if all items are null or an empty string.
+ *
+ * @param pStringArray The string array to check.
+ * @return true if the string array is null or only contains string items
+ * that are null or contain only whitespace, otherwise false.
+ */
+ public static boolean isEmpty(String[] pStringArray) {
+ // No elements to test
+ if (pStringArray == null) {
+ return true;
+ }
+
+ // Test all the elements
+ for (String string : pStringArray) {
+ if (!isEmpty(string)) {
+ return false;
+ }
+ }
+
+ // All elements are empty
+ return true;
+ }
+
+ /**
+ * Tests if a string contains another string.
+ *
+ * @param pContainer The string to test
+ * @param pLookFor The string to look for
+ * @return {@code true} if the container string is contains the string, and
+ * both parameters are non-{@code null}, otherwise {@code false}.
+ */
+ public static boolean contains(String pContainer, String pLookFor) {
+ return ((pContainer != null) && (pLookFor != null) && (pContainer.indexOf(pLookFor) >= 0));
+ }
+
+ /**
+ * Tests if a string contains another string, ignoring case.
+ *
+ * @param pContainer The string to test
+ * @param pLookFor The string to look for
+ * @return {@code true} if the container string is contains the string, and
+ * both parameters are non-{@code null}, otherwise {@code false}.
+ * @see #contains(String,String)
+ */
+ public static boolean containsIgnoreCase(String pContainer, String pLookFor) {
+ return indexOfIgnoreCase(pContainer, pLookFor, 0) >= 0;
+ }
+
+ /**
+ * Tests if a string contains a specific character.
+ *
+ * @param pString The string to check.
+ * @param pChar The character to search for.
+ * @return true if the string contains the specific character.
+ */
+ public static boolean contains(final String pString, final int pChar) {
+ return ((pString != null) && (pString.indexOf(pChar) >= 0));
+ }
+
+ /**
+ * Tests if a string contains a specific character, ignoring case.
+ *
+ * @param pString The string to check.
+ * @param pChar The character to search for.
+ * @return true if the string contains the specific character.
+ */
+ public static boolean containsIgnoreCase(String pString, int pChar) {
+ return ((pString != null)
+ && ((pString.indexOf(Character.toLowerCase((char) pChar)) >= 0)
+ || (pString.indexOf(Character.toUpperCase((char) pChar)) >= 0)));
+
+ // NOTE: I don't convert the string to uppercase, but instead test
+ // the string (potentially) two times, as this is more efficient for
+ // long strings (in most cases).
+ }
+
+ /**
+ * Returns the index within this string of the first occurrence of the
+ * specified substring.
+ *
+ * @param pString The string to test
+ * @param pLookFor The string to look for
+ * @return if the string argument occurs as a substring within this object,
+ * then the index of the first character of the first such substring is
+ * returned; if it does not occur as a substring, -1 is returned.
+ * @see String#indexOf(String)
+ */
+ public static int indexOfIgnoreCase(String pString, String pLookFor) {
+ return indexOfIgnoreCase(pString, pLookFor, 0);
+ }
+
+ /**
+ * Returns the index within this string of the first occurrence of the
+ * specified substring, starting at the specified index.
+ *
+ * @param pString The string to test
+ * @param pLookFor The string to look for
+ * @param pPos The first index to test
+ * @return if the string argument occurs as a substring within this object,
+ * then the index of the first character of the first such substring is
+ * returned; if it does not occur as a substring, -1 is returned.
+ * @see String#indexOf(String,int)
+ */
+ public static int indexOfIgnoreCase(String pString, String pLookFor, int pPos) {
+ if ((pString == null) || (pLookFor == null)) {
+ return -1;
+ }
+ if (pLookFor.length() == 0) {
+ return pPos;// All strings "contains" the empty string
+ }
+ if (pLookFor.length() > pString.length()) {
+ return -1;// Cannot contain string longer than itself
+ }
+
+ // Get first char
+ char firstL = Character.toLowerCase(pLookFor.charAt(0));
+ char firstU = Character.toUpperCase(pLookFor.charAt(0));
+ int indexLower = 0;
+ int indexUpper = 0;
+
+ for (int i = pPos; i <= (pString.length() - pLookFor.length()); i++) {
+
+ // Peek for first char
+ indexLower = ((indexLower >= 0) && (indexLower <= i))
+ ? pString.indexOf(firstL, i)
+ : indexLower;
+ indexUpper = ((indexUpper >= 0) && (indexUpper <= i))
+ ? pString.indexOf(firstU, i)
+ : indexUpper;
+ if (indexLower < 0) {
+ if (indexUpper < 0) {
+ return -1;// First char not found
+ }
+ else {
+ i = indexUpper;// Only upper
+ }
+ }
+ else if (indexUpper < 0) {
+ i = indexLower;// Only lower
+ }
+ else {
+
+ // Both found, select first occurence
+ i = (indexLower < indexUpper)
+ ? indexLower
+ : indexUpper;
+ }
+
+ // Only one?
+ if (pLookFor.length() == 1) {
+ return i;// The only char found!
+ }
+
+ // Test if we still have enough chars
+ else if (i > (pString.length() - pLookFor.length())) {
+ return -1;
+ }
+
+ // Test if last char equals! (regionMatches is expensive)
+ else if ((pString.charAt(i + pLookFor.length() - 1) != Character.toLowerCase(pLookFor.charAt(pLookFor.length() - 1)))
+ && (pString.charAt(i + pLookFor.length() - 1) != Character.toUpperCase(pLookFor.charAt(pLookFor.length() - 1)))) {
+ continue;// Nope, try next
+ }
+
+ // Test from second char, until second-last char
+ else if ((pLookFor.length() <= 2) || pString.regionMatches(true, i + 1, pLookFor, 1, pLookFor.length() - 2)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index within this string of the rightmost occurrence of the
+ * specified substring. The rightmost empty string "" is considered to
+ * occur at the index value {@code pString.length() - 1}.
+ *
+ * @param pString The string to test
+ * @param pLookFor The string to look for
+ * @return If the string argument occurs one or more times as a substring
+ * within this object at a starting index no greater than fromIndex, then
+ * the index of the first character of the last such substring is returned.
+ * If it does not occur as a substring starting at fromIndex or earlier, -1
+ * is returned.
+ * @see String#lastIndexOf(String)
+ */
+ public static int lastIndexOfIgnoreCase(String pString, String pLookFor) {
+ return lastIndexOfIgnoreCase(pString, pLookFor, pString != null ? pString.length() - 1 : -1);
+ }
+
+ /**
+ * Returns the index within this string of the rightmost occurrence of the
+ * specified substring. The rightmost empty string "" is considered to
+ * occur at the index value {@code pPos}
+ *
+ * @param pString The string to test
+ * @param pLookFor The string to look for
+ * @param pPos The last index to test
+ * @return If the string argument occurs one or more times as a substring
+ * within this object at a starting index no greater than fromIndex, then
+ * the index of the first character of the last such substring is returned.
+ * If it does not occur as a substring starting at fromIndex or earlier, -1
+ * is returned.
+ * @see String#lastIndexOf(String,int)
+ */
+ public static int lastIndexOfIgnoreCase(String pString, String pLookFor, int pPos) {
+ if ((pString == null) || (pLookFor == null)) {
+ return -1;
+ }
+ if (pLookFor.length() == 0) {
+ return pPos;// All strings "contains" the empty string
+ }
+ if (pLookFor.length() > pString.length()) {
+ return -1;// Cannot contain string longer than itself
+ }
+
+ // Get first char
+ char firstL = Character.toLowerCase(pLookFor.charAt(0));
+ char firstU = Character.toUpperCase(pLookFor.charAt(0));
+ int indexLower = pPos;
+ int indexUpper = pPos;
+
+ for (int i = pPos; i >= 0; i--) {
+
+ // Peek for first char
+ indexLower = ((indexLower >= 0) && (indexLower >= i))
+ ? pString.lastIndexOf(firstL, i)
+ : indexLower;
+ indexUpper = ((indexUpper >= 0) && (indexUpper >= i))
+ ? pString.lastIndexOf(firstU, i)
+ : indexUpper;
+ if (indexLower < 0) {
+ if (indexUpper < 0) {
+ return -1;// First char not found
+ }
+ else {
+ i = indexUpper;// Only upper
+ }
+ }
+ else if (indexUpper < 0) {
+ i = indexLower;// Only lower
+ }
+ else {
+
+ // Both found, select last occurence
+ i = (indexLower > indexUpper)
+ ? indexLower
+ : indexUpper;
+ }
+
+ // Only one?
+ if (pLookFor.length() == 1) {
+ return i;// The only char found!
+ }
+
+ // Test if we still have enough chars
+ else if (i > (pString.length() - pLookFor.length())) {
+ //return -1;
+ continue;
+ }
+
+ // Test if last char equals! (regionMatches is expensive)
+ else
+ if ((pString.charAt(i + pLookFor.length() - 1) != Character.toLowerCase(pLookFor.charAt(pLookFor.length() - 1)))
+ && (pString.charAt(i + pLookFor.length() - 1) != Character.toUpperCase(pLookFor.charAt(pLookFor.length() - 1)))) {
+ continue;// Nope, try next
+ }
+
+ // Test from second char, until second-last char
+ else
+ if ((pLookFor.length() <= 2) || pString.regionMatches(true, i + 1, pLookFor, 1, pLookFor.length() - 2)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index within this string of the first occurrence of the
+ * specified character.
+ *
+ * @param pString The string to test
+ * @param pChar The character to look for
+ * @return if the string argument occurs as a substring within this object,
+ * then the index of the first character of the first such substring is
+ * returned; if it does not occur as a substring, -1 is returned.
+ * @see String#indexOf(int)
+ */
+ public static int indexOfIgnoreCase(String pString, int pChar) {
+ return indexOfIgnoreCase(pString, pChar, 0);
+ }
+
+ /**
+ * Returns the index within this string of the first occurrence of the
+ * specified character, starting at the specified index.
+ *
+ * @param pString The string to test
+ * @param pChar The character to look for
+ * @param pPos The first index to test
+ * @return if the string argument occurs as a substring within this object,
+ * then the index of the first character of the first such substring is
+ * returned; if it does not occur as a substring, -1 is returned.
+ * @see String#indexOf(int,int)
+ */
+ public static int indexOfIgnoreCase(String pString, int pChar, int pPos) {
+ if ((pString == null)) {
+ return -1;
+ }
+
+ // Get first char
+ char lower = Character.toLowerCase((char) pChar);
+ char upper = Character.toUpperCase((char) pChar);
+ int indexLower;
+ int indexUpper;
+
+ // Test for char
+ indexLower = pString.indexOf(lower, pPos);
+ indexUpper = pString.indexOf(upper, pPos);
+ if (indexLower < 0) {
+
+ /* if (indexUpper < 0)
+ return -1; // First char not found
+ else */
+ return indexUpper;// Only upper
+ }
+ else if (indexUpper < 0) {
+ return indexLower;// Only lower
+ }
+ else {
+
+ // Both found, select first occurence
+ return (indexLower < indexUpper)
+ ? indexLower
+ : indexUpper;
+ }
+ }
+
+ /**
+ * Returns the index within this string of the last occurrence of the
+ * specified character.
+ *
+ * @param pString The string to test
+ * @param pChar The character to look for
+ * @return if the string argument occurs as a substring within this object,
+ * then the index of the first character of the first such substring is
+ * returned; if it does not occur as a substring, -1 is returned.
+ * @see String#lastIndexOf(int)
+ */
+ public static int lastIndexOfIgnoreCase(String pString, int pChar) {
+ return lastIndexOfIgnoreCase(pString, pChar, pString != null ? pString.length() : -1);
+ }
+
+ /**
+ * Returns the index within this string of the last occurrence of the
+ * specified character, searching backward starting at the specified index.
+ *
+ * @param pString The string to test
+ * @param pChar The character to look for
+ * @param pPos The last index to test
+ * @return if the string argument occurs as a substring within this object,
+ * then the index of the first character of the first such substring is
+ * returned; if it does not occur as a substring, -1 is returned.
+ * @see String#lastIndexOf(int,int)
+ */
+ public static int lastIndexOfIgnoreCase(String pString, int pChar, int pPos) {
+ if ((pString == null)) {
+ return -1;
+ }
+
+ // Get first char
+ char lower = Character.toLowerCase((char) pChar);
+ char upper = Character.toUpperCase((char) pChar);
+ int indexLower;
+ int indexUpper;
+
+ // Test for char
+ indexLower = pString.lastIndexOf(lower, pPos);
+ indexUpper = pString.lastIndexOf(upper, pPos);
+ if (indexLower < 0) {
+
+ /* if (indexUpper < 0)
+ return -1; // First char not found
+ else */
+ return indexUpper;// Only upper
+ }
+ else if (indexUpper < 0) {
+ return indexLower;// Only lower
+ }
+ else {
+
+ // Both found, select last occurence
+ return (indexLower > indexUpper)
+ ? indexLower
+ : indexUpper;
+ }
+ }
+
+ /**
+ * Trims the argument string for whitespace on the left side only.
+ *
+ * @param pString the string to trim
+ * @return the string with no whitespace on the left, or {@code null} if
+ * the string argument is {@code null}.
+ * @see #rtrim
+ * @see String#trim()
+ */
+ public static String ltrim(String pString) {
+ if ((pString == null) || (pString.length() == 0)) {
+ return pString;// Null or empty string
+ }
+ for (int i = 0; i < pString.length(); i++) {
+ if (!Character.isWhitespace(pString.charAt(i))) {
+ if (i == 0) {
+ return pString;// First char is not whitespace
+ }
+ else {
+ return pString.substring(i);// Return rest after whitespace
+ }
+ }
+ }
+
+ // If all whitespace, return empty string
+ return "";
+ }
+
+ /**
+ * Trims the argument string for whitespace on the right side only.
+ *
+ * @param pString the string to trim
+ * @return the string with no whitespace on the right, or {@code null} if
+ * the string argument is {@code null}.
+ * @see #ltrim
+ * @see String#trim()
+ */
+ public static String rtrim(String pString) {
+ if ((pString == null) || (pString.length() == 0)) {
+ return pString;// Null or empty string
+ }
+ for (int i = pString.length(); i > 0; i--) {
+ if (!Character.isWhitespace(pString.charAt(i - 1))) {
+ if (i == pString.length()) {
+ return pString;// First char is not whitespace
+ }
+ else {
+ return pString.substring(0, i);// Return before whitespace
+ }
+ }
+ }
+
+ // If all whitespace, return empty string
+ return "";
+ }
+
+ /**
+ * Replaces a substring of a string with another string. All matches are
+ * replaced.
+ *
+ * @param pSource The source String
+ * @param pPattern The pattern to replace
+ * @param pReplace The new String to be inserted instead of the
+ * replace String
+ * @return The new String with the pattern replaced
+ */
+ public static String replace(String pSource, String pPattern, String pReplace) {
+ if (pPattern.length() == 0) {
+ return pSource;// Special case: No pattern to replace
+ }
+
+ int match;
+ int offset = 0;
+ StringBuilder result = new StringBuilder();
+
+ // Loop string, until last occurence of pattern, and replace
+ while ((match = pSource.indexOf(pPattern, offset)) != -1) {
+ // Append everything until pattern
+ result.append(pSource.substring(offset, match));
+ // Append the replace string
+ result.append(pReplace);
+ offset = match + pPattern.length();
+ }
+
+ // Append rest of string and return
+ result.append(pSource.substring(offset));
+
+ return result.toString();
+ }
+
+ /**
+ * Replaces a substring of a string with another string, ignoring case.
+ * All matches are replaced.
+ *
+ * @param pSource The source String
+ * @param pPattern The pattern to replace
+ * @param pReplace The new String to be inserted instead of the
+ * replace String
+ * @return The new String with the pattern replaced
+ * @see #replace(String,String,String)
+ */
+ public static String replaceIgnoreCase(String pSource, String pPattern, String pReplace) {
+ if (pPattern.length() == 0) {
+ return pSource;// Special case: No pattern to replace
+ }
+ int match;
+ int offset = 0;
+ StringBuilder result = new StringBuilder();
+
+ while ((match = indexOfIgnoreCase(pSource, pPattern, offset)) != -1) {
+ result.append(pSource.substring(offset, match));
+ result.append(pReplace);
+ offset = match + pPattern.length();
+ }
+ result.append(pSource.substring(offset));
+ return result.toString();
+ }
+
+ /**
+ * Cuts a string between two words, before a sepcified length, if the
+ * string is longer than the maxium lenght. The string is optionally padded
+ * with the pad argument. The method assumes words to be separated by the
+ * space character (" ").
+ * Note that the maximum length argument is absolute, and will also include
+ * the length of the padding.
+ *
+ * @param pString The string to cut
+ * @param pMaxLen The maximum length before cutting
+ * @param pPad The string to append at the end, aftrer cutting
+ * @return The cutted string with padding, or the original string, if it
+ * was shorter than the max length.
+ * @see #pad(String,int,String,boolean)
+ */
+ public static String cut(String pString, int pMaxLen, String pPad) {
+ if (pString == null) {
+ return null;
+ }
+ if (pPad == null) {
+ pPad = "";
+ }
+ int len = pString.length();
+
+ if (len > pMaxLen) {
+ len = pString.lastIndexOf(' ', pMaxLen - pPad.length());
+ }
+ else {
+ return pString;
+ }
+ return pString.substring(0, len) + pPad;
+ }
+
+ /**
+ * Makes the Nth letter of a String uppercase. If the index is outside the
+ * the length of the argument string, the argument is simply returned.
+ *
+ * @param pString The string to capitalize
+ * @param pIndex The base-0 index of the char to capitalize.
+ * @return The capitalized string, or null, if a null argument was given.
+ */
+ public static String capitalize(String pString, int pIndex) {
+ if (pIndex < 0) {
+ throw new IndexOutOfBoundsException("Negative index not allowed: " + pIndex);
+ }
+ if (pString == null || pString.length() <= pIndex) {
+ return pString;
+ }
+
+ // This is the fastest method, according to my tests
+
+ // Skip array duplication if allready capitalized
+ if (Character.isUpperCase(pString.charAt(pIndex))) {
+ return pString;
+ }
+
+ // Convert to char array, capitalize and create new String
+ char[] charArray = pString.toCharArray();
+ charArray[pIndex] = Character.toUpperCase(charArray[pIndex]);
+ return new String(charArray);
+
+ /**
+ StringBuilder buf = new StringBuilder(pString);
+ buf.setCharAt(pIndex, Character.toUpperCase(buf.charAt(pIndex)));
+ return buf.toString();
+ //*/
+
+ /**
+ return pString.substring(0, pIndex)
+ + Character.toUpperCase(pString.charAt(pIndex))
+ + pString.substring(pIndex + 1);
+ //*/
+ }
+
+ /**
+ * Makes the first letter of a String uppercase.
+ *
+ * @param pString The string to capitalize
+ * @return The capitalized string, or null, if a null argument was given.
+ */
+ public static String capitalize(String pString) {
+ return capitalize(pString, 0);
+ }
+
+ /**
+ * Formats a number with leading zeroes, to a specified length.
+ *
+ * @param pNum The number to format
+ * @param pLen The number of digits
+ * @return A string containing the formatted number
+ * @throws IllegalArgumentException Thrown, if the number contains
+ * more digits than allowed by the length argument.
+ * @see #pad(String,int,String,boolean)
+ * @deprecated Use StringUtil.pad instead!
+ */
+
+ /*public*/
+ static String formatNumber(long pNum, int pLen) throws IllegalArgumentException {
+ StringBuilder result = new StringBuilder();
+
+ if (pNum >= Math.pow(10, pLen)) {
+ throw new IllegalArgumentException("The number to format cannot contain more digits than the length argument specifies!");
+ }
+ for (int i = pLen; i > 1; i--) {
+ if (pNum < Math.pow(10, i - 1)) {
+ result.append('0');
+ }
+ else {
+ break;
+ }
+ }
+ result.append(pNum);
+ return result.toString();
+ }
+
+ /**
+ * String length check with simple concatenation of selected pad-string.
+ * E.g. a zip number from 123 to the correct 0123.
+ *
+ * @param pSource The source string.
+ * @param pRequiredLength The accurate length of the resulting string.
+ * @param pPadString The string for concatenation.
+ * @param pPrepend The location of fill-ins, prepend (true),
+ * or append (false)
+ * @return a concatenated string.
+ * @see #cut(String,int,String)
+ */
+ // TODO: What if source is allready longer than required length?
+ // TODO: Consistency with cut
+ public static String pad(String pSource, int pRequiredLength, String pPadString, boolean pPrepend) {
+ if (pPadString == null || pPadString.length() == 0) {
+ throw new IllegalArgumentException("Pad string: \"" + pPadString + "\"");
+ }
+
+ if (pSource.length() >= pRequiredLength) {
+ return pSource;
+ }
+
+ // TODO: Benchmark the new version against the old one, to see if it's really faster
+ // Rewrite to first create pad
+ // - pad += pad; - until length is >= gap
+ // then append the pad and cut if too long
+ int gap = pRequiredLength - pSource.length();
+ StringBuilder result = new StringBuilder(pPadString);
+ while (result.length() < gap) {
+ result.append(result);
+ }
+
+ if (result.length() > gap) {
+ result.delete(gap, result.length());
+ }
+
+ return pPrepend ? result.append(pSource).toString() : result.insert(0, pSource).toString();
+
+ /*
+ StringBuilder result = new StringBuilder(pSource);
+
+ // Concatenation until proper string length
+ while (result.length() < pRequiredLength) {
+ // Prepend or append
+ if (pPrepend) { // Front
+ result.insert(0, pPadString);
+ }
+ else { // Back
+ result.append(pPadString);
+ }
+ }
+
+ // Truncate
+ if (result.length() > pRequiredLength) {
+ if (pPrepend) {
+ result.delete(0, result.length() - pRequiredLength);
+ }
+ else {
+ result.delete(pRequiredLength, result.length());
+ }
+ }
+ return result.toString();
+ */
+ }
+
+ /**
+ * Converts the string to a date, using the default date format.
+ *
+ * @param pString the string to convert
+ * @return the date
+ * @see DateFormat
+ * @see DateFormat#getInstance()
+ */
+ public static Date toDate(String pString) {
+ // Default
+ return toDate(pString, DateFormat.getInstance());
+ }
+
+ /**
+ * Converts the string to a date, using the given format.
+ *
+ * @param pString the string to convert
+ * @param pFormat the date format
+ * @return the date
+ *
+ * @see java.text.SimpleDateFormat
+ * @see java.text.SimpleDateFormat#SimpleDateFormat(String)
+ */
+ // TODO: cache formats?
+ public static Date toDate(String pString, String pFormat) {
+ // Get the format from cache, or create new and insert
+ // Return new date
+ return toDate(pString, new SimpleDateFormat(pFormat));
+ }
+
+ /**
+ * Converts the string to a date, using the given format.
+ *
+ * @param pString the string to convert
+ * @param pFormat the date format
+ * @return the date
+ * @see SimpleDateFormat
+ * @see SimpleDateFormat#SimpleDateFormat(String)
+ * @see DateFormat
+ */
+ public static Date toDate(final String pString, final DateFormat pFormat) {
+ try {
+ synchronized (pFormat) {
+ // Parse date using given format
+ return pFormat.parse(pString);
+ }
+ }
+ catch (ParseException pe) {
+ // Wrap in RuntimeException
+ throw new IllegalArgumentException(pe.getMessage());
+ }
+ }
+
+ /**
+ * Converts the string to a jdbc Timestamp, using the standard Timestamp
+ * escape format.
+ *
+ * @param pValue the value
+ * @return a new {@code Timestamp}
+ * @see java.sql.Timestamp
+ * @see java.sql.Timestamp#valueOf(String)
+ */
+ public static Timestamp toTimestamp(final String pValue) {
+ // Parse date using default format
+ return Timestamp.valueOf(pValue);
+ }
+
+ /**
+ * Converts a delimiter separated String to an array of Strings.
+ *
+ * @param pString The comma-separated string
+ * @param pDelimiters The delimiter string
+ * @return a {@code String} array containing the delimiter separated elements
+ */
+ public static String[] toStringArray(String pString, String pDelimiters) {
+ if (isEmpty(pString)) {
+ return new String[0];
+ }
+
+ StringTokenIterator st = new StringTokenIterator(pString, pDelimiters);
+ List v = new ArrayList();
+
+ while (st.hasMoreElements()) {
+ v.add(st.nextToken());
+ }
+
+ return v.toArray(new String[v.size()]);
+ }
+
+ /**
+ * Converts a comma-separated String to an array of Strings.
+ *
+ * @param pString The comma-separated string
+ * @return a {@code String} array containing the comma-separated elements
+ * @see #toStringArray(String,String)
+ */
+ public static String[] toStringArray(String pString) {
+ return toStringArray(pString, DELIMITER_STRING);
+ }
+
+ /**
+ * Converts a comma-separated String to an array of ints.
+ *
+ * @param pString The comma-separated string
+ * @param pDelimiters The delimiter string
+ * @param pBase The radix
+ * @return an {@code int} array
+ * @throws NumberFormatException if any of the elements are not parseable
+ * as an int
+ */
+ public static int[] toIntArray(String pString, String pDelimiters, int pBase) {
+ if (isEmpty(pString)) {
+ return new int[0];
+ }
+
+ // Some room for improvement here...
+ String[] temp = toStringArray(pString, pDelimiters);
+ int[] array = new int[temp.length];
+
+ for (int i = 0; i < array.length; i++) {
+ array[i] = Integer.parseInt(temp[i], pBase);
+ }
+ return array;
+ }
+
+ /**
+ * Converts a comma-separated String to an array of ints.
+ *
+ * @param pString The comma-separated string
+ * @return an {@code int} array
+ * @throws NumberFormatException if any of the elements are not parseable
+ * as an int
+ * @see #toStringArray(String,String)
+ * @see #DELIMITER_STRING
+ */
+ public static int[] toIntArray(String pString) {
+ return toIntArray(pString, DELIMITER_STRING, 10);
+ }
+
+ /**
+ * Converts a comma-separated String to an array of ints.
+ *
+ * @param pString The comma-separated string
+ * @param pDelimiters The delimiter string
+ * @return an {@code int} array
+ * @throws NumberFormatException if any of the elements are not parseable
+ * as an int
+ * @see #toIntArray(String,String)
+ */
+ public static int[] toIntArray(String pString, String pDelimiters) {
+ return toIntArray(pString, pDelimiters, 10);
+ }
+
+ /**
+ * Converts a comma-separated String to an array of longs.
+ *
+ * @param pString The comma-separated string
+ * @param pDelimiters The delimiter string
+ * @return a {@code long} array
+ * @throws NumberFormatException if any of the elements are not parseable
+ * as a long
+ */
+ public static long[] toLongArray(String pString, String pDelimiters) {
+ if (isEmpty(pString)) {
+ return new long[0];
+ }
+
+ // Some room for improvement here...
+ String[] temp = toStringArray(pString, pDelimiters);
+ long[] array = new long[temp.length];
+
+ for (int i = 0; i < array.length; i++) {
+ array[i] = Long.parseLong(temp[i]);
+ }
+ return array;
+ }
+
+ /**
+ * Converts a comma-separated String to an array of longs.
+ *
+ * @param pString The comma-separated string
+ * @return a {@code long} array
+ * @throws NumberFormatException if any of the elements are not parseable
+ * as a long
+ * @see #toStringArray(String,String)
+ * @see #DELIMITER_STRING
+ */
+ public static long[] toLongArray(String pString) {
+ return toLongArray(pString, DELIMITER_STRING);
+ }
+
+ /**
+ * Converts a comma-separated String to an array of doubles.
+ *
+ * @param pString The comma-separated string
+ * @param pDelimiters The delimiter string
+ * @return a {@code double} array
+ * @throws NumberFormatException if any of the elements are not parseable
+ * as a double
+ */
+ public static double[] toDoubleArray(String pString, String pDelimiters) {
+ if (isEmpty(pString)) {
+ return new double[0];
+ }
+
+ // Some room for improvement here...
+ String[] temp = toStringArray(pString, pDelimiters);
+ double[] array = new double[temp.length];
+
+ for (int i = 0; i < array.length; i++) {
+ array[i] = Double.valueOf(temp[i]);
+
+ // Double.parseDouble() is 1.2...
+ }
+ return array;
+ }
+
+ /**
+ * Converts a comma-separated String to an array of doubles.
+ *
+ * @param pString The comma-separated string
+ * @return a {@code double} array
+ * @throws NumberFormatException if any of the elements are not parseable
+ * as a double
+ * @see #toDoubleArray(String,String)
+ * @see #DELIMITER_STRING
+ */
+ public static double[] toDoubleArray(String pString) {
+ return toDoubleArray(pString, DELIMITER_STRING);
+ }
+
+ /**
+ * Parses a string to a Color.
+ * The argument can be a color constant (static constant from
+ * {@link java.awt.Color java.awt.Color}), like {@code black} or
+ * {@code red}, or it can be HTML/CSS-style, on the format:
+ *
+ * - {@code #RRGGBB}, where RR, GG and BB means two digit
+ * hexadecimal for red, green and blue values respectively.
+ * - {@code #AARRGGBB}, as above, with AA as alpha component.
+ * - {@code #RGB}, where R, G and B means one digit
+ * hexadecimal for red, green and blue values respectively.
+ * - {@code #ARGB}, as above, with A as alpha component.
+ *
+ *
+ * @param pString the string representation of the color
+ * @return the {@code Color} object, or {@code null} if the argument
+ * is {@code null}
+ * @throws IllegalArgumentException if the string does not map to a color.
+ * @see java.awt.Color
+ */
+ public static Color toColor(String pString) {
+ // No string, no color
+ if (pString == null) {
+ return null;
+ }
+
+ // #RRGGBB format
+ if (pString.charAt(0) == '#') {
+ int r = 0;
+ int g = 0;
+ int b = 0;
+ int a = -1;// alpha
+
+ if (pString.length() >= 7) {
+ int idx = 1;
+
+ // AA
+ if (pString.length() >= 9) {
+ a = Integer.parseInt(pString.substring(idx, idx + 2), 0x10);
+ idx += 2;
+ }
+ // RR GG BB
+ r = Integer.parseInt(pString.substring(idx, idx + 2), 0x10);
+ g = Integer.parseInt(pString.substring(idx + 2, idx + 4), 0x10);
+ b = Integer.parseInt(pString.substring(idx + 4, idx + 6), 0x10);
+
+ }
+ else if (pString.length() >= 4) {
+ int idx = 1;
+
+ // A
+ if (pString.length() >= 5) {
+ a = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10;
+ }
+ // R G B
+ r = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10;
+ g = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10;
+ b = Integer.parseInt(pString.substring(idx, ++idx), 0x10) * 0x10;
+ }
+ if (a != -1) {
+ // With alpha
+ return new Color(r, g, b, a);
+ }
+
+ // No alpha
+ return new Color(r, g, b);
+ }
+
+ // Get color by name
+ try {
+ Class colorClass = Color.class;
+ Field field = null;
+
+ // Workaround for stupidity in Color class constant field names
+ try {
+ field = colorClass.getField(pString);
+ }
+ catch (Exception e) {
+ // Don't care, this is just a workaround...
+ }
+ if (field == null) {
+ // NOTE: The toLowerCase() on the next line will lose darkGray
+ // and lightGray...
+ field = colorClass.getField(pString.toLowerCase());
+ }
+
+ // Only try to get public final fields
+ int mod = field.getModifiers();
+
+ if (Modifier.isPublic(mod) && Modifier.isStatic(mod)) {
+ return (Color) field.get(null);
+ }
+ }
+ catch (NoSuchFieldException nsfe) {
+ // No such color, throw illegal argument?
+ throw new IllegalArgumentException("No such color: " + pString);
+ }
+ catch (SecurityException se) {
+ // Can't access field, return null
+ }
+ catch (IllegalAccessException iae) {
+ // Can't happen, as the field must be public static
+ }
+ catch (IllegalArgumentException iar) {
+ // Can't happen, as the field must be static
+ }
+
+ // This should never be reached, but you never know... ;-)
+ return null;
+ }
+
+ /**
+ * Creates a HTML/CSS String representation of the given color.
+ * The HTML/CSS color format is defined as:
+ *
+ * - {@code #RRGGBB}, where RR, GG and BB means two digit
+ * hexadecimal for red, green and blue values respectively.
+ * - {@code #AARRGGBB}, as above, with AA as alpha component.
+ *
+ *
+ * Examlples: {@code toColorString(Color.red) == "#ff0000"},
+ * {@code toColorString(new Color(0xcc, 0xcc, 0xcc)) == "#cccccc"}.
+ *
+ *
+ * @param pColor the color
+ * @return A String representation of the color on HTML/CSS form
+ */
+ // TODO: Consider moving to ImageUtil?
+ public static String toColorString(Color pColor) {
+ // Not a color...
+ if (pColor == null) {
+ return null;
+ }
+
+ StringBuilder str = new StringBuilder(Integer.toHexString(pColor.getRGB()));
+
+ // Make sure string is 8 chars
+ for (int i = str.length(); i < 8; i++) {
+ str.insert(0, '0');
+ }
+
+ // All opaque is default
+ if (str.charAt(0) == 'f' && str.charAt(1) == 'f') {
+ str.delete(0, 2);
+ }
+
+ // Prepend hash
+ return str.insert(0, '#').toString();
+ }
+
+ /**
+ * Tests a string, to see if it is an number (element of Z).
+ * Valid integers are positive natural numbers (1, 2, 3, ...),
+ * their negatives (?1, ?2, ?3, ...) and the number zero.
+ *
+ * Note that there is no guarantees made, that this number can be
+ * represented as either an int or a long.
+ *
+ *
+ * @param pString The string to check.
+ * @return true if the String is a natural number.
+ */
+ public static boolean isNumber(String pString) {
+ if (isEmpty(pString)) {
+ return false;
+ }
+
+ // Special case for first char, may be minus sign ('-')
+ char ch = pString.charAt(0);
+ if (!(ch == '-' || Character.isDigit(ch))) {
+ return false;
+ }
+
+ // Test every char
+ for (int i = 1; i < pString.length(); i++) {
+ if (!Character.isDigit(pString.charAt(i))) {
+ return false;
+ }
+ }
+
+ // All digits must be a natural number
+ return true;
+ }
+
+ /*
+ * This version is benchmarked against toStringArray and found to be
+ * increasingly slower, the more elements the string contains.
+ * Kept here
+ */
+
+ /**
+ * Removes all occurences of a specific character in a string.
+ * This method is not design for efficiency!
+ *
+ *
+ * @param pSource
+ * @param pSubstring
+ * @param pPosition
+ * @return the modified string.
+ */
+
+ /*
+ public static String removeChar(String pSourceString, final char pBadChar) {
+
+ char[] sourceCharArray = pSourceString.toCharArray();
+ List modifiedCharList = new Vector(sourceCharArray.length, 1);
+
+ // Filter the string
+ for (int i = 0; i < sourceCharArray.length; i++) {
+ if (sourceCharArray[i] != pBadChar) {
+ modifiedCharList.add(new Character(sourceCharArray[i]));
+ }
+ }
+
+ // Clean the character list
+ modifiedCharList = (List) CollectionUtil.purifyCollection((Collection) modifiedCharList);
+
+ // Create new modified String
+ char[] modifiedCharArray = new char[modifiedCharList.size()];
+ for (int i = 0; i < modifiedCharArray.length; i++) {
+ modifiedCharArray[i] = ((Character) modifiedCharList.get(i)).charValue();
+ }
+
+ return new String(modifiedCharArray);
+ }
+ */
+
+ /**
+ *
+ * This method is not design for efficiency!
+ *
+ * @param pSourceString The String for modification.
+ * @param pBadChars The char array containing the characters to remove from the source string.
+ * @return the modified string.
+ * @-deprecated Not tested yet!
+ *
+ */
+
+ /*
+ public static String removeChars(String pSourceString, final char[] pBadChars) {
+
+ char[] sourceCharArray = pSourceString.toCharArray();
+ List modifiedCharList = new Vector(sourceCharArray.length, 1);
+
+ Map badCharMap = new Hashtable();
+ Character dummyChar = new Character('*');
+ for (int i = 0; i < pBadChars.length; i++) {
+ badCharMap.put(new Character(pBadChars[i]), dummyChar);
+ }
+
+ // Filter the string
+ for (int i = 0; i < sourceCharArray.length; i++) {
+ Character arrayChar = new Character(sourceCharArray[i]);
+ if (!badCharMap.containsKey(arrayChar)) {
+ modifiedCharList.add(new Character(sourceCharArray[i]));
+ }
+ }
+
+ // Clean the character list
+ modifiedCharList = (List) CollectionUtil.purifyCollection((Collection) modifiedCharList);
+
+ // Create new modified String
+ char[] modifiedCharArray = new char[modifiedCharList.size()];
+ for (int i = 0; i < modifiedCharArray.length; i++) {
+ modifiedCharArray[i] = ((Character) modifiedCharList.get(i)).charValue();
+ }
+
+ return new String(modifiedCharArray);
+
+ }
+ */
+
+ /**
+ * Ensures that a string includes a given substring at a given position.
+ *
+ * Extends the string with a given string if it is not already there.
+ * E.g an URL "www.vg.no", to "http://www.vg.no".
+ *
+ *
+ * @param pSource The source string.
+ * @param pSubstring The substring to include.
+ * @param pPosition The location of the fill-in, the index starts with 0.
+ * @return the string, with the substring at the given location.
+ */
+ static String ensureIncludesAt(String pSource, String pSubstring, int pPosition) {
+ StringBuilder newString = new StringBuilder(pSource);
+
+ try {
+ String existingSubstring = pSource.substring(pPosition, pPosition + pSubstring.length());
+
+ if (!existingSubstring.equalsIgnoreCase(pSubstring)) {
+ newString.insert(pPosition, pSubstring);
+ }
+ }
+ catch (Exception e) {
+ // Do something!?
+ }
+ return newString.toString();
+ }
+
+ /**
+ * Ensures that a string does not include a given substring at a given
+ * position.
+ *
+ * Removes a given substring from a string if it is there.
+ * E.g an URL "http://www.vg.no", to "www.vg.no".
+ *
+ *
+ * @param pSource The source string.
+ * @param pSubstring The substring to check and possibly remove.
+ * @param pPosition The location of possible substring removal, the index starts with 0.
+ * @return the string, without the substring at the given location.
+ */
+ static String ensureExcludesAt(String pSource, String pSubstring, int pPosition) {
+ StringBuilder newString = new StringBuilder(pSource);
+
+ try {
+ String existingString = pSource.substring(pPosition + 1, pPosition + pSubstring.length() + 1);
+
+ if (!existingString.equalsIgnoreCase(pSubstring)) {
+ newString.delete(pPosition, pPosition + pSubstring.length());
+ }
+ }
+ catch (Exception e) {
+ // Do something!?
+ }
+ return newString.toString();
+ }
+
+ /**
+ * Gets the first substring between the given string boundaries.
+ *
+ * @param pSource The source string.
+ * @param pBeginBoundaryString The string that marks the beginning.
+ * @param pEndBoundaryString The string that marks the end.
+ * @param pOffset The index to start searching in the source
+ * string. If it is less than 0, the index will be set to 0.
+ * @return the substring demarcated by the given string boundaries or null
+ * if not both string boundaries are found.
+ */
+ public static String substring(final String pSource, final String pBeginBoundaryString, final String pEndBoundaryString,
+ final int pOffset) {
+ // Check offset
+ int offset = (pOffset < 0)
+ ? 0
+ : pOffset;
+
+ // Find the start index
+ int startIndex = pSource.indexOf(pBeginBoundaryString, offset) + pBeginBoundaryString.length();
+
+ if (startIndex < 0) {
+ return null;
+ }
+
+ // Find the end index
+ int endIndex = pSource.indexOf(pEndBoundaryString, startIndex);
+
+ if (endIndex < 0) {
+ return null;
+ }
+ return pSource.substring(startIndex, endIndex);
+ }
+
+ /**
+ * Removes the first substring demarcated by the given string boundaries.
+ *
+ * @param pSource The source string.
+ * @param pBeginBoundaryChar The character that marks the beginning of the
+ * unwanted substring.
+ * @param pEndBoundaryChar The character that marks the end of the
+ * unwanted substring.
+ * @param pOffset The index to start searching in the source
+ * string. If it is less than 0, the index will be set to 0.
+ * @return the source string with all the demarcated substrings removed,
+ * included the demarcation characters.
+ * @deprecated this method actually removes all demarcated substring.. doesn't it?
+ */
+
+ /*public*/
+ static String removeSubstring(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar, final int pOffset) {
+ StringBuilder filteredString = new StringBuilder();
+ boolean insideDemarcatedArea = false;
+ char[] charArray = pSource.toCharArray();
+
+ for (char c : charArray) {
+ if (!insideDemarcatedArea) {
+ if (c == pBeginBoundaryChar) {
+ insideDemarcatedArea = true;
+ }
+ else {
+ filteredString.append(c);
+ }
+ }
+ else {
+ if (c == pEndBoundaryChar) {
+ insideDemarcatedArea = false;
+ }
+ }
+ }
+ return filteredString.toString();
+ }
+
+ /**
+ * Removes all substrings demarcated by the given string boundaries.
+ *
+ * @param pSource The source string.
+ * @param pBeginBoundaryChar The character that marks the beginning of the unwanted substring.
+ * @param pEndBoundaryChar The character that marks the end of the unwanted substring.
+ * @return the source string with all the demarcated substrings removed, included the demarcation characters.
+ */
+ /*public*/
+ static String removeSubstrings(final String pSource, final char pBeginBoundaryChar, final char pEndBoundaryChar) {
+ StringBuilder filteredString = new StringBuilder();
+ boolean insideDemarcatedArea = false;
+ char[] charArray = pSource.toCharArray();
+
+ for (char c : charArray) {
+ if (!insideDemarcatedArea) {
+ if (c == pBeginBoundaryChar) {
+ insideDemarcatedArea = true;
+ }
+ else {
+ filteredString.append(c);
+ }
+ }
+ else {
+ if (c == pEndBoundaryChar) {
+ insideDemarcatedArea = false;
+ }
+ }
+ }
+ return filteredString.toString();
+ }
+
+ /**
+ * Gets the first element of a {@code String} containing string elements delimited by a given delimiter.
+ * NB - Straightforward implementation!
+ *
+ * @param pSource The source string.
+ * @param pDelimiter The delimiter used in the source string.
+ * @return The last string element.
+ */
+ // TODO: This method should be re-implemented for more efficient execution.
+ public static String getFirstElement(final String pSource, final String pDelimiter) {
+ if (pDelimiter == null) {
+ throw new IllegalArgumentException("delimiter == null");
+ }
+
+ if (StringUtil.isEmpty(pSource)) {
+ return pSource;
+ }
+
+ int idx = pSource.indexOf(pDelimiter);
+ if (idx >= 0) {
+ return pSource.substring(0, idx);
+ }
+ return pSource;
+ }
+
+ /**
+ * Gets the last element of a {@code String} containing string elements
+ * delimited by a given delimiter.
+ * NB - Straightforward implementation!
+ *
+ * @param pSource The source string.
+ * @param pDelimiter The delimiter used in the source string.
+ * @return The last string element.
+ */
+ public static String getLastElement(final String pSource, final String pDelimiter) {
+ if (pDelimiter == null) {
+ throw new IllegalArgumentException("delimiter == null");
+ }
+
+ if (StringUtil.isEmpty(pSource)) {
+ return pSource;
+ }
+ int idx = pSource.lastIndexOf(pDelimiter);
+ if (idx >= 0) {
+ return pSource.substring(idx + 1);
+ }
+ return pSource;
+ }
+
+ /**
+ * Converts a string array to a string of comma-separated values.
+ *
+ * @param pStringArray the string array
+ * @return A string of comma-separated values
+ */
+ public static String toCSVString(Object[] pStringArray) {
+ return toCSVString(pStringArray, ", ");
+ }
+
+ /**
+ * Converts a string array to a string separated by the given delimiter.
+ *
+ * @param pStringArray the string array
+ * @param pDelimiterString the delimiter string
+ * @return string of delimiter separated values
+ * @throws IllegalArgumentException if {@code pDelimiterString == null}
+ */
+ public static String toCSVString(Object[] pStringArray, String pDelimiterString) {
+ if (pStringArray == null) {
+ return "";
+ }
+ if (pDelimiterString == null) {
+ throw new IllegalArgumentException("delimiter == null");
+ }
+
+ StringBuilder buffer = new StringBuilder();
+ for (int i = 0; i < pStringArray.length; i++) {
+ if (i > 0) {
+ buffer.append(pDelimiterString);
+ }
+
+ buffer.append(pStringArray[i]);
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * @param pObject the object
+ * @return a deep string representation of the given object
+ */
+ public static String deepToString(Object pObject) {
+ return deepToString(pObject, false, 1);
+ }
+
+ /**
+ * @param pObject the object
+ * @param pDepth the maximum depth
+ * @param pForceDeep {@code true} to force deep {@code toString}, even
+ * if object overrides toString
+ * @return a deep string representation of the given object
+ */
+ // TODO: Array handling (print full type and length)
+ // TODO: Register handlers for specific toDebugString handling? :-)
+ public static String deepToString(Object pObject, boolean pForceDeep, int pDepth) {
+ // Null is null
+ if (pObject == null) {
+ return null;
+ }
+
+ // Implements toString, use it as-is unless pForceDeep
+ if (!pForceDeep && !isIdentityToString(pObject)) {
+ return pObject.toString();
+ }
+
+ StringBuilder buffer = new StringBuilder();
+
+ if (pObject.getClass().isArray()) {
+ // Special array handling
+ Class componentClass = pObject.getClass();
+ while (componentClass.isArray()) {
+ buffer.append('[');
+ buffer.append(Array.getLength(pObject));
+ buffer.append(']');
+ componentClass = componentClass.getComponentType();
+ }
+ buffer.insert(0, componentClass);
+ buffer.append(" {hashCode=");
+ buffer.append(Integer.toHexString(pObject.hashCode()));
+ buffer.append("}");
+ }
+ else {
+ // Append toString value only if overridden
+ if (isIdentityToString(pObject)) {
+ buffer.append(" {");
+ }
+ else {
+ buffer.append(" {toString=");
+ buffer.append(pObject.toString());
+ buffer.append(", ");
+ }
+ buffer.append("hashCode=");
+ buffer.append(Integer.toHexString(pObject.hashCode()));
+ // Loop through, and filter out any getters
+ Method[] methods = pObject.getClass().getMethods();
+ for (Method method : methods) {
+ // Filter only public methods
+ if (Modifier.isPublic(method.getModifiers())) {
+ String methodName = method.getName();
+
+ // Find name of property
+ String name = null;
+ if (!methodName.equals("getClass")
+ && methodName.length() > 3 && methodName.startsWith("get")
+ && Character.isUpperCase(methodName.charAt(3))) {
+ name = methodName.substring(3);
+ }
+ else if (methodName.length() > 2 && methodName.startsWith("is")
+ && Character.isUpperCase(methodName.charAt(2))) {
+ name = methodName.substring(2);
+ }
+
+ if (name != null) {
+ // If lowercase name, convert, else keep case
+ if (name.length() > 1 && Character.isLowerCase(name.charAt(1))) {
+ name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
+ }
+
+ Class[] paramTypes = method.getParameterTypes();// involves array copying...
+ boolean hasParams = (paramTypes != null && paramTypes.length > 0);
+ boolean isVoid = Void.TYPE.equals(method.getReturnType());
+
+ // Filter return type & parameters
+ if (!isVoid && !hasParams) {
+ try {
+ Object value = method.invoke(pObject);
+ buffer.append(", ");
+ buffer.append(name);
+ buffer.append('=');
+ if (pDepth != 0 && value != null && isIdentityToString(value)) {
+ buffer.append(deepToString(value, pForceDeep, pDepth > 0 ? pDepth - 1 : -1));
+ }
+ else {
+ buffer.append(value);
+ }
+ }
+ catch (Exception e) {
+ // Next..!
+ }
+ }
+ }
+ }
+ }
+ buffer.append('}');
+
+ // Get toString from original object
+ buffer.insert(0, pObject.getClass().getName());
+ }
+
+ return buffer.toString();
+ }
+
+ /**
+ * Tests if the {@code toString} method of the given object is inherited
+ * from {@code Object}.
+ *
+ * @param pObject the object
+ * @return {@code true} if toString of class Object
+ */
+ private static boolean isIdentityToString(Object pObject) {
+ try {
+ Method toString = pObject.getClass().getMethod("toString");
+ if (toString.getDeclaringClass() == Object.class) {
+ return true;
+ }
+ }
+ catch (Exception ignore) {
+ // Ignore
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns a string on the same format as {@code Object.toString()}.
+ *
+ * @param pObject the object
+ * @return the object as a {@code String} on the format of
+ * {@code Object.toString()}
+ */
+ public static String identityToString(Object pObject) {
+ if (pObject == null) {
+ return null;
+ }
+ else {
+ return pObject.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(pObject));
+ }
+ }
+
+ /**
+ * Tells whether or not the given string string matches the given regular
+ * expression.
+ *
+ * An invocation of this method of the form
+ * matches(str, regex) yields exactly the
+ * same result as the expression
+ *
+ * {@link Pattern}.
+ * {@link Pattern#matches(String, CharSequence) matches}
+ * (regex, str)
+ *
+ * @param pString the string
+ * @param pRegex the regular expression to which this string is to be matched
+ * @return {@code true} if, and only if, this string matches the
+ * given regular expression
+ * @throws PatternSyntaxException if the regular expression's syntax is invalid
+ * @see Pattern
+ * @see String#matches(String)
+ */
+ public boolean matches(String pString, String pRegex) throws PatternSyntaxException {
+ return Pattern.matches(pRegex, pString);
+ }
+
+ /**
+ * Replaces the first substring of the given string that matches the given
+ * regular expression with the given pReplacement.
+ *
+ * An invocation of this method of the form
+ *
+ * replaceFirst(str, regex, repl)
+ *
+ * yields exactly the same result as the expression:
+ *
+ *
+ * {@link Pattern}.{@link Pattern#compile(String) compile}(regex).
+ * {@link Pattern#matcher matcher}(str).
+ * {@link java.util.regex.Matcher#replaceFirst replaceFirst}(repl)
+ *
+ *
+ * @param pString the string
+ * @param pRegex the regular expression to which this string is to be matched
+ * @param pReplacement the replacement text
+ * @return The resulting {@code String}
+ * @throws PatternSyntaxException if the regular expression's syntax is invalid
+ * @see Pattern
+ * @see java.util.regex.Matcher#replaceFirst(String)
+ */
+ public String replaceFirst(String pString, String pRegex, String pReplacement) {
+ return Pattern.compile(pRegex).matcher(pString).replaceFirst(pReplacement);
+ }
+
+ /**
+ * Replaces each substring of this string that matches the given
+ * regular expression with the given pReplacement.
+ *
+ * An invocation of this method of the form
+ * replaceAll(str, pRegex, repl)
+ * yields exactly the same result as the expression
+ *
+ *
+ * {@link Pattern}.{@link Pattern#compile(String) compile}(pRegex).
+ * {@link Pattern#matcher matcher}(str{@code ).
+ * {@link java.util.regex.Matcher#replaceAll replaceAll}(}repl{@code )}
+ *
+ *
+ * @param pString the string
+ * @param pRegex the regular expression to which this string is to be matched
+ * @param pReplacement the replacement string
+ * @return The resulting {@code String}
+ * @throws PatternSyntaxException if the regular expression's syntax is invalid
+ * @see Pattern
+ * @see String#replaceAll(String,String)
+ */
+ public String replaceAll(String pString, String pRegex, String pReplacement) {
+ return Pattern.compile(pRegex).matcher(pString).replaceAll(pReplacement);
+ }
+
+ /**
+ * Splits this string around matches of the given regular expression.
+ *
+ * The array returned by this method contains each substring of this
+ * string that is terminated by another substring that matches the given
+ * expression or is terminated by the end of the string. The substrings in
+ * the array are in the order in which they occur in this string. If the
+ * expression does not match any part of the input then the resulting array
+ * has just one element, namely this string.
+ *
+ *
+ * The {@code pLimit} parameter controls the number of times the
+ * pattern is applied and therefore affects the length of the resulting
+ * array. If the pLimit n is greater than zero then the pattern
+ * will be applied at most n - 1 times, the array's
+ * length will be no greater than n, and the array's last entry
+ * will contain all input beyond the last matched delimiter. If n
+ * is non-positive then the pattern will be applied as many times as
+ * possible and the array can have any length. If n is zero then
+ * the pattern will be applied as many times as possible, the array can
+ * have any length, and trailing empty strings will be discarded.
+ *
+ *
+ * An invocation of this method of the form
+ * split(str, regex, n)
+ * yields the same result as the expression:
+ *
+ * {@link Pattern}.
+ * {@link Pattern#compile(String) compile}(regex).
+ * {@link Pattern#split(CharSequence,int) split}(str, n)
+ *
+ *
+ * @param pString the string
+ * @param pRegex the delimiting regular expression
+ * @param pLimit the result threshold, as described above
+ * @return the array of strings computed by splitting this string
+ * around matches of the given regular expression
+ * @throws PatternSyntaxException
+ * if the regular expression's syntax is invalid
+ * @see Pattern
+ * @see String#split(String,int)
+ */
+ public String[] split(String pString, String pRegex, int pLimit) {
+ return Pattern.compile(pRegex).split(pString, pLimit);
+ }
+
+ /**
+ * Splits this string around matches of the given regular expression.
+ *
+ * This method works as if by invoking the two-argument
+ * {@link #split(String,String,int) split} method with the given
+ * expression and a limit argument of zero.
+ * Trailing empty strings are therefore not included in the resulting array.
+ *
+ *
+ * @param pString the string
+ * @param pRegex the delimiting regular expression
+ * @return the array of strings computed by splitting this string
+ * around matches of the given regular expression
+ * @throws PatternSyntaxException if the regular expression's syntax is invalid
+ * @see Pattern
+ * @see String#split(String)
+ */
+ public String[] split(String pString, String pRegex) {
+ return split(pString, pRegex, 0);
+ }
+
+ /**
+ * Converts the input string
+ * from camel-style (Java in-fix) naming convention
+ * to Lisp-style naming convention (hyphen delimitted, all lower case).
+ * Other characters in the string are left untouched.
+ *
+ * Eg.
+ * {@code "foo" => "foo"},
+ * {@code "fooBar" => "foo-bar"},
+ * {@code "myURL" => "my-url"},
+ * {@code "HttpRequestWrapper" => "http-request-wrapper"}
+ * {@code "HttpURLConnection" => "http-url-connection"}
+ * {@code "my45Caliber" => "my-45-caliber"}
+ * {@code "allready-lisp" => "allready-lisp"}
+ *
+ *
+ * @param pString the camel-style input string
+ * @return the string converted to lisp-style naming convention
+ * @throws IllegalArgumentException if {@code pString == null}
+ * @see #lispToCamel(String)
+ */
+ // TODO: RefactorMe!
+ public static String camelToLisp(final String pString) {
+ if (pString == null) {
+ throw new IllegalArgumentException("string == null");
+ }
+ if (pString.length() == 0) {
+ return pString;
+ }
+
+ StringBuilder buf = null;
+ int lastPos = 0;
+ boolean inCharSequence = false;
+ boolean inNumberSequence = false;
+
+ // NOTE: Start at index 1, as first letter should never be hyphen
+ for (int i = 1; i < pString.length(); i++) {
+ char current = pString.charAt(i);
+ if (Character.isUpperCase(current)) {
+ // Init buffer if necessary
+ if (buf == null) {
+ buf = new StringBuilder(pString.length() + 3);// Allow for some growth
+ }
+
+ if (inNumberSequence) {
+ // Sequence end
+ inNumberSequence = false;
+
+ buf.append(pString.substring(lastPos, i));
+ if (current != '-') {
+ buf.append('-');
+ }
+ lastPos = i;
+ continue;
+ }
+
+ // Treat multiple uppercase chars as single word
+ char previous = pString.charAt(i - 1);
+ if (i == lastPos || Character.isUpperCase(previous)) {
+ inCharSequence = true;
+ continue;
+ }
+
+ // Append word
+ buf.append(pString.substring(lastPos, i).toLowerCase());
+ if (previous != '-') {
+ buf.append('-');
+ }
+ buf.append(Character.toLowerCase(current));
+
+ lastPos = i + 1;
+ }
+ else if (Character.isDigit(current)) {
+ // Init buffer if necessary
+ if (buf == null) {
+ buf = new StringBuilder(pString.length() + 3);// Allow for some growth
+ }
+
+ if (inCharSequence) {
+ // Sequence end
+ inCharSequence = false;
+
+ buf.append(pString.substring(lastPos, i).toLowerCase());
+ if (current != '-') {
+ buf.append('-');
+ }
+ lastPos = i;
+ continue;
+ }
+
+ // Treat multiple digits as single word
+ char previous = pString.charAt(i - 1);
+ if (i == lastPos || Character.isDigit(previous)) {
+ inNumberSequence = true;
+ continue;
+ }
+
+ // Append word
+ buf.append(pString.substring(lastPos, i).toLowerCase());
+ if (previous != '-') {
+ buf.append('-');
+ }
+ buf.append(Character.toLowerCase(current));
+
+ lastPos = i + 1;
+ }
+ else if (inNumberSequence) {
+ // Sequence end
+ inNumberSequence = false;
+
+ buf.append(pString.substring(lastPos, i));
+ if (current != '-') {
+ buf.append('-');
+ }
+ lastPos = i;
+ }
+ else if (inCharSequence) {
+ // Sequence end
+ inCharSequence = false;
+
+ // NOTE: Special treatment! Last upper case, is first char in
+ // next word, not last char in this word
+ buf.append(pString.substring(lastPos, i - 1).toLowerCase());
+ if (current != '-') {
+ buf.append('-');
+ }
+ lastPos = i - 1;
+ }
+ }
+
+ if (buf != null) {
+ // Append the rest
+ buf.append(pString.substring(lastPos).toLowerCase());
+ return buf.toString();
+ }
+ else {
+ return Character.isUpperCase(pString.charAt(0)) ? pString.toLowerCase() : pString;
+ }
+ }
+
+ /**
+ * Converts the input string
+ * from Lisp-style naming convention (hyphen delimitted, all lower case)
+ * to camel-style (Java in-fix) naming convention.
+ * Other characters in the string are left untouched.
+ *
+ * Eg.
+ * {@code "foo" => "foo"},
+ * {@code "foo-bar" => "fooBar"},
+ * {@code "http-request-wrapper" => "httpRequestWrapper"}
+ * {@code "my-45-caliber" => "my45Caliber"}
+ * {@code "allreadyCamel" => "allreadyCamel"}
+ *
+ *
+ * @param pString the lisp-style input string
+ * @return the string converted to camel-style
+ * @throws IllegalArgumentException if {@code pString == null}
+ * @see #lispToCamel(String,boolean)
+ * @see #camelToLisp(String)
+ */
+ public static String lispToCamel(final String pString) {
+ return lispToCamel(pString, false);
+ }
+
+ /**
+ * Converts the input string
+ * from Lisp-style naming convention (hyphen delimitted, all lower case)
+ * to camel-style (Java in-fix) naming convention.
+ * Other characters in the string are left untouched.
+ *
+ * To create a string starting with a lower case letter
+ * (like Java variable names, etc),
+ * specify the {@code pFirstUpperCase} paramter to be {@code false}.
+ * Eg.
+ * {@code "foo" => "foo"},
+ * {@code "foo-bar" => "fooBar"},
+ * {@code "allreadyCamel" => "allreadyCamel"}
+ *
+ *
+ * To create a string starting with an upper case letter
+ * (like Java class name, etc),
+ * specify the {@code pFirstUpperCase} paramter to be {@code true}.
+ * Eg.
+ * {@code "http-request-wrapper" => "HttpRequestWrapper"}
+ * {@code "my-12-monkeys" => "My12Monkeys"}
+ *
+ *
+ * @param pString the lisp-style input string
+ * @param pFirstUpperCase {@code true} if the first char should be
+ * upper case
+ * @return the string converted to camel-style
+ * @throws IllegalArgumentException if {@code pString == null}
+ * @see #camelToLisp(String)
+ */
+ public static String lispToCamel(final String pString, final boolean pFirstUpperCase) {
+ if (pString == null) {
+ throw new IllegalArgumentException("string == null");
+ }
+ if (pString.length() == 0) {
+ return pString;
+ }
+
+ StringBuilder buf = null;
+ int lastPos = 0;
+
+ for (int i = 0; i < pString.length(); i++) {
+ char current = pString.charAt(i);
+ if (current == '-') {
+
+ // Init buffer if necessary
+ if (buf == null) {
+ buf = new StringBuilder(pString.length() - 1);// Can't be larger
+ }
+
+ // Append with upper case
+ if (lastPos != 0 || pFirstUpperCase) {
+ buf.append(Character.toUpperCase(pString.charAt(lastPos)));
+ lastPos++;
+ }
+
+ buf.append(pString.substring(lastPos, i).toLowerCase());
+ lastPos = i + 1;
+ }
+ }
+
+ if (buf != null) {
+ buf.append(Character.toUpperCase(pString.charAt(lastPos)));
+ buf.append(pString.substring(lastPos + 1).toLowerCase());
+ return buf.toString();
+ }
+ else {
+ if (pFirstUpperCase && !Character.isUpperCase(pString.charAt(0))) {
+ return capitalize(pString, 0);
+ }
+ else
+ if (!pFirstUpperCase && Character.isUpperCase(pString.charAt(0))) {
+ return Character.toLowerCase(pString.charAt(0)) + pString.substring(1);
+ }
+
+ return pString;
+ }
+ }
+
+ public static String reverse(final String pString) {
+ final char[] chars = new char[pString.length()];
+ pString.getChars(0, chars.length, chars, 0);
+
+ for (int i = 0; i < chars.length / 2; i++) {
+ char temp = chars[i];
+ chars[i] = chars[chars.length - 1 - i];
+ chars[chars.length - 1 - i] = temp;
+ }
+
+ return new String(chars);
+ }
}
\ No newline at end of file
diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/SystemUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/SystemUtil.java
index 6a6069a0..d824624b 100644
--- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/SystemUtil.java
+++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/SystemUtil.java
@@ -1,688 +1,687 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.lang;
-
-import java.io.*;
-import java.lang.reflect.Array;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * A utility class with some useful system-related functions.
- *
- * NOTE: This class is not considered part of the public API and may be
- * changed without notice
- *
- * @author Harald Kuhr
- * @author last modified by $Author: haku $
- *
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/SystemUtil.java#3 $
- *
- */
-public final class SystemUtil {
- /** {@code ".xml"} */
- public static String XML_PROPERTIES = ".xml";
- /** {@code ".properties"} */
- public static String STD_PROPERTIES = ".properties";
-
- // Disallow creating objects of this type
- private SystemUtil() {
- }
-
- /** This class marks an inputstream as containing XML, does nothing */
- private static class XMLPropertiesInputStream extends FilterInputStream {
- public XMLPropertiesInputStream(InputStream pIS) {
- super(pIS);
- }
- }
-
- /**
- * Gets the named resource as a stream from the given Class' Classoader.
- * If the pGuessSuffix parameter is true, the method will try to append
- * typical properties file suffixes, such as ".properties" or ".xml".
- *
- * @param pClassLoader the class loader to use
- * @param pName name of the resource
- * @param pGuessSuffix guess suffix
- *
- * @return an input stream reading from the resource
- */
- private static InputStream getResourceAsStream(ClassLoader pClassLoader, String pName, boolean pGuessSuffix) {
- InputStream is;
-
- if (!pGuessSuffix) {
- is = pClassLoader.getResourceAsStream(pName);
-
- // If XML, wrap stream
- if (is != null && pName.endsWith(XML_PROPERTIES)) {
- is = new XMLPropertiesInputStream(is);
- }
- }
- else {
- // Try normal properties
- is = pClassLoader.getResourceAsStream(pName + STD_PROPERTIES);
-
- // Try XML
- if (is == null) {
- is = pClassLoader.getResourceAsStream(pName + XML_PROPERTIES);
-
- // Wrap stream
- if (is != null) {
- is = new XMLPropertiesInputStream(is);
- }
- }
- }
-
- // Return stream
- return is;
- }
-
- /**
- * Gets the named file as a stream from the current directory.
- * If the pGuessSuffix parameter is true, the method will try to append
- * typical properties file suffixes, such as ".properties" or ".xml".
- *
- * @param pName name of the resource
- * @param pGuessSuffix guess suffix
- *
- * @return an input stream reading from the resource
- */
- private static InputStream getFileAsStream(String pName, boolean pGuessSuffix) {
- InputStream is = null;
- File propertiesFile;
-
- try {
- if (!pGuessSuffix) {
- // Get file
- propertiesFile = new File(pName);
-
- if (propertiesFile.exists()) {
- is = new FileInputStream(propertiesFile);
-
- // If XML, wrap stream
- if (pName.endsWith(XML_PROPERTIES)) {
- is = new XMLPropertiesInputStream(is);
- }
- }
- }
- else {
- // Try normal properties
- propertiesFile = new File(pName + STD_PROPERTIES);
-
- if (propertiesFile.exists()) {
- is = new FileInputStream(propertiesFile);
- }
- else {
- // Try XML
- propertiesFile = new File(pName + XML_PROPERTIES);
-
- if (propertiesFile.exists()) {
- // Wrap stream
- is = new XMLPropertiesInputStream(new FileInputStream(propertiesFile));
- }
- }
- }
- }
- catch (FileNotFoundException fnf) {
- // Should not happen, as we always test that the file .exists()
- // before creating InputStream
- // assert false;
- }
-
- return is;
- }
-
- /**
- * Utility method for loading a named properties-file for a class.
- *
- * The properties-file is loaded through either:
- *
- * - The given class' class loader (from classpath)
- * - Or, the system class loader (from classpath)
- * - Or, if it cannot be found in the classpath, an attempt to read from
- * the current directory (or full path if given).
- *
- *
- * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
- * are supported (XML-properties must have ".xml" as its file extension).
- *
- * @param pClass The class to load properties for. If this parameter is
- * {@code null}, the method will work exactly as
- * {@link #loadProperties(String)}
- * @param pName The name of the properties-file. If this parameter is
- * {@code null}, the method will work exactly as
- * {@link #loadProperties(Class)}
- *
- * @return A Properties mapping read from the given file or for the given
- * class.
- *
- * @throws NullPointerException if both {@code pName} and
- * {@code pClass} paramters are {@code null}
- * @throws IOException if an error occurs during load.
- * @throws FileNotFoundException if no properties-file could be found.
- *
- * @see #loadProperties(String)
- * @see #loadProperties(Class)
- * @see java.lang.ClassLoader#getResourceAsStream
- * @see java.lang.ClassLoader#getSystemResourceAsStream
- *
- * @todo Reconsider ever using the System ClassLoader: http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html
- * @todo Consider using Context Classloader instead?
- */
- public static Properties loadProperties(Class pClass, String pName) throws IOException
- {
- // Convert to name the classloader understands
- String name = !StringUtil.isEmpty(pName) ? pName : pClass.getName().replace('.', '/');
-
- // Should we try to guess suffix?
- boolean guessSuffix = (pName == null || pName.indexOf('.') < 0);
-
- InputStream is;
-
- // TODO: WHAT IF MULTIPLE RESOURCES EXISTS?!
- // Try loading resource through the current class' classloader
- if (pClass != null && (is = getResourceAsStream(pClass.getClassLoader(), name, guessSuffix)) != null) {
- //&& (is = getResourceAsStream(pClass, name, guessSuffix)) != null) {
- // Nothing to do
- //System.out.println(((is instanceof XMLPropertiesInputStream) ?
- // "XML-properties" : "Normal .properties")
- // + " from Class' ClassLoader");
- }
- // If that fails, try the system classloader
- else if ((is = getResourceAsStream(ClassLoader.getSystemClassLoader(), name, guessSuffix)) != null) {
- //else if ((is = getSystemResourceAsStream(name, guessSuffix)) != null) {
- // Nothing to do
- //System.out.println(((is instanceof XMLPropertiesInputStream) ?
- // "XML-properties" : "Normal .properties")
- // + " from System ClassLoader");
- }
- // All failed, try loading from file
- else if ((is = getFileAsStream(name, guessSuffix)) != null) {
- //System.out.println(((is instanceof XMLPropertiesInputStream) ?
- // "XML-properties" : "Normal .properties")
- // + " from System ClassLoader");
- }
- else {
- if (guessSuffix) {
- // TODO: file extension iterator or something...
- throw new FileNotFoundException(name + ".properties or " + name + ".xml");
- }
- else {
- throw new FileNotFoundException(name);
- }
- }
-
- // We have inputstream now, load...
- try {
- return loadProperties(is);
- }
- finally {
- // NOTE: If is == null, a FileNotFoundException must have been thrown above
- try {
- is.close();
- }
- catch (IOException ioe) {
- // Not critical...
- }
- }
- }
-
- /**
- * Utility method for loading a properties-file for a given class.
- * The properties are searched for on the form
- * "com/package/ClassName.properties" or
- * "com/package/ClassName.xml".
- *
- * The properties-file is loaded through either:
- *
- * - The given class' class loader (from classpath)
- * - Or, the system class loader (from classpath)
- * - Or, if it cannot be found in the classpath, an attempt to read from
- * the current directory (or full path if given).
- *
- *
- * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
- * are supported (XML-properties must have ".xml" as its file extension).
- *
- * @param pClass The class to load properties for
- * @return A Properties mapping for the given class.
- *
- * @throws NullPointerException if the {@code pClass} paramters is
- * {@code null}
- * @throws IOException if an error occurs during load.
- * @throws FileNotFoundException if no properties-file could be found.
- *
- * @see #loadProperties(String)
- * @see #loadProperties(Class, String)
- * @see java.lang.ClassLoader#getResourceAsStream
- * @see java.lang.ClassLoader#getSystemResourceAsStream
- *
- */
- public static Properties loadProperties(Class pClass) throws IOException {
- return loadProperties(pClass, null);
- }
-
- /**
- * Utility method for loading a named properties-file.
- *
- * The properties-file is loaded through either:
- *
- * - The system class loader (from classpath)
- * - Or, if it cannot be found in the classpath, an attempt to read from
- * the current directory.
- *
- *
- * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
- * are supported (XML-properties must have ".xml" as its file extension).
- *
- * @param pName The name of the properties-file.
- * @return A Properties mapping read from the given file.
- *
- * @throws NullPointerException if the {@code pName} paramters is
- * {@code null}
- * @throws IOException if an error occurs during load.
- * @throws FileNotFoundException if no properties-file could be found.
- *
- * @see #loadProperties(Class)
- * @see #loadProperties(Class, String)
- * @see java.lang.ClassLoader#getSystemResourceAsStream
- *
- */
- public static Properties loadProperties(String pName) throws IOException {
- return loadProperties(null, pName);
- }
-
- /*
- * Utility method for loading a properties-file.
- *
- * The properties files may also be contained in a zip/jar-file named
- * by the {@code com.twelvemonkeys.util.Config} system property (use "java -D"
- * to override). Default is "config.zip" in the current directory.
- *
- * @param pName The name of the file to loaded
- * @return A Properties mapping for the given class. If no properties-
- * file was found, an empty Properties object is returned.
- *
- */
- /*
- public static Properties loadProperties(String pName) throws IOException {
- // Use XML?
- boolean useXML = pName.endsWith(XML_PROPERTIES) ? true : false;
-
- InputStream is = null;
-
- File file = new File(pName);
-
- String configName = System.getProperty("com.twelvemonkeys.util.Config");
- File configArchive = new File(!StringUtil.isEmpty(configName)
- ? configName : DEFAULT_CONFIG);
-
- // Get input stream to the file containing the properties
- if (file.exists()) {
- // Try reading from file, normal way
- is = new FileInputStream(file);
- }
- else if (configArchive.exists()) {
- // Try reading properties from zip-file
- ZipFile zip = new ZipFile(configArchive);
- ZipEntry ze = zip.getEntry(pName);
- if (ze != null) {
- is = zip.getInputStream(ze);
- }
-
- }
-
- // Do the loading
- try {
- // Load the properties
- return loadProperties(is, useXML);
- }
- finally {
- // Try closing the archive to free resources
- if (is != null) {
- try {
- is.close();
- }
- catch (IOException ioe) {
- // Not critical...
- }
- }
- }
-
- }
- */
-
- /**
- * Returns a Properties, loaded from the given inputstream. If the given
- * inputstream is null, then an empty Properties object is returned.
- *
- * @param pInput the inputstream to read from
- *
- * @return a Properties object read from the given stream, or an empty
- * Properties mapping, if the stream is null.
- *
- * @throws IOException if an error occurred when reading from the input
- * stream.
- *
- */
- private static Properties loadProperties(InputStream pInput)
- throws IOException {
-
- if (pInput == null) {
- throw new IllegalArgumentException("InputStream == null!");
- }
-
- Properties mapping = new Properties();
- /*if (pInput instanceof XMLPropertiesInputStream) {
- mapping = new XMLProperties();
- }
- else {
- mapping = new Properties();
- }*/
-
- // Load the properties
- mapping.load(pInput);
-
- return mapping;
- }
-
- @SuppressWarnings({"SuspiciousSystemArraycopy"})
- public static Object clone(final Cloneable pObject) throws CloneNotSupportedException {
- if (pObject == null) {
- return null; // Null is clonable.. Easy. ;-)
- }
-
- // All arrays does have a clone method, but it's invisible for reflection...
- // By luck, multi-dimensional primitive arrays are instances of Object[]
- if (pObject instanceof Object[]) {
- return ((Object[]) pObject).clone();
- }
- else if (pObject.getClass().isArray()) {
- // One-dimensional primitive array, cloned manually
- int lenght = Array.getLength(pObject);
- Object clone = Array.newInstance(pObject.getClass().getComponentType(), lenght);
- System.arraycopy(pObject, 0, clone, 0, lenght);
- return clone;
- }
-
- try {
- // Find the clone method
- Method clone = null;
- Class clazz = pObject.getClass();
- do {
- try {
- clone = clazz.getDeclaredMethod("clone");
- break; // Found, or throws exception above
- }
- catch (NoSuchMethodException ignore) {
- // Ignore
- }
- }
- while ((clazz = clazz.getSuperclass()) != null);
-
- // NOTE: This should never happen
- if (clone == null) {
- throw new CloneNotSupportedException(pObject.getClass().getName());
- }
-
- // Override access if needed
- if (!clone.isAccessible()) {
- clone.setAccessible(true);
- }
-
- // Invoke clone method on original object
- return clone.invoke(pObject);
- }
- catch (SecurityException e) {
- CloneNotSupportedException cns = new CloneNotSupportedException(pObject.getClass().getName());
- cns.initCause(e);
- throw cns;
- }
- catch (IllegalAccessException e) {
- throw new CloneNotSupportedException(pObject.getClass().getName());
- }
- catch (InvocationTargetException e) {
- if (e.getTargetException() instanceof CloneNotSupportedException) {
- throw (CloneNotSupportedException) e.getTargetException();
- }
- else if (e.getTargetException() instanceof RuntimeException) {
- throw (RuntimeException) e.getTargetException();
- }
- else if (e.getTargetException() instanceof Error) {
- throw (Error) e.getTargetException();
- }
-
- throw new CloneNotSupportedException(pObject.getClass().getName());
- }
- }
-
-// public static void loadLibrary(String pLibrary) {
-// NativeLoader.loadLibrary(pLibrary);
-// }
-//
-// public static void loadLibrary(String pLibrary, ClassLoader pLoader) {
-// NativeLoader.loadLibrary(pLibrary, pLoader);
-// }
-
- public static void main(String[] args) throws CloneNotSupportedException {
-
- System.out.println("clone: " + args.clone().length + " (" + args.length + ")");
- System.out.println("copy: " + ((String[]) clone(args)).length + " (" + args.length + ")");
-
- int[] ints = {1,2,3};
- int[] copies = (int[]) clone(ints);
- System.out.println("Copies: " + copies.length + " (" + ints.length + ")");
-
- int[][] intsToo = {{1}, {2,3}, {4,5,6}};
- int[][] copiesToo = (int[][]) clone(intsToo);
- System.out.println("Copies: " + copiesToo.length + " (" + intsToo.length + ")");
- System.out.println("Copies0: " + copiesToo[0].length + " (" + intsToo[0].length + ")");
- System.out.println("Copies1: " + copiesToo[1].length + " (" + intsToo[1].length + ")");
- System.out.println("Copies2: " + copiesToo[2].length + " (" + intsToo[2].length + ")");
-
- Map map = new HashMap();
-
- for (String arg : args) {
- map.put(arg, arg);
- }
-
- Map copy = (Map) clone((Cloneable) map);
-
- System.out.println("Map : " + map);
- System.out.println("Copy: " + copy);
-
- /*
- SecurityManager sm = System.getSecurityManager();
-
- try {
- System.setSecurityManager(new SecurityManager() {
- public void checkPermission(Permission perm) {
- if (perm.getName().equals("suppressAccessChecks")) {
- throw new SecurityException();
- }
- //super.checkPermission(perm);
- }
- });
- */
-
- Cloneable cloneable = new Cloneable() {}; // No public clone method
- Cloneable clone = (Cloneable) clone(cloneable);
-
- System.out.println("cloneable: " + cloneable);
- System.out.println("clone: " + clone);
-
- /*
- }
- finally {
- System.setSecurityManager(sm);
- }
- */
-
- AccessController.doPrivileged(new PrivilegedAction() {
- public Void run() {
- return null;
- }
- }, AccessController.getContext());
-
- //String string = args.length > 0 ? args[0] : "jaffa";
- //clone(string);
- }
-
- /**
- * Tests if a named class is generally available.
- * If a class is considered available, a call to
- * {@code Class.forName(pClassName)} will not result in an exception.
- *
- * @param pClassName the class name to test
- * @return {@code true} if available
- */
- public static boolean isClassAvailable(String pClassName) {
- return isClassAvailable(pClassName, (ClassLoader) null);
- }
-
- /**
- * Tests if a named class is available from another class.
- * If a class is considered available, a call to
- * {@code Class.forName(pClassName, true, pFromClass.getClassLoader())}
- * will not result in an exception.
- *
- * @param pClassName the class name to test
- * @param pFromClass the class to test from
- * @return {@code true} if available
- */
- public static boolean isClassAvailable(String pClassName, Class pFromClass) {
- ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
- return isClassAvailable(pClassName, loader);
- }
-
- private static boolean isClassAvailable(String pClassName, ClassLoader pLoader) {
- try {
- // TODO: Sometimes init is not needed, but need to find a way to know...
- getClass(pClassName, true, pLoader);
- return true;
- }
- catch (SecurityException ignore) {
- // Ignore
- }
- catch (ClassNotFoundException ignore) {
- // Ignore
- }
- catch (LinkageError ignore) {
- // Ignore
- }
-
- return false;
- }
-
- public static boolean isFieldAvailable(final String pClassName, final String pFieldName) {
- return isFieldAvailable(pClassName, pFieldName, (ClassLoader) null);
- }
-
- public static boolean isFieldAvailable(final String pClassName, final String pFieldName, final Class pFromClass) {
- ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
- return isFieldAvailable(pClassName, pFieldName, loader);
- }
-
- private static boolean isFieldAvailable(final String pClassName, final String pFieldName, final ClassLoader pLoader) {
- try {
- Class cl = getClass(pClassName, false, pLoader);
-
- Field field = cl.getField(pFieldName);
- if (field != null) {
- return true;
- }
- }
- catch (ClassNotFoundException ignore) {
- // Ignore
- }
- catch (LinkageError ignore) {
- // Ignore
- }
- catch (NoSuchFieldException ignore) {
- // Ignore
- }
- return false;
- }
-
- public static boolean isMethodAvailable(String pClassName, String pMethodName) {
- // Finds void only
- return isMethodAvailable(pClassName, pMethodName, null, (ClassLoader) null);
- }
-
- public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams) {
- return isMethodAvailable(pClassName, pMethodName, pParams, (ClassLoader) null);
- }
-
- public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, Class pFromClass) {
- ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
- return isMethodAvailable(pClassName, pMethodName, pParams, loader);
- }
-
- private static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, ClassLoader pLoader) {
- try {
- Class cl = getClass(pClassName, false, pLoader);
-
- Method method = cl.getMethod(pMethodName, pParams);
- if (method != null) {
- return true;
- }
- }
- catch (ClassNotFoundException ignore) {
- // Ignore
- }
- catch (LinkageError ignore) {
- // Ignore
- }
- catch (NoSuchMethodException ignore) {
- // Ignore
- }
- return false;
- }
-
- private static Class getClass(String pClassName, boolean pInitialize, ClassLoader pLoader) throws ClassNotFoundException {
- // NOTE: We need the context class loader, as SystemUtil's
- // class loader may have a totally different class loader than
- // the original caller class (as in Class.forName(cn, false, null)).
- ClassLoader loader = pLoader != null ? pLoader :
- Thread.currentThread().getContextClassLoader();
-
- return Class.forName(pClassName, pInitialize, loader);
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.lang;
+
+import java.io.*;
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * A utility class with some useful system-related functions.
+ *
+ * NOTE: This class is not considered part of the public API and may be
+ * changed without notice
+ *
+ *
+ * @author Harald Kuhr
+ * @author last modified by $Author: haku $
+ *
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/lang/SystemUtil.java#3 $
+ *
+ */
+public final class SystemUtil {
+ /** {@code ".xml"} */
+ public static String XML_PROPERTIES = ".xml";
+ /** {@code ".properties"} */
+ public static String STD_PROPERTIES = ".properties";
+
+ // Disallow creating objects of this type
+ private SystemUtil() {
+ }
+
+ /** This class marks an inputstream as containing XML, does nothing */
+ private static class XMLPropertiesInputStream extends FilterInputStream {
+ public XMLPropertiesInputStream(InputStream pIS) {
+ super(pIS);
+ }
+ }
+
+ /**
+ * Gets the named resource as a stream from the given Class' Classoader.
+ * If the pGuessSuffix parameter is true, the method will try to append
+ * typical properties file suffixes, such as ".properties" or ".xml".
+ *
+ * @param pClassLoader the class loader to use
+ * @param pName name of the resource
+ * @param pGuessSuffix guess suffix
+ *
+ * @return an input stream reading from the resource
+ */
+ private static InputStream getResourceAsStream(ClassLoader pClassLoader, String pName, boolean pGuessSuffix) {
+ InputStream is;
+
+ if (!pGuessSuffix) {
+ is = pClassLoader.getResourceAsStream(pName);
+
+ // If XML, wrap stream
+ if (is != null && pName.endsWith(XML_PROPERTIES)) {
+ is = new XMLPropertiesInputStream(is);
+ }
+ }
+ else {
+ // Try normal properties
+ is = pClassLoader.getResourceAsStream(pName + STD_PROPERTIES);
+
+ // Try XML
+ if (is == null) {
+ is = pClassLoader.getResourceAsStream(pName + XML_PROPERTIES);
+
+ // Wrap stream
+ if (is != null) {
+ is = new XMLPropertiesInputStream(is);
+ }
+ }
+ }
+
+ // Return stream
+ return is;
+ }
+
+ /**
+ * Gets the named file as a stream from the current directory.
+ * If the pGuessSuffix parameter is true, the method will try to append
+ * typical properties file suffixes, such as ".properties" or ".xml".
+ *
+ * @param pName name of the resource
+ * @param pGuessSuffix guess suffix
+ *
+ * @return an input stream reading from the resource
+ */
+ private static InputStream getFileAsStream(String pName, boolean pGuessSuffix) {
+ InputStream is = null;
+ File propertiesFile;
+
+ try {
+ if (!pGuessSuffix) {
+ // Get file
+ propertiesFile = new File(pName);
+
+ if (propertiesFile.exists()) {
+ is = new FileInputStream(propertiesFile);
+
+ // If XML, wrap stream
+ if (pName.endsWith(XML_PROPERTIES)) {
+ is = new XMLPropertiesInputStream(is);
+ }
+ }
+ }
+ else {
+ // Try normal properties
+ propertiesFile = new File(pName + STD_PROPERTIES);
+
+ if (propertiesFile.exists()) {
+ is = new FileInputStream(propertiesFile);
+ }
+ else {
+ // Try XML
+ propertiesFile = new File(pName + XML_PROPERTIES);
+
+ if (propertiesFile.exists()) {
+ // Wrap stream
+ is = new XMLPropertiesInputStream(new FileInputStream(propertiesFile));
+ }
+ }
+ }
+ }
+ catch (FileNotFoundException fnf) {
+ // Should not happen, as we always test that the file .exists()
+ // before creating InputStream
+ // assert false;
+ }
+
+ return is;
+ }
+
+ /**
+ * Utility method for loading a named properties-file for a class.
+ *
+ * The properties-file is loaded through either:
+ *
+ * - The given class' class loader (from classpath)
+ * - Or, the system class loader (from classpath)
+ * - Or, if it cannot be found in the classpath, an attempt to read from
+ * the current directory (or full path if given).
+ *
+ *
+ * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
+ * are supported (XML-properties must have ".xml" as its file extension).
+ *
+ * @param pClass The class to load properties for. If this parameter is
+ * {@code null}, the method will work exactly as
+ * {@link #loadProperties(String)}
+ * @param pName The name of the properties-file. If this parameter is
+ * {@code null}, the method will work exactly as
+ * {@link #loadProperties(Class)}
+ *
+ * @return A Properties mapping read from the given file or for the given
+ * class.
+ *
+ * @throws NullPointerException if both {@code pName} and
+ * {@code pClass} paramters are {@code null}
+ * @throws IOException if an error occurs during load.
+ * @throws FileNotFoundException if no properties-file could be found.
+ *
+ * @see #loadProperties(String)
+ * @see #loadProperties(Class)
+ * @see java.lang.ClassLoader#getResourceAsStream
+ * @see java.lang.ClassLoader#getSystemResourceAsStream
+ */
+ // TODO: Reconsider ever using the System ClassLoader: http://www.javaworld.com/javaworld/javaqa/2003-06/01-qa-0606-load.html
+ // TODO: Consider using Context Classloader instead?
+ public static Properties loadProperties(Class pClass, String pName) throws IOException {
+ // Convert to name the classloader understands
+ String name = !StringUtil.isEmpty(pName) ? pName : pClass.getName().replace('.', '/');
+
+ // Should we try to guess suffix?
+ boolean guessSuffix = (pName == null || pName.indexOf('.') < 0);
+
+ InputStream is;
+
+ // TODO: WHAT IF MULTIPLE RESOURCES EXISTS?!
+ // Try loading resource through the current class' classloader
+ if (pClass != null && (is = getResourceAsStream(pClass.getClassLoader(), name, guessSuffix)) != null) {
+ //&& (is = getResourceAsStream(pClass, name, guessSuffix)) != null) {
+ // Nothing to do
+ //System.out.println(((is instanceof XMLPropertiesInputStream) ?
+ // "XML-properties" : "Normal .properties")
+ // + " from Class' ClassLoader");
+ }
+ // If that fails, try the system classloader
+ else if ((is = getResourceAsStream(ClassLoader.getSystemClassLoader(), name, guessSuffix)) != null) {
+ //else if ((is = getSystemResourceAsStream(name, guessSuffix)) != null) {
+ // Nothing to do
+ //System.out.println(((is instanceof XMLPropertiesInputStream) ?
+ // "XML-properties" : "Normal .properties")
+ // + " from System ClassLoader");
+ }
+ // All failed, try loading from file
+ else if ((is = getFileAsStream(name, guessSuffix)) != null) {
+ //System.out.println(((is instanceof XMLPropertiesInputStream) ?
+ // "XML-properties" : "Normal .properties")
+ // + " from System ClassLoader");
+ }
+ else {
+ if (guessSuffix) {
+ // TODO: file extension iterator or something...
+ throw new FileNotFoundException(name + ".properties or " + name + ".xml");
+ }
+ else {
+ throw new FileNotFoundException(name);
+ }
+ }
+
+ // We have inputstream now, load...
+ try {
+ return loadProperties(is);
+ }
+ finally {
+ // NOTE: If is == null, a FileNotFoundException must have been thrown above
+ try {
+ is.close();
+ }
+ catch (IOException ioe) {
+ // Not critical...
+ }
+ }
+ }
+
+ /**
+ * Utility method for loading a properties-file for a given class.
+ * The properties are searched for on the form
+ * "com/package/ClassName.properties" or
+ * "com/package/ClassName.xml".
+ *
+ * The properties-file is loaded through either:
+ *
+ * - The given class' class loader (from classpath)
+ * - Or, the system class loader (from classpath)
+ * - Or, if it cannot be found in the classpath, an attempt to read from
+ * the current directory (or full path if given).
+ *
+ *
+ * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
+ * are supported (XML-properties must have ".xml" as its file extension).
+ *
+ * @param pClass The class to load properties for
+ * @return A Properties mapping for the given class.
+ *
+ * @throws NullPointerException if the {@code pClass} paramters is
+ * {@code null}
+ * @throws IOException if an error occurs during load.
+ * @throws FileNotFoundException if no properties-file could be found.
+ *
+ * @see #loadProperties(String)
+ * @see #loadProperties(Class, String)
+ * @see java.lang.ClassLoader#getResourceAsStream
+ * @see java.lang.ClassLoader#getSystemResourceAsStream
+ *
+ */
+ public static Properties loadProperties(Class pClass) throws IOException {
+ return loadProperties(pClass, null);
+ }
+
+ /**
+ * Utility method for loading a named properties-file.
+ *
+ * The properties-file is loaded through either:
+ *
+ * - The system class loader (from classpath)
+ * - Or, if it cannot be found in the classpath, an attempt to read from
+ * the current directory.
+ *
+ *
+ * Both normal java.util.Properties and com.twelvemonkeys.util.XMLProperties
+ * are supported (XML-properties must have ".xml" as its file extension).
+ *
+ * @param pName The name of the properties-file.
+ * @return A Properties mapping read from the given file.
+ *
+ * @throws NullPointerException if the {@code pName} paramters is
+ * {@code null}
+ * @throws IOException if an error occurs during load.
+ * @throws FileNotFoundException if no properties-file could be found.
+ *
+ * @see #loadProperties(Class)
+ * @see #loadProperties(Class, String)
+ * @see java.lang.ClassLoader#getSystemResourceAsStream
+ *
+ */
+ public static Properties loadProperties(String pName) throws IOException {
+ return loadProperties(null, pName);
+ }
+
+ /*
+ * Utility method for loading a properties-file.
+ *
+ * The properties files may also be contained in a zip/jar-file named
+ * by the {@code com.twelvemonkeys.util.Config} system property (use "java -D"
+ * to override). Default is "config.zip" in the current directory.
+ *
+ * @param pName The name of the file to loaded
+ * @return A Properties mapping for the given class. If no properties-
+ * file was found, an empty Properties object is returned.
+ *
+ */
+ /*
+ public static Properties loadProperties(String pName) throws IOException {
+ // Use XML?
+ boolean useXML = pName.endsWith(XML_PROPERTIES) ? true : false;
+
+ InputStream is = null;
+
+ File file = new File(pName);
+
+ String configName = System.getProperty("com.twelvemonkeys.util.Config");
+ File configArchive = new File(!StringUtil.isEmpty(configName)
+ ? configName : DEFAULT_CONFIG);
+
+ // Get input stream to the file containing the properties
+ if (file.exists()) {
+ // Try reading from file, normal way
+ is = new FileInputStream(file);
+ }
+ else if (configArchive.exists()) {
+ // Try reading properties from zip-file
+ ZipFile zip = new ZipFile(configArchive);
+ ZipEntry ze = zip.getEntry(pName);
+ if (ze != null) {
+ is = zip.getInputStream(ze);
+ }
+
+ }
+
+ // Do the loading
+ try {
+ // Load the properties
+ return loadProperties(is, useXML);
+ }
+ finally {
+ // Try closing the archive to free resources
+ if (is != null) {
+ try {
+ is.close();
+ }
+ catch (IOException ioe) {
+ // Not critical...
+ }
+ }
+ }
+
+ }
+ */
+
+ /**
+ * Returns a Properties, loaded from the given inputstream. If the given
+ * inputstream is null, then an empty Properties object is returned.
+ *
+ * @param pInput the inputstream to read from
+ *
+ * @return a Properties object read from the given stream, or an empty
+ * Properties mapping, if the stream is null.
+ *
+ * @throws IOException if an error occurred when reading from the input
+ * stream.
+ *
+ */
+ private static Properties loadProperties(InputStream pInput)
+ throws IOException {
+
+ if (pInput == null) {
+ throw new IllegalArgumentException("InputStream == null!");
+ }
+
+ Properties mapping = new Properties();
+ /*if (pInput instanceof XMLPropertiesInputStream) {
+ mapping = new XMLProperties();
+ }
+ else {
+ mapping = new Properties();
+ }*/
+
+ // Load the properties
+ mapping.load(pInput);
+
+ return mapping;
+ }
+
+ @SuppressWarnings({"SuspiciousSystemArraycopy"})
+ public static Object clone(final Cloneable pObject) throws CloneNotSupportedException {
+ if (pObject == null) {
+ return null; // Null is clonable.. Easy. ;-)
+ }
+
+ // All arrays does have a clone method, but it's invisible for reflection...
+ // By luck, multi-dimensional primitive arrays are instances of Object[]
+ if (pObject instanceof Object[]) {
+ return ((Object[]) pObject).clone();
+ }
+ else if (pObject.getClass().isArray()) {
+ // One-dimensional primitive array, cloned manually
+ int lenght = Array.getLength(pObject);
+ Object clone = Array.newInstance(pObject.getClass().getComponentType(), lenght);
+ System.arraycopy(pObject, 0, clone, 0, lenght);
+ return clone;
+ }
+
+ try {
+ // Find the clone method
+ Method clone = null;
+ Class clazz = pObject.getClass();
+ do {
+ try {
+ clone = clazz.getDeclaredMethod("clone");
+ break; // Found, or throws exception above
+ }
+ catch (NoSuchMethodException ignore) {
+ // Ignore
+ }
+ }
+ while ((clazz = clazz.getSuperclass()) != null);
+
+ // NOTE: This should never happen
+ if (clone == null) {
+ throw new CloneNotSupportedException(pObject.getClass().getName());
+ }
+
+ // Override access if needed
+ if (!clone.isAccessible()) {
+ clone.setAccessible(true);
+ }
+
+ // Invoke clone method on original object
+ return clone.invoke(pObject);
+ }
+ catch (SecurityException e) {
+ CloneNotSupportedException cns = new CloneNotSupportedException(pObject.getClass().getName());
+ cns.initCause(e);
+ throw cns;
+ }
+ catch (IllegalAccessException e) {
+ throw new CloneNotSupportedException(pObject.getClass().getName());
+ }
+ catch (InvocationTargetException e) {
+ if (e.getTargetException() instanceof CloneNotSupportedException) {
+ throw (CloneNotSupportedException) e.getTargetException();
+ }
+ else if (e.getTargetException() instanceof RuntimeException) {
+ throw (RuntimeException) e.getTargetException();
+ }
+ else if (e.getTargetException() instanceof Error) {
+ throw (Error) e.getTargetException();
+ }
+
+ throw new CloneNotSupportedException(pObject.getClass().getName());
+ }
+ }
+
+// public static void loadLibrary(String pLibrary) {
+// NativeLoader.loadLibrary(pLibrary);
+// }
+//
+// public static void loadLibrary(String pLibrary, ClassLoader pLoader) {
+// NativeLoader.loadLibrary(pLibrary, pLoader);
+// }
+
+ public static void main(String[] args) throws CloneNotSupportedException {
+
+ System.out.println("clone: " + args.clone().length + " (" + args.length + ")");
+ System.out.println("copy: " + ((String[]) clone(args)).length + " (" + args.length + ")");
+
+ int[] ints = {1,2,3};
+ int[] copies = (int[]) clone(ints);
+ System.out.println("Copies: " + copies.length + " (" + ints.length + ")");
+
+ int[][] intsToo = {{1}, {2,3}, {4,5,6}};
+ int[][] copiesToo = (int[][]) clone(intsToo);
+ System.out.println("Copies: " + copiesToo.length + " (" + intsToo.length + ")");
+ System.out.println("Copies0: " + copiesToo[0].length + " (" + intsToo[0].length + ")");
+ System.out.println("Copies1: " + copiesToo[1].length + " (" + intsToo[1].length + ")");
+ System.out.println("Copies2: " + copiesToo[2].length + " (" + intsToo[2].length + ")");
+
+ Map map = new HashMap();
+
+ for (String arg : args) {
+ map.put(arg, arg);
+ }
+
+ Map copy = (Map) clone((Cloneable) map);
+
+ System.out.println("Map : " + map);
+ System.out.println("Copy: " + copy);
+
+ /*
+ SecurityManager sm = System.getSecurityManager();
+
+ try {
+ System.setSecurityManager(new SecurityManager() {
+ public void checkPermission(Permission perm) {
+ if (perm.getName().equals("suppressAccessChecks")) {
+ throw new SecurityException();
+ }
+ //super.checkPermission(perm);
+ }
+ });
+ */
+
+ Cloneable cloneable = new Cloneable() {}; // No public clone method
+ Cloneable clone = (Cloneable) clone(cloneable);
+
+ System.out.println("cloneable: " + cloneable);
+ System.out.println("clone: " + clone);
+
+ /*
+ }
+ finally {
+ System.setSecurityManager(sm);
+ }
+ */
+
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Void run() {
+ return null;
+ }
+ }, AccessController.getContext());
+
+ //String string = args.length > 0 ? args[0] : "jaffa";
+ //clone(string);
+ }
+
+ /**
+ * Tests if a named class is generally available.
+ * If a class is considered available, a call to
+ * {@code Class.forName(pClassName)} will not result in an exception.
+ *
+ * @param pClassName the class name to test
+ * @return {@code true} if available
+ */
+ public static boolean isClassAvailable(String pClassName) {
+ return isClassAvailable(pClassName, (ClassLoader) null);
+ }
+
+ /**
+ * Tests if a named class is available from another class.
+ * If a class is considered available, a call to
+ * {@code Class.forName(pClassName, true, pFromClass.getClassLoader())}
+ * will not result in an exception.
+ *
+ * @param pClassName the class name to test
+ * @param pFromClass the class to test from
+ * @return {@code true} if available
+ */
+ public static boolean isClassAvailable(String pClassName, Class pFromClass) {
+ ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
+ return isClassAvailable(pClassName, loader);
+ }
+
+ private static boolean isClassAvailable(String pClassName, ClassLoader pLoader) {
+ try {
+ // TODO: Sometimes init is not needed, but need to find a way to know...
+ getClass(pClassName, true, pLoader);
+ return true;
+ }
+ catch (SecurityException ignore) {
+ // Ignore
+ }
+ catch (ClassNotFoundException ignore) {
+ // Ignore
+ }
+ catch (LinkageError ignore) {
+ // Ignore
+ }
+
+ return false;
+ }
+
+ public static boolean isFieldAvailable(final String pClassName, final String pFieldName) {
+ return isFieldAvailable(pClassName, pFieldName, (ClassLoader) null);
+ }
+
+ public static boolean isFieldAvailable(final String pClassName, final String pFieldName, final Class pFromClass) {
+ ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
+ return isFieldAvailable(pClassName, pFieldName, loader);
+ }
+
+ private static boolean isFieldAvailable(final String pClassName, final String pFieldName, final ClassLoader pLoader) {
+ try {
+ Class cl = getClass(pClassName, false, pLoader);
+
+ Field field = cl.getField(pFieldName);
+ if (field != null) {
+ return true;
+ }
+ }
+ catch (ClassNotFoundException ignore) {
+ // Ignore
+ }
+ catch (LinkageError ignore) {
+ // Ignore
+ }
+ catch (NoSuchFieldException ignore) {
+ // Ignore
+ }
+ return false;
+ }
+
+ public static boolean isMethodAvailable(String pClassName, String pMethodName) {
+ // Finds void only
+ return isMethodAvailable(pClassName, pMethodName, null, (ClassLoader) null);
+ }
+
+ public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams) {
+ return isMethodAvailable(pClassName, pMethodName, pParams, (ClassLoader) null);
+ }
+
+ public static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, Class pFromClass) {
+ ClassLoader loader = pFromClass != null ? pFromClass.getClassLoader() : null;
+ return isMethodAvailable(pClassName, pMethodName, pParams, loader);
+ }
+
+ private static boolean isMethodAvailable(String pClassName, String pMethodName, Class[] pParams, ClassLoader pLoader) {
+ try {
+ Class cl = getClass(pClassName, false, pLoader);
+
+ Method method = cl.getMethod(pMethodName, pParams);
+ if (method != null) {
+ return true;
+ }
+ }
+ catch (ClassNotFoundException ignore) {
+ // Ignore
+ }
+ catch (LinkageError ignore) {
+ // Ignore
+ }
+ catch (NoSuchMethodException ignore) {
+ // Ignore
+ }
+ return false;
+ }
+
+ private static Class getClass(String pClassName, boolean pInitialize, ClassLoader pLoader) throws ClassNotFoundException {
+ // NOTE: We need the context class loader, as SystemUtil's
+ // class loader may have a totally different class loader than
+ // the original caller class (as in Class.forName(cn, false, null)).
+ ClassLoader loader = pLoader != null ? pLoader :
+ Thread.currentThread().getContextClassLoader();
+
+ return Class.forName(pClassName, pInitialize, loader);
+ }
+}
diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/lang/Validate.java b/common/common-lang/src/main/java/com/twelvemonkeys/lang/Validate.java
index 450081d4..6c61981d 100755
--- a/common/common-lang/src/main/java/com/twelvemonkeys/lang/Validate.java
+++ b/common/common-lang/src/main/java/com/twelvemonkeys/lang/Validate.java
@@ -36,10 +36,11 @@ import java.util.Map;
/**
* Kind of like {@code org.apache.commons.lang.Validate}. Just smarter. ;-)
- *
+ *
* Uses type parameterized return values, thus making it possible to check
* constructor arguments before
* they are passed on to {@code super} or {@code this} type constructors.
+ *
*
* @author Harald Kuhr
* @author last modified by $Author: haku $
diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java
index 4d551e1f..a354be08 100755
--- a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java
+++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java
@@ -1,402 +1,404 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.util;
-
-import java.io.Serializable;
-import java.util.*;
-
-/**
- * AbstractDecoratedMap
- *
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java#2 $
- */
-// TODO: The generics in this class looks suspicious..
-abstract class AbstractDecoratedMap extends AbstractMap implements Map, Serializable, Cloneable {
- protected Map> entries;
- protected transient volatile int modCount;
-
- private transient volatile Set> entrySet = null;
- private transient volatile Set keySet = null;
- private transient volatile Collection values = null;
-
- /**
- * Creates a {@code Map} backed by a {@code HashMap}.
- */
- public AbstractDecoratedMap() {
- this(new HashMap>(), null);
- }
-
- /**
- * Creates a {@code Map} backed by a {@code HashMap}, containing all
- * key/value mappings from the given {@code Map}.
- *
- * This is constructor is here to comply with the reccomendations for
- * "standard" constructors in the {@code Map} interface.
- *
- * @see #AbstractDecoratedMap(java.util.Map, java.util.Map)
- *
- * @param pContents the map whose mappings are to be placed in this map.
- * May be {@code null}.
- */
- public AbstractDecoratedMap(Map extends K, ? extends V> pContents) {
- this(new HashMap>(), pContents);
- }
-
- /**
- * Creates a {@code Map} backed by the given backing-{@code Map},
- * containing all key/value mappings from the given contents-{@code Map}.
- *
- * NOTE: The backing map is structuraly cahnged, and it should NOT be
- * accessed directly, after the wrapped map is created.
- *
- * @param pBacking the backing map of this map. Must be either empty, or
- * the same map as {@code pContents}.
- * @param pContents the map whose mappings are to be placed in this map.
- * May be {@code null}.
- *
- * @throws IllegalArgumentException if {@code pBacking} is {@code null}
- * or if {@code pBacking} differs from {@code pContent} and is not empty.
- */
- public AbstractDecoratedMap(Map> pBacking, Map extends K, ? extends V> pContents) {
- if (pBacking == null) {
- throw new IllegalArgumentException("backing == null");
- }
-
- Entry extends K, ? extends V>[] entries = null;
- if (pBacking == pContents) {
- // NOTE: Special treatment to avoid ClassCastExceptions
- Set extends Entry extends K, ? extends V>> es = pContents.entrySet();
- //noinspection unchecked
- entries = new Entry[es.size()];
- entries = es.toArray(entries);
- pContents = null;
- pBacking.clear();
- }
- else if (!pBacking.isEmpty()) {
- throw new IllegalArgumentException("backing must be empty");
- }
-
- this.entries = pBacking;
- init();
-
- if (pContents != null) {
- putAll(pContents);
- }
- else if (entries != null) {
- // Reinsert entries, this time wrapped
- for (Entry extends K, ? extends V> entry : entries) {
- put(entry.getKey(), entry.getValue());
- }
- }
- }
-
- /**
- * Default implementation, does nothing.
- */
- protected void init() {
- }
-
- public int size() {
- return entries.size();
- }
-
- public void clear() {
- entries.clear();
- modCount++;
- init();
- }
-
- public boolean isEmpty() {
- return entries.isEmpty();
- }
-
- public boolean containsKey(Object pKey) {
- return entries.containsKey(pKey);
- }
-
- /**
- * Returns {@code true} if this map maps one or more keys to the
- * specified pValue. More formally, returns {@code true} if and only if
- * this map contains at least one mapping to a pValue {@code v} such that
- * {@code (pValue==null ? v==null : pValue.equals(v))}.
- *
- * This implementation requires time linear in the map size for this
- * operation.
- *
- * @param pValue pValue whose presence in this map is to be tested.
- * @return {@code true} if this map maps one or more keys to the
- * specified pValue.
- */
- public boolean containsValue(Object pValue) {
- for (V value : values()) {
- if (value == pValue || (value != null && value.equals(pValue))) {
- return true;
- }
- }
-
- return false;
- }
-
- public Collection values() {
- Collection values = this.values;
- return values != null ? values : (this.values = new Values());
- }
-
- public Set> entrySet() {
- Set> es = entrySet;
- return es != null ? es : (entrySet = new EntrySet());
- }
-
- public Set keySet() {
- Set ks = keySet;
- return ks != null ? ks : (keySet = new KeySet());
- }
-
- /**
- * Returns a shallow copy of this {@code AbstractMap} instance: the keys
- * and values themselves are not cloned.
- *
- * @return a shallow copy of this map.
- */
- protected Object clone() throws CloneNotSupportedException {
- AbstractDecoratedMap map = (AbstractDecoratedMap) super.clone();
-
- map.values = null;
- map.entrySet = null;
- map.keySet = null;
-
- // TODO: Implement: Need to clone the backing map...
-
- return map;
- }
-
- // Subclass overrides these to alter behavior of views' iterator() method
- protected abstract Iterator newKeyIterator();
-
- protected abstract Iterator newValueIterator();
-
- protected abstract Iterator> newEntryIterator();
-
- // TODO: Implement these (get/put/remove)?
- public abstract V get(Object pKey);
-
- public abstract V remove(Object pKey);
-
- public abstract V put(K pKey, V pValue);
-
- /*protected*/ Entry createEntry(K pKey, V pValue) {
- return new BasicEntry(pKey, pValue);
- }
-
- /*protected*/ Entry getEntry(K pKey) {
- return entries.get(pKey);
- }
-
- /**
- * Removes the given entry from the Map.
- *
- * @param pEntry the entry to be removed
- *
- * @return the removed entry, or {@code null} if nothing was removed.
- */
- protected Entry removeEntry(Entry pEntry) {
- if (pEntry == null) {
- return null;
- }
-
- // Find candidate entry for this key
- Entry candidate = getEntry(pEntry.getKey());
- if (candidate == pEntry || (candidate != null && candidate.equals(pEntry))) {
- // Remove
- remove(pEntry.getKey());
- return pEntry;
- }
- return null;
- }
-
- protected class Values extends AbstractCollection {
- public Iterator iterator() {
- return newValueIterator();
- }
-
- public int size() {
- return AbstractDecoratedMap.this.size();
- }
-
- public boolean contains(Object o) {
- return containsValue(o);
- }
-
- public void clear() {
- AbstractDecoratedMap.this.clear();
- }
- }
-
- protected class EntrySet extends AbstractSet> {
- public Iterator> iterator() {
- return newEntryIterator();
- }
-
- public boolean contains(Object o) {
- if (!(o instanceof Entry))
- return false;
- Entry e = (Entry) o;
-
- //noinspection SuspiciousMethodCalls
- Entry candidate = entries.get(e.getKey());
- return candidate != null && candidate.equals(e);
- }
-
- public boolean remove(Object o) {
- if (!(o instanceof Entry)) {
- return false;
- }
-
- /*
- // NOTE: Extra cautions is taken, to only remove the entry if it
- // equals the entry in the map
- Object key = ((Entry) o).getKey();
- Entry entry = (Entry) entries.get(key);
-
- // Same entry?
- if (entry != null && entry.equals(o)) {
- return AbstractWrappedMap.this.remove(key) != null;
- }
-
- return false;
- */
-
- //noinspection unchecked
- return AbstractDecoratedMap.this.removeEntry((Entry) o) != null;
- }
-
- public int size() {
- return AbstractDecoratedMap.this.size();
- }
-
- public void clear() {
- AbstractDecoratedMap.this.clear();
- }
- }
-
- protected class KeySet extends AbstractSet {
- public Iterator iterator() {
- return newKeyIterator();
- }
- public int size() {
- return AbstractDecoratedMap.this.size();
- }
- public boolean contains(Object o) {
- return containsKey(o);
- }
- public boolean remove(Object o) {
- return AbstractDecoratedMap.this.remove(o) != null;
- }
- public void clear() {
- AbstractDecoratedMap.this.clear();
- }
- }
-
- /**
- * A simple Map.Entry implementaton.
- */
- static class BasicEntry implements Entry, Serializable {
- K mKey;
- V mValue;
-
- BasicEntry(K pKey, V pValue) {
- mKey = pKey;
- mValue = pValue;
- }
-
- /**
- * Default implementation does nothing.
- *
- * @param pMap the map that is accessed
- */
- protected void recordAccess(Map pMap) {
- }
-
- /**
- * Default implementation does nothing.
- * @param pMap the map that is removed from
- */
- protected void recordRemoval(Map pMap) {
- }
-
- public V getValue() {
- return mValue;
- }
-
- public V setValue(V pValue) {
- V oldValue = mValue;
- mValue = pValue;
- return oldValue;
- }
-
- public K getKey() {
- return mKey;
- }
-
- public boolean equals(Object pOther) {
- if (!(pOther instanceof Map.Entry)) {
- return false;
- }
-
- Map.Entry entry = (Map.Entry) pOther;
-
- Object k1 = mKey;
- Object k2 = entry.getKey();
-
- if (k1 == k2 || (k1 != null && k1.equals(k2))) {
- Object v1 = mValue;
- Object v2 = entry.getValue();
-
- if (v1 == v2 || (v1 != null && v1.equals(v2))) {
- return true;
- }
- }
-
- return false;
- }
-
- public int hashCode() {
- return (mKey == null ? 0 : mKey.hashCode()) ^
- (mValue == null ? 0 : mValue.hashCode());
- }
-
- public String toString() {
- return getKey() + "=" + getValue();
- }
- }
+/*
+ * 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 of the copyright holder 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 HOLDER 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.util;
+
+import java.io.Serializable;
+import java.util.*;
+
+/**
+ * AbstractDecoratedMap
+ *
+ * @author Harald Kuhr
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractDecoratedMap.java#2 $
+ */
+// TODO: The generics in this class looks suspicious..
+abstract class AbstractDecoratedMap extends AbstractMap implements Map, Serializable, Cloneable {
+ protected Map> entries;
+ protected transient volatile int modCount;
+
+ private transient volatile Set> entrySet = null;
+ private transient volatile Set keySet = null;
+ private transient volatile Collection values = null;
+
+ /**
+ * Creates a {@code Map} backed by a {@code HashMap}.
+ */
+ public AbstractDecoratedMap() {
+ this(new HashMap>(), null);
+ }
+
+ /**
+ * Creates a {@code Map} backed by a {@code HashMap}, containing all
+ * key/value mappings from the given {@code Map}.
+ *
+ * This is constructor is here to comply with the reccomendations for
+ * "standard" constructors in the {@code Map} interface.
+ *
+ *
+ * @see #AbstractDecoratedMap(java.util.Map, java.util.Map)
+ *
+ * @param pContents the map whose mappings are to be placed in this map.
+ * May be {@code null}.
+ */
+ public AbstractDecoratedMap(Map extends K, ? extends V> pContents) {
+ this(new HashMap>(), pContents);
+ }
+
+ /**
+ * Creates a {@code Map} backed by the given backing-{@code Map},
+ * containing all key/value mappings from the given contents-{@code Map}.
+ *
+ * NOTE: The backing map is structuraly cahnged, and it should NOT be
+ * accessed directly, after the wrapped map is created.
+ *
+ *
+ * @param pBacking the backing map of this map. Must be either empty, or
+ * the same map as {@code pContents}.
+ * @param pContents the map whose mappings are to be placed in this map.
+ * May be {@code null}.
+ *
+ * @throws IllegalArgumentException if {@code pBacking} is {@code null}
+ * or if {@code pBacking} differs from {@code pContent} and is not empty.
+ */
+ public AbstractDecoratedMap(Map> pBacking, Map extends K, ? extends V> pContents) {
+ if (pBacking == null) {
+ throw new IllegalArgumentException("backing == null");
+ }
+
+ Entry extends K, ? extends V>[] entries = null;
+ if (pBacking == pContents) {
+ // NOTE: Special treatment to avoid ClassCastExceptions
+ Set extends Entry extends K, ? extends V>> es = pContents.entrySet();
+ //noinspection unchecked
+ entries = new Entry[es.size()];
+ entries = es.toArray(entries);
+ pContents = null;
+ pBacking.clear();
+ }
+ else if (!pBacking.isEmpty()) {
+ throw new IllegalArgumentException("backing must be empty");
+ }
+
+ this.entries = pBacking;
+ init();
+
+ if (pContents != null) {
+ putAll(pContents);
+ }
+ else if (entries != null) {
+ // Reinsert entries, this time wrapped
+ for (Entry extends K, ? extends V> entry : entries) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+
+ /**
+ * Default implementation, does nothing.
+ */
+ protected void init() {
+ }
+
+ public int size() {
+ return entries.size();
+ }
+
+ public void clear() {
+ entries.clear();
+ modCount++;
+ init();
+ }
+
+ public boolean isEmpty() {
+ return entries.isEmpty();
+ }
+
+ public boolean containsKey(Object pKey) {
+ return entries.containsKey(pKey);
+ }
+
+ /**
+ * Returns {@code true} if this map maps one or more keys to the
+ * specified pValue. More formally, returns {@code true} if and only if
+ * this map contains at least one mapping to a pValue {@code v} such that
+ * {@code (pValue==null ? v==null : pValue.equals(v))}.
+ *
+ * This implementation requires time linear in the map size for this
+ * operation.
+ *
+ *
+ * @param pValue pValue whose presence in this map is to be tested.
+ * @return {@code true} if this map maps one or more keys to the
+ * specified pValue.
+ */
+ public boolean containsValue(Object pValue) {
+ for (V value : values()) {
+ if (value == pValue || (value != null && value.equals(pValue))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public Collection values() {
+ Collection values = this.values;
+ return values != null ? values : (this.values = new Values());
+ }
+
+ public Set> entrySet() {
+ Set> es = entrySet;
+ return es != null ? es : (entrySet = new EntrySet());
+ }
+
+ public Set keySet() {
+ Set ks = keySet;
+ return ks != null ? ks : (keySet = new KeySet());
+ }
+
+ /**
+ * Returns a shallow copy of this {@code AbstractMap} instance: the keys
+ * and values themselves are not cloned.
+ *
+ * @return a shallow copy of this map.
+ */
+ protected Object clone() throws CloneNotSupportedException {
+ AbstractDecoratedMap map = (AbstractDecoratedMap) super.clone();
+
+ map.values = null;
+ map.entrySet = null;
+ map.keySet = null;
+
+ // TODO: Implement: Need to clone the backing map...
+
+ return map;
+ }
+
+ // Subclass overrides these to alter behavior of views' iterator() method
+ protected abstract Iterator newKeyIterator();
+
+ protected abstract Iterator newValueIterator();
+
+ protected abstract Iterator> newEntryIterator();
+
+ // TODO: Implement these (get/put/remove)?
+ public abstract V get(Object pKey);
+
+ public abstract V remove(Object pKey);
+
+ public abstract V put(K pKey, V pValue);
+
+ /*protected*/ Entry createEntry(K pKey, V pValue) {
+ return new BasicEntry(pKey, pValue);
+ }
+
+ /*protected*/ Entry getEntry(K pKey) {
+ return entries.get(pKey);
+ }
+
+ /**
+ * Removes the given entry from the Map.
+ *
+ * @param pEntry the entry to be removed
+ *
+ * @return the removed entry, or {@code null} if nothing was removed.
+ */
+ protected Entry removeEntry(Entry pEntry) {
+ if (pEntry == null) {
+ return null;
+ }
+
+ // Find candidate entry for this key
+ Entry candidate = getEntry(pEntry.getKey());
+ if (candidate == pEntry || (candidate != null && candidate.equals(pEntry))) {
+ // Remove
+ remove(pEntry.getKey());
+ return pEntry;
+ }
+ return null;
+ }
+
+ protected class Values extends AbstractCollection {
+ public Iterator iterator() {
+ return newValueIterator();
+ }
+
+ public int size() {
+ return AbstractDecoratedMap.this.size();
+ }
+
+ public boolean contains(Object o) {
+ return containsValue(o);
+ }
+
+ public void clear() {
+ AbstractDecoratedMap.this.clear();
+ }
+ }
+
+ protected class EntrySet extends AbstractSet> {
+ public Iterator> iterator() {
+ return newEntryIterator();
+ }
+
+ public boolean contains(Object o) {
+ if (!(o instanceof Entry))
+ return false;
+ Entry e = (Entry) o;
+
+ //noinspection SuspiciousMethodCalls
+ Entry candidate = entries.get(e.getKey());
+ return candidate != null && candidate.equals(e);
+ }
+
+ public boolean remove(Object o) {
+ if (!(o instanceof Entry)) {
+ return false;
+ }
+
+ /*
+ // NOTE: Extra cautions is taken, to only remove the entry if it
+ // equals the entry in the map
+ Object key = ((Entry) o).getKey();
+ Entry entry = (Entry) entries.get(key);
+
+ // Same entry?
+ if (entry != null && entry.equals(o)) {
+ return AbstractWrappedMap.this.remove(key) != null;
+ }
+
+ return false;
+ */
+
+ //noinspection unchecked
+ return AbstractDecoratedMap.this.removeEntry((Entry) o) != null;
+ }
+
+ public int size() {
+ return AbstractDecoratedMap.this.size();
+ }
+
+ public void clear() {
+ AbstractDecoratedMap.this.clear();
+ }
+ }
+
+ protected class KeySet extends AbstractSet {
+ public Iterator iterator() {
+ return newKeyIterator();
+ }
+ public int size() {
+ return AbstractDecoratedMap.this.size();
+ }
+ public boolean contains(Object o) {
+ return containsKey(o);
+ }
+ public boolean remove(Object o) {
+ return AbstractDecoratedMap.this.remove(o) != null;
+ }
+ public void clear() {
+ AbstractDecoratedMap.this.clear();
+ }
+ }
+
+ /**
+ * A simple Map.Entry implementaton.
+ */
+ static class BasicEntry implements Entry, Serializable {
+ K mKey;
+ V mValue;
+
+ BasicEntry(K pKey, V pValue) {
+ mKey = pKey;
+ mValue = pValue;
+ }
+
+ /**
+ * Default implementation does nothing.
+ *
+ * @param pMap the map that is accessed
+ */
+ protected void recordAccess(Map pMap) {
+ }
+
+ /**
+ * Default implementation does nothing.
+ * @param pMap the map that is removed from
+ */
+ protected void recordRemoval(Map pMap) {
+ }
+
+ public V getValue() {
+ return mValue;
+ }
+
+ public V setValue(V pValue) {
+ V oldValue = mValue;
+ mValue = pValue;
+ return oldValue;
+ }
+
+ public K getKey() {
+ return mKey;
+ }
+
+ public boolean equals(Object pOther) {
+ if (!(pOther instanceof Map.Entry)) {
+ return false;
+ }
+
+ Map.Entry entry = (Map.Entry) pOther;
+
+ Object k1 = mKey;
+ Object k2 = entry.getKey();
+
+ if (k1 == k2 || (k1 != null && k1.equals(k2))) {
+ Object v1 = mValue;
+ Object v2 = entry.getValue();
+
+ if (v1 == v2 || (v1 != null && v1.equals(v2))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public int hashCode() {
+ return (mKey == null ? 0 : mKey.hashCode()) ^
+ (mValue == null ? 0 : mValue.hashCode());
+ }
+
+ public String toString() {
+ return getKey() + "=" + getValue();
+ }
+ }
}
\ No newline at end of file
diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java
index aaebbe4e..bd4e284c 100755
--- a/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java
+++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java
@@ -1,89 +1,88 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.util;
-
-/**
- * Abstract base class for {@code TokenIterator}s to extend.
- *
- *
- * @author Harald Kuhr
- * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java#1 $
- */
-public abstract class AbstractTokenIterator implements TokenIterator {
-
- /**
- * Not supported.
- *
- * @throws UnsupportedOperationException {@code remove} is not supported by
- * this Iterator.
- */
- public void remove() {
- // TODO: This is not difficult:
- // - Convert String to StringBuilder in constructor
- // - delete(pos, next.lenght())
- // - Add toString() method
- // BUT: Would it ever be useful? :-)
-
- throw new UnsupportedOperationException("remove");
- }
-
- public final boolean hasMoreTokens() {
- return hasNext();
- }
-
- /**
- * Returns the next element in the iteration as a {@code String}.
- * This implementation simply returns {@code (String) next()}.
- *
- * @return the next element in the iteration.
- * @exception java.util.NoSuchElementException iteration has no more elements.
- * @see #next()
- */
- public final String nextToken() {
- return next();
- }
-
- /**
- * This implementation simply returns {@code hasNext()}.
- * @see #hasNext()
- */
- public final boolean hasMoreElements() {
- return hasNext();
- }
-
- /**
- * This implementation simply returns {@code next()}.
- * @see #next()
- */
- public final String nextElement() {
- return next();
- }
-}
+/*
+ * 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 of the copyright holder 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 HOLDER 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.util;
+
+/**
+ * Abstract base class for {@code TokenIterator}s to extend.
+ *
+ * @author Harald Kuhr
+ * @version $Id: //depot/branches/personal/haraldk/twelvemonkeys/release-2/twelvemonkeys-core/src/main/java/com/twelvemonkeys/util/AbstractTokenIterator.java#1 $
+ */
+public abstract class AbstractTokenIterator implements TokenIterator {
+
+ /**
+ * Not supported.
+ *
+ * @throws UnsupportedOperationException {@code remove} is not supported by
+ * this Iterator.
+ */
+ public void remove() {
+ // TODO: This is not difficult:
+ // - Convert String to StringBuilder in constructor
+ // - delete(pos, next.lenght())
+ // - Add toString() method
+ // BUT: Would it ever be useful? :-)
+
+ throw new UnsupportedOperationException("remove");
+ }
+
+ public final boolean hasMoreTokens() {
+ return hasNext();
+ }
+
+ /**
+ * Returns the next element in the iteration as a {@code String}.
+ * This implementation simply returns {@code (String) next()}.
+ *
+ * @return the next element in the iteration.
+ * @exception java.util.NoSuchElementException iteration has no more elements.
+ * @see #next()
+ */
+ public final String nextToken() {
+ return next();
+ }
+
+ /**
+ * This implementation simply returns {@code hasNext()}.
+ * @see #hasNext()
+ */
+ public final boolean hasMoreElements() {
+ return hasNext();
+ }
+
+ /**
+ * This implementation simply returns {@code next()}.
+ * @see #next()
+ */
+ public final String nextElement() {
+ return next();
+ }
+}
diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/BeanMap.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/BeanMap.java
index 906c3391..6dd0edbe 100755
--- a/common/common-lang/src/main/java/com/twelvemonkeys/util/BeanMap.java
+++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/BeanMap.java
@@ -1,247 +1,248 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.util;
-
-import java.beans.IndexedPropertyDescriptor;
-import java.beans.IntrospectionException;
-import java.beans.Introspector;
-import java.beans.PropertyDescriptor;
-import java.io.Serializable;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.*;
-
-/**
- * A {@code Map} adapter for a Java Bean.
- *
- * Ruthlessly stolen from
- * initDescriptors(Object pBean) throws IntrospectionException {
- final Set descriptors = new HashSet();
-
- PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(pBean.getClass()).getPropertyDescriptors();
- for (PropertyDescriptor descriptor : propertyDescriptors) {
- // Skip Object.getClass(), as you probably never want it
- if ("class".equals(descriptor.getName()) && descriptor.getPropertyType() == Class.class) {
- continue;
- }
-
- // Only support simple setter/getters.
- if (!(descriptor instanceof IndexedPropertyDescriptor)) {
- descriptors.add(descriptor);
- }
- }
-
- return Collections.unmodifiableSet(descriptors);
- }
-
- public Set> entrySet() {
- return new BeanSet();
- }
-
- public Object get(final Object pKey) {
- return super.get(pKey);
- }
-
- public Object put(final String pKey, final Object pValue) {
- checkKey(pKey);
-
- for (Entry entry : entrySet()) {
- if (entry.getKey().equals(pKey)) {
- return entry.setValue(pValue);
- }
- }
-
- return null;
- }
-
- public Object remove(final Object pKey) {
- return super.remove(checkKey(pKey));
- }
-
- public int size() {
- return descriptors.size();
- }
-
- private String checkKey(final Object pKey) {
- if (pKey == null) {
- throw new IllegalArgumentException("key == null");
- }
- // NB - the cast forces CCE if key is the wrong type.
- final String name = (String) pKey;
-
- if (!containsKey(name)) {
- throw new IllegalArgumentException("Bad key: " + pKey);
- }
-
- return name;
- }
-
- private Object readResolve() throws IntrospectionException {
- // Initialize the property descriptors
- descriptors = initDescriptors(bean);
- return this;
- }
-
- private class BeanSet extends AbstractSet> {
- public Iterator> iterator() {
- return new BeanIterator(descriptors.iterator());
- }
-
- public int size() {
- return descriptors.size();
- }
- }
-
- private class BeanIterator implements Iterator> {
- private final Iterator mIterator;
-
- public BeanIterator(final Iterator pIterator) {
- mIterator = pIterator;
- }
-
- public boolean hasNext() {
- return mIterator.hasNext();
- }
-
- public BeanEntry next() {
- return new BeanEntry(mIterator.next());
- }
-
- public void remove() {
- mIterator.remove();
- }
- }
-
- private class BeanEntry implements Entry {
- private final PropertyDescriptor mDescriptor;
-
- public BeanEntry(final PropertyDescriptor pDescriptor) {
- this.mDescriptor = pDescriptor;
- }
-
- public String getKey() {
- return mDescriptor.getName();
- }
-
- public Object getValue() {
- return unwrap(new Wrapped() {
- public Object run() throws IllegalAccessException, InvocationTargetException {
- final Method method = mDescriptor.getReadMethod();
- // A write-only bean.
- if (method == null) {
- throw new UnsupportedOperationException("No getter: " + mDescriptor.getName());
- }
-
- return method.invoke(bean);
- }
- });
- }
-
- public Object setValue(final Object pValue) {
- return unwrap(new Wrapped() {
- public Object run() throws IllegalAccessException, InvocationTargetException {
- final Method method = mDescriptor.getWriteMethod();
- // A read-only bean.
- if (method == null) {
- throw new UnsupportedOperationException("No write method for property: " + mDescriptor.getName());
- }
-
- final Object old = getValue();
- method.invoke(bean, pValue);
- return old;
- }
- });
- }
-
- public boolean equals(Object pOther) {
- if (!(pOther instanceof Map.Entry)) {
- return false;
- }
-
- Map.Entry entry = (Map.Entry) pOther;
-
- Object k1 = getKey();
- Object k2 = entry.getKey();
-
- if (k1 == k2 || (k1 != null && k1.equals(k2))) {
- Object v1 = getValue();
- Object v2 = entry.getValue();
-
- if (v1 == v2 || (v1 != null && v1.equals(v2))) {
- return true;
- }
- }
-
- return false;
- }
-
- public int hashCode() {
- return (getKey() == null ? 0 : getKey().hashCode()) ^
- (getValue() == null ? 0 : getValue().hashCode());
- }
-
- public String toString() {
- return getKey() + "=" + getValue();
- }
- }
-
- private static interface Wrapped {
- Object run() throws IllegalAccessException, InvocationTargetException;
- }
-
- private static Object unwrap(final Wrapped wrapped) {
- try {
- return wrapped.run();
- }
- catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- }
- catch (final InvocationTargetException e) {
- // Javadocs for setValue indicate cast is ok.
- throw (RuntimeException) e.getCause();
- }
- }
+/*
+ * 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 of the copyright holder 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 HOLDER 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.util;
+
+import java.beans.IndexedPropertyDescriptor;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.*;
+
+/**
+ * A {@code Map} adapter for a Java Bean.
+ *
+ * Ruthlessly stolen from
+ * Binkley's Blog
+ *
+ */
+public final class BeanMap extends AbstractMap implements Serializable, Cloneable {
+ private final Object bean;
+ private transient Set descriptors;
+
+ public BeanMap(Object pBean) throws IntrospectionException {
+ if (pBean == null) {
+ throw new IllegalArgumentException("bean == null");
+ }
+
+ bean = pBean;
+ descriptors = initDescriptors(pBean);
+ }
+
+ private static Set initDescriptors(Object pBean) throws IntrospectionException {
+ final Set descriptors = new HashSet();
+
+ PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(pBean.getClass()).getPropertyDescriptors();
+ for (PropertyDescriptor descriptor : propertyDescriptors) {
+ // Skip Object.getClass(), as you probably never want it
+ if ("class".equals(descriptor.getName()) && descriptor.getPropertyType() == Class.class) {
+ continue;
+ }
+
+ // Only support simple setter/getters.
+ if (!(descriptor instanceof IndexedPropertyDescriptor)) {
+ descriptors.add(descriptor);
+ }
+ }
+
+ return Collections.unmodifiableSet(descriptors);
+ }
+
+ public Set> entrySet() {
+ return new BeanSet();
+ }
+
+ public Object get(final Object pKey) {
+ return super.get(pKey);
+ }
+
+ public Object put(final String pKey, final Object pValue) {
+ checkKey(pKey);
+
+ for (Entry entry : entrySet()) {
+ if (entry.getKey().equals(pKey)) {
+ return entry.setValue(pValue);
+ }
+ }
+
+ return null;
+ }
+
+ public Object remove(final Object pKey) {
+ return super.remove(checkKey(pKey));
+ }
+
+ public int size() {
+ return descriptors.size();
+ }
+
+ private String checkKey(final Object pKey) {
+ if (pKey == null) {
+ throw new IllegalArgumentException("key == null");
+ }
+ // NB - the cast forces CCE if key is the wrong type.
+ final String name = (String) pKey;
+
+ if (!containsKey(name)) {
+ throw new IllegalArgumentException("Bad key: " + pKey);
+ }
+
+ return name;
+ }
+
+ private Object readResolve() throws IntrospectionException {
+ // Initialize the property descriptors
+ descriptors = initDescriptors(bean);
+ return this;
+ }
+
+ private class BeanSet extends AbstractSet> {
+ public Iterator> iterator() {
+ return new BeanIterator(descriptors.iterator());
+ }
+
+ public int size() {
+ return descriptors.size();
+ }
+ }
+
+ private class BeanIterator implements Iterator> {
+ private final Iterator mIterator;
+
+ public BeanIterator(final Iterator pIterator) {
+ mIterator = pIterator;
+ }
+
+ public boolean hasNext() {
+ return mIterator.hasNext();
+ }
+
+ public BeanEntry next() {
+ return new BeanEntry(mIterator.next());
+ }
+
+ public void remove() {
+ mIterator.remove();
+ }
+ }
+
+ private class BeanEntry implements Entry {
+ private final PropertyDescriptor mDescriptor;
+
+ public BeanEntry(final PropertyDescriptor pDescriptor) {
+ this.mDescriptor = pDescriptor;
+ }
+
+ public String getKey() {
+ return mDescriptor.getName();
+ }
+
+ public Object getValue() {
+ return unwrap(new Wrapped() {
+ public Object run() throws IllegalAccessException, InvocationTargetException {
+ final Method method = mDescriptor.getReadMethod();
+ // A write-only bean.
+ if (method == null) {
+ throw new UnsupportedOperationException("No getter: " + mDescriptor.getName());
+ }
+
+ return method.invoke(bean);
+ }
+ });
+ }
+
+ public Object setValue(final Object pValue) {
+ return unwrap(new Wrapped() {
+ public Object run() throws IllegalAccessException, InvocationTargetException {
+ final Method method = mDescriptor.getWriteMethod();
+ // A read-only bean.
+ if (method == null) {
+ throw new UnsupportedOperationException("No write method for property: " + mDescriptor.getName());
+ }
+
+ final Object old = getValue();
+ method.invoke(bean, pValue);
+ return old;
+ }
+ });
+ }
+
+ public boolean equals(Object pOther) {
+ if (!(pOther instanceof Map.Entry)) {
+ return false;
+ }
+
+ Map.Entry entry = (Map.Entry) pOther;
+
+ Object k1 = getKey();
+ Object k2 = entry.getKey();
+
+ if (k1 == k2 || (k1 != null && k1.equals(k2))) {
+ Object v1 = getValue();
+ Object v2 = entry.getValue();
+
+ if (v1 == v2 || (v1 != null && v1.equals(v2))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public int hashCode() {
+ return (getKey() == null ? 0 : getKey().hashCode()) ^
+ (getValue() == null ? 0 : getValue().hashCode());
+ }
+
+ public String toString() {
+ return getKey() + "=" + getValue();
+ }
+ }
+
+ private static interface Wrapped {
+ Object run() throws IllegalAccessException, InvocationTargetException;
+ }
+
+ private static Object unwrap(final Wrapped wrapped) {
+ try {
+ return wrapped.run();
+ }
+ catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ catch (final InvocationTargetException e) {
+ // Javadocs for setValue indicate cast is ok.
+ throw (RuntimeException) e.getCause();
+ }
+ }
}
\ No newline at end of file
diff --git a/common/common-lang/src/main/java/com/twelvemonkeys/util/CollectionUtil.java b/common/common-lang/src/main/java/com/twelvemonkeys/util/CollectionUtil.java
index 3b492a61..b85b892a 100755
--- a/common/common-lang/src/main/java/com/twelvemonkeys/util/CollectionUtil.java
+++ b/common/common-lang/src/main/java/com/twelvemonkeys/util/CollectionUtil.java
@@ -1,635 +1,637 @@
-/*
- * 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 of the copyright holder 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 HOLDER 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.util;
-
-import com.twelvemonkeys.lang.Validate;
-
-import java.lang.reflect.Array;
-import java.util.*;
-
-import static com.twelvemonkeys.lang.Validate.isTrue;
-import static com.twelvemonkeys.lang.Validate.notNull;
-
-/**
- * A utility class with some useful collection-related functions.
- *
- * @author Harald Kuhr
- * @author Eirik Torske
- * @author last modified by $Author: haku $
- * @version $Id: com/twelvemonkeys/util/CollectionUtil.java#3 $
- * @see Collections
- * @see Arrays
- */
-public final class CollectionUtil {
-
- /**
- * Testing only.
- *
- * @param pArgs command line arguents
- */
- @SuppressWarnings({"UnusedDeclaration", "UnusedAssignment", "unchecked"})
- public static void main(String[] pArgs) {
- int howMany = 1000;
-
- if (pArgs.length > 0) {
- howMany = Integer.parseInt(pArgs[0]);
- }
- long start;
- long end;
-
- /*
- int[] intArr1 = new int[] {
- 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
- };
- int[] intArr2 = new int[] {
- 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
- };
- start = System.currentTimeMillis();
-
- for (int i = 0; i < howMany; i++) {
- intArr1 = (int[]) mergeArrays(intArr1, 0, intArr1.length, intArr2, 0, intArr2.length);
-
- }
- end = System.currentTimeMillis();
-
- System.out.println("mergeArrays: " + howMany + " * " + intArr2.length + " ints took " + (end - start) + " milliseconds (" + intArr1.length
- + ")");
- */
-
- ////////////////////////////////
- String[] stringArr1 = new String[]{
- "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"
- };
-
- /*
- String[] stringArr2 = new String[] {
- "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
- };
-
- start = System.currentTimeMillis();
- for (int i = 0; i < howMany; i++) {
- stringArr1 = (String[]) mergeArrays(stringArr1, 0, stringArr1.length, stringArr2, 0, stringArr2.length);
-
- }
- end = System.currentTimeMillis();
- System.out.println("mergeArrays: " + howMany + " * " + stringArr2.length + " Strings took " + (end - start) + " milliseconds ("
- + stringArr1.length + ")");
-
-
- start = System.currentTimeMillis();
- while (intArr1.length > stringArr2.length) {
- intArr1 = (int[]) subArray(intArr1, 0, intArr1.length - stringArr2.length);
-
- }
- end = System.currentTimeMillis();
-
- System.out.println("subArray: " + howMany + " * " + intArr2.length + " ints took " + (end - start) + " milliseconds (" + intArr1.length
- + ")");
-
- start = System.currentTimeMillis();
- while (stringArr1.length > stringArr2.length) {
- stringArr1 = (String[]) subArray(stringArr1, stringArr2.length);
-
- }
- end = System.currentTimeMillis();
- System.out.println("subArray: " + howMany + " * " + stringArr2.length + " Strings took " + (end - start) + " milliseconds ("
- + stringArr1.length + ")");
-
- */
- stringArr1 = new String[]{
- "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen",
- "fifteen", "sixteen", "seventeen", "eighteen", "nineteen"
- };
- System.out.println("\nFilterIterators:\n");
- List list = Arrays.asList(stringArr1);
- Iterator iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() {
-
- public boolean accept(Object pElement) {
- return ((String) pElement).length() > 5;
- }
- });
-
- while (iter.hasNext()) {
- String str = (String) iter.next();
-
- System.out.println(str + " has more than 5 letters!");
- }
- iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() {
-
- public boolean accept(Object pElement) {
- return ((String) pElement).length() <= 5;
- }
- });
-
- while (iter.hasNext()) {
- String str = (String) iter.next();
-
- System.out.println(str + " has less than, or exactly 5 letters!");
- }
- start = System.currentTimeMillis();
-
- for (int i = 0; i < howMany; i++) {
- iter = new FilterIterator(list.iterator(), new FilterIterator.Filter() {
-
- public boolean accept(Object pElement) {
- return ((String) pElement).length() <= 5;
- }
- });
- while (iter.hasNext()) {
- iter.next();
- System.out.print("");
- }
- }
-
-// end = System.currentTimeMillis();
-// System.out.println("Time: " + (end - start) + " ms");
-// System.out.println("\nClosureCollection:\n");
-// forEach(list, new Closure() {
-//
-// public void execute(Object pElement) {
-//
-// String str = (String) pElement;
-//
-// if (str.length() > 5) {
-// System.out.println(str + " has more than 5 letters!");
-// }
-// else {
-// System.out.println(str + " has less than, or exactly 5 letters!");
-// }
-// }
-// });
-// start = System.currentTimeMillis();
-// for (int i = 0; i < howMany; i++) {
-// forEach(list, new Closure() {
-//
-// public void execute(Object pElement) {
-//
-// String str = (String) pElement;
-//
-// if (str.length() <= 5) {
-// System.out.print("");
-// }
-// }
-// });
-// }
-// end = System.currentTimeMillis();
-// System.out.println("Time: " + (end - start) + " ms");
- }
-
- // Disallow creating objects of this type
- private CollectionUtil() {}
-
- /**
- * Merges two arrays into a new array. Elements from array1 and array2 will
- * be copied into a new array, that has array1.length + array2.length
- * elements.
- *
- * @param pArray1 First array
- * @param pArray2 Second array, must be compatible with (assignable from)
- * the first array
- * @return A new array, containing the values of array1 and array2. The
- * array (wrapped as an object), will have the length of array1 +
- * array2, and can be safely cast to the type of the array1
- * parameter.
- * @see #mergeArrays(Object,int,int,Object,int,int)
- * @see java.lang.System#arraycopy(Object,int,Object,int,int)
- */
- public static Object mergeArrays(Object pArray1, Object pArray2) {
- return mergeArrays(pArray1, 0, Array.getLength(pArray1), pArray2, 0, Array.getLength(pArray2));
- }
-
- /**
- * Merges two arrays into a new array. Elements from pArray1 and pArray2 will
- * be copied into a new array, that has pLength1 + pLength2 elements.
- *
- * @param pArray1 First array
- * @param pOffset1 the offset into the first array
- * @param pLength1 the number of elements to copy from the first array
- * @param pArray2 Second array, must be compatible with (assignable from)
- * the first array
- * @param pOffset2 the offset into the second array
- * @param pLength2 the number of elements to copy from the second array
- * @return A new array, containing the values of pArray1 and pArray2. The
- * array (wrapped as an object), will have the length of pArray1 +
- * pArray2, and can be safely cast to the type of the pArray1
- * parameter.
- * @see java.lang.System#arraycopy(Object,int,Object,int,int)
- */
- @SuppressWarnings({"SuspiciousSystemArraycopy"})
- public static Object mergeArrays(Object pArray1, int pOffset1, int pLength1, Object pArray2, int pOffset2, int pLength2) {
- Class class1 = pArray1.getClass();
- Class type = class1.getComponentType();
-
- // Create new array of the new length
- Object array = Array.newInstance(type, pLength1 + pLength2);
-
- System.arraycopy(pArray1, pOffset1, array, 0, pLength1);
- System.arraycopy(pArray2, pOffset2, array, pLength1, pLength2);
- return array;
- }
-
- /**
- * Creates an array containing a subset of the original array.
- * If the sub array is same length as the original
- * ({@code pStart == 0}), the original array will be returned.
- *
- * @param pArray the original array
- * @param pStart the start index of the original array
- * @return a subset of the original array, or the original array itself,
- * if {@code pStart} is 0.
- *
- * @throws IllegalArgumentException if {@code pArray} is {@code null} or
- * if {@code pArray} is not an array.
- * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
- */
- public static Object subArray(Object pArray, int pStart) {
- return subArray(pArray, pStart, -1);
- }
-
- /**
- * Creates an array containing a subset of the original array.
- * If the sub array is same length as the original
- * ({@code pStart == 0}), the original array will be returned.
- *
- * @param pArray the original array
- * @param pStart the start index of the original array
- * @return a subset of the original array, or the original array itself,
- * if {@code pStart} is 0.
- *
- * @throws IllegalArgumentException if {@code pArray} is {@code null}
- * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
- */
- public static T[] subArray(T[] pArray, int pStart) {
- return subArray(pArray, pStart, -1);
- }
-
- /**
- * Creates an array containing a subset of the original array.
- * If the {@code pLength} parameter is negative, it will be ignored.
- * If there are not {@code pLength} elements in the original array
- * after {@code pStart}, the {@code pLength} parameter will be
- * ignored.
- * If the sub array is same length as the original, the original array will
- * be returned.
- *
- * @param pArray the original array
- * @param pStart the start index of the original array
- * @param pLength the length of the new array
- * @return a subset of the original array, or the original array itself,
- * if {@code pStart} is 0 and {@code pLength} is either
- * negative, or greater or equal to {@code pArray.length}.
- *
- * @throws IllegalArgumentException if {@code pArray} is {@code null} or
- * if {@code pArray} is not an array.
- * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
- */
- @SuppressWarnings({"SuspiciousSystemArraycopy"})
- public static Object subArray(Object pArray, int pStart, int pLength) {
- Validate.notNull(pArray, "array");
-
- // Get component type
- Class type;
-
- // Sanity check start index
- if (pStart < 0) {
- throw new ArrayIndexOutOfBoundsException(pStart + " < 0");
- }
- // Check if argument is array
- else if ((type = pArray.getClass().getComponentType()) == null) {
- // NOTE: No need to test class.isArray(), really
- throw new IllegalArgumentException("Not an array: " + pArray);
- }
-
- // Store original length
- int originalLength = Array.getLength(pArray);
-
- // Find new length, stay within bounds
- int newLength = (pLength < 0)
- ? Math.max(0, originalLength - pStart)
- : Math.min(pLength, Math.max(0, originalLength - pStart));
-
- // Store result
- Object result;
-
- if (newLength < originalLength) {
- // Create sub array & copy into
- result = Array.newInstance(type, newLength);
- System.arraycopy(pArray, pStart, result, 0, newLength);
- }
- else {
- // Just return original array
- // NOTE: This can ONLY happen if pStart == 0
- result = pArray;
- }
-
- // Return
- return result;
- }
-
- /**
- * Creates an array containing a subset of the original array.
- * If the {@code pLength} parameter is negative, it will be ignored.
- * If there are not {@code pLength} elements in the original array
- * after {@code pStart}, the {@code pLength} parameter will be
- * ignored.
- * If the sub array is same length as the original, the original array will
- * be returned.
- *
- * @param pArray the original array
- * @param pStart the start index of the original array
- * @param pLength the length of the new array
- * @return a subset of the original array, or the original array itself,
- * if {@code pStart} is 0 and {@code pLength} is either
- * negative, or greater or equal to {@code pArray.length}.
- *
- * @throws IllegalArgumentException if {@code pArray} is {@code null}
- * @throws ArrayIndexOutOfBoundsException if {@code pStart} < 0
- */
- @SuppressWarnings("unchecked")
- public static T[] subArray(T[] pArray, int pStart, int pLength) {
- return (T[]) subArray((Object) pArray, pStart, pLength);
- }
-
- public static Iterator iterator(final Enumeration pEnum) {
- notNull(pEnum, "enumeration");
-
- return new Iterator() {
- public boolean hasNext() {
- return pEnum.hasMoreElements();
- }
-
- public T next() {
- return pEnum.nextElement();
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
-
- /**
- * Adds all elements of the iterator to the collection.
- *
- * @param pCollection the collection
- * @param pIterator the elements to add
- *
- * @throws UnsupportedOperationException if {@code add} is not supported by
- * the given collection.
- * @throws ClassCastException class of the specified element prevents it
- * from being added to this collection.
- * @throws NullPointerException if the specified element is {@code null} and this
- * collection does not support {@code null} elements.
- * @throws IllegalArgumentException some aspect of this element prevents
- * it from being added to this collection.
- */
- public static void addAll(Collection pCollection, Iterator extends E> pIterator) {
- while (pIterator.hasNext()) {
- pCollection.add(pIterator.next());
- }
- }
-
- // Is there a use case where Arrays.asList(pArray).iterator() can't ne used?
- /**
- * Creates a thin {@link Iterator} wrapper around an array.
- *
- * @param pArray the array to iterate
- * @return a new {@link ListIterator}
- * @throws IllegalArgumentException if {@code pArray} is {@code null},
- * {@code pStart < 0}, or
- * {@code pLength > pArray.length - pStart}
- */
- public static ListIterator iterator(final E[] pArray) {
- return iterator(pArray, 0, notNull(pArray).length);
- }
-
- /**
- * Creates a thin {@link Iterator} wrapper around an array.
- *
- * @param pArray the array to iterate
- * @param pStart the offset into the array
- * @param pLength the number of elements to include in the iterator
- * @return a new {@link ListIterator}
- * @throws IllegalArgumentException if {@code pArray} is {@code null},
- * {@code pStart < 0}, or
- * {@code pLength > pArray.length - pStart}
- */
- public static ListIterator iterator(final E[] pArray, final int pStart, final int pLength) {
- return new ArrayIterator(pArray, pStart, pLength);
- }
-
- /**
- * Creates an inverted mapping of the key/value pairs in the given map.
- *
- * @param pSource the source map
- * @return a new {@code Map} of same type as {@code pSource}
- * @throws IllegalArgumentException if {@code pSource == null},
- * or if a new map can't be instantiated,
- * or if source map contains duplicates.
- *
- * @see #invert(java.util.Map, java.util.Map, DuplicateHandler)
- */
- public static Map invert(Map pSource) {
- return invert(pSource, null, null);
- }
-
- /**
- * Creates an inverted mapping of the key/value pairs in the given map.
- * Optionally, a duplicate handler may be specified, to resolve duplicate keys in the result map.
- *
- * @param pSource the source map
- * @param pResult the map used to contain the result, may be {@code null},
- * in that case a new {@code Map} of same type as {@code pSource} is created.
- * The result map should be empty, otherwise duplicate values will need to be resolved.
- * @param pHandler duplicate handler, may be {@code null} if source map don't contain duplicate values
- * @return {@code pResult}, or a new {@code Map} if {@code pResult == null}
- * @throws IllegalArgumentException if {@code pSource == null},
- * or if result map is {@code null} and a new map can't be instantiated,
- * or if source map contains duplicate values and {@code pHandler == null}.
- */
- // TODO: Create a better duplicate handler, that takes Entries as parameters and returns an Entry
- public static Map invert(Map pSource, Map pResult, DuplicateHandler pHandler) {
- if (pSource == null) {
- throw new IllegalArgumentException("source == null");
- }
-
- Map result = pResult;
- if (result == null) {
- try {
- //noinspection unchecked
- result = pSource.getClass().newInstance();
- }
- catch (InstantiationException e) {
- // Handled below
- }
- catch (IllegalAccessException e) {
- // Handled below
- }
-
- if (result == null) {
- throw new IllegalArgumentException("result == null and source class " + pSource.getClass() + " cannot be instantiated.");
- }
- }
-
- // Copy entries into result map, inversed
- Set> entries = pSource.entrySet();
- for (Map.Entry entry : entries) {
- V newKey = entry.getValue();
- K newValue = entry.getKey();
-
- // Handle dupliates
- if (result.containsKey(newKey)) {
- if (pHandler != null) {
- newValue = pHandler.resolve(result.get(newKey), newValue);
- }
- else {
- throw new IllegalArgumentException("Result would include duplicate keys, but no DuplicateHandler specified.");
- }
- }
-
- result.put(newKey, newValue);
- }
-
- return result;
- }
-
- public static Comparator reverseOrder(final Comparator pOriginal) {
- return new ReverseComparator(pOriginal);
- }
-
- private static class ReverseComparator implements Comparator