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 typedWidget = (InputWidget) 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 handle = dataHandleService.create(loc); + assertEquals(getExpectedHandleType(), handle.getClass()); + + checkReads(handle); + checkWrites(handle); + handle.close(); + } + + // -- DataHandleTest methods -- + + public abstract Class> 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> 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); + } + +}