- * 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.
*
*/
From 04b08df08c08f7e448d65715568434ecfb3a8166 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 4 May 2015 13:07:50 -0500
Subject: [PATCH 03/23] PTService: add utility method to create a plugin
Surprisingly, the PluginService API does not have a method to create a
plugin from its class. You can only create one from its associated
PluginInfo. So instantiating a plugin becomes a two-step process:
1. Ask the PluginService for the PluginInfo of a given plugin Class.
2. Tell the PluginService to create an instance from that PluginInfo.
And this process is not fully type-safe, since the
PluginInfo#createInstance(PluginInfo) method only knows the object is
of type PT, not P.
To help address this limitation, each PTService now has a handy
create(Class) method for creating an instance of that
plugin class. This is useful, since using "new" directly is not enough;
the plugin will not have the application context injected as needed.
---
src/main/java/org/scijava/plugin/AbstractPTService.java | 9 +++++++++
src/main/java/org/scijava/plugin/PTService.java | 3 +++
2 files changed, 12 insertions(+)
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/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);
+
}
From 3b99ca885f61884fa53f701389da7d810e62f83c Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 4 May 2015 13:15:07 -0500
Subject: [PATCH 04/23] Do not create new singleton plugin instances
Instead, throw an exception with a hint.
This will hopefully avoid logic errors in downstream code,
which attempt to create new instances of singleton plugins.
---
.../org/scijava/plugin/AbstractSingletonService.java | 9 +++++++++
1 file changed, 9 insertions(+)
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
From 03a6c4761096427d504ba9851723049e0aa21542 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 11 May 2015 11:31:23 -0500
Subject: [PATCH 05/23] ConsolePane: fix license header
---
src/main/java/org/scijava/ui/console/ConsolePane.java | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
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:
From 71ae0d14aab873ad3052d6dd4d839b249ad5fb2e Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 11 May 2015 10:53:25 -0500
Subject: [PATCH 06/23] DefaultRecentFileService: tweak javadoc
---
.../java/org/scijava/io/DefaultRecentFileService.java | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/src/main/java/org/scijava/io/DefaultRecentFileService.java b/src/main/java/org/scijava/io/DefaultRecentFileService.java
index d9445199d..ba00c708e 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
From 9e330e12178ba7e92fe64815eb2a9b18a557aa99 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 11 May 2015 10:56:54 -0500
Subject: [PATCH 07/23] RecentFileService: split out persistence methods
---
.../org/scijava/io/DefaultRecentFileService.java | 16 +++++++++++++---
1 file changed, 13 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/scijava/io/DefaultRecentFileService.java b/src/main/java/org/scijava/io/DefaultRecentFileService.java
index ba00c708e..ce0ddd9e6 100644
--- a/src/main/java/org/scijava/io/DefaultRecentFileService.java
+++ b/src/main/java/org/scijava/io/DefaultRecentFileService.java
@@ -111,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
@@ -135,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);
@@ -164,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));
@@ -183,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
From 32ced3c7b90bb021e37d1d39e5bbb94e52768042 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 11 May 2015 19:16:34 -0500
Subject: [PATCH 08/23] AbstractWrapperService: tweak method name
The private method called "wrap" really just located an appropriate
wrapper object. It did not actually populate it with the data.
---
.../java/org/scijava/plugin/AbstractWrapperService.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/scijava/plugin/AbstractWrapperService.java b/src/main/java/org/scijava/plugin/AbstractWrapperService.java
index c03189b28..d7074a900 100644
--- a/src/main/java/org/scijava/plugin/AbstractWrapperService.java
+++ b/src/main/java/org/scijava/plugin/AbstractWrapperService.java
@@ -51,7 +51,7 @@ public abstract class AbstractWrapperService>
@Override
public PT create(final D data) {
- final PT instance = wrap(data);
+ final PT instance = findWrapper(data);
if (instance == null) {
throw new IllegalArgumentException("No compatible " +
getPluginType().getSimpleName() + " for data object: " + data);
@@ -73,12 +73,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;
From 443550d2e1e1c5b4ef713f5dea41da003fbd5aef Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 11 May 2015 19:17:42 -0500
Subject: [PATCH 09/23] AbstractWrapperService: fix serious wrapping bug
When calling the create method, after a match is found, the data needs
to actually get populated by calling the set method. Otherwise, the
create method will return an unpopulated wrapper object.
This bug was not discovered until now because the only core type of
WrapperPlugin, the InputWidget, overrode the create method with its
own implementation that populated the data properly.
---
src/main/java/org/scijava/plugin/AbstractWrapperService.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/main/java/org/scijava/plugin/AbstractWrapperService.java b/src/main/java/org/scijava/plugin/AbstractWrapperService.java
index d7074a900..e4978de0d 100644
--- a/src/main/java/org/scijava/plugin/AbstractWrapperService.java
+++ b/src/main/java/org/scijava/plugin/AbstractWrapperService.java
@@ -56,6 +56,7 @@ public PT create(final D data) {
throw new IllegalArgumentException("No compatible " +
getPluginType().getSimpleName() + " for data object: " + data);
}
+ instance.set(data);
return instance;
}
From 5d65b3efb3b64268eff2836c5182ca9838afb01e Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 11 May 2015 19:23:52 -0500
Subject: [PATCH 10/23] Make WrapperService#create not throw exceptions
Instead, let's just return null, since null can only mean one thing:
no suitable wrapper was found.
This commit also makes the appropriate changes downstream to adjust
for the update in behavior.
---
.../scijava/plugin/AbstractWrapperService.java | 6 +-----
.../java/org/scijava/plugin/WrapperService.java | 4 ++--
.../scijava/widget/AbstractInputHarvester.java | 7 +++++++
.../scijava/widget/DefaultWidgetService.java | 17 -----------------
.../java/org/scijava/widget/WidgetService.java | 4 ----
5 files changed, 10 insertions(+), 28 deletions(-)
diff --git a/src/main/java/org/scijava/plugin/AbstractWrapperService.java b/src/main/java/org/scijava/plugin/AbstractWrapperService.java
index e4978de0d..85e2cea88 100644
--- a/src/main/java/org/scijava/plugin/AbstractWrapperService.java
+++ b/src/main/java/org/scijava/plugin/AbstractWrapperService.java
@@ -52,11 +52,7 @@ public abstract class AbstractWrapperService>
@Override
public PT create(final D data) {
final PT instance = findWrapper(data);
- if (instance == null) {
- throw new IllegalArgumentException("No compatible " +
- getPluginType().getSimpleName() + " for data object: " + data);
- }
- instance.set(data);
+ if (instance != null) instance.set(data);
return instance;
}
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/widget/AbstractInputHarvester.java b/src/main/java/org/scijava/widget/AbstractInputHarvester.java
index 1e90695fe..15d2ab8fb 100644
--- a/src/main/java/org/scijava/widget/AbstractInputHarvester.java
+++ b/src/main/java/org/scijava/widget/AbstractInputHarvester.java
@@ -36,6 +36,7 @@
import org.scijava.AbstractContextual;
import org.scijava.convert.ConvertService;
+import org.scijava.log.LogService;
import org.scijava.module.Module;
import org.scijava.module.ModuleCanceledException;
import org.scijava.module.ModuleException;
@@ -58,6 +59,9 @@ public abstract class AbstractInputHarvester extends AbstractContextual
implements InputHarvester
{
+ @Parameter
+ private LogService log;
+
@Parameter
private WidgetService widgetService;
@@ -130,6 +134,9 @@ private WidgetModel addInput(final InputPanel inputPanel,
final Class widgetType = inputPanel.getWidgetComponentType();
final InputWidget, ?> widget = widgetService.create(model);
+ if (widget == null) {
+ log.warn("No widget found for input: " + model.getItem().getName());
+ }
if (widget != null && widget.getComponentType() == widgetType) {
@SuppressWarnings("unchecked")
final InputWidget, W> typedWidget = (InputWidget, W>) widget;
diff --git a/src/main/java/org/scijava/widget/DefaultWidgetService.java b/src/main/java/org/scijava/widget/DefaultWidgetService.java
index 6ad45c49a..518f869d6 100644
--- a/src/main/java/org/scijava/widget/DefaultWidgetService.java
+++ b/src/main/java/org/scijava/widget/DefaultWidgetService.java
@@ -39,7 +39,6 @@
import org.scijava.plugin.AbstractWrapperService;
import org.scijava.plugin.Parameter;
import org.scijava.plugin.Plugin;
-import org.scijava.plugin.PluginInfo;
import org.scijava.service.Service;
/**
@@ -66,22 +65,6 @@ public WidgetModel createModel(InputPanel, ?> inputPanel, Module module,
objectPool);
}
- // -- WrapperService methods --
-
- @Override
- public InputWidget, ?> create(final WidgetModel model) {
- for (final PluginInfo> info : getPlugins()) {
- final InputWidget, ?> widget = getPluginService().createInstance(info);
- if (widget == null) continue;
- if (widget.supports(model)) {
- widget.set(model);
- return widget;
- }
- }
- log.warn("No widget found for input: " + model.getItem().getName());
- return null;
- }
-
// -- PTService methods --
@Override
diff --git a/src/main/java/org/scijava/widget/WidgetService.java b/src/main/java/org/scijava/widget/WidgetService.java
index 1a903c9d7..5147a14aa 100644
--- a/src/main/java/org/scijava/widget/WidgetService.java
+++ b/src/main/java/org/scijava/widget/WidgetService.java
@@ -51,10 +51,6 @@ public interface WidgetService extends
// -- WrapperService methods --
- /** Creates a widget that represents the given widget model. */
- @Override
- InputWidget, ?> create(WidgetModel model);
-
/**
* Create a {@link WidgetModel} for the given module input.
*
From 84b5c2b50948013ecfd2247c38c46d4c66cdcef9 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Sat, 7 Jun 2014 11:37:55 +0200
Subject: [PATCH 11/23] Add an interface to encapsulate data locations
A data "location" is a path (source/destination) that points to data.
Many data locations, such as files on disk and remote URLs, can be
represented as URIs, so the Location interface provides a getURI()
method to easily obtain such a representation.
---
.../java/org/scijava/io/AbstractLocation.java | 50 ++++++++++++++++
src/main/java/org/scijava/io/Location.java | 58 +++++++++++++++++++
2 files changed, 108 insertions(+)
create mode 100644 src/main/java/org/scijava/io/AbstractLocation.java
create mode 100644 src/main/java/org/scijava/io/Location.java
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/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();
+
+}
From 1f1e3858affdbffedce108f0b96d750aff5b71f1 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 4 May 2015 17:14:33 -0500
Subject: [PATCH 12/23] Add a location type backed by a URI
---
src/main/java/org/scijava/io/URILocation.java | 126 ++++++++++++++++++
1 file changed, 126 insertions(+)
create mode 100644 src/main/java/org/scijava/io/URILocation.java
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;
+ }
+ }
+
+}
From 630fae52eeb5deb29188a41e26c6249bebb8f770 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Tue, 26 Aug 2014 10:34:17 -0500
Subject: [PATCH 13/23] Add a location type backed by a file on disk
---
.../java/org/scijava/io/FileLocation.java | 68 +++++++++++++++++++
1 file changed, 68 insertions(+)
create mode 100644 src/main/java/org/scijava/io/FileLocation.java
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();
+ }
+
+}
From dae87da84b6a8100df8256624f201d74a754a1c6 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Tue, 26 Aug 2014 10:34:31 -0500
Subject: [PATCH 14/23] Add a location type backed by a byte array
---
.../java/org/scijava/io/BytesLocation.java | 64 +++++++++++++++++++
1 file changed, 64 insertions(+)
create mode 100644 src/main/java/org/scijava/io/BytesLocation.java
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;
+ }
+
+}
From 2873ad7329138abaf49e6b8c3dd3bc24a690d731 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 4 May 2015 13:46:57 -0500
Subject: [PATCH 15/23] Add a location type backed by a remote URL
---
src/main/java/org/scijava/io/URLLocation.java | 75 +++++++++++++++++++
1 file changed, 75 insertions(+)
create mode 100644 src/main/java/org/scijava/io/URLLocation.java
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;
+ }
+ }
+
+}
From b74d78ad9d64c76baddda1bddd694bf37c21634d Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 11 May 2015 11:03:48 -0500
Subject: [PATCH 16/23] Add unit tests for Location implementations
---
.../org/scijava/io/BytesLocationTest.java | 67 +++++++++++++++++++
.../java/org/scijava/io/FileLocationTest.java | 53 +++++++++++++++
.../java/org/scijava/io/URILocationTest.java | 64 ++++++++++++++++++
.../java/org/scijava/io/URLLocationTest.java | 56 ++++++++++++++++
4 files changed, 240 insertions(+)
create mode 100644 src/test/java/org/scijava/io/BytesLocationTest.java
create mode 100644 src/test/java/org/scijava/io/FileLocationTest.java
create mode 100644 src/test/java/org/scijava/io/URILocationTest.java
create mode 100644 src/test/java/org/scijava/io/URLLocationTest.java
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/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());
+ }
+
+}
From c424667f1b00b8ed5417b67a8c461221c23f5fbc Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Sat, 7 Jun 2014 21:54:27 -0500
Subject: [PATCH 17/23] Migrate SCIFIO I/O framework to SciJava Common
This implementation is a restructuring of SCIFIO's io.scif.io package,
to fit within the SciJava plugin framework.
This commit introduces a new plugin type, DataHandle, that defines all
the methods for random access reading and writing. The DataHandle
interface is a rich SciJava plugin type that extends the
java.io.DataInput, java.io.DataOutput and java.io.Closeable interfaces.
Furthermore, the DataHandle interface also defines additional API
methods that make it very straightforward to adapt DataHandle objects
into InputStreams and OutputStreams (it cannot extend InputStream and
OutputStream, because they are abstract classes, not interfaces).
The actual adaptation to streams is done using the DataHandleInputStream
and DataHandleOutputStream wrapper objects, simply by delegating to the
various DataHandle methods of the same name.
Overall, the mapping from the old io.scif.io API is as follows:
* IRandomAccess + IStreamAccess -> org.scijava.io.DataHandle
* RandomAccessInputStream -> org.scijava.io.DataHandleInputStream
* RandomAccessOutputStream -> org.scijava.io.DataHandleOutputStream
* Location -> subsumed into the org.scijava.io.Location type hierarchy
* HandleException -> unnecessary; IOException et. al are good enough
* FileHandle, URLHandle, etc. -> same names, implementing DataHandle
Note that the mapping is rather loose, with several layers consolidated
or eliminated. Additional commits will readd features, such as specific
DataHandle plugins, as appropriate to achieve needed functionality.
---
.../org/scijava/io/AbstractDataHandle.java | 275 ++++++++++++++++++
src/main/java/org/scijava/io/DataHandle.java | 226 ++++++++++++++
.../org/scijava/io/DataHandleInputStream.java | 119 ++++++++
.../scijava/io/DataHandleOutputStream.java | 89 ++++++
.../org/scijava/io/DataHandleService.java | 49 ++++
.../scijava/io/DefaultDataHandleService.java | 64 ++++
.../java/org/scijava/ContextCreationTest.java | 1 +
7 files changed, 823 insertions(+)
create mode 100644 src/main/java/org/scijava/io/AbstractDataHandle.java
create mode 100644 src/main/java/org/scijava/io/DataHandle.java
create mode 100644 src/main/java/org/scijava/io/DataHandleInputStream.java
create mode 100644 src/main/java/org/scijava/io/DataHandleOutputStream.java
create mode 100644 src/main/java/org/scijava/io/DataHandleService.java
create mode 100644 src/main/java/org/scijava/io/DefaultDataHandleService.java
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/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/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,
From ef471bdcac7fdc4a5b88d95b9fe8d0695a487e87 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 11 May 2015 16:23:18 -0500
Subject: [PATCH 18/23] IOService: clarify javadoc
The IOService is high-level; the DataHandleService is low-level.
---
src/main/java/org/scijava/io/IOService.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
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
From a0a5038e3714a3688d5c9c701fb9e9c378f3d92e Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Tue, 12 May 2015 11:25:08 -0500
Subject: [PATCH 19/23] Migrate DataTools methods from SCIFIO/Bio-Formats
The io.scif.common.DataTools class (originally from Bio-Formats at
loci.common.DataTools) contains many useful methods for working with
primitive numerical types, and arrays of those types. In particular,
it includes methods for converting between types, endianness, and
signedness. These utility methods will continue to be extremely
useful for the various DataHandle implementations, especially for
proper support for both big- and little-endian byte ordering.
The class was split as follows:
* org.scijava.util.Bytes - primitive numeric methods
* org.scijava.util.StringUtils - utility methods for strings
* org.scijava.util.ArrayUtils - utility methods for arrays
Regarding provenance of the code: I originally wrote much of it for
the Bio-Formats library; Melissa Linkert wrote a lot as well, with
additional contributions by Chris Allan and probably others on the
OME team. The relevant code is all BSD-2 licensed; I have added an
appropriate extra legal notice at the top of each affected file.
---
.../java/org/scijava/util/ArrayUtils.java | 274 ++++++
src/main/java/org/scijava/util/Bytes.java | 816 ++++++++++++++++++
.../java/org/scijava/util/StringUtils.java | 133 +++
3 files changed, 1223 insertions(+)
create mode 100644 src/main/java/org/scijava/util/Bytes.java
create mode 100644 src/main/java/org/scijava/util/StringUtils.java
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/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();
+ }
+
+}
From 06863019cc4abbcb0e6da5ae2b38584ae9c16989 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Wed, 13 May 2015 14:16:59 -0500
Subject: [PATCH 20/23] Add unit tests for ArrayUtils safeMultiply methods
These were migrated from SCIFIO, just like the methods they are testing.
---
.../java/org/scijava/util/ArrayUtilsTest.java | 150 ++++++++++++++++++
1 file changed, 150 insertions(+)
create mode 100644 src/test/java/org/scijava/util/ArrayUtilsTest.java
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);
+ }
+
+}
From ebca914b4a8917deba4f862611aaa0b8d4ce4e9a Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Mon, 11 May 2015 16:21:19 -0500
Subject: [PATCH 21/23] Add DataHandle implementation for FileLocations
This is a redesigned version of SCIFIO's old FileHandle class.
It delegates to java.io.RandomAccessFile for the bulk of its
implementation.
---
src/main/java/org/scijava/io/FileHandle.java | 285 +++++++++++++++++++
1 file changed, 285 insertions(+)
create mode 100644 src/main/java/org/scijava/io/FileHandle.java
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());
+ }
+
+}
From e3c179318555091483802d498b83e82cd7068faf Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Sat, 7 Jun 2014 21:54:27 -0500
Subject: [PATCH 22/23] Add initial DataHandle unit tests
These tests are agnostic to the type of DataHandle, which
should make it easier to get full test coverage of every
concrete DataHandle implementation, as they are introduced.
---
.../java/org/scijava/io/DataHandleTest.java | 227 ++++++++++++++++++
1 file changed, 227 insertions(+)
create mode 100644 src/test/java/org/scijava/io/DataHandleTest.java
diff --git a/src/test/java/org/scijava/io/DataHandleTest.java b/src/test/java/org/scijava/io/DataHandleTest.java
new file mode 100644
index 000000000..decc7d29c
--- /dev/null
+++ b/src/test/java/org/scijava/io/DataHandleTest.java
@@ -0,0 +1,227 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.scijava.Context;
+import org.scijava.util.Bytes;
+
+/**
+ * Abstract base class for {@link DataHandle} implementation tests.
+ *
+ * @author Curtis Rueden
+ */
+public abstract class DataHandleTest {
+
+ private static final byte[] BYTES = { //
+ 'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '\n', //
+ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, -128, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, //
+ 125, 127, -127, -125, -3, -2, -1 };
+
+ // -- Test methods --
+
+ @Test
+ public void testDataHandle() throws IOException {
+ final Context context = new Context(DataHandleService.class);
+ final DataHandleService dataHandleService =
+ context.service(DataHandleService.class);
+
+ final Location loc = createLocation();
+ final DataHandle extends Location> handle = dataHandleService.create(loc);
+ assertEquals(getExpectedHandleType(), handle.getClass());
+
+ checkReads(handle);
+ checkWrites(handle);
+ handle.close();
+ }
+
+ // -- DataHandleTest methods --
+
+ public abstract Class extends DataHandle>> getExpectedHandleType();
+
+ public abstract Location createLocation() throws IOException;
+
+ // -- Internal methods --
+
+ protected void populateData(final OutputStream out) throws IOException {
+ out.write(BYTES);
+ out.close();
+ }
+
+ protected void checkReads(final DataHandle handle)
+ throws IOException
+ {
+ assertEquals(0, handle.offset());
+ assertEquals(BYTES.length, handle.length());
+ assertEquals("UTF-8", handle.getEncoding());
+ assertEquals(ByteOrder.BIG_ENDIAN, handle.getOrder());
+ assertEquals(false, handle.isLittleEndian());
+
+ // test read()
+ for (int i = 0; i < BYTES.length; i++) {
+ assertEquals(msg(i), 0xff & BYTES[i], handle.read());
+ }
+ assertEquals(-1, handle.read());
+ handle.seek(10);
+ assertEquals(10, handle.offset());
+ assertEquals(BYTES[10], handle.read());
+
+ // test read(byte[])
+ final byte[] buf = new byte[10];
+ handle.seek(1);
+ assertBytesMatch(1, handle.read(buf), buf);
+
+ // test read(ByteBuffer)
+ Arrays.fill(buf, (byte) 0);
+ final ByteBuffer byteBuffer = ByteBuffer.wrap(buf);
+ handle.seek(2);
+ assertBytesMatch(2, handle.read(byteBuffer), byteBuffer.array());
+
+ // test readByte()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length; i++) {
+ assertEquals(msg(i), BYTES[i], handle.readByte());
+ }
+
+ // test readShort()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 2; i += 2) {
+ assertEquals(msg(i), Bytes.toShort(BYTES, i, false), handle.readShort());
+ }
+
+ // test readInt()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 4; i += 4) {
+ assertEquals(msg(i), Bytes.toInt(BYTES, i, false), handle.readInt());
+ }
+
+ // test readLong()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 8; i += 8) {
+ assertEquals(msg(i), Bytes.toLong(BYTES, i, false), handle.readLong());
+ }
+
+ // test readFloat()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 4; i += 4) {
+ assertEquals(msg(i), Bytes.toFloat(BYTES, i, false), handle.readFloat(),
+ 0);
+ }
+
+ // test readDouble()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 8; i += 8) {
+ assertEquals(msg(i), Bytes.toDouble(BYTES, i, false),
+ handle.readDouble(), 0);
+ }
+
+ // test readBoolean()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length; i++) {
+ assertEquals(msg(i), BYTES[i] == 0 ? false : true, handle.readBoolean());
+ }
+
+ // test readChar()
+ handle.seek(0);
+ for (int i = 0; i < BYTES.length / 2; i += 2) {
+ assertEquals(msg(i), (char) Bytes.toInt(BYTES, i, 2, false), handle
+ .readChar());
+ }
+
+ // test readFully(byte[])
+ Arrays.fill(buf, (byte) 0);
+ handle.seek(3);
+ handle.readFully(buf);
+ assertBytesMatch(3, buf.length, buf);
+
+ // test readCString() - _includes_ the null terminator!
+ handle.seek(16);
+ assertBytesMatch(16, 7, handle.readCString().getBytes());
+
+ // test readLine() - _excludes_ the newline terminator!
+ handle.seek(7);
+ assertBytesMatch(7, 5, handle.readLine().getBytes());
+
+ // test readString(String) - _includes_ the matching terminator!
+ handle.seek(7);
+ assertBytesMatch(7, 5, handle.readString("abcdefg").getBytes());
+
+ // test findString(String) - _includes_ the matching terminator!
+ handle.seek(1);
+ assertBytesMatch(1, 11, handle.findString("world").getBytes());
+ }
+
+ protected void checkWrites(final DataHandle handle)
+ throws IOException
+ {
+ final byte[] copy = BYTES.clone();
+
+ // change the data
+ handle.seek(7);
+ final String splice = "there";
+ for (int i = 0; i < splice.length(); i++) {
+ final char c = splice.charAt(i);
+ handle.write(c);
+ copy[7 + i] = (byte) c;
+ }
+
+ // verify the changes
+ handle.seek(0);
+ for (int i = 0; i < copy.length; i++) {
+ assertEquals(msg(i), 0xff & copy[i], handle.read());
+ }
+ }
+
+ // -- Helper methods --
+
+ private void assertBytesMatch(final int offset, final int length,
+ final byte[] b)
+ {
+ assertEquals(length, b.length);
+ for (int i = 0; i < length; i++) {
+ assertEquals(msg(i), BYTES[i + offset], b[i]);
+ }
+ }
+
+ private String msg(final int i) {
+ return "[" + i + "]:";
+ }
+
+}
From 98e490ba09ccfdc4aa39a0434d698bf78d38fb53 Mon Sep 17 00:00:00 2001
From: Curtis Rueden
Date: Sat, 7 Jun 2014 21:54:27 -0500
Subject: [PATCH 23/23] Add unit tests for FileHandle
---
.../java/org/scijava/io/FileHandleTest.java | 59 +++++++++++++++++++
1 file changed, 59 insertions(+)
create mode 100644 src/test/java/org/scijava/io/FileHandleTest.java
diff --git a/src/test/java/org/scijava/io/FileHandleTest.java b/src/test/java/org/scijava/io/FileHandleTest.java
new file mode 100644
index 000000000..d4ad153a2
--- /dev/null
+++ b/src/test/java/org/scijava/io/FileHandleTest.java
@@ -0,0 +1,59 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2015 Board of Regents of the University of
+ * Wisconsin-Madison, Broad Institute of MIT and Harvard, and Max Planck
+ * Institute of Molecular Cell Biology and Genetics.
+ * %%
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ * #L%
+ */
+
+package org.scijava.io;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Tests {@link FileHandle}.
+ *
+ * @author Curtis Rueden
+ */
+public class FileHandleTest extends DataHandleTest {
+
+ @Override
+ public Class extends DataHandle>> getExpectedHandleType() {
+ return FileHandle.class;
+ }
+
+ @Override
+ public Location createLocation() throws IOException {
+ // create and populate a temp file
+ final File tmpFile = File.createTempFile("FileHandleTest", "test-file");
+ tmpFile.deleteOnExit();
+ populateData(new FileOutputStream(tmpFile));
+ return new FileLocation(tmpFile);
+ }
+
+}