diff --git a/src/main/java/org/scijava/Typed.java b/src/main/java/org/scijava/Typed.java
index b0f90090c..9100a8400 100644
--- a/src/main/java/org/scijava/Typed.java
+++ b/src/main/java/org/scijava/Typed.java
@@ -42,9 +42,8 @@ public interface Typed {
/**
* Gets whether this object is compatible with the given data object.
*
- * Typically, this will be the case when {@code data.getClass()} is assignable
- * to the type associated with the object (i.e., the one returned by
- * {@link #getType()}). But individual implementations may have other
+ * By default, this method will return {@code true} always, since the type is
+ * known to be compatible. But individual implementations may have other
* requirements beyond class assignability.
*
*/
diff --git a/src/main/java/org/scijava/io/AbstractDataHandle.java b/src/main/java/org/scijava/io/AbstractDataHandle.java
new file mode 100644
index 000000000..cfb917810
--- /dev/null
+++ b/src/main/java/org/scijava/io/AbstractDataHandle.java
@@ -0,0 +1,275 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import org.scijava.plugin.AbstractWrapperPlugin;
+
+/**
+ * Abstract base class for {@link DataHandle} plugins.
+ *
+ * @author Curtis Rueden
+ */
+public abstract class AbstractDataHandle extends
+ AbstractWrapperPlugin implements DataHandle
+{
+
+ // -- Constants --
+
+ /** Block size to use when searching through the stream. */
+ private static final int DEFAULT_BLOCK_SIZE = 256 * 1024; // 256 KB
+
+ /** Maximum number of bytes to search when searching through the stream. */
+ private static final int MAX_SEARCH_SIZE = 512 * 1024 * 1024; // 512 MB
+
+ // -- Fields --
+
+ private ByteOrder order = ByteOrder.BIG_ENDIAN;
+ private String encoding = "UTF-8";
+
+ // -- DataHandle methods --
+
+ @Override
+ public ByteOrder getOrder() {
+ return order;
+ }
+
+ @Override
+ public boolean isLittleEndian() {
+ return getOrder() == ByteOrder.LITTLE_ENDIAN;
+ }
+
+ @Override
+ public void setOrder(final ByteOrder order) {
+ this.order = order;
+ }
+
+ @Override
+ public void setOrder(final boolean little) {
+ setOrder(little ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
+ }
+
+ @Override
+ public String getEncoding() {
+ return encoding;
+ }
+
+ @Override
+ public void setEncoding(final String encoding) {
+ this.encoding = encoding;
+ }
+
+ @Override
+ public int read(final ByteBuffer buf) throws IOException {
+ return read(buf, buf.remaining());
+ }
+
+ @Override
+ public int read(final ByteBuffer buf, final int len)
+ throws IOException
+ {
+ final int n;
+ if (buf.hasArray()) {
+ // read directly into the array
+ n = read(buf.array(), buf.arrayOffset(), len);
+ }
+ else {
+ // read into a temporary array, then copy
+ final byte[] b = new byte[len];
+ n = read(b);
+ buf.put(b, 0, n);
+ }
+ return n;
+ }
+
+ @Override
+ public void write(final ByteBuffer buf) throws IOException {
+ write(buf, buf.remaining());
+ }
+
+ @Override
+ public void write(final ByteBuffer buf, final int len)
+ throws IOException
+ {
+ if (buf.hasArray()) {
+ // write directly from the buffer's array
+ write(buf.array(), buf.arrayOffset(), len);
+ }
+ else {
+ // copy into a temporary array, then write
+ final byte[] b = new byte[len];
+ buf.get(b);
+ write(b);
+ }
+ }
+
+ @Override
+ public String readCString() throws IOException {
+ final String line = findString("\0");
+ return line.length() == 0 ? null : line;
+ }
+
+ @Override
+ public String readString(int n) throws IOException {
+ final long avail = length() - offset();
+ if (n > avail) n = (int) avail;
+ final byte[] b = new byte[n];
+ readFully(b);
+ return new String(b, encoding);
+ }
+
+ @Override
+ public String readString(final String lastChars) throws IOException {
+ if (lastChars.length() == 1) return findString(lastChars);
+ final String[] terminators = new String[lastChars.length()];
+ for (int i = 0; i < terminators.length; i++) {
+ terminators[i] = lastChars.substring(i, i + 1);
+ }
+ return findString(terminators);
+ }
+
+ @Override
+ public String findString(final String... terminators) throws IOException {
+ return findString(true, DEFAULT_BLOCK_SIZE, terminators);
+ }
+
+ @Override
+ public String findString(final boolean saveString,
+ final String... terminators) throws IOException
+ {
+ return findString(saveString, DEFAULT_BLOCK_SIZE, terminators);
+ }
+
+ @Override
+ public String findString(final int blockSize, final String... terminators)
+ throws IOException
+ {
+ return findString(true, blockSize, terminators);
+ }
+
+ @Override
+ public String findString(final boolean saveString, final int blockSize,
+ final String... terminators) throws IOException
+ {
+ final StringBuilder out = new StringBuilder();
+ final long startPos = offset();
+ long bytesDropped = 0;
+ final long inputLen = length();
+ long maxLen = inputLen - startPos;
+ final boolean tooLong = saveString && maxLen > MAX_SEARCH_SIZE;
+ if (tooLong) maxLen = MAX_SEARCH_SIZE;
+ boolean match = false;
+ int maxTermLen = 0;
+ for (final String term : terminators) {
+ final int len = term.length();
+ if (len > maxTermLen) maxTermLen = len;
+ }
+
+ @SuppressWarnings("resource")
+ final InputStreamReader in =
+ new InputStreamReader(new DataHandleInputStream(this), getEncoding());
+ final char[] buf = new char[blockSize];
+ long loc = 0;
+ while (loc < maxLen && offset() < length() - 1) {
+ // if we're not saving the string, drop any old, unnecessary output
+ if (!saveString) {
+ final int outLen = out.length();
+ if (outLen >= maxTermLen) {
+ final int dropIndex = outLen - maxTermLen + 1;
+ final String last = out.substring(dropIndex, outLen);
+ out.setLength(0);
+ out.append(last);
+ bytesDropped += dropIndex;
+ }
+ }
+
+ // read block from stream
+ final int r = in.read(buf, 0, blockSize);
+ if (r <= 0) throw new IOException("Cannot read from stream: " + r);
+
+ // append block to output
+ out.append(buf, 0, r);
+
+ // check output, returning smallest possible string
+ int min = Integer.MAX_VALUE, tagLen = 0;
+ for (final String t : terminators) {
+ final int len = t.length();
+ final int start = (int) (loc - bytesDropped - len);
+ final int value = out.indexOf(t, start < 0 ? 0 : start);
+ if (value >= 0 && value < min) {
+ match = true;
+ min = value;
+ tagLen = len;
+ }
+ }
+
+ if (match) {
+ // reset stream to proper location
+ seek(startPos + bytesDropped + min + tagLen);
+
+ // trim output string
+ if (saveString) {
+ out.setLength(min + tagLen);
+ return out.toString();
+ }
+ return null;
+ }
+
+ loc += r;
+ }
+
+ // no match
+ if (tooLong) throw new IOException("Maximum search length reached.");
+ return saveString ? out.toString() : null;
+ }
+
+ // -- InputStream look-alikes --
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public long skip(final long n) throws IOException {
+ if (n < 0) return 0;
+ final long remain = length() - offset();
+ final long num = n < remain ? n : remain;
+ seek(offset() + num);
+ return num;
+ }
+
+}
diff --git a/src/main/java/org/scijava/io/AbstractLocation.java b/src/main/java/org/scijava/io/AbstractLocation.java
new file mode 100644
index 000000000..ce0e12ad6
--- /dev/null
+++ b/src/main/java/org/scijava/io/AbstractLocation.java
@@ -0,0 +1,50 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.net.URI;
+
+/**
+ * Abstract base class for {@link Location} implementations.
+ *
+ * @author Curtis Rueden
+ */
+public abstract class AbstractLocation implements Location {
+
+ // -- Location methods --
+
+ @Override
+ public URI getURI() {
+ return null;
+ }
+
+}
diff --git a/src/main/java/org/scijava/io/BytesLocation.java b/src/main/java/org/scijava/io/BytesLocation.java
new file mode 100644
index 000000000..c58ddb0c6
--- /dev/null
+++ b/src/main/java/org/scijava/io/BytesLocation.java
@@ -0,0 +1,64 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.nio.ByteBuffer;
+
+/**
+ * {@link Location} backed by a {@link ByteBuffer}.
+ *
+ * @author Curtis Rueden
+ */
+public class BytesLocation extends AbstractLocation {
+
+ private final ByteBuffer bytes;
+
+ public BytesLocation(final ByteBuffer bytes) {
+ this.bytes = bytes;
+ }
+
+ public BytesLocation(final byte[] bytes) {
+ this(ByteBuffer.wrap(bytes));
+ }
+
+ public BytesLocation(final byte[] bytes, final int offset, final int length) {
+ this(ByteBuffer.wrap(bytes, offset, length));
+ }
+
+ // -- ByteArrayLocation methods --
+
+ /** Gets the associated {@link ByteBuffer}. */
+ public ByteBuffer getByteBuffer() {
+ return bytes;
+ }
+
+}
diff --git a/src/main/java/org/scijava/io/DataHandle.java b/src/main/java/org/scijava/io/DataHandle.java
new file mode 100644
index 000000000..24162ef77
--- /dev/null
+++ b/src/main/java/org/scijava/io/DataHandle.java
@@ -0,0 +1,226 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.io.Closeable;
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+import org.scijava.plugin.WrapperPlugin;
+
+/**
+ * A data handle is a plugin which provides access to bytes in a data
+ * stream (e.g., files or arrays), identified by a {@link Location}.
+ *
+ * @author Curtis Rueden
+ * @see DataHandleInputStream
+ * @see DataHandleOutputStream
+ */
+public interface DataHandle extends WrapperPlugin,
+ DataInput, DataOutput, Closeable
+{
+
+ /** Returns the current offset in the stream. */
+ long offset() throws IOException;
+
+ /** Returns the length of the stream. */
+ long length() throws IOException;
+
+ /**
+ * Returns the current order of the stream.
+ *
+ * @return See above.
+ */
+ ByteOrder getOrder();
+
+ /** Gets the endianness of the stream. */
+ boolean isLittleEndian();
+
+ /**
+ * Sets the byte order of the stream.
+ *
+ * @param order Order to set.
+ */
+ void setOrder(ByteOrder order);
+
+ /** Sets the endianness of the stream. */
+ void setOrder(final boolean little);
+
+ /** Gets the native encoding of the stream. */
+ String getEncoding();
+
+ /** Sets the native encoding of the stream. */
+ void setEncoding(String encoding);
+
+ /**
+ * Reads up to {@code buf.remaining()} bytes of data from the stream into a
+ * {@link ByteBuffer}.
+ */
+ int read(ByteBuffer buf) throws IOException;
+
+ /**
+ * Reads up to {@code len} bytes of data from the stream into a
+ * {@link ByteBuffer}.
+ *
+ * @return the total number of bytes read into the buffer.
+ */
+ int read(ByteBuffer buf, int len) throws IOException;
+
+ /**
+ * Sets the stream pointer offset, measured from the beginning of the stream,
+ * at which the next read or write occurs.
+ */
+ void seek(long pos) throws IOException;
+
+ /**
+ * Writes up to {@code buf.remaining()} bytes of data from the given
+ * {@link ByteBuffer} to the stream.
+ */
+ void write(ByteBuffer buf) throws IOException;
+
+ /**
+ * Writes up to len bytes of data from the given ByteBuffer to the stream.
+ */
+ void write(ByteBuffer buf, int len) throws IOException;
+
+ /** Reads a string of arbitrary length, terminated by a null char. */
+ String readCString() throws IOException;
+
+ /** Reads a string of up to length n. */
+ String readString(int n) throws IOException;
+
+ /**
+ * Reads a string ending with one of the characters in the given string.
+ *
+ * @see #findString(String...)
+ */
+ String readString(String lastChars) throws IOException;
+
+ /**
+ * Reads a string ending with one of the given terminating substrings.
+ *
+ * @param terminators The strings for which to search.
+ * @return The string from the initial position through the end of the
+ * terminating sequence, or through the end of the stream if no
+ * terminating sequence is found.
+ */
+ String findString(String... terminators) throws IOException;
+
+ /**
+ * Reads or skips a string ending with one of the given terminating
+ * substrings.
+ *
+ * @param saveString Whether to collect the string from the current file
+ * pointer to the terminating bytes, and return it. If false, returns
+ * null.
+ * @param terminators The strings for which to search.
+ * @throws IOException If saveString flag is set and the maximum search length
+ * (512 MB) is exceeded.
+ * @return The string from the initial position through the end of the
+ * terminating sequence, or through the end of the stream if no
+ * terminating sequence is found, or null if saveString flag is unset.
+ */
+ String findString(boolean saveString, String... terminators)
+ throws IOException;
+
+ /**
+ * Reads a string ending with one of the given terminating substrings, using
+ * the specified block size for buffering.
+ *
+ * @param blockSize The block size to use when reading bytes in chunks.
+ * @param terminators The strings for which to search.
+ * @return The string from the initial position through the end of the
+ * terminating sequence, or through the end of the stream if no
+ * terminating sequence is found.
+ */
+ String findString(int blockSize, String... terminators) throws IOException;
+
+ /**
+ * Reads or skips a string ending with one of the given terminating
+ * substrings, using the specified block size for buffering.
+ *
+ * @param saveString Whether to collect the string from the current file
+ * pointer to the terminating bytes, and return it. If false, returns
+ * null.
+ * @param blockSize The block size to use when reading bytes in chunks.
+ * @param terminators The strings for which to search.
+ * @throws IOException If saveString flag is set and the maximum search length
+ * (512 MB) is exceeded.
+ * @return The string from the initial position through the end of the
+ * terminating sequence, or through the end of the stream if no
+ * terminating sequence is found, or null if saveString flag is unset.
+ */
+ String findString(boolean saveString, int blockSize, String... terminators)
+ throws IOException;
+
+ // -- InputStream look-alikes --
+
+ /**
+ * Reads the next byte of data from the stream.
+ *
+ * @return the next byte of data, or -1 if the end of the stream is reached.
+ * @throws IOException - if an I/O error occurs.
+ */
+ int read() throws IOException;
+
+ /**
+ * Reads up to b.length bytes of data from the stream into an array of bytes.
+ *
+ * @return the total number of bytes read into the buffer.
+ */
+ int read(byte[] b) throws IOException;
+
+ /**
+ * Reads up to len bytes of data from the stream into an array of bytes.
+ *
+ * @return the total number of bytes read into the buffer.
+ */
+ int read(byte[] b, int off, int len) throws IOException;
+
+ /**
+ * Skips over and discards {@code n} bytes of data from the stream. The
+ * {@code skip} method may, for a variety of reasons, end up skipping over
+ * some smaller number of bytes, possibly {@code 0}. This may result from any
+ * of a number of conditions; reaching end of file before {@code n} bytes have
+ * been skipped is only one possibility. The actual number of bytes skipped is
+ * returned. If {@code n} is negative, no bytes are skipped.
+ *
+ * @param n - the number of bytes to be skipped.
+ * @return the actual number of bytes skipped.
+ * @throws IOException - if an I/O error occurs.
+ */
+ long skip(long n) throws IOException;
+
+}
diff --git a/src/main/java/org/scijava/io/DataHandleInputStream.java b/src/main/java/org/scijava/io/DataHandleInputStream.java
new file mode 100644
index 000000000..1d341f449
--- /dev/null
+++ b/src/main/java/org/scijava/io/DataHandleInputStream.java
@@ -0,0 +1,119 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * {@link InputStream} backed by a {@link DataHandle}.
+ *
+ * @author Curtis Rueden
+ * @author Melissa Linkert
+ */
+public class DataHandleInputStream extends InputStream {
+
+ // -- Fields --
+
+ private final DataHandle handle;
+
+ private long mark = -1;
+
+ // -- Constructors --
+
+ /** Creates an input stream around the given {@link DataHandle}. */
+ public DataHandleInputStream(final DataHandle handle) {
+ this.handle = handle;
+ }
+
+ // -- DataHandleInputStream methods --
+
+ public DataHandle getDataHandle() {
+ return handle;
+ }
+
+ // -- InputStream methods --
+
+ @Override
+ public int read() throws IOException {
+ return handle.read();
+ }
+
+ @Override
+ public int read(final byte[] array, final int offset, final int n)
+ throws IOException
+ {
+ return handle.read(array, offset, n);
+ }
+
+ @Override
+ public long skip(final long n) throws IOException {
+ return handle.skip(n);
+ }
+
+ @Override
+ public int available() throws IOException {
+ long remain = handle.length() - handle.offset();
+ if (remain > Integer.MAX_VALUE) remain = Integer.MAX_VALUE;
+ return (int) remain;
+ }
+
+ @Override
+ public synchronized void mark(final int readLimit) {
+ try {
+ mark = handle.offset();
+ }
+ catch (final IOException exc) {
+ throw new IllegalStateException(exc);
+ }
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ if (mark < 0) throw new IOException("No mark set");
+ handle.seek(mark);
+ }
+
+ @Override
+ public boolean markSupported() {
+ return true;
+ }
+
+ // -- Closeable methods --
+
+ @Override
+ public void close() throws IOException {
+ handle.close();
+ mark = -1;
+ }
+
+}
diff --git a/src/main/java/org/scijava/io/DataHandleOutputStream.java b/src/main/java/org/scijava/io/DataHandleOutputStream.java
new file mode 100644
index 000000000..42ec2d104
--- /dev/null
+++ b/src/main/java/org/scijava/io/DataHandleOutputStream.java
@@ -0,0 +1,89 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * {@link OutputStream} backed by a {@link DataHandle}.
+ *
+ * @author Curtis Rueden
+ * @author Melissa Linkert
+ */
+public class DataHandleOutputStream extends OutputStream {
+
+ // -- Fields --
+
+ private final DataHandle handle;
+
+ // -- Constructor --
+
+ /** Creates an output stream around the given {@link DataHandle}. */
+ public DataHandleOutputStream(final DataHandle handle) {
+ this.handle = handle;
+ }
+
+ // -- OutputStream methods --
+
+ @Override
+ public void write(final int i) throws IOException {
+ handle.write(i);
+ }
+
+ @Override
+ public void write(final byte[] b) throws IOException {
+ handle.write(b);
+ }
+
+ @Override
+ public void write(final byte[] b, final int off, final int len)
+ throws IOException
+ {
+ handle.write(b, off, len);
+ }
+
+ // -- Closeable methods --
+
+ @Override
+ public void close() throws IOException {
+ handle.close();
+ }
+
+ // -- Flushable methods --
+
+ @Override
+ public void flush() throws IOException {
+ // NB: No action needed.
+ }
+
+}
diff --git a/src/main/java/org/scijava/io/DataHandleService.java b/src/main/java/org/scijava/io/DataHandleService.java
new file mode 100644
index 000000000..eaf93e28c
--- /dev/null
+++ b/src/main/java/org/scijava/io/DataHandleService.java
@@ -0,0 +1,49 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import org.scijava.plugin.WrapperService;
+import org.scijava.service.SciJavaService;
+
+/**
+ * Interface for low-level data I/O: reading and writing bytes using
+ * {@link DataHandle}s.
+ *
+ * @author Curtis Rueden
+ * @see IOService
+ * @see Location
+ */
+public interface DataHandleService extends
+ WrapperService>, SciJavaService
+{
+ // NB: Marker interface.
+}
diff --git a/src/main/java/org/scijava/io/DefaultDataHandleService.java b/src/main/java/org/scijava/io/DefaultDataHandleService.java
new file mode 100644
index 000000000..2be00711f
--- /dev/null
+++ b/src/main/java/org/scijava/io/DefaultDataHandleService.java
@@ -0,0 +1,64 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import org.scijava.plugin.AbstractWrapperService;
+import org.scijava.plugin.Plugin;
+import org.scijava.service.Service;
+
+/**
+ * Default implementation of {@link DataHandleService}.
+ *
+ * @author Curtis Rueden
+ */
+@Plugin(type = Service.class)
+public class DefaultDataHandleService extends
+ AbstractWrapperService> implements
+ DataHandleService
+{
+
+ // -- PTService methods --
+
+ @Override
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public Class> getPluginType() {
+ return (Class) DataHandle.class;
+ }
+
+ // -- Typed methods --
+
+ @Override
+ public Class getType() {
+ return Location.class;
+ }
+
+}
diff --git a/src/main/java/org/scijava/io/DefaultRecentFileService.java b/src/main/java/org/scijava/io/DefaultRecentFileService.java
index d9445199d..ce0ddd9e6 100644
--- a/src/main/java/org/scijava/io/DefaultRecentFileService.java
+++ b/src/main/java/org/scijava/io/DefaultRecentFileService.java
@@ -62,13 +62,9 @@
*
* Behavior: There is a limited number of files presented (maxFilesShown),
* regardless of the list length. When a file is opened, its path is added to
- * the top of the list. If an image has been saved as a new file, its path is
- * added to the top of the list.
+ * the top of the list. If data has been saved as a new file, its path is added
+ * to the top of the list.
*
- *
- * - add(String path)
- * - remove(String path)
- *
*
* @author Grant Harris
* @author Curtis Rueden
@@ -115,7 +111,7 @@ public void add(final String path) {
recentFiles.add(path);
// persist the updated list
- prefService.putList(recentFiles, RECENT_FILES_KEY);
+ saveList();
if (present) {
// path already present; update linked module info
@@ -139,7 +135,7 @@ public boolean remove(final String path) {
final boolean success = recentFiles.remove(path);
// persist the updated list
- prefService.putList(recentFiles, RECENT_FILES_KEY);
+ saveList();
// remove linked module info
final ModuleInfo info = recentModules.remove(path);
@@ -168,7 +164,7 @@ public List getRecentFiles() {
@Override
public void initialize() {
- recentFiles = prefService.getList(RECENT_FILES_KEY);
+ loadList();
recentModules = new HashMap();
for (final String path : recentFiles) {
recentModules.put(path, createInfo(path));
@@ -187,6 +183,16 @@ protected void onEvent(final IOEvent event) {
// -- Helper methods --
+ /** Loads the list of recent files from persistent storage. */
+ private void loadList() {
+ recentFiles = prefService.getList(RECENT_FILES_KEY);
+ }
+
+ /** Saves the list of recent files to persistent storage. */
+ private void saveList() {
+ prefService.putList(recentFiles, RECENT_FILES_KEY);
+ }
+
/** Creates a {@link ModuleInfo} to reopen data at the given path. */
private ModuleInfo createInfo(final String path) {
// CTR FIXME: Avoid circular (and compile-time-unsafe) dependency between
diff --git a/src/main/java/org/scijava/io/FileHandle.java b/src/main/java/org/scijava/io/FileHandle.java
new file mode 100644
index 000000000..045c254fd
--- /dev/null
+++ b/src/main/java/org/scijava/io/FileHandle.java
@@ -0,0 +1,285 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import org.scijava.plugin.Plugin;
+
+/**
+ * {@link DataHandle} for a {@link FileLocation}.
+ *
+ * @author Curtis Rueden
+ */
+@Plugin(type = DataHandle.class)
+public class FileHandle extends AbstractDataHandle {
+
+ // -- Fields --
+
+ /** The {@link RandomAccessFile} backing this file handle. */
+ private RandomAccessFile raf;
+
+ /** The mode of the {@link RandomAccessFile}. */
+ private String mode = "rw";
+
+ // -- FileHandle methods --
+
+ /** Gets the random access file object backing this FileHandle. */
+ public RandomAccessFile getRandomAccessFile() throws IOException {
+ return raf();
+ }
+
+ public String getMode() {
+ return mode;
+ }
+
+ public void setMode(final String mode) {
+ if (raf != null) {
+ throw new IllegalStateException("File already initialized");
+ }
+ this.mode = mode;
+ }
+
+ // -- DataHandle methods --
+
+ @Override
+ public long offset() throws IOException {
+ return raf().getFilePointer();
+ }
+
+ @Override
+ public long length() throws IOException {
+ return raf().length();
+ }
+
+ @Override
+ public int read() throws IOException {
+ return raf().read();
+ }
+
+ @Override
+ public int read(final byte[] b) throws IOException {
+ return raf().read(b);
+ }
+
+ @Override
+ public int read(final byte[] b, final int off, final int len)
+ throws IOException
+ {
+ return raf().read(b, off, len);
+ }
+
+ @Override
+ public void seek(final long pos) throws IOException {
+ raf().seek(pos);
+ }
+
+ // -- DataInput methods --
+
+ @Override
+ public boolean readBoolean() throws IOException {
+ return raf().readBoolean();
+ }
+
+ @Override
+ public byte readByte() throws IOException {
+ return raf().readByte();
+ }
+
+ @Override
+ public char readChar() throws IOException {
+ return raf().readChar();
+ }
+
+ @Override
+ public double readDouble() throws IOException {
+ return raf().readDouble();
+ }
+
+ @Override
+ public float readFloat() throws IOException {
+ return raf().readFloat();
+ }
+
+ @Override
+ public void readFully(final byte[] b) throws IOException {
+ raf().readFully(b);
+ }
+
+ @Override
+ public void readFully(final byte[] b, final int off, final int len)
+ throws IOException
+ {
+ raf().readFully(b, off, len);
+ }
+
+ @Override
+ public int readInt() throws IOException {
+ return raf().readInt();
+ }
+
+ @Override
+ public String readLine() throws IOException {
+ return raf().readLine();
+ }
+
+ @Override
+ public long readLong() throws IOException {
+ return raf().readLong();
+ }
+
+ @Override
+ public short readShort() throws IOException {
+ return raf().readShort();
+ }
+
+ @Override
+ public int readUnsignedByte() throws IOException {
+ return raf().readUnsignedByte();
+ }
+
+ @Override
+ public int readUnsignedShort() throws IOException {
+ return raf().readUnsignedShort();
+ }
+
+ @Override
+ public String readUTF() throws IOException {
+ return raf().readUTF();
+ }
+
+ @Override
+ public int skipBytes(final int n) throws IOException {
+ return raf().skipBytes(n);
+ }
+
+ // -- DataOutput methods --
+
+ @Override
+ public void write(final byte[] b) throws IOException {
+ raf().write(b);
+ }
+
+ @Override
+ public void write(final byte[] b, final int off, final int len)
+ throws IOException
+ {
+ raf().write(b, off, len);
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ raf().write(b);
+ }
+
+ @Override
+ public void writeBoolean(final boolean v) throws IOException {
+ raf().writeBoolean(v);
+ }
+
+ @Override
+ public void writeByte(final int v) throws IOException {
+ raf().writeByte(v);
+ }
+
+ @Override
+ public void writeBytes(final String s) throws IOException {
+ raf().writeBytes(s);
+ }
+
+ @Override
+ public void writeChar(final int v) throws IOException {
+ raf().writeChar(v);
+ }
+
+ @Override
+ public void writeChars(final String s) throws IOException {
+ raf().writeChars(s);
+ }
+
+ @Override
+ public void writeDouble(final double v) throws IOException {
+ raf().writeDouble(v);
+ }
+
+ @Override
+ public void writeFloat(final float v) throws IOException {
+ raf().writeFloat(v);
+ }
+
+ @Override
+ public void writeInt(final int v) throws IOException {
+ raf().writeInt(v);
+ }
+
+ @Override
+ public void writeLong(final long v) throws IOException {
+ raf().writeLong(v);
+ }
+
+ @Override
+ public void writeShort(final int v) throws IOException {
+ raf().writeShort(v);
+ }
+
+ @Override
+ public void writeUTF(final String str) throws IOException {
+ raf().writeUTF(str);
+ }
+
+ // -- Closeable methods --
+
+ @Override
+ public void close() throws IOException {
+ raf().close();
+ }
+
+ // -- Typed methods --
+
+ @Override
+ public Class getType() {
+ return FileLocation.class;
+ }
+
+ // -- Helper methods --
+
+ private RandomAccessFile raf() throws IOException {
+ if (raf == null) initRAF();
+ return raf;
+ }
+
+ private synchronized void initRAF() throws IOException {
+ raf = new RandomAccessFile(get().getFile(), getMode());
+ }
+
+}
diff --git a/src/main/java/org/scijava/io/FileLocation.java b/src/main/java/org/scijava/io/FileLocation.java
new file mode 100644
index 000000000..757c4b738
--- /dev/null
+++ b/src/main/java/org/scijava/io/FileLocation.java
@@ -0,0 +1,68 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.io.File;
+import java.net.URI;
+
+/**
+ * {@link Location} backed by a {@link File} on disk.
+ *
+ * @author Curtis Rueden
+ */
+public class FileLocation extends AbstractLocation {
+
+ private final File file;
+
+ public FileLocation(final File file) {
+ this.file = file;
+ }
+
+ public FileLocation(final String path) {
+ this(new File(path));
+ }
+
+ // -- FileLocation methods --
+
+ /** Gets the associated {@link File}. */
+ public File getFile() {
+ return file;
+ }
+
+ // -- Location methods --
+
+ @Override
+ public URI getURI() {
+ return getFile().toURI();
+ }
+
+}
diff --git a/src/main/java/org/scijava/io/IOService.java b/src/main/java/org/scijava/io/IOService.java
index 04d980717..b82fd0f1e 100644
--- a/src/main/java/org/scijava/io/IOService.java
+++ b/src/main/java/org/scijava/io/IOService.java
@@ -37,9 +37,11 @@
import org.scijava.service.SciJavaService;
/**
- * Interface for data I/O operations: opening and saving data.
+ * Interface for high-level data I/O: opening and saving data.
*
* @author Curtis Rueden
+ * @see DataHandleService
+ * @see Location
*/
public interface IOService extends HandlerService>,
SciJavaService
diff --git a/src/main/java/org/scijava/io/Location.java b/src/main/java/org/scijava/io/Location.java
new file mode 100644
index 000000000..4f08be1b9
--- /dev/null
+++ b/src/main/java/org/scijava/io/Location.java
@@ -0,0 +1,58 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.net.URI;
+
+/**
+ * A location is a data descriptor, such as a file on disk, a remote
+ * URL, or a database connection.
+ *
+ * Analogous to a uniform
+ * resource identifier ({@link URI}), a location identifies where
+ * the data resides, without necessarily specifying how to access that
+ * data. The {@link DataHandle} interface defines a plugin that knows how to
+ * provide a stream of bytes for a particular kind of location.
+ *
+ *
+ * @author Curtis Rueden
+ */
+public interface Location {
+
+ /**
+ * Gets the location expressed as a {@link URI}, or null if the location
+ * cannot be expressed as such.
+ */
+ URI getURI();
+
+}
diff --git a/src/main/java/org/scijava/io/URILocation.java b/src/main/java/org/scijava/io/URILocation.java
new file mode 100644
index 000000000..87f9e7d4d
--- /dev/null
+++ b/src/main/java/org/scijava/io/URILocation.java
@@ -0,0 +1,126 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URLDecoder;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.scijava.log.LogService;
+import org.scijava.plugin.Parameter;
+
+/**
+ * {@link Location} backed by a {@link URI} string.
+ *
+ * @author Curtis Rueden
+ */
+public class URILocation extends AbstractLocation {
+
+ @Parameter
+ private LogService log;
+
+ private final URI uri;
+
+ public URILocation(final URI uri) {
+ this.uri = uri;
+ }
+
+ public URILocation(final String uriPath) throws URISyntaxException {
+ this(new URI(uriPath));
+ }
+
+ // -- URILocation methods --
+
+ public Map getQueryMap() {
+ return decodeQuery(getURI().getRawQuery());
+ }
+
+ public String getQueryValue(final String key) {
+ return getQueryMap().get(key);
+ }
+
+ // FIXME: look up whether anyone has created a mutatable URI class,
+ // with individual setters for the various parts. Otherwise, we'll
+ // have to handle it here!
+
+ // -- Location methods --
+
+ @Override
+ public URI getURI() {
+ return uri;
+ }
+
+ // -- Helper methods --
+
+ /**
+ * Decodes a query string of ampersand-separated key/value pairs. E.g.:
+ * {@code apples=yummy&bananas=delicious&grapefruits=scrumptious}.
+ *
+ * @param query The query string to decode.
+ * @return A map of the decoded key/value pairs.
+ */
+ private Map decodeQuery(final String query) {
+ final Map map = new LinkedHashMap();
+ if (query == null) return map;
+ for (final String param : query.split("&")) {
+ final int equals = param.indexOf("=");
+ if (equals < 0) {
+ map.put(decode(param), "true");
+ }
+ else {
+ final String key = decode(param.substring(0, equals));
+ final String value = decode(param.substring(equals + 1));
+ map.put(key, value);
+ }
+ }
+ return map;
+ }
+
+ /**
+ * Decodes a single uuencoded string.
+ *
+ * @see URLDecoder
+ */
+ private String decode(final String s) {
+ // http://stackoverflow.com/a/6926987
+ try {
+ return URLDecoder.decode(s.replace("+", "%2B"), "UTF-8");
+ }
+ catch (UnsupportedEncodingException exc) {
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/scijava/io/URLLocation.java b/src/main/java/org/scijava/io/URLLocation.java
new file mode 100644
index 000000000..4148d668a
--- /dev/null
+++ b/src/main/java/org/scijava/io/URLLocation.java
@@ -0,0 +1,75 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+/**
+ * {@link Location} backed by a {@link URL}.
+ *
+ * @author Curtis Rueden
+ */
+public class URLLocation extends AbstractLocation {
+
+ /** The URL backing this location. */
+ private final URL url;
+
+ public URLLocation(final URL url) {
+ this.url = url;
+ }
+
+ // -- URLLocation methods --
+
+ /** Gets the associated {@link URL}. */
+ public URL getURL() {
+ return url;
+ }
+
+ // -- Location methods --
+
+ /**
+ * Gets the associated {@link URI}, or null if this URL is not formatted
+ * strictly according to to RFC2396 and cannot be converted to a URI.
+ */
+ @Override
+ public URI getURI() {
+ try {
+ return getURL().toURI();
+ }
+ catch (final URISyntaxException exc) {
+ return null;
+ }
+ }
+
+}
diff --git a/src/main/java/org/scijava/plugin/AbstractPTService.java b/src/main/java/org/scijava/plugin/AbstractPTService.java
index 2f7d89704..9c31eb2fc 100644
--- a/src/main/java/org/scijava/plugin/AbstractPTService.java
+++ b/src/main/java/org/scijava/plugin/AbstractPTService.java
@@ -60,4 +60,13 @@ public List> getPlugins() {
return pluginService.getPluginsOfType(getPluginType());
}
+ @Override
+ public P create(final Class
pluginClass) {
+ final PluginInfo info =
+ pluginService.getPlugin(pluginClass, getPluginType());
+ @SuppressWarnings("unchecked")
+ final P plugin = (P) pluginService.createInstance(info);
+ return plugin;
+ }
+
}
diff --git a/src/main/java/org/scijava/plugin/AbstractSingletonService.java b/src/main/java/org/scijava/plugin/AbstractSingletonService.java
index 83852fa8a..cc19bacab 100644
--- a/src/main/java/org/scijava/plugin/AbstractSingletonService.java
+++ b/src/main/java/org/scijava/plugin/AbstractSingletonService.java
@@ -80,6 +80,15 @@ public P getInstance(final Class
pluginClass) {
return (P) instanceMap.get(pluginClass);
}
+ // -- PTService methods --
+
+ @Override
+ public
P create(final Class
pluginClass) {
+ throw new UnsupportedOperationException(
+ "Cannot create singleton plugin instance. "
+ + "Use getInstance(Class) instead.");
+ }
+
// -- Service methods --
@Override
diff --git a/src/main/java/org/scijava/plugin/AbstractWrapperService.java b/src/main/java/org/scijava/plugin/AbstractWrapperService.java
index c03189b28..85e2cea88 100644
--- a/src/main/java/org/scijava/plugin/AbstractWrapperService.java
+++ b/src/main/java/org/scijava/plugin/AbstractWrapperService.java
@@ -51,11 +51,8 @@ public abstract class AbstractWrapperService
>
@Override
public PT create(final D data) {
- final PT instance = wrap(data);
- if (instance == null) {
- throw new IllegalArgumentException("No compatible " +
- getPluginType().getSimpleName() + " for data object: " + data);
- }
+ final PT instance = findWrapper(data);
+ if (instance != null) instance.set(data);
return instance;
}
@@ -73,12 +70,12 @@ public void initialize() {
@Override
public boolean supports(final DT data) {
- return wrap(data) != null;
+ return findWrapper(data) != null;
}
// -- Helper methods --
- private PT wrap(final D data) {
+ private PT findWrapper(final D data) {
for (final PluginInfo plugin : getPlugins()) {
final PT instance = getPluginService().createInstance(plugin);
if (instance.supports(data)) return instance;
diff --git a/src/main/java/org/scijava/plugin/PTService.java b/src/main/java/org/scijava/plugin/PTService.java
index 8c9d5db7e..8abc4be7d 100644
--- a/src/main/java/org/scijava/plugin/PTService.java
+++ b/src/main/java/org/scijava/plugin/PTService.java
@@ -95,4 +95,7 @@ public interface PTService extends Service {
/** Gets the type of plugins managed by this service. */
Class getPluginType();
+ /** Creates an instance of the given plugin class. */
+ P create(final Class
pluginClass);
+
}
diff --git a/src/main/java/org/scijava/plugin/WrapperService.java b/src/main/java/org/scijava/plugin/WrapperService.java
index 6d231e4ef..e46882387 100644
--- a/src/main/java/org/scijava/plugin/WrapperService.java
+++ b/src/main/java/org/scijava/plugin/WrapperService.java
@@ -59,8 +59,8 @@ public interface WrapperService
> extends
/**
* Creates a new plugin instance wrapping the given associated data object.
*
- * @throws IllegalArgumentException if the data is not compatible with any
- * available plugin.
+ * @return An appropriate plugin instance, or null if the data is not
+ * compatible with any available plugin.
*/
PT create(D data);
diff --git a/src/main/java/org/scijava/ui/console/ConsolePane.java b/src/main/java/org/scijava/ui/console/ConsolePane.java
index eb4fdcbcd..34a2c0ccf 100644
--- a/src/main/java/org/scijava/ui/console/ConsolePane.java
+++ b/src/main/java/org/scijava/ui/console/ConsolePane.java
@@ -1,9 +1,10 @@
/*
* #%L
- * SciJava UI components for Java Swing.
+ * SciJava Common shared library for SciJava software.
* %%
- * Copyright (C) 2010 - 2015 Board of Regents of the University of
- * Wisconsin-Madison.
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
diff --git a/src/main/java/org/scijava/util/ArrayUtils.java b/src/main/java/org/scijava/util/ArrayUtils.java
index a0f4ab649..aa2d6d47d 100644
--- a/src/main/java/org/scijava/util/ArrayUtils.java
+++ b/src/main/java/org/scijava/util/ArrayUtils.java
@@ -29,6 +29,35 @@
* #L%
*/
+// Portions of this class were derived from the loci.common.DataTools class of
+// the Bio-Formats library, licensed according to Simplified BSD, as follows:
+//
+// Copyright (C) 2005 - 2015 Open Microscopy Environment:
+// - Board of Regents of the University of Wisconsin-Madison
+// - Glencoe Software, Inc.
+// - University of Dundee
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. 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.
+//
+// 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 HOLDERS 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 org.scijava.util;
import java.util.Collection;
@@ -38,6 +67,9 @@
* Utility class for creating and manipulating {@link PrimitiveArray} instances.
*
* @author Mark Hiner
+ * @author Curtis Rueden
+ * @author Melissa Linkert
+ * @author Chris Allan
*/
public final class ArrayUtils {
@@ -91,4 +123,246 @@ public static Collection> toCollection(final Object value) {
return list;
}
+ /**
+ * Allocates a 1-dimensional byte array matching the product of the given
+ * sizes.
+ *
+ * @param sizes list of sizes from which to allocate the array
+ * @return a byte array of the appropriate size
+ * @throws IllegalArgumentException if the total size exceeds 2GB, which is
+ * the maximum size of an array in Java; or if any size argument is
+ * zero or negative
+ */
+ public static byte[] allocate(final long... sizes)
+ throws IllegalArgumentException
+ {
+ if (sizes == null) return null;
+ if (sizes.length == 0) return new byte[0];
+ final int total = safeMultiply32(sizes);
+ return new byte[total];
+ }
+
+ /**
+ * Checks that the product of the given sizes does not exceed the 32-bit
+ * integer limit (i.e., {@link Integer#MAX_VALUE}).
+ *
+ * @param sizes list of sizes from which to compute the product
+ * @return the product of the given sizes
+ * @throws IllegalArgumentException if the total size exceeds 2GiB, which is
+ * the maximum size of an int in Java; or if any size argument is
+ * zero or negative
+ */
+ public static int safeMultiply32(final long... sizes)
+ throws IllegalArgumentException
+ {
+ if (sizes.length == 0) return 0;
+ long total = 1;
+ for (final long size : sizes) {
+ if (size < 1) {
+ throw new IllegalArgumentException("Invalid array size: " +
+ sizeAsProduct(sizes));
+ }
+ total *= size;
+ if (total > Integer.MAX_VALUE) {
+ throw new IllegalArgumentException("Array size too large: " +
+ sizeAsProduct(sizes));
+ }
+ }
+ // NB: The downcast to int is safe here, due to the checks above.
+ return (int) total;
+ }
+
+ /**
+ * Checks that the product of the given sizes does not exceed the 64-bit
+ * integer limit (i.e., {@link Long#MAX_VALUE}).
+ *
+ * @param sizes list of sizes from which to compute the product
+ * @return the product of the given sizes
+ * @throws IllegalArgumentException if the total size exceeds 8EiB, which is
+ * the maximum size of a long in Java; or if any size argument is
+ * zero or negative
+ */
+ public static long safeMultiply64(final long... sizes)
+ throws IllegalArgumentException
+ {
+ if (sizes.length == 0) return 0;
+ long total = 1;
+ for (final long size : sizes) {
+ if (size < 1) {
+ throw new IllegalArgumentException("Invalid array size: " +
+ sizeAsProduct(sizes));
+ }
+ if (willOverflow(total, size)) {
+ throw new IllegalArgumentException("Array size too large: " +
+ sizeAsProduct(sizes));
+ }
+ total *= size;
+ }
+ return total;
+ }
+
+ /** Returns true if the given value is contained in the specified array. */
+ public static boolean contains(final byte[] array, final byte value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /** Returns true if the given value is contained in the specified array. */
+ public static boolean contains(final boolean[] array, final boolean value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /** Returns true if the given value is contained in the specified array. */
+ public static boolean contains(final char[] array, final char value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /** Returns true if the given value is contained in the specified array. */
+ public static boolean contains(final double[] array, final double value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /** Returns true if the given value is contained in the specified array. */
+ public static boolean contains(final float[] array, final float value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /** Returns true if the given value is contained in the specified array. */
+ public static boolean contains(final int[] array, final int value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /** Returns true if the given value is contained in the specified array. */
+ public static boolean contains(final long[] array, final long value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /** Returns true if the given value is contained in the specified array. */
+ public static boolean contains(final short[] array, final short value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /** Returns true if the given value is contained in the specified array. */
+ public static boolean contains(final Object[] array, final Object value) {
+ return indexOf(array, value) != -1;
+ }
+
+ /**
+ * Returns the index of the first occurrence of the given value in the given
+ * array. If the value is not in the array, returns -1.
+ */
+ public static int indexOf(final boolean[] array, final boolean value) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == value) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index of the first occurrence of the given value in the given
+ * array. If the value is not in the array, returns -1.
+ */
+ public static int indexOf(final byte[] array, final byte value) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == value) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index of the first occurrence of the given value in the given
+ * array. If the value is not in the array, returns -1.
+ */
+ public static int indexOf(final char[] array, final char value) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == value) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index of the first occurrence of the given value in the given
+ * array. If the value is not in the array, returns -1.
+ */
+ public static int indexOf(final double[] array, final double value) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == value) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index of the first occurrence of the given value in the given
+ * array. If the value is not in the array, returns -1.
+ */
+ public static int indexOf(final float[] array, final float value) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == value) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index of the first occurrence of the given value in the given
+ * array. If the value is not in the array, returns -1.
+ */
+ public static int indexOf(final int[] array, final int value) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == value) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index of the first occurrence of the given value in the given
+ * array. If the value is not in the array, returns -1.
+ */
+ public static int indexOf(final long[] array, final long value) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == value) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index of the first occurrence of the given value in the given
+ * array. If the value is not in the array, returns -1.
+ */
+ public static int indexOf(final short[] array, final short value) {
+ for (int i = 0; i < array.length; i++) {
+ if (array[i] == value) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index of the first occurrence of the given value in the given
+ * Object array. If the value is not in the array, returns -1.
+ */
+ public static int indexOf(final Object[] array, final Object value) {
+ for (int i = 0; i < array.length; i++) {
+ if (value == null) {
+ if (array[i] == null) return i;
+ }
+ else if (value.equals(array[i])) return i;
+ }
+ return -1;
+ }
+
+ // -- Helper methods --
+
+ private static String sizeAsProduct(final long... sizes) {
+ final StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (final long size : sizes) {
+ if (first) first = false;
+ else sb.append(" x ");
+ sb.append(size);
+ }
+ return sb.toString();
+ }
+
+ private static boolean willOverflow(final long v1, final long v2) {
+ return Long.MAX_VALUE / v1 < v2;
+ }
+
}
diff --git a/src/main/java/org/scijava/util/Bytes.java b/src/main/java/org/scijava/util/Bytes.java
new file mode 100644
index 000000000..f82784d57
--- /dev/null
+++ b/src/main/java/org/scijava/util/Bytes.java
@@ -0,0 +1,816 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+// This class was derived from the loci.common.DataTools class of the
+// Bio-Formats library, licensed according to Simplified BSD, as follows:
+//
+// Copyright (C) 2005 - 2015 Open Microscopy Environment:
+// - Board of Regents of the University of Wisconsin-Madison
+// - Glencoe Software, Inc.
+// - University of Dundee
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. 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.
+//
+// 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 HOLDERS 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 org.scijava.util;
+
+/**
+ * Useful methods for reading, writing, decoding and converting {@code byte}s
+ * and {@code byte} arrays.
+ *
+ * @author Curtis Rueden
+ * @author Melissa Linkert
+ * @author Chris Allan
+ */
+public final class Bytes {
+
+ private Bytes() {
+ // NB: prevent instantiation of utility class.
+ }
+
+ // -- Word decoding - bytes to primitive types --
+
+ /**
+ * Translates up to the first {@code len} bytes of a {@code byte} array beyond
+ * the given offset to a {@code short}. If there are fewer than {@code len}
+ * bytes available, the MSBs are all assumed to be zero (regardless of
+ * endianness).
+ */
+ public static short toShort(final byte[] bytes, final int off, int len,
+ final boolean little)
+ {
+ if (bytes.length - off < len) len = bytes.length - off;
+ short total = 0;
+ for (int i = 0, ndx = off; i < len; i++, ndx++) {
+ total |=
+ (bytes[ndx] < 0 ? 256 + bytes[ndx] : (int) bytes[ndx]) << ((little ? i
+ : len - i - 1) * 8);
+ }
+ return total;
+ }
+
+ /**
+ * Translates up to the first 2 bytes of a {@code byte} array beyond the given
+ * offset to a {@code short}. If there are fewer than 2 bytes available the
+ * MSBs are all assumed to be zero (regardless of endianness).
+ */
+ public static short toShort(final byte[] bytes, final int off,
+ final boolean little)
+ {
+ return toShort(bytes, off, 2, little);
+ }
+
+ /**
+ * Translates up to the first 2 bytes of a {@code byte} array to a
+ * {@code short}. If there are fewer than 2 bytes available, the MSBs are all
+ * assumed to be zero (regardless of endianness).
+ */
+ public static short toShort(final byte[] bytes, final boolean little) {
+ return toShort(bytes, 0, 2, little);
+ }
+
+ /**
+ * Translates up to the first {@code len} bytes of a {@code byte} array beyond
+ * the given offset to a {@code short}. If there are fewer than {@code len}
+ * bytes available, the MSBs are all assumed to be zero (regardless of
+ * endianness).
+ */
+ public static short toShort(final short[] bytes, final int off, int len,
+ final boolean little)
+ {
+ if (bytes.length - off < len) len = bytes.length - off;
+ short total = 0;
+ for (int i = 0, ndx = off; i < len; i++, ndx++) {
+ total |= bytes[ndx] << ((little ? i : len - i - 1) * 8);
+ }
+ return total;
+ }
+
+ /**
+ * Translates up to the first 2 bytes of a {@code byte} array beyond the given
+ * offset to a {@code short}. If there are fewer than 2 bytes available, the
+ * MSBs are all assumed to be zero (regardless of endianness).
+ */
+ public static short toShort(final short[] bytes, final int off,
+ final boolean little)
+ {
+ return toShort(bytes, off, 2, little);
+ }
+
+ /**
+ * Translates up to the first 2 bytes of a {@code byte} array to a
+ * {@code short}. If there are fewer than 2 bytes available, the MSBs are all
+ * assumed to be zero (regardless of endianness).
+ */
+ public static short toShort(final short[] bytes, final boolean little) {
+ return toShort(bytes, 0, 2, little);
+ }
+
+ /**
+ * Translates up to the first {@code len} bytes of a {@code byte} array beyond
+ * the given offset to an {@code int}. If there are fewer than {@code len}
+ * bytes available, the MSBs are all assumed to be zero (regardless of
+ * endianness).
+ */
+ public static int toInt(final byte[] bytes, final int off, int len,
+ final boolean little)
+ {
+ if (bytes.length - off < len) len = bytes.length - off;
+ int total = 0;
+ for (int i = 0, ndx = off; i < len; i++, ndx++) {
+ total |=
+ (bytes[ndx] < 0 ? 256 + bytes[ndx] : (int) bytes[ndx]) << ((little ? i
+ : len - i - 1) * 8);
+ }
+ return total;
+ }
+
+ /**
+ * Translates up to the first 4 bytes of a {@code byte} array beyond the given
+ * offset to an {@code int}. If there are fewer than 4 bytes available, the
+ * MSBs are all assumed to be zero (regardless of endianness).
+ */
+ public static int toInt(final byte[] bytes, final int off,
+ final boolean little)
+ {
+ return toInt(bytes, off, 4, little);
+ }
+
+ /**
+ * Translates up to the first 4 bytes of a {@code byte} array to an
+ * {@code int}. If there are fewer than 4 bytes available, the MSBs are all
+ * assumed to be zero (regardless of endianness).
+ */
+ public static int toInt(final byte[] bytes, final boolean little) {
+ return toInt(bytes, 0, 4, little);
+ }
+
+ /**
+ * Translates up to the first {@code len} bytes of a {@code byte} array beyond
+ * the given offset to an {@code int}. If there are fewer than {@code len}
+ * bytes available, the MSBs are all assumed to be zero (regardless of
+ * endianness).
+ */
+ public static int toInt(final short[] bytes, final int off, int len,
+ final boolean little)
+ {
+ if (bytes.length - off < len) len = bytes.length - off;
+ int total = 0;
+ for (int i = 0, ndx = off; i < len; i++, ndx++) {
+ total |= bytes[ndx] << ((little ? i : len - i - 1) * 8);
+ }
+ return total;
+ }
+
+ /**
+ * Translates up to the first 4 bytes of a {@code byte} array beyond the given
+ * offset to an {@code int}. If there are fewer than 4 bytes available, the
+ * MSBs are all assumed to be zero (regardless of endianness).
+ */
+ public static int toInt(final short[] bytes, final int off,
+ final boolean little)
+ {
+ return toInt(bytes, off, 4, little);
+ }
+
+ /**
+ * Translates up to the first 4 bytes of a {@code byte} array to an
+ * {@code int}. If there are fewer than 4 bytes available, the MSBs are all
+ * assumed to be zero (regardless of endianness).
+ */
+ public static int toInt(final short[] bytes, final boolean little) {
+ return toInt(bytes, 0, 4, little);
+ }
+
+ /**
+ * Translates up to the first {@code len} bytes of a {@code byte} array beyond
+ * the given offset to a {@code float}. If there are fewer than {@code len}
+ * bytes available, the MSBs are all assumed to be zero (regardless of
+ * endianness).
+ */
+ public static float toFloat(final byte[] bytes, final int off, final int len,
+ final boolean little)
+ {
+ return Float.intBitsToFloat(toInt(bytes, off, len, little));
+ }
+
+ /**
+ * Translates up to the first 4 bytes of a {@code byte} array beyond a given
+ * offset to a {@code float}. If there are fewer than 4 bytes available, the
+ * MSBs are all assumed to be zero (regardless of endianness).
+ */
+ public static float toFloat(final byte[] bytes, final int off,
+ final boolean little)
+ {
+ return toFloat(bytes, off, 4, little);
+ }
+
+ /**
+ * Translates up to the first 4 bytes of a {@code byte} array to a
+ * {@code float}. If there are fewer than 4 bytes available, the MSBs are all
+ * assumed to be zero (regardless of endianness).
+ */
+ public static float toFloat(final byte[] bytes, final boolean little) {
+ return toFloat(bytes, 0, 4, little);
+ }
+
+ /**
+ * Translates up to the first {@code len} bytes of a {@code byte} array beyond
+ * a given offset to a {@code float}. If there are fewer than {@code len}
+ * bytes available, the MSBs are all assumed to be zero (regardless of
+ * endianness).
+ */
+ public static float toFloat(final short[] bytes, final int off,
+ final int len, final boolean little)
+ {
+ return Float.intBitsToFloat(toInt(bytes, off, len, little));
+ }
+
+ /**
+ * Translates up to the first 4 bytes of a {@code byte} array beyond a given
+ * offset to a {@code float}. If there are fewer than 4 bytes available, the
+ * MSBs are all assumed to be zero (regardless of endianness).
+ */
+ public static float toFloat(final short[] bytes, final int off,
+ final boolean little)
+ {
+ return toInt(bytes, off, 4, little);
+ }
+
+ /**
+ * Translates up to the first 4 bytes of a {@code byte} array to a
+ * {@code float}. If there are fewer than 4 bytes available, the MSBs are all
+ * assumed to be zero (regardless of endianness).
+ */
+ public static float toFloat(final short[] bytes, final boolean little) {
+ return toInt(bytes, 0, 4, little);
+ }
+
+ /**
+ * Translates up to the first {@code len} bytes of a {@code byte} array beyond
+ * the given offset to a {@code long}. If there are fewer than {@code len}
+ * bytes available, the MSBs are all assumed to be zero (regardless of
+ * endianness).
+ */
+ public static long toLong(final byte[] bytes, final int off, int len,
+ final boolean little)
+ {
+ if (bytes.length - off < len) len = bytes.length - off;
+ long total = 0;
+ for (int i = 0, ndx = off; i < len; i++, ndx++) {
+ total |=
+ (bytes[ndx] < 0 ? 256L + bytes[ndx] : (long) bytes[ndx]) << ((little
+ ? i : len - i - 1) * 8);
+ }
+ return total;
+ }
+
+ /**
+ * Translates up to the first 8 bytes of a {@code byte} array beyond the given
+ * offset to a {@code long}. If there are fewer than 8 bytes available, the
+ * MSBs are all assumed to be zero (regardless of endianness).
+ */
+ public static long toLong(final byte[] bytes, final int off,
+ final boolean little)
+ {
+ return toLong(bytes, off, 8, little);
+ }
+
+ /**
+ * Translates up to the first 8 bytes of a {@code byte} array to a
+ * {@code long}. If there are fewer than 8 bytes available, the MSBs are all
+ * assumed to be zero (regardless of endianness).
+ */
+ public static long toLong(final byte[] bytes, final boolean little) {
+ return toLong(bytes, 0, 8, little);
+ }
+
+ /**
+ * Translates up to the first {@code len} bytes of a {@code byte} array beyond
+ * the given offset to a {@code long}. If there are fewer than {@code len}
+ * bytes available, the MSBs are all assumed to be zero (regardless of
+ * endianness).
+ */
+ public static long toLong(final short[] bytes, final int off, int len,
+ final boolean little)
+ {
+ if (bytes.length - off < len) len = bytes.length - off;
+ long total = 0;
+ for (int i = 0, ndx = off; i < len; i++, ndx++) {
+ total |= ((long) bytes[ndx]) << ((little ? i : len - i - 1) * 8);
+ }
+ return total;
+ }
+
+ /**
+ * Translates up to the first 8 bytes of a {@code byte} array beyond the given
+ * offset to a {@code long}. If there are fewer than 8 bytes available, the
+ * MSBs are all assumed to be zero (regardless of endianness).
+ */
+ public static long toLong(final short[] bytes, final int off,
+ final boolean little)
+ {
+ return toLong(bytes, off, 8, little);
+ }
+
+ /**
+ * Translates up to the first 8 bytes of a {@code byte} array to a
+ * {@code long}. If there are fewer than 8 bytes available, the MSBs are all
+ * assumed to be zero (regardless of endianness).
+ */
+ public static long toLong(final short[] bytes, final boolean little) {
+ return toLong(bytes, 0, 8, little);
+ }
+
+ /**
+ * Translates up to the first {@code len} bytes of a {@code byte} array beyond
+ * the given offset to a {@code double}. If there are fewer than {@code len}
+ * bytes available, the MSBs are all assumed to be zero (regardless of
+ * endianness).
+ */
+ public static double toDouble(final byte[] bytes, final int off,
+ final int len, final boolean little)
+ {
+ return Double.longBitsToDouble(toLong(bytes, off, len, little));
+ }
+
+ /**
+ * Translates up to the first 8 bytes of a {@code byte} array beyond the given
+ * offset to a {@code double}. If there are fewer than 8 bytes available, the
+ * MSBs are all assumed to be zero (regardless of endianness).
+ */
+ public static double toDouble(final byte[] bytes, final int off,
+ final boolean little)
+ {
+ return toDouble(bytes, off, 8, little);
+ }
+
+ /**
+ * Translates up to the first 8 bytes of a {@code byte} array to a
+ * {@code double}. If there are fewer than 8 bytes available, the MSBs are all
+ * assumed to be zero (regardless of endianness).
+ */
+ public static double toDouble(final byte[] bytes, final boolean little) {
+ return toDouble(bytes, 0, 8, little);
+ }
+
+ /**
+ * Translates up to the first {@code len} bytes of a {@code byte} array beyond
+ * the given offset to a {@code double}. If there are fewer than {@code len}
+ * bytes available, the MSBs are all assumed to be zero (regardless of
+ * endianness).
+ */
+ public static double toDouble(final short[] bytes, final int off,
+ final int len, final boolean little)
+ {
+ return Double.longBitsToDouble(toLong(bytes, off, len, little));
+ }
+
+ /**
+ * Translates up to the first 8 bytes of a {@code byte} array beyond the given
+ * offset to a {@code double}. If there are fewer than 8 bytes available, the
+ * MSBs are all assumed to be zero (regardless of endianness).
+ */
+ public static double toDouble(final short[] bytes, final int off,
+ final boolean little)
+ {
+ return toDouble(bytes, off, 8, little);
+ }
+
+ /**
+ * Translates up to the first 8 bytes of a {@code byte} array to a
+ * {@code double}. If there are fewer than 8 bytes available, the MSBs are all
+ * assumed to be zero (regardless of endianness).
+ */
+ public static double toDouble(final short[] bytes, final boolean little) {
+ return toDouble(bytes, 0, 8, little);
+ }
+
+ // -- Word decoding - primitive types to bytes --
+
+ /** Translates the {@code short} value into an array of two {@code byte}s. */
+ public static byte[] fromShort(final short value, final boolean little) {
+ final byte[] v = new byte[2];
+ unpack(value, v, 0, 2, little);
+ return v;
+ }
+
+ /** Translates the {@code int} value into an array of four {@code byte}s. */
+ public static byte[] fromInt(final int value, final boolean little) {
+ final byte[] v = new byte[4];
+ unpack(value, v, 0, 4, little);
+ return v;
+ }
+
+ /** Translates the {@code float} value into an array of four {@code byte}s. */
+ public static byte[] fromFloat(final float value, final boolean little) {
+ final byte[] v = new byte[4];
+ unpack(Float.floatToIntBits(value), v, 0, 4, little);
+ return v;
+ }
+
+ /** Translates the {@code long} value into an array of eight {@code byte}s. */
+ public static byte[] fromLong(final long value, final boolean little) {
+ final byte[] v = new byte[8];
+ unpack(value, v, 0, 8, little);
+ return v;
+ }
+
+ /** Translates the {@code double} value into an array of eight {@code byte}s. */
+ public static byte[] fromDouble(final double value, final boolean little) {
+ final byte[] v = new byte[8];
+ unpack(Double.doubleToLongBits(value), v, 0, 8, little);
+ return v;
+ }
+
+ /**
+ * Translates an array of {@code short} values into an array of {@code byte}
+ * values.
+ */
+ public static byte[] fromShorts(final short[] values, final boolean little) {
+ final byte[] v = new byte[values.length * 2];
+ for (int i = 0; i < values.length; i++) {
+ unpack(values[i], v, i * 2, 2, little);
+ }
+ return v;
+ }
+
+ /**
+ * Translates an array of {@code int} values into an array of {@code byte}
+ * values.
+ */
+ public static byte[] fromInts(final int[] values, final boolean little) {
+ final byte[] v = new byte[values.length * 4];
+ for (int i = 0; i < values.length; i++) {
+ unpack(values[i], v, i * 4, 4, little);
+ }
+ return v;
+ }
+
+ /**
+ * Translates an array of {@code float} values into an array of {@code byte}
+ * values.
+ */
+ public static byte[] fromFloats(final float[] values, final boolean little) {
+ final byte[] v = new byte[values.length * 4];
+ for (int i = 0; i < values.length; i++) {
+ unpack(Float.floatToIntBits(values[i]), v, i * 4, 4, little);
+ }
+ return v;
+ }
+
+ /**
+ * Translates an array of {@code long} values into an array of {@code byte}
+ * values.
+ */
+ public static byte[] fromLongs(final long[] values, final boolean little) {
+ final byte[] v = new byte[values.length * 8];
+ for (int i = 0; i < values.length; i++) {
+ unpack(values[i], v, i * 8, 8, little);
+ }
+ return v;
+ }
+
+ /**
+ * Translates an array of {@code double} values into an array of {@code byte}
+ * values.
+ */
+ public static byte[] fromDoubles(final double[] values, final boolean little)
+ {
+ final byte[] v = new byte[values.length * 8];
+ for (int i = 0; i < values.length; i++) {
+ unpack(Double.doubleToLongBits(values[i]), v, i * 8, 8, little);
+ }
+ return v;
+ }
+
+ /**
+ * Translates {@code nBytes} of the given {@code long} and places the result
+ * in the given {@code byte} array.
+ *
+ * @throws IllegalArgumentException if the specified indices fall outside the
+ * buffer
+ */
+ public static void unpack(final long value, final byte[] buf, final int ndx,
+ final int nBytes, final boolean little)
+ {
+ if (buf.length < ndx + nBytes) {
+ throw new IllegalArgumentException("Invalid indices: buf.length=" +
+ buf.length + ", ndx=" + ndx + ", nBytes=" + nBytes);
+ }
+ if (little) {
+ for (int i = 0; i < nBytes; i++) {
+ buf[ndx + i] = (byte) ((value >> (8 * i)) & 0xff);
+ }
+ }
+ else {
+ for (int i = 0; i < nBytes; i++) {
+ buf[ndx + i] = (byte) ((value >> (8 * (nBytes - i - 1))) & 0xff);
+ }
+ }
+ }
+
+ /**
+ * Converts a {@code byte} array to the appropriate 1D primitive type array.
+ *
+ * @param b Byte array to convert.
+ * @param bpp Denotes the number of bytes in the returned primitive type (e.g.
+ * if bpp == 2, we should return an array of type {@code short}).
+ * @param fp If set and bpp == 4 or bpp == 8, then return {@code float}s or
+ * {@code double}s.
+ * @param little Whether {@code byte} array is in little-endian order.
+ */
+ public static Object makeArray(final byte[] b, final int bpp,
+ final boolean fp, final boolean little)
+ {
+ if (bpp == 1) {
+ return b;
+ }
+ else if (bpp == 2) {
+ final short[] s = new short[b.length / 2];
+ for (int i = 0; i < s.length; i++) {
+ s[i] = toShort(b, i * 2, 2, little);
+ }
+ return s;
+ }
+ else if (bpp == 4 && fp) {
+ final float[] f = new float[b.length / 4];
+ for (int i = 0; i < f.length; i++) {
+ f[i] = toFloat(b, i * 4, 4, little);
+ }
+ return f;
+ }
+ else if (bpp == 4) {
+ final int[] i = new int[b.length / 4];
+ for (int j = 0; j < i.length; j++) {
+ i[j] = toInt(b, j * 4, 4, little);
+ }
+ return i;
+ }
+ else if (bpp == 8 && fp) {
+ final double[] d = new double[b.length / 8];
+ for (int i = 0; i < d.length; i++) {
+ d[i] = toDouble(b, i * 8, 8, little);
+ }
+ return d;
+ }
+ else if (bpp == 8) {
+ final long[] l = new long[b.length / 8];
+ for (int i = 0; i < l.length; i++) {
+ l[i] = toLong(b, i * 8, 8, little);
+ }
+ return l;
+ }
+ return null;
+ }
+
+ /**
+ * Converts a {@code byte} array to the appropriate 2D primitive type array.
+ *
+ * @param b Byte array to convert.
+ * @param bpp Denotes the number of bytes in the returned primitive type (e.g.
+ * if bpp == 2, we should return an array of type {@code short}).
+ * @param fp If set and bpp == 4 or bpp == 8, then return {@code float}s or
+ * {@code double}s.
+ * @param little Whether {@code byte} array is in little-endian order.
+ * @param height The height of the output primitive array (2nd dim length).
+ * @return a 2D primitive array of appropriate type, dimensioned
+ * [height][b.length / (bpp * height)]
+ * @throws IllegalArgumentException if input {@code byte} array does not
+ * divide evenly into height pieces
+ */
+ public static Object makeArray2D(final byte[] b, final int bpp,
+ final boolean fp, final boolean little, final int height)
+ {
+ if (b.length % (bpp * height) != 0) {
+ throw new IllegalArgumentException("Array length mismatch: " +
+ "b.length=" + b.length + "; bpp=" + bpp + "; height=" + height);
+ }
+ final int width = b.length / (bpp * height);
+ if (bpp == 1) {
+ final byte[][] b2 = new byte[height][width];
+ for (int y = 0; y < height; y++) {
+ final int index = width * y;
+ System.arraycopy(b, index, b2[y], 0, width);
+ }
+ return b2;
+ }
+ else if (bpp == 2) {
+ final short[][] s = new short[height][width];
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ final int index = 2 * (width * y + x);
+ s[y][x] = toShort(b, index, 2, little);
+ }
+ }
+ return s;
+ }
+ else if (bpp == 4 && fp) {
+ final float[][] f = new float[height][width];
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ final int index = 4 * (width * y + x);
+ f[y][x] = toFloat(b, index, 4, little);
+ }
+ }
+ return f;
+ }
+ else if (bpp == 4) {
+ final int[][] i = new int[height][width];
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ final int index = 4 * (width * y + x);
+ i[y][x] = toInt(b, index, 4, little);
+ }
+ }
+ return i;
+ }
+ else if (bpp == 8 && fp) {
+ final double[][] d = new double[height][width];
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ final int index = 8 * (width * y + x);
+ d[y][x] = toDouble(b, index, 8, little);
+ }
+ }
+ return d;
+ }
+ else if (bpp == 8) {
+ final long[][] l = new long[height][width];
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ final int index = 8 * (width * y + x);
+ l[y][x] = toLong(b, index, 8, little);
+ }
+ }
+ return l;
+ }
+ return null;
+ }
+
+ // -- Byte swapping --
+
+ public static short swap(final short x) {
+ return (short) ((x << 8) | ((x >> 8) & 0xFF));
+ }
+
+ public static char swap(final char x) {
+ return (char) ((x << 8) | ((x >> 8) & 0xFF));
+ }
+
+ public static int swap(final int x) {
+ return (swap((short) x) << 16) | (swap((short) (x >> 16)) & 0xFFFF);
+ }
+
+ public static long swap(final long x) {
+ return ((long) swap((int) x) << 32) | (swap((int) (x >> 32)) & 0xFFFFFFFFL);
+ }
+
+ public static float swap(final float x) {
+ return Float.intBitsToFloat(swap(Float.floatToIntBits(x)));
+ }
+
+ public static double swap(final double x) {
+ return Double.longBitsToDouble(swap(Double.doubleToLongBits(x)));
+ }
+
+ // -- Normalization --
+
+ /**
+ * Normalize the given {@code float} array so that the minimum value maps to
+ * 0.0 and the maximum value maps to 1.0.
+ */
+ public static float[] normalize(final float[] data) {
+ final float[] rtn = new float[data.length];
+
+ // determine the finite min and max values
+ float min = Float.MAX_VALUE;
+ float max = Float.MIN_VALUE;
+ for (final float floatValue : data) {
+ if (floatValue == Float.POSITIVE_INFINITY ||
+ floatValue == Float.NEGATIVE_INFINITY)
+ {
+ continue;
+ }
+ if (floatValue < min) min = floatValue;
+ if (floatValue > max) max = floatValue;
+ }
+
+ // normalize infinity values
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] == Float.POSITIVE_INFINITY) data[i] = max;
+ else if (data[i] == Float.NEGATIVE_INFINITY) data[i] = min;
+ }
+
+ // now normalize; min => 0.0, max => 1.0
+ final float range = max - min;
+ for (int i = 0; i < rtn.length; i++) {
+ rtn[i] = (data[i] - min) / range;
+ }
+ return rtn;
+ }
+
+ /**
+ * Normalize the given {@code double} array so that the minimum value maps to
+ * 0.0 and the maximum value maps to 1.0.
+ */
+ public static double[] normalize(final double[] data) {
+ final double[] rtn = new double[data.length];
+
+ // determine the finite min and max values
+ double min = Double.MAX_VALUE;
+ double max = Double.MIN_VALUE;
+ for (final double doubleValue : data) {
+ if (doubleValue == Double.POSITIVE_INFINITY ||
+ doubleValue == Double.NEGATIVE_INFINITY)
+ {
+ continue;
+ }
+ if (doubleValue < min) min = doubleValue;
+ if (doubleValue > max) max = doubleValue;
+ }
+
+ // normalize infinity values
+ for (int i = 0; i < data.length; i++) {
+ if (data[i] == Double.POSITIVE_INFINITY) data[i] = max;
+ else if (data[i] == Double.NEGATIVE_INFINITY) data[i] = min;
+ }
+
+ // now normalize; min => 0.0, max => 1.0
+ final double range = max - min;
+ for (int i = 0; i < rtn.length; i++) {
+ rtn[i] = (data[i] - min) / range;
+ }
+ return rtn;
+ }
+
+ // -- Signed data conversion --
+
+ public static byte[] makeSigned(final byte[] b) {
+ for (int i = 0; i < b.length; i++) {
+ b[i] = (byte) (b[i] + 128);
+ }
+ return b;
+ }
+
+ public static short[] makeSigned(final short[] s) {
+ for (int i = 0; i < s.length; i++) {
+ s[i] = (short) (s[i] + 32768);
+ }
+ return s;
+ }
+
+ public static int[] makeSigned(final int[] i) {
+ for (int j = 0; j < i.length; j++) {
+ i[j] = (int) (i[j] + 2147483648L);
+ }
+ return i;
+ }
+
+}
diff --git a/src/main/java/org/scijava/util/FileUtils.java b/src/main/java/org/scijava/util/FileUtils.java
index fff553b6d..57e53319d 100644
--- a/src/main/java/org/scijava/util/FileUtils.java
+++ b/src/main/java/org/scijava/util/FileUtils.java
@@ -407,7 +407,7 @@ public static String limitPath(final String path, final int limit) {
*
*
* It is the caller's responsibility to make sure that the directory is
- * deleted.
+ * deleted; see {@link #deleteRecursively(File)}.
*
*
* @param prefix The prefix string to be used in generating the file's name;
@@ -433,7 +433,7 @@ public static File createTemporaryDirectory(final String prefix,
*
*
* It is the caller's responsibility to make sure that the directory is
- * deleted.
+ * deleted; see {@link #deleteRecursively(File)}.
*
*
* @param prefix The prefix string to be used in generating the file's name;
diff --git a/src/main/java/org/scijava/util/StringUtils.java b/src/main/java/org/scijava/util/StringUtils.java
new file mode 100644
index 000000000..817e9d116
--- /dev/null
+++ b/src/main/java/org/scijava/util/StringUtils.java
@@ -0,0 +1,133 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+// Portions of this class were derived from the loci.common.DataTools class of
+// the Bio-Formats library, licensed according to Simplified BSD, as follows:
+//
+// Copyright (C) 2005 - 2015 Open Microscopy Environment:
+// - Board of Regents of the University of Wisconsin-Madison
+// - Glencoe Software, Inc.
+// - University of Dundee
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2. 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.
+//
+// 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 HOLDERS 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 org.scijava.util;
+
+import java.io.File;
+import java.text.DecimalFormatSymbols;
+
+/**
+ * Useful methods for working with {@link String}s.
+ *
+ * @author Curtis Rueden
+ * @author Chris Allan
+ * @author Melissa Linkert
+ */
+public final class StringUtils {
+
+ private StringUtils() {
+ // NB: prevent instantiation of utility class.
+ }
+
+ /** Normalizes the decimal separator for the user's locale. */
+ public static String sanitizeDouble(String value) {
+ value = value.replaceAll("[^0-9,\\.]", "");
+ final char separator = new DecimalFormatSymbols().getDecimalSeparator();
+ final char usedSeparator = separator == '.' ? ',' : '.';
+ value = value.replace(usedSeparator, separator);
+ try {
+ Double.parseDouble(value);
+ }
+ catch (final Exception e) {
+ value = value.replace(separator, usedSeparator);
+ }
+ return value;
+ }
+
+ /** Removes null bytes from a string. */
+ public static String stripNulls(final String toStrip) {
+ final StringBuilder s = new StringBuilder();
+ for (int i = 0; i < toStrip.length(); i++) {
+ if (toStrip.charAt(i) != 0) {
+ s.append(toStrip.charAt(i));
+ }
+ }
+ return s.toString().trim();
+ }
+
+ /** Checks if two filenames have the same prefix. */
+ public static boolean samePrefix(final String s1, final String s2) {
+ if (s1 == null || s2 == null) return false;
+ final int n1 = s1.indexOf(".");
+ final int n2 = s2.indexOf(".");
+ if ((n1 == -1) || (n2 == -1)) return false;
+
+ final int slash1 = s1.lastIndexOf(File.pathSeparator);
+ final int slash2 = s2.lastIndexOf(File.pathSeparator);
+
+ final String sub1 = s1.substring(slash1 + 1, n1);
+ final String sub2 = s2.substring(slash2 + 1, n2);
+ return sub1.equals(sub2) || sub1.startsWith(sub2) || sub2.startsWith(sub1);
+ }
+
+ /** Removes unprintable characters from the given string. */
+ public static String sanitize(final String s) {
+ if (s == null) return null;
+ StringBuffer buf = new StringBuffer(s);
+ for (int i = 0; i < buf.length(); i++) {
+ final char c = buf.charAt(i);
+ if (c != '\t' && c != '\n' && (c < ' ' || c > '~')) {
+ buf = buf.deleteCharAt(i--);
+ }
+ }
+ return buf.toString();
+ }
+
+}
diff --git a/src/main/java/org/scijava/widget/AbstractInputHarvester.java b/src/main/java/org/scijava/widget/AbstractInputHarvester.java
index 1e90695fe..15d2ab8fb 100644
--- a/src/main/java/org/scijava/widget/AbstractInputHarvester.java
+++ b/src/main/java/org/scijava/widget/AbstractInputHarvester.java
@@ -36,6 +36,7 @@
import org.scijava.AbstractContextual;
import org.scijava.convert.ConvertService;
+import org.scijava.log.LogService;
import org.scijava.module.Module;
import org.scijava.module.ModuleCanceledException;
import org.scijava.module.ModuleException;
@@ -58,6 +59,9 @@ public abstract class AbstractInputHarvester extends AbstractContextual
implements InputHarvester
{
+ @Parameter
+ private LogService log;
+
@Parameter
private WidgetService widgetService;
@@ -130,6 +134,9 @@ private WidgetModel addInput(final InputPanel inputPanel,
final Class widgetType = inputPanel.getWidgetComponentType();
final InputWidget, ?> widget = widgetService.create(model);
+ if (widget == null) {
+ log.warn("No widget found for input: " + model.getItem().getName());
+ }
if (widget != null && widget.getComponentType() == widgetType) {
@SuppressWarnings("unchecked")
final InputWidget, W> typedWidget = (InputWidget, W>) widget;
diff --git a/src/main/java/org/scijava/widget/DefaultWidgetService.java b/src/main/java/org/scijava/widget/DefaultWidgetService.java
index 6ad45c49a..518f869d6 100644
--- a/src/main/java/org/scijava/widget/DefaultWidgetService.java
+++ b/src/main/java/org/scijava/widget/DefaultWidgetService.java
@@ -39,7 +39,6 @@
import org.scijava.plugin.AbstractWrapperService;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
-import org.scijava.plugin.PluginInfo;
import org.scijava.service.Service;
/**
@@ -66,22 +65,6 @@ public WidgetModel createModel(InputPanel, ?> inputPanel, Module module,
objectPool);
}
- // -- WrapperService methods --
-
- @Override
- public InputWidget, ?> create(final WidgetModel model) {
- for (final PluginInfo> info : getPlugins()) {
- final InputWidget, ?> widget = getPluginService().createInstance(info);
- if (widget == null) continue;
- if (widget.supports(model)) {
- widget.set(model);
- return widget;
- }
- }
- log.warn("No widget found for input: " + model.getItem().getName());
- return null;
- }
-
// -- PTService methods --
@Override
diff --git a/src/main/java/org/scijava/widget/WidgetService.java b/src/main/java/org/scijava/widget/WidgetService.java
index 1a903c9d7..5147a14aa 100644
--- a/src/main/java/org/scijava/widget/WidgetService.java
+++ b/src/main/java/org/scijava/widget/WidgetService.java
@@ -51,10 +51,6 @@ public interface WidgetService extends
// -- WrapperService methods --
- /** Creates a widget that represents the given widget model. */
- @Override
- InputWidget, ?> create(WidgetModel model);
-
/**
* Create a {@link WidgetModel} for the given module input.
*
diff --git a/src/test/java/org/scijava/ContextCreationTest.java b/src/test/java/org/scijava/ContextCreationTest.java
index 04edd543c..608184846 100644
--- a/src/test/java/org/scijava/ContextCreationTest.java
+++ b/src/test/java/org/scijava/ContextCreationTest.java
@@ -81,6 +81,7 @@ public void testFull() {
org.scijava.display.DefaultDisplayService.class,
org.scijava.event.DefaultEventHistory.class,
org.scijava.input.DefaultInputService.class,
+ org.scijava.io.DefaultDataHandleService.class,
org.scijava.io.DefaultIOService.class,
org.scijava.io.DefaultRecentFileService.class,
org.scijava.menu.DefaultMenuService.class,
diff --git a/src/test/java/org/scijava/io/BytesLocationTest.java b/src/test/java/org/scijava/io/BytesLocationTest.java
new file mode 100644
index 000000000..c36de091e
--- /dev/null
+++ b/src/test/java/org/scijava/io/BytesLocationTest.java
@@ -0,0 +1,67 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+
+/**
+ * Tests {@link BytesLocation}.
+ *
+ * @author Curtis Rueden
+ */
+public class BytesLocationTest {
+
+ /** Tests {@link BytesLocation#BytesLocation(byte[])}. */
+ @Test
+ public void testBytes() {
+ final byte[] digits = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9};
+ final BytesLocation loc = new BytesLocation(digits);
+ assertSame(digits, loc.getByteBuffer().array());
+ assertEquals(0, loc.getByteBuffer().position());
+ assertEquals(digits.length, loc.getByteBuffer().remaining());
+ }
+
+ /** Tests {@link BytesLocation#BytesLocation(byte[], int, int)}. */
+ @Test
+ public void testBytesOffsetLength() {
+ final byte[] digits = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7, 9};
+ final int offset = 3, length = 5;
+ final BytesLocation loc = new BytesLocation(digits, offset, length);
+ assertSame(digits, loc.getByteBuffer().array());
+ assertEquals(offset, loc.getByteBuffer().position());
+ assertEquals(length, loc.getByteBuffer().remaining());
+ }
+
+}
diff --git a/src/test/java/org/scijava/io/DataHandleTest.java b/src/test/java/org/scijava/io/DataHandleTest.java
new file mode 100644
index 000000000..decc7d29c
--- /dev/null
+++ b/src/test/java/org/scijava/io/DataHandleTest.java
@@ -0,0 +1,227 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.scijava.Context;
+import org.scijava.util.Bytes;
+
+/**
+ * Abstract base class for {@link DataHandle} implementation tests.
+ *
+ * @author Curtis Rueden
+ */
+public abstract class DataHandleTest {
+
+ private static final byte[] BYTES = { //
+ 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n', //
+ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -128, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, //
+ 125, 127, -127, -125, -3, -2, -1 };
+
+ // -- Test methods --
+
+ @Test
+ public void testDataHandle() throws IOException {
+ final Context context = new Context(DataHandleService.class);
+ final DataHandleService dataHandleService =
+ context.service(DataHandleService.class);
+
+ final Location loc = createLocation();
+ final DataHandle extends Location> handle = dataHandleService.create(loc);
+ assertEquals(getExpectedHandleType(), handle.getClass());
+
+ checkReads(handle);
+ checkWrites(handle);
+ handle.close();
+ }
+
+ // -- DataHandleTest methods --
+
+ public abstract Class extends DataHandle>> getExpectedHandleType();
+
+ public abstract Location createLocation() throws IOException;
+
+ // -- Internal methods --
+
+ protected void populateData(final OutputStream out) throws IOException {
+ out.write(BYTES);
+ out.close();
+ }
+
+ protected void checkReads(final DataHandle handle)
+ throws IOException
+ {
+ assertEquals(0, handle.offset());
+ assertEquals(BYTES.length, handle.length());
+ assertEquals("UTF-8", handle.getEncoding());
+ assertEquals(ByteOrder.BIG_ENDIAN, handle.getOrder());
+ assertEquals(false, handle.isLittleEndian());
+
+ // test read()
+ for (int i = 0; i < BYTES.length; i++) {
+ assertEquals(msg(i), 0xff & BYTES[i], handle.read());
+ }
+ assertEquals(-1, handle.read());
+ handle.seek(10);
+ assertEquals(10, handle.offset());
+ assertEquals(BYTES[10], handle.read());
+
+ // test read(byte[])
+ final byte[] buf = new byte[10];
+ handle.seek(1);
+ assertBytesMatch(1, handle.read(buf), buf);
+
+ // test read(ByteBuffer)
+ Arrays.fill(buf, (byte) 0);
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(buf);
+ handle.seek(2);
+ assertBytesMatch(2, handle.read(byteBuffer), byteBuffer.array());
+
+ // test readByte()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length; i++) {
+ assertEquals(msg(i), BYTES[i], handle.readByte());
+ }
+
+ // test readShort()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 2; i += 2) {
+ assertEquals(msg(i), Bytes.toShort(BYTES, i, false), handle.readShort());
+ }
+
+ // test readInt()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 4; i += 4) {
+ assertEquals(msg(i), Bytes.toInt(BYTES, i, false), handle.readInt());
+ }
+
+ // test readLong()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 8; i += 8) {
+ assertEquals(msg(i), Bytes.toLong(BYTES, i, false), handle.readLong());
+ }
+
+ // test readFloat()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 4; i += 4) {
+ assertEquals(msg(i), Bytes.toFloat(BYTES, i, false), handle.readFloat(),
+ 0);
+ }
+
+ // test readDouble()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 8; i += 8) {
+ assertEquals(msg(i), Bytes.toDouble(BYTES, i, false),
+ handle.readDouble(), 0);
+ }
+
+ // test readBoolean()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length; i++) {
+ assertEquals(msg(i), BYTES[i] == 0 ? false : true, handle.readBoolean());
+ }
+
+ // test readChar()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 2; i += 2) {
+ assertEquals(msg(i), (char) Bytes.toInt(BYTES, i, 2, false), handle
+ .readChar());
+ }
+
+ // test readFully(byte[])
+ Arrays.fill(buf, (byte) 0);
+ handle.seek(3);
+ handle.readFully(buf);
+ assertBytesMatch(3, buf.length, buf);
+
+ // test readCString() - _includes_ the null terminator!
+ handle.seek(16);
+ assertBytesMatch(16, 7, handle.readCString().getBytes());
+
+ // test readLine() - _excludes_ the newline terminator!
+ handle.seek(7);
+ assertBytesMatch(7, 5, handle.readLine().getBytes());
+
+ // test readString(String) - _includes_ the matching terminator!
+ handle.seek(7);
+ assertBytesMatch(7, 5, handle.readString("abcdefg").getBytes());
+
+ // test findString(String) - _includes_ the matching terminator!
+ handle.seek(1);
+ assertBytesMatch(1, 11, handle.findString("world").getBytes());
+ }
+
+ protected void checkWrites(final DataHandle handle)
+ throws IOException
+ {
+ final byte[] copy = BYTES.clone();
+
+ // change the data
+ handle.seek(7);
+ final String splice = "there";
+ for (int i = 0; i < splice.length(); i++) {
+ final char c = splice.charAt(i);
+ handle.write(c);
+ copy[7 + i] = (byte) c;
+ }
+
+ // verify the changes
+ handle.seek(0);
+ for (int i = 0; i < copy.length; i++) {
+ assertEquals(msg(i), 0xff & copy[i], handle.read());
+ }
+ }
+
+ // -- Helper methods --
+
+ private void assertBytesMatch(final int offset, final int length,
+ final byte[] b)
+ {
+ assertEquals(length, b.length);
+ for (int i = 0; i < length; i++) {
+ assertEquals(msg(i), BYTES[i + offset], b[i]);
+ }
+ }
+
+ private String msg(final int i) {
+ return "[" + i + "]:";
+ }
+
+}
diff --git a/src/test/java/org/scijava/io/FileHandleTest.java b/src/test/java/org/scijava/io/FileHandleTest.java
new file mode 100644
index 000000000..d4ad153a2
--- /dev/null
+++ b/src/test/java/org/scijava/io/FileHandleTest.java
@@ -0,0 +1,59 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Tests {@link FileHandle}.
+ *
+ * @author Curtis Rueden
+ */
+public class FileHandleTest extends DataHandleTest {
+
+ @Override
+ public Class extends DataHandle>> getExpectedHandleType() {
+ return FileHandle.class;
+ }
+
+ @Override
+ public Location createLocation() throws IOException {
+ // create and populate a temp file
+ final File tmpFile = File.createTempFile("FileHandleTest", "test-file");
+ tmpFile.deleteOnExit();
+ populateData(new FileOutputStream(tmpFile));
+ return new FileLocation(tmpFile);
+ }
+
+}
diff --git a/src/test/java/org/scijava/io/FileLocationTest.java b/src/test/java/org/scijava/io/FileLocationTest.java
new file mode 100644
index 000000000..9181cfde4
--- /dev/null
+++ b/src/test/java/org/scijava/io/FileLocationTest.java
@@ -0,0 +1,53 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+/**
+ * Tests {@link FileLocation}.
+ *
+ * @author Curtis Rueden
+ */
+public class FileLocationTest {
+
+ /** Tests {@link FileLocation#FileLocation(String)}. */
+ @Test
+ public void testFile() {
+ final String path = "/not/actually/a/real-file";
+ final FileLocation loc = new FileLocation(path);
+ assertEquals(path, loc.getFile().getPath());
+ }
+
+}
diff --git a/src/test/java/org/scijava/io/URILocationTest.java b/src/test/java/org/scijava/io/URILocationTest.java
new file mode 100644
index 000000000..7a30f154f
--- /dev/null
+++ b/src/test/java/org/scijava/io/URILocationTest.java
@@ -0,0 +1,64 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+
+import org.junit.Test;
+
+/**
+ * Tests {@link URILocation}.
+ *
+ * @author Curtis Rueden
+ */
+public class URILocationTest {
+
+ /** Tests {@link URILocation#URILocation(URI)}. */
+ @Test
+ public void testURI() throws URISyntaxException {
+ final String uriString = "scheme://bob@big.server.somewhere:12345" +
+ "/foo/bar?pineapple=exquisite&strawberries=very%20delicious#anchor";
+ final URI uri = new URI(uriString);
+ final URILocation loc = new URILocation(uri);
+ assertSame(uri, loc.getURI());
+ final Map queryMap = loc.getQueryMap();
+ assertEquals(2, queryMap.size());
+ assertEquals("exquisite", queryMap.get("pineapple"));
+ assertEquals("very delicious", queryMap.get("strawberries"));
+ }
+
+}
diff --git a/src/test/java/org/scijava/io/URLLocationTest.java b/src/test/java/org/scijava/io/URLLocationTest.java
new file mode 100644
index 000000000..2e33b142b
--- /dev/null
+++ b/src/test/java/org/scijava/io/URLLocationTest.java
@@ -0,0 +1,56 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import static org.junit.Assert.assertSame;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.junit.Test;
+
+/**
+ * Tests {@link URLLocation}.
+ *
+ * @author Curtis Rueden
+ */
+public class URLLocationTest {
+
+ /** Tests {@link URLLocation#URLLocation(URL)}. */
+ @Test
+ public void testURL() throws MalformedURLException {
+ final URL url = new URL("file:///non/existent/url");
+ final URLLocation loc = new URLLocation(url);
+ assertSame(url, loc.getURL());
+ }
+
+}
diff --git a/src/test/java/org/scijava/util/ArrayUtilsTest.java b/src/test/java/org/scijava/util/ArrayUtilsTest.java
new file mode 100644
index 000000000..235f1898d
--- /dev/null
+++ b/src/test/java/org/scijava/util/ArrayUtilsTest.java
@@ -0,0 +1,150 @@
+/*
+ * #%L
+ * SCIFIO library for reading and converting scientific file formats.
+ * %%
+ * Copyright (C) 2011 - 2014 Board of Regents of the University of
+ * Wisconsin-Madison
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDERS 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.
+ * #L%
+ */
+
+package org.scijava.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+/** Unit tests for {@link ArrayUtils}. */
+public class ArrayUtilsTest {
+
+ @Test
+ public void testSafeMultiply32() {
+ // test vacuous edge cases
+ boolean ok = false;
+ try {
+ ArrayUtils.safeMultiply32(null);
+ }
+ catch (final NullPointerException e) {
+ ok = true;
+ }
+ if (!ok) fail("Expected NullPointerException");
+ assertSafeMultiply32Pass(0);
+
+ // test simple cases
+ assertSafeMultiply32Pass(1, 1);
+ assertSafeMultiply32Pass(54, 9, 6);
+ assertSafeMultiply32Pass(1037836800, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
+
+ // test invalid arguments
+ assertSafeMultiply32Fail("Invalid array size: -1", -1);
+ assertSafeMultiply32Fail("Invalid array size: 0", 0);
+
+ // test edge cases near Integer.MAX_VALUE
+ assertSafeMultiply32Pass(2147483646, Integer.MAX_VALUE / 2, 2);
+ assertSafeMultiply32Fail("Array size too large: 1073741824 x 2",
+ Integer.MAX_VALUE / 2 + 1, 2);
+ assertSafeMultiply32Pass(2147441940, 46340, 46341);
+ assertSafeMultiply32Fail("Array size too large: 46341 x 46341", 46341,
+ 46341);
+ assertSafeMultiply32Fail("Array size too large: 46340 x 46342", 46340,
+ 46342);
+ assertSafeMultiply32Pass(2147418112, 65536, 32767);
+ assertSafeMultiply32Pass(2147450880, 65535, 32768);
+ assertSafeMultiply32Fail("Array size too large: 65536 x 32768", 65536,
+ 32768);
+ }
+
+ @Test
+ public void testSafeMultiply64() {
+ // test vacuous edge cases
+ boolean ok = false;
+ try {
+ ArrayUtils.safeMultiply64(null);
+ }
+ catch (final NullPointerException e) {
+ ok = true;
+ }
+ if (!ok) fail("Expected NullPointerException");
+ assertSafeMultiply64Pass(0);
+
+ // test simple cases
+ assertSafeMultiply64Pass(1, 1);
+ assertSafeMultiply64Pass(54, 9, 6);
+ assertSafeMultiply64Pass(3632428800L, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14);
+
+ // test invalid arguments
+ assertSafeMultiply64Fail("Invalid array size: -1", -1);
+ assertSafeMultiply64Fail("Invalid array size: 0", 0);
+
+ // test edge cases near Long.MAX_VALUE
+ assertSafeMultiply64Pass(9223372036854775806L, Long.MAX_VALUE / 6, 2, 3);
+ assertSafeMultiply64Fail(
+ "Array size too large: 1537228672809129302 x 2 x 3",
+ Long.MAX_VALUE / 6 + 1, 2, 3);
+ assertSafeMultiply64Pass(9223372033963249500L, 3037000499L, 3037000500L);
+ assertSafeMultiply64Fail("Array size too large: 3037000500 x 3037000500",
+ 3037000500L, 3037000500L);
+ assertSafeMultiply64Fail("Array size too large: 3037000499 x 3037000501",
+ 3037000499L, 3037000501L);
+ }
+
+ // -- Helper methods --
+
+ private void
+ assertSafeMultiply32Pass(final int expected, final long... sizes)
+ {
+ assertEquals(expected, ArrayUtils.safeMultiply32(sizes));
+ }
+
+ private void assertSafeMultiply32Fail(final String msg, final long... sizes) {
+ int actual;
+ try {
+ actual = ArrayUtils.safeMultiply32(sizes);
+ }
+ catch (final IllegalArgumentException e) {
+ assertEquals(e.getMessage(), msg);
+ return;
+ }
+ fail("Safe multiply succeeded with value: " + actual);
+ }
+
+ private void assertSafeMultiply64Pass(final long expected,
+ final long... sizes)
+ {
+ assertEquals(expected, ArrayUtils.safeMultiply64(sizes));
+ }
+
+ private void assertSafeMultiply64Fail(final String msg, final long... sizes) {
+ long actual;
+ try {
+ actual = ArrayUtils.safeMultiply64(sizes);
+ }
+ catch (final IllegalArgumentException e) {
+ assertEquals(e.getMessage(), msg);
+ return;
+ }
+ fail("Safe multiply succeeded with value: " + actual);
+ }
+
+}