From 49c055b05f5d46340eeade887744ab72bbba7525 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 17 May 2017 08:08:25 -0500 Subject: [PATCH 001/741] Fix author tags --- src/main/java/org/scijava/event/ContextDisposingEvent.java | 2 +- src/test/java/org/scijava/util/ProcessUtilsTest.java | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/scijava/event/ContextDisposingEvent.java b/src/main/java/org/scijava/event/ContextDisposingEvent.java index 0dc1aaf34..bbe3fba40 100644 --- a/src/main/java/org/scijava/event/ContextDisposingEvent.java +++ b/src/main/java/org/scijava/event/ContextDisposingEvent.java @@ -34,6 +34,6 @@ /** * Event to be published just before disposing a context. * - * @author Johannes Schindein + * @author Johannes Schindelin */ public class ContextDisposingEvent extends SciJavaEvent { } diff --git a/src/test/java/org/scijava/util/ProcessUtilsTest.java b/src/test/java/org/scijava/util/ProcessUtilsTest.java index c2a4ae0b2..227703ca9 100644 --- a/src/test/java/org/scijava/util/ProcessUtilsTest.java +++ b/src/test/java/org/scijava/util/ProcessUtilsTest.java @@ -71,11 +71,7 @@ private void assumePOSIX() { assumeTrue(PlatformUtils.isPOSIX()); } - /** - * A class executing a 'sleep' call, to be interrupted. - * - * @author Johannes Schindelin - */ + /** A class executing a 'sleep' call, to be interrupted. */ private static class SleepThread extends Thread { private int seconds; private Throwable result; From 96ae9bed1c67b3832a8ac47aed3db6ffe037b2a7 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 17 May 2017 09:13:01 -0500 Subject: [PATCH 002/741] Add missing contributors All of these folks wrote code which ended up here in SJC. --- pom.xml | 43 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index fd1a7ddea..c04cd8bcc 100644 --- a/pom.xml +++ b/pom.xml @@ -55,16 +55,15 @@ http://imagej.net/User:Schindelin dscho + + Chris Allan + callan + Barry DeZonia http://imagej.net/User:Bdezonia bdezonia - - Lee Kamentsky - http://imagej.net/User:Leek - LeeKamentsky - Christian Dietz http://imagej.net/User:Dietzc @@ -80,15 +79,49 @@ http://imagej.net/User:Gab1one gab1one + + Aivar Grislis + http://imagej.net/User:Grislis + grislis + Jonathan Hale Squareys + + Grant Harris + http://imagej.net/User:Harris + tnargsirrah + + + Lee Kamentsky + http://imagej.net/User:Leek + LeeKamentsky + + + Rick Lentz + http://imagej.net/User:Lentz + + + Melissa Linkert + http://imagej.net/User:Linkert + melissalinkert + Kevin Mader http://imagej.net/User:Ksmader kmader + + Hadrien Mary + http://imagej.net/User:Hadim + hadim + + + Alison Walter + http://imagej.net/User:Awalter2 + awalter17 + Jay Warrick jaywarrick From 418de99580412c34ab8afc0d50e637c9a6f7ef58 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 18 May 2017 10:31:19 -0500 Subject: [PATCH 003/741] ThreadService: improve thread safety We now use double-checked locking for initialization. Otherwise, conceivably, two threads could trigger simultaneous creation of competing ExecutorServices. And we also synchronize the disposal, so that the ExecutorService cannot possibly be created after dispose() is called. --- .../org/scijava/thread/DefaultThreadService.java | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/scijava/thread/DefaultThreadService.java b/src/main/java/org/scijava/thread/DefaultThreadService.java index 945a6fe43..1de7899fe 100644 --- a/src/main/java/org/scijava/thread/DefaultThreadService.java +++ b/src/main/java/org/scijava/thread/DefaultThreadService.java @@ -141,9 +141,12 @@ public ThreadContext getThreadContext(final Thread thread) { // -- Disposable methods -- @Override - public void dispose() { + public synchronized void dispose() { disposed = true; - if (executor != null) executor.shutdown(); + if (executor != null) { + executor.shutdown(); + executor = null; + } } // -- ThreadFactory methods -- @@ -157,12 +160,15 @@ public Thread newThread(final Runnable r) { // -- Helper methods -- private ExecutorService executor() { - if (executor == null) { - executor = Executors.newCachedThreadPool(this); - } + if (executor == null) initExecutor(); return executor; } + private synchronized void initExecutor() { + if (executor != null) return; + executor = Executors.newCachedThreadPool(this); + } + private Runnable wrap(final Runnable r) { final Thread parent = Thread.currentThread(); return new Runnable() { From 35ee2f494f9869bed55883181986a9af696b267c Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 18 May 2017 10:32:10 -0500 Subject: [PATCH 004/741] ThreadService: clarify javadoc --- src/main/java/org/scijava/thread/ThreadService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/thread/ThreadService.java b/src/main/java/org/scijava/thread/ThreadService.java index 7be845d97..bbc03acfd 100644 --- a/src/main/java/org/scijava/thread/ThreadService.java +++ b/src/main/java/org/scijava/thread/ThreadService.java @@ -112,7 +112,7 @@ public enum ThreadContext { /** * Gets whether the current thread is a dispatch thread for use with - * {@link #invoke} and {@link #queue}. + * {@link #invoke(Runnable)} and {@link #queue(Runnable)}. *

* In the case of AWT-based applications (e.g., Java on the desktop), this is * typically the AWT Event Dispatch Thread (EDT). However, ultimately the @@ -141,7 +141,8 @@ void invoke(Runnable code) throws InterruptedException, InvocationTargetException; /** - * Queues the given code for later execution in a special dispatch thread. + * Queues the given code for later execution in a special dispatch thread, + * returning immediately. *

* In the case of AWT-based applications (e.g., Java on the desktop), this is * typically the AWT Event Dispatch Thread (EDT). However, ultimately the From ec4f6e0336148bd10f0fd3538d9b1cac216a9f82 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 18 May 2017 10:50:00 -0500 Subject: [PATCH 005/741] ThreadService: add ability to queue jobs flexibly Previously, you could only queue a job to the special dispatch thread (typically the EDT). But in some scenarios, the EDT is exactly the wrong one to use. This commit adds the capability to queue jobs to any arbitrarily named queue. Essentially, each ID corresponds to a different single thread processing its jobs one at a time. This commit is dedicated to Hadrien Mary. --- .../scijava/thread/DefaultThreadService.java | 30 +++++++++++++++++++ .../org/scijava/thread/ThreadService.java | 25 ++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/main/java/org/scijava/thread/DefaultThreadService.java b/src/main/java/org/scijava/thread/DefaultThreadService.java index 1de7899fe..88f36f893 100644 --- a/src/main/java/org/scijava/thread/DefaultThreadService.java +++ b/src/main/java/org/scijava/thread/DefaultThreadService.java @@ -33,6 +33,8 @@ import java.awt.EventQueue; import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; import java.util.WeakHashMap; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -65,6 +67,9 @@ public final class DefaultThreadService extends AbstractService implements private ExecutorService executor; + /** Mapping from ID to single-thread {@link ExecutorService} queue. */ + private Map queues; + private int nextThread = 0; private boolean disposed; @@ -117,6 +122,16 @@ public void queue(final Runnable code) { EventQueue.invokeLater(wrap(code)); } + @Override + public Future queue(final String id, final Runnable code) { + return executor(id).submit(wrap(code)); + } + + @Override + public Future queue(final String id, final Callable code) { + return executor(id).submit(wrap(code)); + } + @Override public Thread getParent(final Thread thread) { return parents.get(thread != null ? thread : Thread.currentThread()); @@ -147,6 +162,11 @@ public synchronized void dispose() { executor.shutdown(); executor = null; } + if (queues != null) { + for (final ExecutorService queue : queues.values()) { + queue.shutdown(); + } + } } // -- ThreadFactory methods -- @@ -164,6 +184,16 @@ private ExecutorService executor() { return executor; } + private synchronized ExecutorService executor(final String id) { + if (disposed) return null; + if (queues == null) queues = new HashMap<>(); + if (!queues.containsKey(id)) { + final ExecutorService queue = Executors.newSingleThreadExecutor(); + queues.put(id, queue); + } + return queues.get(id); + } + private synchronized void initExecutor() { if (executor != null) return; executor = Executors.newCachedThreadPool(this); diff --git a/src/main/java/org/scijava/thread/ThreadService.java b/src/main/java/org/scijava/thread/ThreadService.java index bbc03acfd..9ae680fc2 100644 --- a/src/main/java/org/scijava/thread/ThreadService.java +++ b/src/main/java/org/scijava/thread/ThreadService.java @@ -153,6 +153,31 @@ void invoke(Runnable code) throws InterruptedException, */ void queue(Runnable code); + /** + * Queues the given code for later execution in a dispatch thread associated + * with the specified ID, returning immediately. + * + * @param id The ID designating which dispatch thread will execute the code. + * @param code The code to execute. + * @return A {@link Future} whose {@link Future#get()} method blocks until the + * queued code has completed executing and returns {@code null}. + * @see ExecutorService#submit(Runnable) + */ + Future queue(String id, Runnable code); + + /** + * Queues the given code for later execution in a dispatch thread associated + * with the specified ID, returning immediately. + * + * @param id The ID designating which dispatch thread will execute the code. + * @param code The code to execute. + * @return A {@link Future} whose {@link Future#get()} method blocks until the + * queued code has completed executing and returns the result of the + * execution. + * @see ExecutorService#submit(Callable) + */ + Future queue(String id, Callable code); + /** * Returns the thread that called the specified thread. *

From 02b59b0398e408c18a3025b365e7bbc1c698ea72 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 18 May 2017 11:40:12 -0500 Subject: [PATCH 006/741] POM: bump the minor version The ThreadService has new API. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c04cd8bcc..d3e2e0eab 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ scijava-common - 2.62.2-SNAPSHOT + 2.63.0-SNAPSHOT SciJava Common SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO. From 6fa4c8e68d89c42c89ef3ed10fa70740903ddd62 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 18 May 2017 21:15:57 -0500 Subject: [PATCH 007/741] ThreadService: give job queue threads better names Now the thread names use the same convention as ThreadService#run, but instead of thread number suffixes, the suffix is the ID given. --- .../org/scijava/thread/DefaultThreadService.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/thread/DefaultThreadService.java b/src/main/java/org/scijava/thread/DefaultThreadService.java index 88f36f893..f16ec6beb 100644 --- a/src/main/java/org/scijava/thread/DefaultThreadService.java +++ b/src/main/java/org/scijava/thread/DefaultThreadService.java @@ -40,6 +40,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; import org.scijava.log.LogService; import org.scijava.plugin.Parameter; @@ -188,7 +189,16 @@ private synchronized ExecutorService executor(final String id) { if (disposed) return null; if (queues == null) queues = new HashMap<>(); if (!queues.containsKey(id)) { - final ExecutorService queue = Executors.newSingleThreadExecutor(); + final ThreadFactory factory = new ThreadFactory() { + + @Override + public Thread newThread(final Runnable r) { + final String threadName = contextThreadPrefix() + id; + return new Thread(r, threadName); + } + + }; + final ExecutorService queue = Executors.newSingleThreadExecutor(factory); queues.put(id, queue); } return queues.get(id); From 0856f790d987b2c88861f78263b8f7175f65b441 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 17 May 2017 10:28:39 -0500 Subject: [PATCH 008/741] Deprecate CodeGenerator classes --- src/main/java/org/scijava/script/CodeGenerator.java | 2 ++ src/main/java/org/scijava/script/CodeGeneratorJava.java | 2 ++ src/main/java/org/scijava/script/InvocationObject.java | 2 ++ src/main/java/org/scijava/script/ParameterObject.java | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/main/java/org/scijava/script/CodeGenerator.java b/src/main/java/org/scijava/script/CodeGenerator.java index d79c7f6c4..12591841a 100644 --- a/src/main/java/org/scijava/script/CodeGenerator.java +++ b/src/main/java/org/scijava/script/CodeGenerator.java @@ -35,7 +35,9 @@ * Code Generator Interface * * @author Grant Harris + * @deprecated To be removed in SciJava Common 3.0.0. */ +@Deprecated public interface CodeGenerator { /** Adds delimiter character between arguments (typically a ','). */ diff --git a/src/main/java/org/scijava/script/CodeGeneratorJava.java b/src/main/java/org/scijava/script/CodeGeneratorJava.java index 750aa55b3..1dba40118 100644 --- a/src/main/java/org/scijava/script/CodeGeneratorJava.java +++ b/src/main/java/org/scijava/script/CodeGeneratorJava.java @@ -35,7 +35,9 @@ * {@link CodeGenerator} for Java. * * @author Grant Harris + * @deprecated To be removed in SciJava Common 3.0.0. */ +@Deprecated public class CodeGeneratorJava implements CodeGenerator { static final String lsep = System.getProperty("line.separator"); diff --git a/src/main/java/org/scijava/script/InvocationObject.java b/src/main/java/org/scijava/script/InvocationObject.java index 4ace6b083..639c33833 100644 --- a/src/main/java/org/scijava/script/InvocationObject.java +++ b/src/main/java/org/scijava/script/InvocationObject.java @@ -38,7 +38,9 @@ * the parameters that were passed to it. * * @author Grant Harris + * @deprecated To be removed in SciJava Common 3.0.0. */ +@Deprecated public class InvocationObject { public String moduleCalled; diff --git a/src/main/java/org/scijava/script/ParameterObject.java b/src/main/java/org/scijava/script/ParameterObject.java index 7f02b1d39..5073e147c 100644 --- a/src/main/java/org/scijava/script/ParameterObject.java +++ b/src/main/java/org/scijava/script/ParameterObject.java @@ -35,7 +35,9 @@ * Holds a parameter, its type and value, for a recorded macro. * * @author Grant Harris + * @deprecated To be removed in SciJava Common 3.0.0. */ +@Deprecated public class ParameterObject { public ParameterObject(final String param, final Class type, From 8c84c5660eadd1ec7539bcc0848590effefed47b Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 17 May 2017 10:32:41 -0500 Subject: [PATCH 009/741] Move internal class into DefaultScriptInterpreter --- .../script/DefaultScriptInterpreter.java | 110 +++++++++++++ src/main/java/org/scijava/script/History.java | 145 ------------------ 2 files changed, 110 insertions(+), 145 deletions(-) delete mode 100644 src/main/java/org/scijava/script/History.java diff --git a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java index 543ea506a..4a2599713 100644 --- a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java +++ b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java @@ -42,6 +42,7 @@ import org.scijava.log.LogService; import org.scijava.plugin.Parameter; import org.scijava.prefs.PrefService; +import org.scijava.util.LastRecentlyUsed; /** * The default implementation of a {@link ScriptInterpreter}. @@ -363,4 +364,113 @@ private static T callMethod(final Object object, final String methodName, return null; } + // -- Helper classes -- + + /** Container for a script language's interpreter history. */ + private static class History { + + @SuppressWarnings("unused") + protected static final long serialVersionUID = 2L; + + private static final String PREFIX = "History."; + private final int MAX_ENTRIES = 1000; + + private final PrefService prefs; + private final String name; + private final LastRecentlyUsed entries = + new LastRecentlyUsed<>(MAX_ENTRIES); + private String currentCommand = ""; + private int position = -1; + + /** + * Constructs a history object for a given scripting language. + * + * @param name the name of the scripting language + */ + public History(final PrefService prefs, final String name) { + this.prefs = prefs; + this.name = name; + } + + /** + * Read back a persisted history. + */ + public void read() { + entries.clear(); + for (final String item : prefs.getIterable(getClass(), PREFIX + name)) { + entries.addToEnd(item); + } + } + + /** + * Persist the history. + * + * @see PrefService + */ + public void write() { + prefs.putIterable(getClass(), entries, PREFIX + name); + } + + /** + * Adds the most recently issued command. + * + * @param command the most recent command to add to the history + */ + public void add(final String command) { + entries.add(command); + position = -1; + currentCommand = ""; + } + + public boolean replace(final String command) { + if (position < 0) { + currentCommand = command; + return false; + } + return entries.replace(position, command); + } + + /** + * Navigates to the next (more recent) command. + *

+ * This method wraps around, i.e. it returns {@code null} when there is no + * more-recent command in the history. + *

+ * + * @return the next command + */ + public String next() { + position = entries.next(position); + return position < 0 ? currentCommand : entries.get(position); + } + + /** + * Navigates to the previous (i.e less recent) command. + *

+ * This method wraps around, i.e. it returns {@code null} when there is no + * less-recent command in the history. + *

+ * + * @return the previous command + */ + public String previous() { + position = entries.previous(position); + return position < 0 ? currentCommand : entries.get(position); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + int pos = -1; + for (;;) { + pos = entries.previous(pos); + if (pos < 0) break; + if (builder.length() > 0) builder.append(" -> "); + if (this.position == pos) builder.append("["); + builder.append(entries.get(pos)); + if (this.position == pos) builder.append("]"); + } + return builder.toString(); + } + } } diff --git a/src/main/java/org/scijava/script/History.java b/src/main/java/org/scijava/script/History.java deleted file mode 100644 index 648a466c9..000000000 --- a/src/main/java/org/scijava/script/History.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * #%L - * SciJava Common shared library for SciJava software. - * %% - * Copyright (C) 2009 - 2017 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.script; - -import org.scijava.prefs.PrefService; -import org.scijava.util.LastRecentlyUsed; - -/** - * Container for a script language's interpreter history. - * - * @author Johannes Schindelin - */ -class History { - - protected static final long serialVersionUID = 1L; - - private static final String PREFIX = "History."; - private final int MAX_ENTRIES = 1000; - - private final PrefService prefs; - private final String name; - private final LastRecentlyUsed entries = new LastRecentlyUsed<>(MAX_ENTRIES); - private String currentCommand = ""; - private int position = -1; - - /** - * Constructs a history object for a given scripting language. - * - * @param name the name of the scripting language - */ - public History(final PrefService prefs, final String name) { - this.prefs = prefs; - this.name = name; - } - - /** - * Read back a persisted history. - */ - public void read() { - entries.clear(); - for (final String item : prefs.getIterable(getClass(), PREFIX + name)) { - entries.addToEnd(item); - } - } - - /** - * Persist the history. - * - * @see PrefService - */ - public void write() { - prefs.putIterable(getClass(), entries, PREFIX + name); - } - - /** - * Adds the most recently issued command. - * - * @param command the most recent command to add to the history - */ - public void add(final String command) { - entries.add(command); - position = -1; - currentCommand = ""; - } - - public boolean replace(final String command) { - if (position < 0) { - currentCommand = command; - return false; - } - return entries.replace(position, command); - } - - /** - * Navigates to the next (more recent) command. - *

- * This method wraps around, i.e. it returns {@code null} when there is no - * more-recent command in the history. - *

- * - * @return the next command - */ - public String next() { - position = entries.next(position); - return position < 0 ? currentCommand : entries.get(position); - } - - /** - * Navigates to the previous (i.e less recent) command. - *

- * This method wraps around, i.e. it returns {@code null} when there is no - * less-recent command in the history. - *

- * - * @return the previous command - */ - public String previous() { - position = entries.previous(position); - return position < 0 ? currentCommand : entries.get(position); - } - - @Override - public String toString() { - final StringBuilder builder = new StringBuilder(); - int pos = -1; - for (;;) { - pos = entries.previous(pos); - if (pos < 0) break; - if (builder.length() > 0) builder.append(" -> "); - if (this.position == pos) builder.append("["); - builder.append(entries.get(pos)); - if (this.position == pos) builder.append("]"); - } - return builder.toString(); - } -} From 2faeac5d4380196fd0545b83b443254f3ccb12c5 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 17 May 2017 10:33:42 -0500 Subject: [PATCH 010/741] Add missing license headers --- .../scijava/script/AbstractAutoCompleter.java | 4 +-- .../scijava/script/AutoCompletionResult.java | 33 ++++++++++++++++--- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/scijava/script/AbstractAutoCompleter.java b/src/main/java/org/scijava/script/AbstractAutoCompleter.java index 5155ba784..05956001a 100644 --- a/src/main/java/org/scijava/script/AbstractAutoCompleter.java +++ b/src/main/java/org/scijava/script/AbstractAutoCompleter.java @@ -8,13 +8,13 @@ * %% * 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 diff --git a/src/main/java/org/scijava/script/AutoCompletionResult.java b/src/main/java/org/scijava/script/AutoCompletionResult.java index 6ea4dbcc3..ec48da96c 100644 --- a/src/main/java/org/scijava/script/AutoCompletionResult.java +++ b/src/main/java/org/scijava/script/AutoCompletionResult.java @@ -1,7 +1,32 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. +/*- + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2017 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.script; From 55518586f39d9b58d2686bd8064b51c9d5f492da Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 18 May 2017 23:03:28 -0500 Subject: [PATCH 011/741] ScriptInfo: fix big bug in the return value logic The actual return value never got assigned to the implicit "result" output, because addReturnValue called addItem to add "result" as an output, which then mistakenly disabled the appendReturnValue flag. That flag should only be disabled when an _explicit_ output exists. --- src/main/java/org/scijava/script/ScriptInfo.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java index d22e3902a..1b3d7f62e 100644 --- a/src/main/java/org/scijava/script/ScriptInfo.java +++ b/src/main/java/org/scijava/script/ScriptInfo.java @@ -432,7 +432,7 @@ private void parseParam(final String param, varName = tokens[1]; } final Class type = scriptService.lookupClass(typeName); - addItem(varName, type, attrs); + addItem(varName, type, attrs, true); if (ScriptModule.RETURN_VALUE.equals(varName)) { // NB: The return value variable is declared as an explicit OUTPUT. @@ -460,11 +460,11 @@ private void checkValid(final boolean valid, final String param) private void addReturnValue() { final HashMap attrs = new HashMap<>(); attrs.put("type", "OUTPUT"); - addItem(ScriptModule.RETURN_VALUE, Object.class, attrs); + addItem(ScriptModule.RETURN_VALUE, Object.class, attrs, false); } private void addItem(final String name, final Class type, - final Map attrs) + final Map attrs, final boolean explicit) { final DefaultMutableModuleItem item = new DefaultMutableModuleItem<>(this, name, type); @@ -477,7 +477,7 @@ private void addItem(final String name, final Class type, registerOutput(item); // NB: Only append the return value as an extra // output when no explicit outputs are declared. - appendReturnValue = false; + if (explicit) appendReturnValue = false; } } From 059e6993df31deb0270ea81185894861da98ec08 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 19 May 2017 19:02:02 -0500 Subject: [PATCH 012/741] Bump to next development cycle Signed-off-by: Curtis Rueden --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d3e2e0eab..08815add8 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ scijava-common - 2.63.0-SNAPSHOT + 2.63.1-SNAPSHOT SciJava Common SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO. From 15e232db8dd82cb0b7a81faad30805638bb1ac62 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Tue, 23 May 2017 07:18:17 -0500 Subject: [PATCH 013/741] PTService: fix javadoc The examples given are all from SciJava Common, not ImageJ. --- src/main/java/org/scijava/plugin/PTService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/plugin/PTService.java b/src/main/java/org/scijava/plugin/PTService.java index b131a8600..36a128498 100644 --- a/src/main/java/org/scijava/plugin/PTService.java +++ b/src/main/java/org/scijava/plugin/PTService.java @@ -40,7 +40,7 @@ *

* There are many kinds of services, but most of them share one common * characteristic: they provide API specific to a particular type of plugin. A - * few examples from ImageJ: + * few examples: *

*
    *
  • The {@link org.scijava.command.CommandService} works with From ed9cb477312cb9bee4c2ba5bc9ca5d4c25817ca8 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 22 May 2017 13:52:09 -0500 Subject: [PATCH 014/741] Move ScriptModule#getLanguage() into ScriptInfo For a given script, which language to use is a constant. It should be part of the script _metadata_, not the script module _instance_. --- pom.xml | 2 +- .../java/org/scijava/script/ScriptInfo.java | 19 ++++++++++ .../java/org/scijava/script/ScriptModule.java | 37 ++++++++----------- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/pom.xml b/pom.xml index 08815add8..9a0b01354 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ scijava-common - 2.63.1-SNAPSHOT + 2.64.0-SNAPSHOT SciJava Common SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO. diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java index 1b3d7f62e..c3668767e 100644 --- a/src/main/java/org/scijava/script/ScriptInfo.java +++ b/src/main/java/org/scijava/script/ScriptInfo.java @@ -101,6 +101,9 @@ public class ScriptInfo extends AbstractModuleInfo implements Contextual { /** True iff the return value should be appended as an output. */ private boolean appendReturnValue; + /** Script language in which the script should be executed. */ + private ScriptLanguage scriptLanguage; + /** * Creates a script metadata object which describes the given script file. * @@ -224,6 +227,22 @@ public BufferedReader getReader() { return new BufferedReader(new StringReader(script), PARAM_CHAR_MAX); } + /** Gets the scripting language of the script. */ + public ScriptLanguage getLanguage() { + if (scriptLanguage == null) { + // infer the language from the script path's extension + final String scriptPath = getPath(); + final String extension = FileUtils.getExtension(scriptPath); + scriptLanguage = scriptService.getLanguageByExtension(extension); + } + return scriptLanguage; + } + + /** Overrides the script language to use when executing the script. */ + public void setLanguage(final ScriptLanguage scriptLanguage) { + this.scriptLanguage = scriptLanguage; + } + /** * Parses the script's input and output parameters from the script header. *

    diff --git a/src/main/java/org/scijava/script/ScriptModule.java b/src/main/java/org/scijava/script/ScriptModule.java index 92cf3ced9..d00b7ab5e 100644 --- a/src/main/java/org/scijava/script/ScriptModule.java +++ b/src/main/java/org/scijava/script/ScriptModule.java @@ -50,7 +50,6 @@ import org.scijava.module.Module; import org.scijava.module.ModuleItem; import org.scijava.plugin.Parameter; -import org.scijava.util.FileUtils; /** * A {@link Module} which executes a script. @@ -76,9 +75,6 @@ public class ScriptModule extends AbstractModule implements Contextual { @Parameter private LogService log; - /** Script language in which the script should be executed. */ - private ScriptLanguage scriptLanguage; - /** Script engine with which the script should be executed. */ private ScriptEngine scriptEngine; @@ -96,22 +92,6 @@ public ScriptModule(final ScriptInfo info) { // -- ScriptModule methods -- - /** Gets the scripting language of the script. */ - public ScriptLanguage getLanguage() { - if (scriptLanguage == null) { - // infer the language from the script path's extension - final String path = getInfo().getPath(); - final String extension = FileUtils.getExtension(path); - scriptLanguage = scriptService.getLanguageByExtension(extension); - } - return scriptLanguage; - } - - /** Overrides the script language to use when executing the script. */ - public void setLanguage(final ScriptLanguage scriptLanguage) { - this.scriptLanguage = scriptLanguage; - } - /** Sets the writer used to record the standard output stream. */ public void setOutputWriter(final Writer output) { this.output = output; @@ -125,7 +105,7 @@ public void setErrorWriter(final Writer error) { /** Gets the script engine used to execute the script. */ public ScriptEngine getEngine() { if (scriptEngine == null) { - scriptEngine = getLanguage().getScriptEngine(); + scriptEngine = getInfo().getLanguage().getScriptEngine(); } return scriptEngine; } @@ -185,7 +165,7 @@ public void run() { } // populate output values - final ScriptLanguage language = getLanguage(); + final ScriptLanguage language = getInfo().getLanguage(); for (final ModuleItem item : getInfo().outputs()) { final String name = item.getName(); final Object value; @@ -230,4 +210,17 @@ public void setContext(final Context context) { context.inject(this); } + // -- Deprecated methods -- + + /** @deprecated Use {@link ScriptInfo#getLanguage()} instead. */ + @Deprecated + public ScriptLanguage getLanguage() { + return getInfo().getLanguage(); + } + + /** @deprecated Use {@link ScriptInfo#setLanguage(ScriptLanguage)} instead. */ + @Deprecated + public void setLanguage(final ScriptLanguage scriptLanguage) { + getInfo().setLanguage(scriptLanguage); + } } From e467c5d30435c5614974afabd2fc0819aac6b09d Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 22 May 2017 13:53:06 -0500 Subject: [PATCH 015/741] ScriptInfo: add fallback code for null url & path Now, the URL and (psuedo-)path can both be null. If that happens, the getURL() and getPath() methods will both return null. And the ScriptLanguage will be detected as the highest priority plugin available (typically Groovy, but depends on runtime classpath). --- .../java/org/scijava/script/ScriptInfo.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java index c3668767e..768a9ee23 100644 --- a/src/main/java/org/scijava/script/ScriptInfo.java +++ b/src/main/java/org/scijava/script/ScriptInfo.java @@ -232,8 +232,16 @@ public ScriptLanguage getLanguage() { if (scriptLanguage == null) { // infer the language from the script path's extension final String scriptPath = getPath(); - final String extension = FileUtils.getExtension(scriptPath); - scriptLanguage = scriptService.getLanguageByExtension(extension); + if (scriptPath != null) { + // use language associated with the script path extension + final String extension = FileUtils.getExtension(scriptPath); + scriptLanguage = scriptService.getLanguageByExtension(extension); + } + else { + // use the highest priority language + final List langs = scriptService.getLanguages(); + if (langs != null && !langs.isEmpty()) scriptLanguage = langs.get(0); + } } return scriptLanguage; } @@ -371,7 +379,7 @@ public void setContext(final Context context) { @Override public String getIdentifier() { - return "script:" + path; + return "script:" + (path == null ? "" : path); } // -- Locatable methods -- @@ -403,6 +411,7 @@ public String getVersion() { private URL url(final URL u, final String p) { if (u != null) return u; + if (p == null) return null; try { return new File(p).toURI().toURL(); } @@ -413,7 +422,8 @@ private URL url(final URL u, final String p) { } private String path(final URL u, final String p) { - return p == null ? u.getPath() : p; + if (p != null) return p; + return u == null ? null : u.getPath(); } private void parseParam(final String param) throws ScriptException { From bebaf6b179ae77402bfd2d6212d5407511901a9f Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 22 May 2017 15:18:24 -0500 Subject: [PATCH 016/741] ScriptInfo: relocate isReturnValueAppended method See: https://imagej.net/Coding_style#Ordering_of_code_blocks --- src/main/java/org/scijava/script/ScriptInfo.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java index 768a9ee23..b5f4ccdbc 100644 --- a/src/main/java/org/scijava/script/ScriptInfo.java +++ b/src/main/java/org/scijava/script/ScriptInfo.java @@ -251,6 +251,13 @@ public void setLanguage(final ScriptLanguage scriptLanguage) { this.scriptLanguage = scriptLanguage; } + /** Gets whether the return value is appended as an additional output. */ + public boolean isReturnValueAppended() { + return appendReturnValue; + } + + // -- AbstractModuleInfo methods -- + /** * Parses the script's input and output parameters from the script header. *

    @@ -335,11 +342,6 @@ public void parseParameters() { } } - /** Gets whether the return value is appended as an additional output. */ - public boolean isReturnValueAppended() { - return appendReturnValue; - } - // -- ModuleInfo methods -- @Override From 012b00d4fe92587973e304c264081a7749b93c31 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 22 May 2017 15:19:04 -0500 Subject: [PATCH 017/741] ScriptInfo: add setter for appendReturnValue This matches the getter isReturnValueAppended. It will be useful when we externalize the parameter parsing logic. --- src/main/java/org/scijava/script/ScriptInfo.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java index b5f4ccdbc..1e36139c2 100644 --- a/src/main/java/org/scijava/script/ScriptInfo.java +++ b/src/main/java/org/scijava/script/ScriptInfo.java @@ -256,6 +256,11 @@ public boolean isReturnValueAppended() { return appendReturnValue; } + /** Gets whether the return value is appended as an additional output. */ + public void setReturnValueAppended(final boolean appendReturnValue) { + this.appendReturnValue = appendReturnValue; + } + // -- AbstractModuleInfo methods -- /** From 27423a8874f5d2d4c55ec7bf28cb7228c5ca2ea9 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 22 May 2017 15:20:41 -0500 Subject: [PATCH 018/741] ScriptInfo: widen visibility of parameter methods We will need to be able to manipulate a ScriptInfo's inputs and outputs from external code, as part of the parameter parsing externalization. --- .../java/org/scijava/script/ScriptInfo.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java index 1e36139c2..844f6d912 100644 --- a/src/main/java/org/scijava/script/ScriptInfo.java +++ b/src/main/java/org/scijava/script/ScriptInfo.java @@ -60,6 +60,7 @@ import org.scijava.module.AbstractModuleInfo; import org.scijava.module.DefaultMutableModuleItem; import org.scijava.module.ModuleException; +import org.scijava.module.ModuleItem; import org.scijava.parse.ParseService; import org.scijava.plugin.Parameter; import org.scijava.util.DigestUtils; @@ -347,6 +348,24 @@ public void parseParameters() { } } + // NB: Widened visibility from AbstractModuleInfo. + @Override + public void clearParameters() { + super.clearParameters(); + } + + // NB: Widened visibility from AbstractModuleInfo. + @Override + public void registerInput(final ModuleItem input) { + super.registerInput(input); + } + + // NB: Widened visibility from AbstractModuleInfo. + @Override + public void registerOutput(final ModuleItem output) { + super.registerOutput(output); + } + // -- ModuleInfo methods -- @Override From bcb24546ac3aba8db49801141f70b778f8ec6425 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 22 May 2017 13:27:19 -0500 Subject: [PATCH 019/741] Add service and plugin type to process scripts This will be useful for implementing script processing directives in an extensible way. Here is one example, for dependency declaration: #@repository('https://maven.imagej.net/content/groups/public') #@dependency('net.imagej:imagej:2.0.0-rc-60') And here is another, for defining the intended script language: #!clojure We will also migrate the script parameter syntax to this scheme: #@input String name #@input int age #@output String greeting Or with the familiar sloppy shorthand: #@String name #@int age #@output String greeting See #265 See https://github.com/hadim/scijava-jupyter-kernel/issues/51#issuecomment-301816226 --- .../scijava/script/DefaultScriptService.java | 1 + .../DefaultScriptProcessorService.java | 49 ++++++++++ .../script/process/ScriptProcessor.java | 54 +++++++++++ .../process/ScriptProcessorService.java | 93 +++++++++++++++++++ .../java/org/scijava/ContextCreationTest.java | 1 + 5 files changed, 198 insertions(+) create mode 100644 src/main/java/org/scijava/script/process/DefaultScriptProcessorService.java create mode 100644 src/main/java/org/scijava/script/process/ScriptProcessor.java create mode 100644 src/main/java/org/scijava/script/process/ScriptProcessorService.java diff --git a/src/main/java/org/scijava/script/DefaultScriptService.java b/src/main/java/org/scijava/script/DefaultScriptService.java index 41ec47dab..9480cf863 100644 --- a/src/main/java/org/scijava/script/DefaultScriptService.java +++ b/src/main/java/org/scijava/script/DefaultScriptService.java @@ -62,6 +62,7 @@ import org.scijava.plugin.Plugin; import org.scijava.plugin.PluginService; import org.scijava.plugin.SciJavaPlugin; +import org.scijava.script.process.ScriptProcessorService; import org.scijava.service.Service; import org.scijava.util.ClassUtils; import org.scijava.util.ColorRGB; diff --git a/src/main/java/org/scijava/script/process/DefaultScriptProcessorService.java b/src/main/java/org/scijava/script/process/DefaultScriptProcessorService.java new file mode 100644 index 000000000..3cbce02e3 --- /dev/null +++ b/src/main/java/org/scijava/script/process/DefaultScriptProcessorService.java @@ -0,0 +1,49 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2017 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.script.process; + +import org.scijava.plugin.AbstractSingletonService; +import org.scijava.plugin.Plugin; +import org.scijava.service.Service; + +/** + * Default implementation of {@link ScriptProcessorService}. + * + * @author Curtis Rueden + */ +@Plugin(type = Service.class) +public class DefaultScriptProcessorService extends + AbstractSingletonService implements + ScriptProcessorService +{ + // NB: No implementation needed. +} diff --git a/src/main/java/org/scijava/script/process/ScriptProcessor.java b/src/main/java/org/scijava/script/process/ScriptProcessor.java new file mode 100644 index 000000000..3d412d074 --- /dev/null +++ b/src/main/java/org/scijava/script/process/ScriptProcessor.java @@ -0,0 +1,54 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2017 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.script.process; + +import org.scijava.plugin.SingletonPlugin; +import org.scijava.script.ScriptInfo; + +/** + * A script processor defines some sort of processing that primes a particular + * script for execution. + *

    + * Typically, these plugins look for special directives in the script itself + * beginning with distinctive character sequences like {@code #@}, and then + * perform some action in response. + *

    + * + * @author Curtis Rueden + */ +public interface ScriptProcessor extends SingletonPlugin { + + void begin(ScriptInfo info); + void process(String line); + default void end() {} + +} diff --git a/src/main/java/org/scijava/script/process/ScriptProcessorService.java b/src/main/java/org/scijava/script/process/ScriptProcessorService.java new file mode 100644 index 000000000..2f1d431cf --- /dev/null +++ b/src/main/java/org/scijava/script/process/ScriptProcessorService.java @@ -0,0 +1,93 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2017 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.script.process; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import org.scijava.plugin.PTService; +import org.scijava.script.ScriptInfo; +import org.scijava.service.SciJavaService; + +/** + * Interface for service that processes scripts. This service discovers + * available {@link ScriptProcessor} plugins, and provides convenience methods + * to interact with them. + * + * @author Curtis Rueden + */ +public interface ScriptProcessorService extends + PTService, SciJavaService +{ + + /** + * Invokes all {@link ScriptProcessor} plugins on the given script, line by + * line in sequence. + */ + default void process(final ScriptInfo info) throws IOException { + final List processors = getPlugins().stream().map( + p -> pluginService().createInstance(p)).collect(Collectors.toList()); + + BufferedReader reader = info.getReader(); + if (reader == null) { + reader = new BufferedReader(new FileReader(info.getPath())); + } + + for (final ScriptProcessor p : processors) { + p.begin(info); + } + + try (final BufferedReader in = reader) { + while (true) { + final String line = in.readLine(); + if (line == null) break; + for (final ScriptProcessor p : processors) { + p.process(line); + } + } + } + + for (final ScriptProcessor p : processors) { + p.end(); + } + } + + // -- PTService methods -- + + @Override + default Class getPluginType() { + return ScriptProcessor.class; + } +} diff --git a/src/test/java/org/scijava/ContextCreationTest.java b/src/test/java/org/scijava/ContextCreationTest.java index 8de76276e..5dc0ed1b4 100644 --- a/src/test/java/org/scijava/ContextCreationTest.java +++ b/src/test/java/org/scijava/ContextCreationTest.java @@ -109,6 +109,7 @@ public void testFull() { org.scijava.prefs.DefaultPrefService.class, org.scijava.run.DefaultRunService.class, org.scijava.script.DefaultScriptHeaderService.class, + org.scijava.script.process.DefaultScriptProcessorService.class, org.scijava.text.DefaultTextService.class, org.scijava.thread.DefaultThreadService.class, org.scijava.tool.DefaultToolService.class, From 50d4997c3bae8a437a6aa691bcfc8c01c4093a78 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 22 May 2017 15:21:56 -0500 Subject: [PATCH 020/741] Externalize the script parameter parsing logic It now lives in a new ParameterScriptProcessor. For the moment, script processing is triggered internally by the ScriptInfo's parseParameters method, which is a bit hacky. --- .../scijava/script/DefaultScriptService.java | 3 + .../java/org/scijava/script/ScriptInfo.java | 241 +-------------- .../process/ParameterScriptProcessor.java | 286 ++++++++++++++++++ 3 files changed, 300 insertions(+), 230 deletions(-) create mode 100644 src/main/java/org/scijava/script/process/ParameterScriptProcessor.java diff --git a/src/main/java/org/scijava/script/DefaultScriptService.java b/src/main/java/org/scijava/script/DefaultScriptService.java index 9480cf863..f63eb7938 100644 --- a/src/main/java/org/scijava/script/DefaultScriptService.java +++ b/src/main/java/org/scijava/script/DefaultScriptService.java @@ -91,6 +91,9 @@ public class DefaultScriptService extends @Parameter private AppService appService; + @Parameter + private ScriptProcessorService scriptProcessorService; + @Parameter private ParseService parser; diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java index 844f6d912..fb3859b2b 100644 --- a/src/main/java/org/scijava/script/ScriptInfo.java +++ b/src/main/java/org/scijava/script/ScriptInfo.java @@ -33,7 +33,6 @@ import java.io.BufferedReader; import java.io.File; -import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; @@ -41,37 +40,24 @@ import java.net.MalformedURLException; import java.net.URL; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import javax.script.ScriptException; import org.scijava.Context; import org.scijava.Contextual; -import org.scijava.ItemIO; -import org.scijava.ItemVisibility; import org.scijava.NullContextException; -import org.scijava.command.Command; -import org.scijava.convert.ConvertService; import org.scijava.log.LogService; import org.scijava.module.AbstractModuleInfo; -import org.scijava.module.DefaultMutableModuleItem; import org.scijava.module.ModuleException; import org.scijava.module.ModuleItem; -import org.scijava.parse.ParseService; import org.scijava.plugin.Parameter; +import org.scijava.script.process.ParameterScriptProcessor; +import org.scijava.script.process.ScriptProcessorService; import org.scijava.util.DigestUtils; import org.scijava.util.FileUtils; /** * Metadata about a script. - *

    - * This class is responsible for parsing the script for parameters. See - * {@link #parseParameters()} for details. - *

    * * @author Curtis Rueden * @author Johannes Schindelin @@ -94,10 +80,7 @@ public class ScriptInfo extends AbstractModuleInfo implements Contextual { private ScriptService scriptService; @Parameter - private ParseService parser; - - @Parameter - private ConvertService convertService; + private ScriptProcessorService scriptProcessorService; /** True iff the return value should be appended as an output. */ private boolean appendReturnValue; @@ -265,86 +248,21 @@ public void setReturnValueAppended(final boolean appendReturnValue) { // -- AbstractModuleInfo methods -- /** - * Parses the script's input and output parameters from the script header. - *

    - * This method is called automatically the first time any parameter accessor - * method is called ({@link #getInput}, {@link #getOutput}, {@link #inputs()}, - * {@link #outputs()}, etc.). Subsequent calls will reparse the parameters. - *

    - * SciJava's scripting framework supports specifying @{@link Parameter}-style - * inputs and outputs in a preamble. The format is a simplified version of the - * Java @{@link Parameter} annotation syntax. The following syntaxes are - * supported: - *

    - *
      - *
    • {@code // @ }
    • - *
    • {@code // @(=, ..., =) } - *
    • - *
    • {@code // @ }
    • - *
    • {@code // @(=, ..., =) - * }
    • - *
    - *

    - * Where: - *

    - *
      - *
    • {@code //} = the comment style of the scripting language, so that the - * parameter line is ignored by the script engine itself.
    • - *
    • {@code } = one of {@code INPUT}, {@code OUTPUT}, or - * {@code BOTH}.
    • - *
    • {@code } = the name of the input or output variable.
    • - *
    • {@code } = the Java {@link Class} of the variable.
    • - *
    • {@code } = an attribute key.
    • - *
    • {@code } = an attribute value.
    • - *
    - *

    - * See the @{@link Parameter} annotation for a list of valid attributes. - *

    - *

    - * Here are a few examples: - *

    - *
      - *
    • {@code // @Dataset dataset}
    • - *
    • {@code // @double(type=OUTPUT) result}
    • - *
    • {@code // @BOTH ImageDisplay display}
    • - *
    • {@code // @INPUT(persist=false, visibility=INVISIBLE) boolean verbose} - *
    • - *
    - *

    - * Parameters will be parsed and filled just like @{@link Parameter}-annotated - * fields in {@link Command}s. - *

    + * Performs script processing. In particular, parses the script parameters. + * + * @see ParameterScriptProcessor + * @see ScriptProcessorService#process */ // NB: Widened visibility from AbstractModuleInfo. @Override public void parseParameters() { clearParameters(); - appendReturnValue = true; - - try (final BufferedReader in = script == null ? // - new BufferedReader(new FileReader(getPath())) : getReader()) // - { - while (true) { - final String line = in.readLine(); - if (line == null) break; - - // NB: Scan for lines containing an '@' with no prior alphameric - // characters. This assumes that only non-alphanumeric characters can - // be used as comment line markers. - if (line.matches("^[^\\w]*@.*")) { - final int at = line.indexOf('@'); - parseParam(line.substring(at + 1)); - } - else if (line.matches(".*\\w.*")) break; - } - - if (appendReturnValue) addReturnValue(); + try { + scriptProcessorService.process(this); } catch (final IOException exc) { - log.error("Error reading script: " + path, exc); - } - catch (final ScriptException exc) { - log.error("Invalid parameter syntax for script: " + path, exc); + // TODO: Consider a better error handling approach. + throw new RuntimeException(exc); } } @@ -452,143 +370,6 @@ private String path(final URL u, final String p) { return u == null ? null : u.getPath(); } - private void parseParam(final String param) throws ScriptException { - final int lParen = param.indexOf("("); - final int rParen = param.lastIndexOf(")"); - if (rParen < lParen) { - throw new ScriptException("Invalid parameter: " + param); - } - if (lParen < 0) parseParam(param, parseAttrs("()")); - else { - final String cutParam = - param.substring(0, lParen) + param.substring(rParen + 1); - final String attrs = param.substring(lParen + 1, rParen); - parseParam(cutParam, parseAttrs(attrs)); - } - } - - private void parseParam(final String param, - final Map attrs) throws ScriptException - { - final String[] tokens = param.trim().split("[ \t\n]+"); - checkValid(tokens.length >= 1, param); - final String typeName, varName; - if (isIOType(tokens[0])) { - // assume syntax: - checkValid(tokens.length >= 3, param); - attrs.put("type", tokens[0]); - typeName = tokens[1]; - varName = tokens[2]; - } - else { - // assume syntax: - checkValid(tokens.length >= 2, param); - typeName = tokens[0]; - varName = tokens[1]; - } - final Class type = scriptService.lookupClass(typeName); - addItem(varName, type, attrs, true); - - if (ScriptModule.RETURN_VALUE.equals(varName)) { - // NB: The return value variable is declared as an explicit OUTPUT. - // So we should not append the return value as an extra output. - appendReturnValue = false; - } - } - - /** Parses a comma-delimited list of {@code key=value} pairs into a map. */ - private Map parseAttrs(final String attrs) { - return parser.parse(attrs, false).asMap(); - } - - private boolean isIOType(final String token) { - return convertService.convert(token, ItemIO.class) != null; - } - - private void checkValid(final boolean valid, final String param) - throws ScriptException - { - if (!valid) throw new ScriptException("Invalid parameter: " + param); - } - - /** Adds an output for the value returned by the script itself. */ - private void addReturnValue() { - final HashMap attrs = new HashMap<>(); - attrs.put("type", "OUTPUT"); - addItem(ScriptModule.RETURN_VALUE, Object.class, attrs, false); - } - - private void addItem(final String name, final Class type, - final Map attrs, final boolean explicit) - { - final DefaultMutableModuleItem item = - new DefaultMutableModuleItem<>(this, name, type); - for (final String key : attrs.keySet()) { - final Object value = attrs.get(key); - assignAttribute(item, key, value); - } - if (item.isInput()) registerInput(item); - if (item.isOutput()) { - registerOutput(item); - // NB: Only append the return value as an extra - // output when no explicit outputs are declared. - if (explicit) appendReturnValue = false; - } - } - - private void assignAttribute(final DefaultMutableModuleItem item, - final String k, final Object v) - { - // CTR: There must be an easier way to do this. - // Just compile the thing using javac? Or parse via javascript, maybe? - if (is(k, "callback")) item.setCallback(as(v, String.class)); - else if (is(k, "choices")) item.setChoices(asList(v, item.getType())); - else if (is(k, "columns")) item.setColumnCount(as(v, int.class)); - else if (is(k, "description")) item.setDescription(as(v, String.class)); - else if (is(k, "initializer")) item.setInitializer(as(v, String.class)); - else if (is(k, "validater")) item.setValidater(as(v, String.class)); - else if (is(k, "type")) item.setIOType(as(v, ItemIO.class)); - else if (is(k, "label")) item.setLabel(as(v, String.class)); - else if (is(k, "max")) item.setMaximumValue(as(v, item.getType())); - else if (is(k, "min")) item.setMinimumValue(as(v, item.getType())); - else if (is(k, "name")) item.setName(as(v, String.class)); - else if (is(k, "persist")) item.setPersisted(as(v, boolean.class)); - else if (is(k, "persistKey")) item.setPersistKey(as(v, String.class)); - else if (is(k, "required")) item.setRequired(as(v, boolean.class)); - else if (is(k, "softMax")) item.setSoftMaximum(as(v, item.getType())); - else if (is(k, "softMin")) item.setSoftMinimum(as(v, item.getType())); - else if (is(k, "stepSize")) item.setStepSize(as(v, double.class)); - else if (is(k, "style")) item.setWidgetStyle(as(v, String.class)); - else if (is(k, "visibility")) item.setVisibility(as(v, ItemVisibility.class)); - else if (is(k, "value")) item.setDefaultValue(as(v, item.getType())); - else item.set(k, v.toString()); - } - - /** Super terse comparison helper method. */ - private boolean is(final String key, final String desired) { - return desired.equalsIgnoreCase(key); - } - - /** Super terse conversion helper method. */ - private T as(final Object v, final Class type) { - final T converted = convertService.convert(v, type); - if (converted != null) return converted; - // NB: Attempt to convert via string. - // This is useful in cases where a weird type of object came back - // (e.g., org.scijava.parse.eval.Unresolved), but which happens to have a - // nice string representation which ultimately is expressible as the type. - return convertService.convert(v.toString(), type); - } - - private List asList(final Object v, final Class type) { - final ArrayList result = new ArrayList<>(); - final List list = as(v, List.class); - for (final Object item : list) { - result.add(as(item, type)); - } - return result; - } - /** * Read entire contents of a Reader and return as String. * diff --git a/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java b/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java new file mode 100644 index 000000000..9652e3f77 --- /dev/null +++ b/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java @@ -0,0 +1,286 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2017 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.script.process; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.script.ScriptException; + +import org.scijava.ItemIO; +import org.scijava.ItemVisibility; +import org.scijava.command.Command; +import org.scijava.convert.ConvertService; +import org.scijava.log.LogService; +import org.scijava.module.DefaultMutableModuleItem; +import org.scijava.parse.ParseService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.script.ScriptInfo; +import org.scijava.script.ScriptModule; +import org.scijava.script.ScriptService; + +/** + * A {@link ScriptProcessor} which parses the script's input and output + * parameters from the script header. + *

    + * SciJava's scripting framework supports specifying @{@link Parameter}-style + * inputs and outputs in a preamble. The format is a simplified version of the + * Java @{@link Parameter} annotation syntax. The following syntaxes are + * supported: + *

    + *
      + *
    • {@code // @ }
    • + *
    • {@code // @(=, ..., =) } + *
    • + *
    • {@code // @ }
    • + *
    • {@code // @(=, ..., =) + * }
    • + *
    + *

    + * Where: + *

    + *
      + *
    • {@code //} = the comment style of the scripting language, so that the + * parameter line is ignored by the script engine itself.
    • + *
    • {@code } = one of {@code INPUT}, {@code OUTPUT}, or {@code BOTH}. + *
    • + *
    • {@code } = the name of the input or output variable.
    • + *
    • {@code } = the Java {@link Class} of the variable.
    • + *
    • {@code } = an attribute key.
    • + *
    • {@code } = an attribute value.
    • + *
    + *

    + * See the @{@link Parameter} annotation for a list of valid attributes. + *

    + *

    + * Here are a few examples: + *

    + *
      + *
    • {@code // @Dataset dataset}
    • + *
    • {@code // @double(type=OUTPUT) result}
    • + *
    • {@code // @BOTH ImageDisplay display}
    • + *
    • {@code // @INPUT(persist=false, visibility=INVISIBLE) boolean verbose} + *
    • + *
    + *

    + * Parameters will be parsed and filled just like @{@link Parameter}-annotated + * fields in {@link Command}s. + *

    + * + * @author Curtis Rueden + */ +@Plugin(type = ScriptProcessor.class) +public class ParameterScriptProcessor implements ScriptProcessor { + + @Parameter + private ScriptService scriptService; + + @Parameter + private ConvertService convertService; + + @Parameter + private ParseService parser; + + @Parameter + private LogService log; + + private ScriptInfo info; + private boolean header = true; + + // -- ScriptProcessor methods -- + + @Override + public void begin(final ScriptInfo scriptInfo) { + info = scriptInfo; + info.setReturnValueAppended(true); + } + + @Override + public void process(final String line) { + if (header) { + // NB: Check if line contains an '@' with no prior alphameric + // characters. This assumes that only non-alphanumeric characters can + // be used as comment line markers. + if (line.matches("^[^\\w]*@.*")) { + final int at = line.indexOf('@'); + parseParam(line.substring(at + 1)); + } + else if (line.matches(".*\\w.*")) header = false; + } + } + + @Override + public void end() { + if (info.isReturnValueAppended()) { + // add an output for the value returned by the script itself + final HashMap attrs = new HashMap<>(); + attrs.put("type", "OUTPUT"); + addItem(ScriptModule.RETURN_VALUE, Object.class, attrs, false); + } + } + + // -- Helper methods -- + + private void parseParam(final String param) { + final int lParen = param.indexOf("("); + final int rParen = param.lastIndexOf(")"); + if (rParen < lParen) { warnInvalid(param); return; } + if (lParen < 0) parseParam(param, parseAttrs("()")); + else { + final String cutParam = + param.substring(0, lParen) + param.substring(rParen + 1); + final String attrs = param.substring(lParen + 1, rParen); + parseParam(cutParam, parseAttrs(attrs)); + } + } + + private void parseParam(final String param, final Map attrs) { + final String[] tokens = param.trim().split("[ \t\n]+"); + if (tokens.length < 1) { warnInvalid(param); return; } + final String typeName, varName; + if (isIOType(tokens[0])) { + // assume syntax: + if (tokens.length < 3) { warnInvalid(param); return; } + attrs.put("type", tokens[0]); + typeName = tokens[1]; + varName = tokens[2]; + } + else { + // assume syntax: + if (tokens.length < 2) { warnInvalid(param); return; } + typeName = tokens[0]; + varName = tokens[1]; + } + try { + final Class type = scriptService.lookupClass(typeName); + addItem(varName, type, attrs, true); + } + catch (final ScriptException exc) { + log.warn("Invalid class: " + typeName, exc); + return; + } + + if (ScriptModule.RETURN_VALUE.equals(varName)) { + // NB: The return value variable is declared as an explicit parameter. + // So we should not append the return value as an extra output. + info.setReturnValueAppended(false); + } + } + + /** Parses a comma-delimited list of {@code key=value} pairs into a map. */ + private Map parseAttrs(final String attrs) { + return parser.parse(attrs, false).asMap(); + } + + private boolean isIOType(final String token) { + return convertService.convert(token, ItemIO.class) != null; + } + + private void warnInvalid(final String param) { + log.warn("Ignoring invalid parameter: " + param); + } + + private void addItem(final String name, final Class type, + final Map attrs, final boolean explicit) + { + final DefaultMutableModuleItem item = + new DefaultMutableModuleItem<>(info, name, type); + for (final String key : attrs.keySet()) { + final Object value = attrs.get(key); + assignAttribute(item, key, value); + } + if (item.isInput()) info.registerInput(item); + if (item.isOutput()) { + info.registerOutput(item); + // NB: Only append the return value as an extra + // output when no explicit outputs are declared. + if (explicit) info.setReturnValueAppended(false); + } + } + + private void assignAttribute(final DefaultMutableModuleItem item, + final String k, final Object v) + { + // CTR: There must be an easier way to do this. + // Just compile the thing using javac? Or parse via javascript, maybe? + if (is(k, "callback")) item.setCallback(as(v, String.class)); + else if (is(k, "choices")) item.setChoices(asList(v, item.getType())); + else if (is(k, "columns")) item.setColumnCount(as(v, int.class)); + else if (is(k, "description")) item.setDescription(as(v, String.class)); + else if (is(k, "initializer")) item.setInitializer(as(v, String.class)); + else if (is(k, "validater")) item.setValidater(as(v, String.class)); + else if (is(k, "type")) item.setIOType(as(v, ItemIO.class)); + else if (is(k, "label")) item.setLabel(as(v, String.class)); + else if (is(k, "max")) item.setMaximumValue(as(v, item.getType())); + else if (is(k, "min")) item.setMinimumValue(as(v, item.getType())); + else if (is(k, "name")) item.setName(as(v, String.class)); + else if (is(k, "persist")) item.setPersisted(as(v, boolean.class)); + else if (is(k, "persistKey")) item.setPersistKey(as(v, String.class)); + else if (is(k, "required")) item.setRequired(as(v, boolean.class)); + else if (is(k, "softMax")) item.setSoftMaximum(as(v, item.getType())); + else if (is(k, "softMin")) item.setSoftMinimum(as(v, item.getType())); + else if (is(k, "stepSize")) item.setStepSize(as(v, double.class)); + else if (is(k, "style")) item.setWidgetStyle(as(v, String.class)); + else if (is(k, "visibility")) item.setVisibility(as(v, ItemVisibility.class)); + else if (is(k, "value")) item.setDefaultValue(as(v, item.getType())); + else item.set(k, v.toString()); + } + + /** Super terse comparison helper method. */ + private boolean is(final String key, final String desired) { + return desired.equalsIgnoreCase(key); + } + + /** Super terse conversion helper method. */ + private T as(final Object v, final Class type) { + final T converted = convertService.convert(v, type); + if (converted != null) return converted; + // NB: Attempt to convert via string. + // This is useful in cases where a weird type of object came back + // (e.g., org.scijava.parse.eval.Unresolved), but which happens to have a + // nice string representation which ultimately is expressible as the type. + return convertService.convert(v.toString(), type); + } + + private List asList(final Object v, final Class type) { + final ArrayList result = new ArrayList<>(); + final List list = as(v, List.class); + for (final Object item : list) { + result.add(as(item, type)); + } + return result; + } + +} From 347b484a79f4d080ab224cd2c89dd14e0cd04bdf Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Tue, 23 May 2017 19:04:39 -0500 Subject: [PATCH 021/741] Add script processor for shebang syntax This lets a script declare its own intended language. See: https://github.com/hadim/scijava-jupyter-kernel/issues/51#issuecomment-301816226 --- .../process/ShebangScriptProcessor.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 src/main/java/org/scijava/script/process/ShebangScriptProcessor.java diff --git a/src/main/java/org/scijava/script/process/ShebangScriptProcessor.java b/src/main/java/org/scijava/script/process/ShebangScriptProcessor.java new file mode 100644 index 000000000..63dc09db3 --- /dev/null +++ b/src/main/java/org/scijava/script/process/ShebangScriptProcessor.java @@ -0,0 +1,78 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2017 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.script.process; + +import org.scijava.log.LogService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.script.ScriptInfo; +import org.scijava.script.ScriptLanguage; +import org.scijava.script.ScriptService; + +/** + * A {@link ScriptProcessor} which looks for a {@code #!} at the beginning of a + * script, and set the language accordingly. + * + * @author Curtis Rueden + */ +@Plugin(type = ScriptProcessor.class) +public class ShebangScriptProcessor implements ScriptProcessor { + + @Parameter + private ScriptService scriptService; + + @Parameter + private LogService log; + + private ScriptInfo info; + private boolean first = true; + + // -- ScriptProcessor methods -- + + @Override + public void begin(final ScriptInfo scriptInfo) { + info = scriptInfo; + } + + @Override + public void process(final String line) { + if (!first) return; + if (line.startsWith("#!")) { + // shebang! + final String langName = line.substring(2); + final ScriptLanguage lang = scriptService.getLanguageByName(langName); + if (lang != null) info.setLanguage(lang); + else log.warn("Unknown script language: " + langName); + } + first = false; + } +} \ No newline at end of file From 1541cf1075c6ec826e4e95ca447e6d005f257cae Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Tue, 23 May 2017 20:44:33 -0500 Subject: [PATCH 022/741] Parse a new style of parameter syntax See: https://github.com/scijava/scijava-common/pull/265#issuecomment-302124612 --- .../process/ParameterScriptProcessor.java | 44 +++++++++++-------- .../org/scijava/script/ScriptInfoTest.java | 36 ++++++++++++++- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java b/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java index 9652e3f77..c65e713c0 100644 --- a/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java +++ b/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java @@ -61,25 +61,24 @@ * supported: *

    *
      - *
    • {@code // @ }
    • - *
    • {@code // @(=, ..., =) } - *
    • - *
    • {@code // @ }
    • - *
    • {@code // @(=, ..., =) + *
    • {@code #@ }
    • + *
    • {@code #@(=, ..., =) }
    • + *
    • {@code #@ }
    • + *
    • {@code #@(=, ..., =) * }
    • *
    *

    * Where: *

    *
      - *
    • {@code //} = the comment style of the scripting language, so that the + *
    • {@code #@} - signals a special script processing instruction, so that the * parameter line is ignored by the script engine itself.
    • - *
    • {@code } = one of {@code INPUT}, {@code OUTPUT}, or {@code BOTH}. + *
    • {@code } - one of {@code INPUT}, {@code OUTPUT}, or {@code BOTH}. *
    • - *
    • {@code } = the name of the input or output variable.
    • - *
    • {@code } = the Java {@link Class} of the variable.
    • - *
    • {@code } = an attribute key.
    • - *
    • {@code } = an attribute value.
    • + *
    • {@code } - the name of the input or output variable.
    • + *
    • {@code } - the Java {@link Class} of the variable.
    • + *
    • {@code } - an attribute key.
    • + *
    • {@code } - an attribute value.
    • *
    *

    * See the @{@link Parameter} annotation for a list of valid attributes. @@ -88,10 +87,10 @@ * Here are a few examples: *

    *
      - *
    • {@code // @Dataset dataset}
    • - *
    • {@code // @double(type=OUTPUT) result}
    • - *
    • {@code // @BOTH ImageDisplay display}
    • - *
    • {@code // @INPUT(persist=false, visibility=INVISIBLE) boolean verbose} + *
    • {@code #@Dataset dataset}
    • + *
    • {@code #@double(type=OUTPUT) result}
    • + *
    • {@code #@BOTH ImageDisplay display}
    • + *
    • {@code #@INPUT(persist=false, visibility=INVISIBLE) boolean verbose} *
    • *
    *

    @@ -129,6 +128,14 @@ public void begin(final ScriptInfo scriptInfo) { @Override public void process(final String line) { + // parse new-style parameters starting with @# anywhere in the script. + if (line.matches("^#@.*")) { + final int at = line.indexOf('@'); + parseParam(line.substring(at + 1)); + return; + } + + // parse old-style parameters in the initial script header if (header) { // NB: Check if line contains an '@' with no prior alphameric // characters. This assumes that only non-alphanumeric characters can @@ -170,10 +177,11 @@ private void parseParam(final String param, final Map attrs) { final String[] tokens = param.trim().split("[ \t\n]+"); if (tokens.length < 1) { warnInvalid(param); return; } final String typeName, varName; - if (isIOType(tokens[0])) { + final String maybeIOType = tokens[0].toUpperCase(); + if (isIOType(maybeIOType)) { // assume syntax: if (tokens.length < 3) { warnInvalid(param); return; } - attrs.put("type", tokens[0]); + attrs.put("type", maybeIOType); typeName = tokens[1]; varName = tokens[2]; } @@ -205,7 +213,7 @@ private Map parseAttrs(final String attrs) { } private boolean isIOType(final String token) { - return convertService.convert(token, ItemIO.class) != null; + return convertService.convert(token.toUpperCase(), ItemIO.class) != null; } private void warnInvalid(final String param) { diff --git a/src/test/java/org/scijava/script/ScriptInfoTest.java b/src/test/java/org/scijava/script/ScriptInfoTest.java index 8d022f748..7e99b439e 100644 --- a/src/test/java/org/scijava/script/ScriptInfoTest.java +++ b/src/test/java/org/scijava/script/ScriptInfoTest.java @@ -87,6 +87,41 @@ public static void tearDown() { // -- Tests -- + /** Tests whether new-style parameter syntax are parsed correctly. */ + @Test + public void testNewStyle() throws Exception { + final String script = "" + // + "##########\n" + // + "# Inputs #\n" + // + "##########\n" + // + "#@input int stuff\n" + // + "#@input int things\n" + // + "\n" + // + "###########\n" + // + "# Credits #\n" + // + "###########\n" + // + "Brought to you by:\n" + // + "person@example.com\n" + // + "\n" + // + "###########\n" + // + "# Outputs #\n" + // + "###########\n" + // + "#@output String blackHoles\n" + + "#@output String revelations\n" + + "\n" + // + "THE END!\n"; + final ScriptModule scriptModule = + scriptService.run("newStyle.bsizes", script, true).get(); + + final Object output = scriptModule.getReturnValue(); + + if (output == null) fail("null result"); + else if (!(output instanceof Integer)) { + fail("result is a " + output.getClass().getName()); + } + else assertEquals(4, ((Integer) output).intValue()); + } + /** * Tests that the return value is appended as an extra output when no * explicit outputs were declared. @@ -122,7 +157,6 @@ public void testReturnValueExcluded() throws Exception { assertFalse(outputs.containsKey(ScriptModule.RETURN_VALUE)); } - /** * Ensures parameters are parsed correctly from scripts, even in the presence * of noise like e-mail addresses. From e57683baf15a106f65ce68c197adc55f1cecd07d Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 24 May 2017 07:35:19 -0500 Subject: [PATCH 023/741] Add a callback mechanism for script execution The intended use case is for ScriptProcessor plugins to use it when they need to do something every time (or deferred until the first time) a script executes. Initially, this will be useful for dependency grabbing. --- .../java/org/scijava/script/ScriptInfo.java | 17 ++++++ .../java/org/scijava/script/ScriptModule.java | 8 ++- .../script/process/ScriptCallback.java | 52 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/scijava/script/process/ScriptCallback.java diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java index fb3859b2b..954a6594e 100644 --- a/src/main/java/org/scijava/script/ScriptInfo.java +++ b/src/main/java/org/scijava/script/ScriptInfo.java @@ -40,6 +40,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; import java.util.List; @@ -52,6 +53,7 @@ import org.scijava.module.ModuleItem; import org.scijava.plugin.Parameter; import org.scijava.script.process.ParameterScriptProcessor; +import org.scijava.script.process.ScriptCallback; import org.scijava.script.process.ScriptProcessorService; import org.scijava.util.DigestUtils; import org.scijava.util.FileUtils; @@ -88,6 +90,9 @@ public class ScriptInfo extends AbstractModuleInfo implements Contextual { /** Script language in which the script should be executed. */ private ScriptLanguage scriptLanguage; + /** Routines to be invoked prior to script execution. */ + private ArrayList callbacks; + /** * Creates a script metadata object which describes the given script file. * @@ -245,6 +250,18 @@ public void setReturnValueAppended(final boolean appendReturnValue) { this.appendReturnValue = appendReturnValue; } + /** + * Gets the list of routines which should be invoked each time the script is + * about to execute. + * + * @return Reference to the mutable list of {@link Runnable} objects which the + * {@link ScriptModule} will run prior to executing the script itself. + */ + public List callbacks() { + if (callbacks == null) callbacks = new ArrayList<>(); + return callbacks; + } + // -- AbstractModuleInfo methods -- /** diff --git a/src/main/java/org/scijava/script/ScriptModule.java b/src/main/java/org/scijava/script/ScriptModule.java index d00b7ab5e..80a5d928a 100644 --- a/src/main/java/org/scijava/script/ScriptModule.java +++ b/src/main/java/org/scijava/script/ScriptModule.java @@ -50,6 +50,7 @@ import org.scijava.module.Module; import org.scijava.module.ModuleItem; import org.scijava.plugin.Parameter; +import org.scijava.script.process.ScriptCallback; /** * A {@link Module} which executes a script. @@ -149,9 +150,14 @@ public void run() { engine.put(name, getInput(name)); } - // execute script! returnValue = null; try { + // invoke the callbacks + for (final ScriptCallback c : getInfo().callbacks()) { + c.invoke(this); + } + + // execute script! final Reader reader = getInfo().getReader(); if (reader == null) returnValue = engine.eval(new FileReader(path)); else returnValue = engine.eval(reader); diff --git a/src/main/java/org/scijava/script/process/ScriptCallback.java b/src/main/java/org/scijava/script/process/ScriptCallback.java new file mode 100644 index 000000000..fa6f8f0ed --- /dev/null +++ b/src/main/java/org/scijava/script/process/ScriptCallback.java @@ -0,0 +1,52 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2017 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.script.process; + +import javax.script.ScriptException; + +import org.scijava.script.ScriptModule; + +/** + * A routine which will be invoked just prior to script execution. + * + * @author Curtis Rueden + */ +public interface ScriptCallback { + + /** + * Invokes the callback routine. + * + * @param module The {@link ScriptModule} instance which will + * execute the script. + */ + void invoke(final ScriptModule module) throws ScriptException; +} From 3f89bd015244b2078bf06b8198f14673a5f40d77 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 24 May 2017 09:55:29 -0500 Subject: [PATCH 024/741] Allow script processors to modify the script In particular, we want the #@ processing directives to disappear from the executed version of the script, since they are invalid syntax for many of the script languages. This change gives ScriptProcessor plugins the flexibility to modify each line of the script however they choose, although our initial use case here will simply blank out lines which have been handled. What processors should not do is change the number of lines; we want the line numbers in error messages to match those of the original script. --- .../java/org/scijava/script/ScriptInfo.java | 15 +++++- .../java/org/scijava/script/ScriptModule.java | 6 +-- .../process/ParameterScriptProcessor.java | 49 ++++++++++--------- .../script/process/ScriptProcessor.java | 2 +- .../process/ScriptProcessorService.java | 12 +++-- .../process/ShebangScriptProcessor.java | 8 +-- 6 files changed, 57 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/scijava/script/ScriptInfo.java b/src/main/java/org/scijava/script/ScriptInfo.java index 954a6594e..bee3b1c06 100644 --- a/src/main/java/org/scijava/script/ScriptInfo.java +++ b/src/main/java/org/scijava/script/ScriptInfo.java @@ -84,6 +84,9 @@ public class ScriptInfo extends AbstractModuleInfo implements Contextual { @Parameter private ScriptProcessorService scriptProcessorService; + /** Final version of the script, after script processing. */ + private String processedScript; + /** True iff the return value should be appended as an output. */ private boolean appendReturnValue; @@ -216,6 +219,16 @@ public BufferedReader getReader() { return new BufferedReader(new StringReader(script), PARAM_CHAR_MAX); } + /** + * Gets the script contents after script processing. + * + * @return The processed script. + * @see ScriptProcessorService#process + */ + public String getProcessedScript() { + return processedScript; + } + /** Gets the scripting language of the script. */ public ScriptLanguage getLanguage() { if (scriptLanguage == null) { @@ -275,7 +288,7 @@ public List callbacks() { public void parseParameters() { clearParameters(); try { - scriptProcessorService.process(this); + processedScript = scriptProcessorService.process(this); } catch (final IOException exc) { // TODO: Consider a better error handling approach. diff --git a/src/main/java/org/scijava/script/ScriptModule.java b/src/main/java/org/scijava/script/ScriptModule.java index 80a5d928a..5ed0f39a6 100644 --- a/src/main/java/org/scijava/script/ScriptModule.java +++ b/src/main/java/org/scijava/script/ScriptModule.java @@ -31,10 +31,8 @@ package org.scijava.script; -import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; -import java.io.Reader; import java.io.Writer; import javax.script.ScriptContext; @@ -158,9 +156,7 @@ public void run() { } // execute script! - final Reader reader = getInfo().getReader(); - if (reader == null) returnValue = engine.eval(new FileReader(path)); - else returnValue = engine.eval(reader); + returnValue = engine.eval(getInfo().getProcessedScript()); } catch (Throwable e) { while (e instanceof ScriptException && e.getCause() != null) { diff --git a/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java b/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java index c65e713c0..d7c50d2b0 100644 --- a/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java +++ b/src/main/java/org/scijava/script/process/ParameterScriptProcessor.java @@ -127,12 +127,11 @@ public void begin(final ScriptInfo scriptInfo) { } @Override - public void process(final String line) { + public String process(final String line) { // parse new-style parameters starting with @# anywhere in the script. if (line.matches("^#@.*")) { final int at = line.indexOf('@'); - parseParam(line.substring(at + 1)); - return; + return process(line, line.substring(at + 1)); } // parse old-style parameters in the initial script header @@ -142,10 +141,12 @@ public void process(final String line) { // be used as comment line markers. if (line.matches("^[^\\w]*@.*")) { final int at = line.indexOf('@'); - parseParam(line.substring(at + 1)); + return process(line, line.substring(at + 1)); } else if (line.matches(".*\\w.*")) header = false; } + + return line; } @Override @@ -160,34 +161,40 @@ public void end() { // -- Helper methods -- - private void parseParam(final String param) { + private String process(final String line, final String param) { + if (parseParam(param)) return ""; + log.warn("Ignoring invalid parameter: " + param); + return line; + } + + private boolean parseParam(final String param) { final int lParen = param.indexOf("("); final int rParen = param.lastIndexOf(")"); - if (rParen < lParen) { warnInvalid(param); return; } - if (lParen < 0) parseParam(param, parseAttrs("()")); - else { - final String cutParam = - param.substring(0, lParen) + param.substring(rParen + 1); - final String attrs = param.substring(lParen + 1, rParen); - parseParam(cutParam, parseAttrs(attrs)); - } + if (rParen < lParen) return false; + if (lParen < 0) return parseParam(param, parseAttrs("()")); + final String cutParam = + param.substring(0, lParen) + param.substring(rParen + 1); + final String attrs = param.substring(lParen + 1, rParen); + return parseParam(cutParam, parseAttrs(attrs)); } - private void parseParam(final String param, final Map attrs) { + private boolean parseParam(final String param, + final Map attrs) + { final String[] tokens = param.trim().split("[ \t\n]+"); - if (tokens.length < 1) { warnInvalid(param); return; } + if (tokens.length < 1) return false; final String typeName, varName; final String maybeIOType = tokens[0].toUpperCase(); if (isIOType(maybeIOType)) { // assume syntax: - if (tokens.length < 3) { warnInvalid(param); return; } + if (tokens.length < 3) return false; attrs.put("type", maybeIOType); typeName = tokens[1]; varName = tokens[2]; } else { // assume syntax: - if (tokens.length < 2) { warnInvalid(param); return; } + if (tokens.length < 2) return false; typeName = tokens[0]; varName = tokens[1]; } @@ -197,7 +204,7 @@ private void parseParam(final String param, final Map attrs) { } catch (final ScriptException exc) { log.warn("Invalid class: " + typeName, exc); - return; + return false; } if (ScriptModule.RETURN_VALUE.equals(varName)) { @@ -205,6 +212,8 @@ private void parseParam(final String param, final Map attrs) { // So we should not append the return value as an extra output. info.setReturnValueAppended(false); } + + return true; } /** Parses a comma-delimited list of {@code key=value} pairs into a map. */ @@ -216,10 +225,6 @@ private boolean isIOType(final String token) { return convertService.convert(token.toUpperCase(), ItemIO.class) != null; } - private void warnInvalid(final String param) { - log.warn("Ignoring invalid parameter: " + param); - } - private void addItem(final String name, final Class type, final Map attrs, final boolean explicit) { diff --git a/src/main/java/org/scijava/script/process/ScriptProcessor.java b/src/main/java/org/scijava/script/process/ScriptProcessor.java index 3d412d074..e89ca7150 100644 --- a/src/main/java/org/scijava/script/process/ScriptProcessor.java +++ b/src/main/java/org/scijava/script/process/ScriptProcessor.java @@ -48,7 +48,7 @@ public interface ScriptProcessor extends SingletonPlugin { void begin(ScriptInfo info); - void process(String line); + String process(String line); default void end() {} } diff --git a/src/main/java/org/scijava/script/process/ScriptProcessorService.java b/src/main/java/org/scijava/script/process/ScriptProcessorService.java index 2f1d431cf..202071f62 100644 --- a/src/main/java/org/scijava/script/process/ScriptProcessorService.java +++ b/src/main/java/org/scijava/script/process/ScriptProcessorService.java @@ -56,7 +56,7 @@ public interface ScriptProcessorService extends * Invokes all {@link ScriptProcessor} plugins on the given script, line by * line in sequence. */ - default void process(final ScriptInfo info) throws IOException { + default String process(final ScriptInfo info) throws IOException { final List processors = getPlugins().stream().map( p -> pluginService().createInstance(p)).collect(Collectors.toList()); @@ -69,19 +69,25 @@ default void process(final ScriptInfo info) throws IOException { p.begin(info); } + final StringBuilder sb = new StringBuilder(); + try (final BufferedReader in = reader) { while (true) { - final String line = in.readLine(); + String line = in.readLine(); if (line == null) break; for (final ScriptProcessor p : processors) { - p.process(line); + line = p.process(line); } + sb.append(line); + sb.append("\n"); } } for (final ScriptProcessor p : processors) { p.end(); } + + return sb.toString(); } // -- PTService methods -- diff --git a/src/main/java/org/scijava/script/process/ShebangScriptProcessor.java b/src/main/java/org/scijava/script/process/ShebangScriptProcessor.java index 63dc09db3..f1b75d546 100644 --- a/src/main/java/org/scijava/script/process/ShebangScriptProcessor.java +++ b/src/main/java/org/scijava/script/process/ShebangScriptProcessor.java @@ -64,15 +64,17 @@ public void begin(final ScriptInfo scriptInfo) { } @Override - public void process(final String line) { - if (!first) return; + public String process(final String line) { + if (!first) return line; + first = false; if (line.startsWith("#!")) { // shebang! final String langName = line.substring(2); final ScriptLanguage lang = scriptService.getLanguageByName(langName); if (lang != null) info.setLanguage(lang); else log.warn("Unknown script language: " + langName); + return ""; } - first = false; + return line; } } \ No newline at end of file From e5e496d297b9a3d7e32a772dd1cda03a3663edf0 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 24 May 2017 10:09:40 -0500 Subject: [PATCH 025/741] ScriptInfoTest: test script modification --- src/test/java/org/scijava/script/ScriptInfoTest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/test/java/org/scijava/script/ScriptInfoTest.java b/src/test/java/org/scijava/script/ScriptInfoTest.java index 7e99b439e..a5b0e6029 100644 --- a/src/test/java/org/scijava/script/ScriptInfoTest.java +++ b/src/test/java/org/scijava/script/ScriptInfoTest.java @@ -113,6 +113,10 @@ public void testNewStyle() throws Exception { final ScriptModule scriptModule = scriptService.run("newStyle.bsizes", script, true).get(); + final String expectedProcessed = script.replaceAll("#@.*", ""); + final String actualProcessed = scriptModule.getInfo().getProcessedScript(); + assertEquals(expectedProcessed, actualProcessed); + final Object output = scriptModule.getReturnValue(); if (output == null) fail("null result"); From 8896894ea52465c77d8816a321c4aa1e5e634b19 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 24 May 2017 12:56:53 -0500 Subject: [PATCH 026/741] Bump to next development cycle Signed-off-by: Curtis Rueden --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9a0b01354..3700d7ab0 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ scijava-common - 2.64.0-SNAPSHOT + 2.64.1-SNAPSHOT SciJava Common SciJava Common is a shared library for SciJava software. It provides a plugin framework, with an extensible mechanism for service discovery, backed by its own annotation processor, so that plugins can be loaded dynamically. It is used by downstream projects in the SciJava ecosystem, such as ImageJ and SCIFIO. From cc99b9ba3f2e8bb556055ac808b8f4a7500d5132 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 2 Jun 2017 21:05:44 -0500 Subject: [PATCH 027/741] POM: fix Chris Allan ID --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3700d7ab0..e0beb5f14 100644 --- a/pom.xml +++ b/pom.xml @@ -57,7 +57,7 @@ Chris Allan - callan + chris-allan Barry DeZonia From 7f9bcccd4b55cf1eb1765639ab5cbbfcb92e18e1 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 12 Jun 2017 12:42:02 -0500 Subject: [PATCH 028/741] Parameter: deprecate columns() method --- src/main/java/org/scijava/plugin/Parameter.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/scijava/plugin/Parameter.java b/src/main/java/org/scijava/plugin/Parameter.java index c8591c48a..21edaa358 100644 --- a/src/main/java/org/scijava/plugin/Parameter.java +++ b/src/main/java/org/scijava/plugin/Parameter.java @@ -158,12 +158,6 @@ /** Defines the step size to use (numeric parameters only). */ String stepSize() default ""; - /** - * Defines the width of the input field in characters (text field parameters - * only). - */ - int columns() default 6; - /** Defines the list of possible values (multiple choice text fields only). */ String[] choices() default {}; @@ -173,4 +167,7 @@ */ Attr[] attrs() default {}; + /** @deprecated Replaced by {@link #style()}. */ + @Deprecated + int columns() default 6; } From 75382b65c9517814332541d1c9b9ac5dde17f9b9 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 19 Jun 2017 15:30:54 +0200 Subject: [PATCH 029/741] PrefService: never let key or value be null The Java Preferences API hates nulls: public void put(String key, String value) { if (key==null || value==null) throw new NullPointerException(); ... --- src/main/java/org/scijava/prefs/DefaultPrefService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/scijava/prefs/DefaultPrefService.java b/src/main/java/org/scijava/prefs/DefaultPrefService.java index 288d2f2dc..d03423e55 100644 --- a/src/main/java/org/scijava/prefs/DefaultPrefService.java +++ b/src/main/java/org/scijava/prefs/DefaultPrefService.java @@ -653,6 +653,7 @@ private String safeName(final String name) { *

*/ private String makeSafe(final String s, final int max) { + if (s == null) return ""; // Java Preferences API hates nulls. final int len = s.length(); if (len < max) return s; return "..." + s.substring(len - max + 3, len); From 526b7163b9ae7442c74cfe184be7eae2798bb2ad Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 14 Dec 2016 02:50:36 +0100 Subject: [PATCH 030/741] AbstractLogService: fix typo in javadoc --- src/main/java/org/scijava/log/AbstractLogService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/log/AbstractLogService.java b/src/main/java/org/scijava/log/AbstractLogService.java index 69eddd8c0..748cac6a2 100644 --- a/src/main/java/org/scijava/log/AbstractLogService.java +++ b/src/main/java/org/scijava/log/AbstractLogService.java @@ -38,7 +38,7 @@ import org.scijava.service.AbstractService; /** - * Base class for {@link LogService} implementationst. + * Base class for {@link LogService} implementations. * * @author Johannes Schindelin */ From 294f37c5500437701a4e4a7dcc1eb9e16c0b5355 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Wed, 14 Dec 2016 02:53:18 +0100 Subject: [PATCH 031/741] AbstractLogService: fix incorrect comment The default level went back and forth between INFO and WARN a couple of times, but it is currently INFO. --- src/main/java/org/scijava/log/AbstractLogService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/log/AbstractLogService.java b/src/main/java/org/scijava/log/AbstractLogService.java index 748cac6a2..037975724 100644 --- a/src/main/java/org/scijava/log/AbstractLogService.java +++ b/src/main/java/org/scijava/log/AbstractLogService.java @@ -78,7 +78,7 @@ public AbstractLogService() { if (level >= 0) setLevel(level); if (getLevel() == 0) { - // use the default, which is WARN unless the DEBUG env. variable is set + // use the default, which is INFO unless the DEBUG env. variable is set setLevel(System.getenv("DEBUG") == null ? INFO : DEBUG); } From 8138f114485ab4bd3743abfdb3d9c86f981d39ad Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Tue, 13 Dec 2016 16:59:05 +0100 Subject: [PATCH 032/741] AbstractLogService: refactor duplicate code --- .../java/org/scijava/log/AbstractLogService.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/scijava/log/AbstractLogService.java b/src/main/java/org/scijava/log/AbstractLogService.java index 037975724..44f9fea29 100644 --- a/src/main/java/org/scijava/log/AbstractLogService.java +++ b/src/main/java/org/scijava/log/AbstractLogService.java @@ -46,7 +46,7 @@ public abstract class AbstractLogService extends AbstractService implements LogService { - private int currentLevel = System.getenv("DEBUG") == null ? INFO : DEBUG; + private int currentLevel = levelFromEnvironment(); private final Map classAndPackageLevels = new HashMap<>(); @@ -77,10 +77,8 @@ public AbstractLogService() { final int level = level(logProp); if (level >= 0) setLevel(level); - if (getLevel() == 0) { - // use the default, which is INFO unless the DEBUG env. variable is set - setLevel(System.getenv("DEBUG") == null ? INFO : DEBUG); - } + if (getLevel() == 0) + setLevel(levelFromEnvironment()); // populate custom class- and package-specific log level properties final String logLevelPrefix = LOG_LEVEL_PROPERTY + ":"; @@ -297,4 +295,9 @@ private String parentPackage(final String classOrPackageName) { return classOrPackageName.substring(0, dot); } + private int levelFromEnvironment() { + // use the default, which is INFO unless the DEBUG env. variable is set + return System.getenv("DEBUG") == null ? INFO : DEBUG; + } + } From 7cccfac373d7f34fc5288ccaeccf0f3030ee2fe7 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Thu, 22 Jun 2017 17:35:05 +0200 Subject: [PATCH 033/741] AnnotationProcessor: it works with Java 7 & 8, too The SupportedSourceVersion indicates the _newest_ release of Java supported by the processor. SciJava supports Java 8 and earlier. --- .../java/org/scijava/annotations/AnnotationProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/annotations/AnnotationProcessor.java b/src/main/java/org/scijava/annotations/AnnotationProcessor.java index 0f96bab4f..472f57335 100644 --- a/src/main/java/org/scijava/annotations/AnnotationProcessor.java +++ b/src/main/java/org/scijava/annotations/AnnotationProcessor.java @@ -70,11 +70,11 @@ import org.scijava.annotations.AbstractIndexWriter.StreamFactory; /** - * The annotation processor for use with Java 6 and above. + * The annotation processor for use with Java 8 and earlier. * * @author Johannes Schindelin */ -@SupportedSourceVersion(SourceVersion.RELEASE_6) +@SupportedSourceVersion(SourceVersion.RELEASE_8) @SupportedAnnotationTypes("*") public class AnnotationProcessor extends AbstractProcessor { From 596813c198d3f6eb77efac458a4d3dd71cfcfaf6 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Sun, 3 Jul 2016 10:34:04 -0500 Subject: [PATCH 034/741] Do runtime type checking in Typed.supports method There are situations where we cannot solely rely on the compiler. For example, with DataHandle plugins, the generic parameter L is heterogeneous, so we need to actually check the type of the data. I tried to make this the default implementation of the Typed interface itself, but ran into problems with calling super.supports in downstream classes; apparently, you cannot directly reference default interface methods by writing e.g. Typed.super.supports(...)? Strange. --- .../java/org/scijava/plugin/AbstractTypedPlugin.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/plugin/AbstractTypedPlugin.java b/src/main/java/org/scijava/plugin/AbstractTypedPlugin.java index e8b3ff338..ac29ee129 100644 --- a/src/main/java/org/scijava/plugin/AbstractTypedPlugin.java +++ b/src/main/java/org/scijava/plugin/AbstractTypedPlugin.java @@ -43,5 +43,15 @@ public abstract class AbstractTypedPlugin extends AbstractRichPlugin implements TypedPlugin { - // NB: No implementation needed. + // -- Typed methods -- + + @Override + public boolean supports(final D data) { + // NB: Even though the compiler will often guarantee that only data + // of type T is provided here, we still need the runtime check + // for cases where the exact type is not known to compiler -- + // e.g., if the object was manufactured by reflection. + return getType().isInstance(data); + } + } From 28700f4676fda5d8a676ae2bc2c8bee31b0698e0 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 1 Jul 2016 23:27:43 -0500 Subject: [PATCH 035/741] Add NIOService for working with NIO classes Migrated from the NIOService of SCIFIO, which was previously adapted from the NIOByteBufferProvider class of Bio-Formats. --- .../org/scijava/io/nio/DefaultNIOService.java | 119 ++++++++++++++++++ .../java/org/scijava/io/nio/NIOService.java | 73 +++++++++++ .../java/org/scijava/ContextCreationTest.java | 1 + 3 files changed, 193 insertions(+) create mode 100644 src/main/java/org/scijava/io/nio/DefaultNIOService.java create mode 100644 src/main/java/org/scijava/io/nio/NIOService.java diff --git a/src/main/java/org/scijava/io/nio/DefaultNIOService.java b/src/main/java/org/scijava/io/nio/DefaultNIOService.java new file mode 100644 index 000000000..9a631daf6 --- /dev/null +++ b/src/main/java/org/scijava/io/nio/DefaultNIOService.java @@ -0,0 +1,119 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2016 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.nio; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; + +import org.scijava.log.LogService; +import org.scijava.plugin.Parameter; +import org.scijava.plugin.Plugin; +import org.scijava.service.AbstractService; +import org.scijava.service.Service; + +/** + * Default service for working with the {@link java.nio} package, particularly + * NIO {@link ByteBuffer} objects. + * + * @author Chris Allan + * @author Curtis Rueden + */ +@Plugin(type = Service.class) +public class DefaultNIOService extends AbstractService implements NIOService { + + // -- Fields -- + + @Parameter + private LogService log; + + /** Whether or not we are to use memory mapped I/O. */ + private final boolean useMappedByteBuffer = Boolean.parseBoolean(System + .getProperty("mappedBuffers")); + + // -- NIOService API methods -- + + @Override + public ByteBuffer allocate(final FileChannel channel, final MapMode mapMode, + final long bufferStartPosition, final int newSize) throws IOException + { + log.debug("NIO: allocate: mapped=" + useMappedByteBuffer + ", start=" + + bufferStartPosition + ", size=" + newSize); + if (useMappedByteBuffer) { + return allocateMappedByteBuffer(channel, mapMode, bufferStartPosition, + newSize); + } + return allocateDirect(channel, bufferStartPosition, newSize); + } + + // -- Helper methods -- + + /** + * Allocates memory and copies the desired file data into it. + * + * @param channel File channel to allocate or map byte buffers from. + * @param bufferStartPosition The absolute position of the start of the + * buffer. + * @param newSize The buffer size. + * @return A newly allocated NIO byte buffer. + * @throws IOException If there is an issue aligning or allocating the buffer. + */ + private ByteBuffer allocateDirect(final FileChannel channel, + final long bufferStartPosition, final int newSize) throws IOException + { + final ByteBuffer buffer = ByteBuffer.allocate(newSize); + channel.read(buffer, bufferStartPosition); + return buffer; + } + + /** + * Memory maps the desired file data into memory. + * + * @param channel File channel to allocate or map byte buffers from. + * @param mapMode The map mode. Required but only used if memory mapped I/O is + * to occur. + * @param bufferStartPosition The absolute position of the start of the + * buffer. + * @param newSize The buffer size. + * @return A newly mapped NIO byte buffer. + * @throws IOException If there is an issue mapping, aligning or allocating + * the buffer. + */ + private ByteBuffer allocateMappedByteBuffer(final FileChannel channel, + final MapMode mapMode, final long bufferStartPosition, final int newSize) + throws IOException + { + return channel.map(mapMode, bufferStartPosition, newSize); + } + +} diff --git a/src/main/java/org/scijava/io/nio/NIOService.java b/src/main/java/org/scijava/io/nio/NIOService.java new file mode 100644 index 000000000..0c6865045 --- /dev/null +++ b/src/main/java/org/scijava/io/nio/NIOService.java @@ -0,0 +1,73 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2016 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.nio; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.FileChannel.MapMode; + +import org.scijava.service.SciJavaService; + +/** + * Interface for services that work with the {@link java.nio} package, + * particularly NIO {@link ByteBuffer} objects. + * + * @author Chris Allan + * @author Curtis Rueden + */ +public interface NIOService extends SciJavaService { + + /** + * Allocates or maps the desired file data into memory. + *

+ * This method provides a facade to byte buffer allocation that enables + * FileChannel.map() usage on platforms where it's unlikely to + * give us problems and heap allocation where it is. + *

+ * + * @param channel File channel to allocate or map byte buffers from. + * @param mapMode The map mode. Required but only used if memory mapped I/O is + * to occur. + * @param bufferStartPosition The absolute position of the start of the + * buffer. + * @param newSize The buffer size. + * @return A newly allocated or mapped NIO byte buffer. + * @see "http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5092131" + * @see "http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6417205" + * @throws IOException If there is an issue mapping, aligning or allocating + * the buffer. + */ + ByteBuffer allocate(FileChannel channel, MapMode mapMode, + long bufferStartPosition, int newSize) throws IOException; + +} diff --git a/src/test/java/org/scijava/ContextCreationTest.java b/src/test/java/org/scijava/ContextCreationTest.java index 5dc0ed1b4..449fcc26f 100644 --- a/src/test/java/org/scijava/ContextCreationTest.java +++ b/src/test/java/org/scijava/ContextCreationTest.java @@ -98,6 +98,7 @@ public void testFull() { org.scijava.io.DefaultDataHandleService.class, org.scijava.io.DefaultIOService.class, org.scijava.io.DefaultRecentFileService.class, + org.scijava.io.nio.DefaultNIOService.class, org.scijava.main.DefaultMainService.class, org.scijava.menu.DefaultMenuService.class, org.scijava.module.DefaultModuleService.class, From ac6a54ae9a00a379fd3539dba130fc8da844d24f Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 7 Jul 2017 15:18:13 -0500 Subject: [PATCH 036/741] Reorganize the org.scijava.io classes This moves Location stuff to org.scijava.io.location, and DataHandle stuff to org.scijava.io.handle. It breaks backwards compatibility, but the Location API was not being used by any SJC-based systems yet. We make the change now to minimize future damage. --- src/main/java/org/scijava/io/IOService.java | 2 -- .../org/scijava/io/{ => handle}/AbstractDataHandle.java | 3 ++- src/main/java/org/scijava/io/{ => handle}/DataHandle.java | 3 ++- .../org/scijava/io/{ => handle}/DataHandleInputStream.java | 4 +++- .../scijava/io/{ => handle}/DataHandleOutputStream.java | 4 +++- .../org/scijava/io/{ => handle}/DataHandleService.java | 4 +++- .../scijava/io/{ => handle}/DefaultDataHandleService.java | 3 ++- src/main/java/org/scijava/io/{ => handle}/FileHandle.java | 3 ++- .../org/scijava/io/{ => location}/AbstractLocation.java | 2 +- .../java/org/scijava/io/{ => location}/BytesLocation.java | 2 +- .../java/org/scijava/io/{ => location}/FileLocation.java | 2 +- src/main/java/org/scijava/io/{ => location}/Location.java | 4 +++- .../java/org/scijava/io/{ => location}/URILocation.java | 2 +- .../java/org/scijava/io/{ => location}/URLLocation.java | 2 +- src/test/java/org/scijava/ContextCreationTest.java | 2 +- .../java/org/scijava/io/{ => handle}/DataHandleTest.java | 5 ++++- .../java/org/scijava/io/{ => handle}/FileHandleTest.java | 7 ++++++- .../org/scijava/io/{ => location}/BytesLocationTest.java | 3 ++- .../org/scijava/io/{ => location}/FileLocationTest.java | 3 ++- .../org/scijava/io/{ => location}/URILocationTest.java | 3 ++- .../org/scijava/io/{ => location}/URLLocationTest.java | 3 ++- 21 files changed, 44 insertions(+), 22 deletions(-) rename src/main/java/org/scijava/io/{ => handle}/AbstractDataHandle.java (96%) rename src/main/java/org/scijava/io/{ => handle}/DataHandle.java (99%) rename src/main/java/org/scijava/io/{ => handle}/DataHandleInputStream.java (97%) rename src/main/java/org/scijava/io/{ => handle}/DataHandleOutputStream.java (97%) rename src/main/java/org/scijava/io/{ => handle}/DataHandleService.java (95%) rename src/main/java/org/scijava/io/{ => handle}/DefaultDataHandleService.java (96%) rename src/main/java/org/scijava/io/{ => handle}/FileHandle.java (98%) rename src/main/java/org/scijava/io/{ => location}/AbstractLocation.java (98%) rename src/main/java/org/scijava/io/{ => location}/BytesLocation.java (98%) rename src/main/java/org/scijava/io/{ => location}/FileLocation.java (98%) rename src/main/java/org/scijava/io/{ => location}/Location.java (96%) rename src/main/java/org/scijava/io/{ => location}/URILocation.java (99%) rename src/main/java/org/scijava/io/{ => location}/URLLocation.java (98%) rename src/test/java/org/scijava/io/{ => handle}/DataHandleTest.java (97%) rename src/test/java/org/scijava/io/{ => handle}/FileHandleTest.java (91%) rename src/test/java/org/scijava/io/{ => location}/BytesLocationTest.java (97%) rename src/test/java/org/scijava/io/{ => location}/FileLocationTest.java (96%) rename src/test/java/org/scijava/io/{ => location}/URILocationTest.java (96%) rename src/test/java/org/scijava/io/{ => location}/URLLocationTest.java (96%) diff --git a/src/main/java/org/scijava/io/IOService.java b/src/main/java/org/scijava/io/IOService.java index a4f284680..3774de692 100644 --- a/src/main/java/org/scijava/io/IOService.java +++ b/src/main/java/org/scijava/io/IOService.java @@ -40,8 +40,6 @@ * Interface for high-level data I/O: opening and saving data. * * @author Curtis Rueden - * @see DataHandleService - * @see Location */ public interface IOService extends HandlerService>, SciJavaService diff --git a/src/main/java/org/scijava/io/AbstractDataHandle.java b/src/main/java/org/scijava/io/handle/AbstractDataHandle.java similarity index 96% rename from src/main/java/org/scijava/io/AbstractDataHandle.java rename to src/main/java/org/scijava/io/handle/AbstractDataHandle.java index 7a30a6d3b..b072df9ad 100644 --- a/src/main/java/org/scijava/io/AbstractDataHandle.java +++ b/src/main/java/org/scijava/io/handle/AbstractDataHandle.java @@ -29,10 +29,11 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; import java.nio.ByteOrder; +import org.scijava.io.location.Location; import org.scijava.plugin.AbstractWrapperPlugin; /** diff --git a/src/main/java/org/scijava/io/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java similarity index 99% rename from src/main/java/org/scijava/io/DataHandle.java rename to src/main/java/org/scijava/io/handle/DataHandle.java index f058d9241..9f6d1e3dc 100644 --- a/src/main/java/org/scijava/io/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -29,7 +29,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; import java.io.Closeable; import java.io.DataInput; @@ -39,6 +39,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; +import org.scijava.io.location.Location; import org.scijava.plugin.WrapperPlugin; /** diff --git a/src/main/java/org/scijava/io/DataHandleInputStream.java b/src/main/java/org/scijava/io/handle/DataHandleInputStream.java similarity index 97% rename from src/main/java/org/scijava/io/DataHandleInputStream.java rename to src/main/java/org/scijava/io/handle/DataHandleInputStream.java index 6b2e21e4a..2a215c69a 100644 --- a/src/main/java/org/scijava/io/DataHandleInputStream.java +++ b/src/main/java/org/scijava/io/handle/DataHandleInputStream.java @@ -29,11 +29,13 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; import java.io.IOException; import java.io.InputStream; +import org.scijava.io.location.Location; + /** * {@link InputStream} backed by a {@link DataHandle}. * diff --git a/src/main/java/org/scijava/io/DataHandleOutputStream.java b/src/main/java/org/scijava/io/handle/DataHandleOutputStream.java similarity index 97% rename from src/main/java/org/scijava/io/DataHandleOutputStream.java rename to src/main/java/org/scijava/io/handle/DataHandleOutputStream.java index 6f876694d..c2320281f 100644 --- a/src/main/java/org/scijava/io/DataHandleOutputStream.java +++ b/src/main/java/org/scijava/io/handle/DataHandleOutputStream.java @@ -29,11 +29,13 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; import java.io.IOException; import java.io.OutputStream; +import org.scijava.io.location.Location; + /** * {@link OutputStream} backed by a {@link DataHandle}. * diff --git a/src/main/java/org/scijava/io/DataHandleService.java b/src/main/java/org/scijava/io/handle/DataHandleService.java similarity index 95% rename from src/main/java/org/scijava/io/DataHandleService.java rename to src/main/java/org/scijava/io/handle/DataHandleService.java index 723461870..5ed20cfc0 100644 --- a/src/main/java/org/scijava/io/DataHandleService.java +++ b/src/main/java/org/scijava/io/handle/DataHandleService.java @@ -29,8 +29,10 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; +import org.scijava.io.IOService; +import org.scijava.io.location.Location; import org.scijava.plugin.WrapperService; import org.scijava.service.SciJavaService; diff --git a/src/main/java/org/scijava/io/DefaultDataHandleService.java b/src/main/java/org/scijava/io/handle/DefaultDataHandleService.java similarity index 96% rename from src/main/java/org/scijava/io/DefaultDataHandleService.java rename to src/main/java/org/scijava/io/handle/DefaultDataHandleService.java index dc98ce28f..02123344a 100644 --- a/src/main/java/org/scijava/io/DefaultDataHandleService.java +++ b/src/main/java/org/scijava/io/handle/DefaultDataHandleService.java @@ -29,8 +29,9 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; +import org.scijava.io.location.Location; import org.scijava.plugin.AbstractWrapperService; import org.scijava.plugin.Plugin; import org.scijava.service.Service; diff --git a/src/main/java/org/scijava/io/FileHandle.java b/src/main/java/org/scijava/io/handle/FileHandle.java similarity index 98% rename from src/main/java/org/scijava/io/FileHandle.java rename to src/main/java/org/scijava/io/handle/FileHandle.java index f3ac1f422..8d60a27ee 100644 --- a/src/main/java/org/scijava/io/FileHandle.java +++ b/src/main/java/org/scijava/io/handle/FileHandle.java @@ -29,11 +29,12 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; import java.io.IOException; import java.io.RandomAccessFile; +import org.scijava.io.location.FileLocation; import org.scijava.plugin.Plugin; /** diff --git a/src/main/java/org/scijava/io/AbstractLocation.java b/src/main/java/org/scijava/io/location/AbstractLocation.java similarity index 98% rename from src/main/java/org/scijava/io/AbstractLocation.java rename to src/main/java/org/scijava/io/location/AbstractLocation.java index 8937d3847..89d1402e3 100644 --- a/src/main/java/org/scijava/io/AbstractLocation.java +++ b/src/main/java/org/scijava/io/location/AbstractLocation.java @@ -29,7 +29,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; /** * Abstract base class for {@link Location} implementations. diff --git a/src/main/java/org/scijava/io/BytesLocation.java b/src/main/java/org/scijava/io/location/BytesLocation.java similarity index 98% rename from src/main/java/org/scijava/io/BytesLocation.java rename to src/main/java/org/scijava/io/location/BytesLocation.java index 8070dc225..dc41fbade 100644 --- a/src/main/java/org/scijava/io/BytesLocation.java +++ b/src/main/java/org/scijava/io/location/BytesLocation.java @@ -29,7 +29,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import java.nio.ByteBuffer; diff --git a/src/main/java/org/scijava/io/FileLocation.java b/src/main/java/org/scijava/io/location/FileLocation.java similarity index 98% rename from src/main/java/org/scijava/io/FileLocation.java rename to src/main/java/org/scijava/io/location/FileLocation.java index 3d93ee12c..31a331bd8 100644 --- a/src/main/java/org/scijava/io/FileLocation.java +++ b/src/main/java/org/scijava/io/location/FileLocation.java @@ -29,7 +29,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import java.io.File; import java.net.URI; diff --git a/src/main/java/org/scijava/io/Location.java b/src/main/java/org/scijava/io/location/Location.java similarity index 96% rename from src/main/java/org/scijava/io/Location.java rename to src/main/java/org/scijava/io/location/Location.java index 34b79f63b..ec2e7c363 100644 --- a/src/main/java/org/scijava/io/Location.java +++ b/src/main/java/org/scijava/io/location/Location.java @@ -29,10 +29,12 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import java.net.URI; +import org.scijava.io.handle.DataHandle; + /** * A location is a data descriptor, such as a file on disk, a remote * URL, or a database connection. diff --git a/src/main/java/org/scijava/io/URILocation.java b/src/main/java/org/scijava/io/location/URILocation.java similarity index 99% rename from src/main/java/org/scijava/io/URILocation.java rename to src/main/java/org/scijava/io/location/URILocation.java index 819fb2282..1c4045955 100644 --- a/src/main/java/org/scijava/io/URILocation.java +++ b/src/main/java/org/scijava/io/location/URILocation.java @@ -29,7 +29,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import java.io.UnsupportedEncodingException; import java.net.URI; diff --git a/src/main/java/org/scijava/io/URLLocation.java b/src/main/java/org/scijava/io/location/URLLocation.java similarity index 98% rename from src/main/java/org/scijava/io/URLLocation.java rename to src/main/java/org/scijava/io/location/URLLocation.java index 1b7490894..24789ff14 100644 --- a/src/main/java/org/scijava/io/URLLocation.java +++ b/src/main/java/org/scijava/io/location/URLLocation.java @@ -29,7 +29,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import java.net.URI; import java.net.URISyntaxException; diff --git a/src/test/java/org/scijava/ContextCreationTest.java b/src/test/java/org/scijava/ContextCreationTest.java index 449fcc26f..6f364ddfc 100644 --- a/src/test/java/org/scijava/ContextCreationTest.java +++ b/src/test/java/org/scijava/ContextCreationTest.java @@ -95,9 +95,9 @@ 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.io.handle.DefaultDataHandleService.class, org.scijava.io.nio.DefaultNIOService.class, org.scijava.main.DefaultMainService.class, org.scijava.menu.DefaultMenuService.class, diff --git a/src/test/java/org/scijava/io/DataHandleTest.java b/src/test/java/org/scijava/io/handle/DataHandleTest.java similarity index 97% rename from src/test/java/org/scijava/io/DataHandleTest.java rename to src/test/java/org/scijava/io/handle/DataHandleTest.java index 185f47881..c91b7c4ea 100644 --- a/src/test/java/org/scijava/io/DataHandleTest.java +++ b/src/test/java/org/scijava/io/handle/DataHandleTest.java @@ -29,7 +29,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; import static org.junit.Assert.assertEquals; @@ -41,6 +41,9 @@ import org.junit.Test; import org.scijava.Context; +import org.scijava.io.handle.DataHandle; +import org.scijava.io.handle.DataHandleService; +import org.scijava.io.location.Location; import org.scijava.util.Bytes; /** diff --git a/src/test/java/org/scijava/io/FileHandleTest.java b/src/test/java/org/scijava/io/handle/FileHandleTest.java similarity index 91% rename from src/test/java/org/scijava/io/FileHandleTest.java rename to src/test/java/org/scijava/io/handle/FileHandleTest.java index ed8effa58..fd754a673 100644 --- a/src/test/java/org/scijava/io/FileHandleTest.java +++ b/src/test/java/org/scijava/io/handle/FileHandleTest.java @@ -29,12 +29,17 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.handle; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; +import org.scijava.io.handle.DataHandle; +import org.scijava.io.handle.FileHandle; +import org.scijava.io.location.FileLocation; +import org.scijava.io.location.Location; + /** * Tests {@link FileHandle}. * diff --git a/src/test/java/org/scijava/io/BytesLocationTest.java b/src/test/java/org/scijava/io/location/BytesLocationTest.java similarity index 97% rename from src/test/java/org/scijava/io/BytesLocationTest.java rename to src/test/java/org/scijava/io/location/BytesLocationTest.java index 014dff813..b406e76eb 100644 --- a/src/test/java/org/scijava/io/BytesLocationTest.java +++ b/src/test/java/org/scijava/io/location/BytesLocationTest.java @@ -29,12 +29,13 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import org.junit.Test; +import org.scijava.io.location.BytesLocation; /** * Tests {@link BytesLocation}. diff --git a/src/test/java/org/scijava/io/FileLocationTest.java b/src/test/java/org/scijava/io/location/FileLocationTest.java similarity index 96% rename from src/test/java/org/scijava/io/FileLocationTest.java rename to src/test/java/org/scijava/io/location/FileLocationTest.java index 60b95e1b0..1c68568f6 100644 --- a/src/test/java/org/scijava/io/FileLocationTest.java +++ b/src/test/java/org/scijava/io/location/FileLocationTest.java @@ -29,13 +29,14 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import static org.junit.Assert.assertEquals; import java.io.File; import org.junit.Test; +import org.scijava.io.location.FileLocation; /** * Tests {@link FileLocation}. diff --git a/src/test/java/org/scijava/io/URILocationTest.java b/src/test/java/org/scijava/io/location/URILocationTest.java similarity index 96% rename from src/test/java/org/scijava/io/URILocationTest.java rename to src/test/java/org/scijava/io/location/URILocationTest.java index 866837b04..251242f3e 100644 --- a/src/test/java/org/scijava/io/URILocationTest.java +++ b/src/test/java/org/scijava/io/location/URILocationTest.java @@ -29,7 +29,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; @@ -39,6 +39,7 @@ import java.util.Map; import org.junit.Test; +import org.scijava.io.location.URILocation; /** * Tests {@link URILocation}. diff --git a/src/test/java/org/scijava/io/URLLocationTest.java b/src/test/java/org/scijava/io/location/URLLocationTest.java similarity index 96% rename from src/test/java/org/scijava/io/URLLocationTest.java rename to src/test/java/org/scijava/io/location/URLLocationTest.java index 0fde7d087..191102fe4 100644 --- a/src/test/java/org/scijava/io/URLLocationTest.java +++ b/src/test/java/org/scijava/io/location/URLLocationTest.java @@ -29,7 +29,7 @@ * #L% */ -package org.scijava.io; +package org.scijava.io.location; import static org.junit.Assert.assertSame; @@ -37,6 +37,7 @@ import java.net.URL; import org.junit.Test; +import org.scijava.io.location.URLLocation; /** * Tests {@link URLLocation}. From 195438da5fa9ce4c2da7283925af3473f9ff555e Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Tue, 14 Mar 2017 13:48:43 +0100 Subject: [PATCH 037/741] Location: tweak javadoc Clarify that locations can be read and/or write. --- src/main/java/org/scijava/io/location/Location.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/io/location/Location.java b/src/main/java/org/scijava/io/location/Location.java index ec2e7c363..7dc000855 100644 --- a/src/main/java/org/scijava/io/location/Location.java +++ b/src/main/java/org/scijava/io/location/Location.java @@ -44,7 +44,7 @@ * 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. + * read and/or write bytes for a particular kind of location. *

* * @author Curtis Rueden From b87dd694c31ca4a779de731d7cc2034bb2fc4071 Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Tue, 14 Mar 2017 17:04:12 +0100 Subject: [PATCH 038/741] Location: add getName() method The name returned is meant as an analogue to a file name and might be used for meta-data purposes. Signed-off-by: Curtis Rueden --- src/main/java/org/scijava/io/location/Location.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/org/scijava/io/location/Location.java b/src/main/java/org/scijava/io/location/Location.java index 7dc000855..da96a1f98 100644 --- a/src/main/java/org/scijava/io/location/Location.java +++ b/src/main/java/org/scijava/io/location/Location.java @@ -48,6 +48,7 @@ *

* * @author Curtis Rueden + * @author Gabriel Einsdorf */ public interface Location { @@ -59,4 +60,13 @@ default URI getURI() { return null; } + /** + * Gets the name of the object addressed by this location, or an empty string + * if it has no name. + */ + default String getName() { + final URI uri = getURI(); + return uri == null ? "" : uri.toString(); + } + } From 64aa6873fb38b3e994c0e901d659f89ddb07e87b Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Wed, 15 Mar 2017 15:22:30 +0100 Subject: [PATCH 039/741] AbstractLocation: add hashCode() and equals() Locations need to be properly distinguishable. This change makes it feasible to put Location objects into a HashMap and to compare them using the equals() method. --- .../scijava/io/location/AbstractLocation.java | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/io/location/AbstractLocation.java b/src/main/java/org/scijava/io/location/AbstractLocation.java index 89d1402e3..2d9d238e2 100644 --- a/src/main/java/org/scijava/io/location/AbstractLocation.java +++ b/src/main/java/org/scijava/io/location/AbstractLocation.java @@ -31,11 +31,30 @@ package org.scijava.io.location; +import java.util.Objects; + /** * Abstract base class for {@link Location} implementations. * * @author Curtis Rueden */ public abstract class AbstractLocation implements Location { - // NB: No implementation needed. + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((getURI() == null) ? 0 : getURI().hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (obj == this) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + final Location other = (Location) obj; + return Objects.equals(getURI(), other.getURI()); + } + } From fd5bf70618eed48fce1cf0fc780e98921f2d86fa Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Sun, 3 Jul 2016 11:10:10 -0500 Subject: [PATCH 040/741] DataHandle: update some javadoc We avoid the term "pointer" (especially "file pointer"). DataHandle plugins are more general than just files. --- .../java/org/scijava/io/handle/DataHandle.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 9f6d1e3dc..768b2801e 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -126,8 +126,8 @@ default int read(final ByteBuffer buf, final int len) throws IOException { } /** - * Sets the stream pointer offset, measured from the beginning of the stream, - * at which the next read or write occurs. + * Sets the stream offset, measured from the beginning of the stream, at which + * the next read or write occurs. */ void seek(long pos) throws IOException; @@ -203,9 +203,8 @@ default String findString(final 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 saveString Whether to collect the string from the current offset 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. @@ -239,9 +238,8 @@ default String findString(final int blockSize, final String... terminators) * 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 saveString Whether to collect the string from the current offset + * 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 From c134f4afe31deed764fd5d6d22982f3034b1cd66 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 1 Jul 2016 23:04:28 -0500 Subject: [PATCH 041/741] DataHandle: provide some default implementations --- .../org/scijava/io/handle/DataHandle.java | 102 +++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 768b2801e..3fae84f1b 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -331,7 +331,9 @@ default String findString(final boolean saveString, final int blockSize, * @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; + default int read() throws IOException { + return offset() < length() ? readByte() & 0xff : -1; + } /** * Reads up to b.length bytes of data from the stream into an array of bytes. @@ -369,4 +371,102 @@ default long skip(final long n) throws IOException { return num; } + // -- DataInput methods -- + + @Override + default boolean readBoolean() throws IOException { + return readByte() != 0; + } + + @Override + default void readFully(final byte[] b) throws IOException { + readFully(b, 0, b.length); + } + + @Override + default int readUnsignedByte() throws IOException { + return readByte() & 0xff; + } + + @Override + default int readUnsignedShort() throws IOException { + return readShort() & 0xffff; + } + + @Override + default String readLine() throws IOException { + // NB: Code adapted from java.io.RandomAccessFile.readLine(). + + final StringBuffer input = new StringBuffer(); + int c = -1; + boolean eol = false; + + while (!eol) { + switch (c = read()) { + case -1: + case '\n': + eol = true; + break; + case '\r': + eol = true; + long cur = offset(); + if (read() != '\n') seek(cur); + break; + default: + input.append((char)c); + break; + } + } + + if (c == -1 && input.length() == 0) { + return null; + } + return input.toString(); + } + + @Override + default String readUTF() throws IOException { + final int length = readUnsignedShort(); + final byte[] b = new byte[length]; + read(b); + return new String(b, "UTF-8"); + } + + @Override + default int skipBytes(final int n) throws IOException { + final int skipped = (int) Math.min(n, length() - offset()); + if (skipped < 0) return 0; + seek(offset() + skipped); + return skipped; + } + + // -- DataOutput methods -- + + @Override + default void write(final byte[] b) throws IOException { + write(b, 0, b.length); + } + + @Override + default void writeBoolean(final boolean v) throws IOException { + write(v ? 1 : 0); + } + + @Override + default void writeByte(final int v) throws IOException { + write(v); + } + + @Override + default void writeBytes(final String s) throws IOException { + write(s.getBytes("UTF-8")); + } + + @Override + default void writeUTF(final String str) throws IOException { + final byte[] b = str.getBytes("UTF-8"); + writeShort(b.length); + write(b); + } + } From 05a0d2bed4a7dfe4804403a136f5a20a0a16659c Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 1 Jul 2016 23:12:43 -0500 Subject: [PATCH 042/741] DataHandle: add a mutator for the length In SCIFIO and Bio-Formats, this was a feature of the AbstractNIOHandle. But there is no reason to limit it to NIO-flavored handles only. --- src/main/java/org/scijava/io/handle/DataHandle.java | 8 ++++++++ src/main/java/org/scijava/io/handle/FileHandle.java | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 3fae84f1b..adb88b394 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -66,6 +66,14 @@ public interface DataHandle extends WrapperPlugin, /** Returns the length of the stream. */ long length() throws IOException; + /** + * Sets the new length of the handle. + * + * @param length New length. + * @throws IOException If there is an error changing the handle's length. + */ + void setLength(long length) throws IOException; + /** * Returns the current order of the stream. * diff --git a/src/main/java/org/scijava/io/handle/FileHandle.java b/src/main/java/org/scijava/io/handle/FileHandle.java index 8d60a27ee..944e384d5 100644 --- a/src/main/java/org/scijava/io/handle/FileHandle.java +++ b/src/main/java/org/scijava/io/handle/FileHandle.java @@ -83,6 +83,11 @@ public long length() throws IOException { return raf().length(); } + @Override + public void setLength(final long length) throws IOException { + raf().setLength(length); + } + @Override public int read() throws IOException { return raf().read(); From efec596db50a5715326082bd654dca745f17b2ed Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 1 Jul 2016 23:15:24 -0500 Subject: [PATCH 043/741] DataHandle: add ensureWritable method In SCIFIO and Bio-Formats, this was present as the method AbstractNIOHandle#validateLength. But there is no reason to limit it to NIO-flavored handles only. We use the name ensureWritable for clarity, and for symmetry with a future ensureReadable method. --- .../org/scijava/io/handle/DataHandle.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index adb88b394..3f52f1705 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -74,6 +74,25 @@ public interface DataHandle extends WrapperPlugin, */ void setLength(long length) throws IOException; + /** + * Ensures that the handle has the correct length to be written to and extends + * it as required. + * + * @param count Number of bytes to write. + * @return {@code true} if the handle's length was sufficient, or + * {@code false} if the handle's length required an extension. + * @throws IOException If something goes wrong with the check, or there is an + * error changing the handle's length. + */ + default boolean ensureWritable(final long count) throws IOException { + final long minLength = offset() + count; + if (length() < minLength) { + setLength(minLength); + return false; + } + return true; + } + /** * Returns the current order of the stream. * From e36a8fa44ae4ad3be080b4e1e4a02fd657ab4382 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 20:56:55 -0500 Subject: [PATCH 044/741] DataHandle: add available and ensureReadable --- .../org/scijava/io/handle/DataHandle.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 3f52f1705..efdb804ae 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -34,6 +34,7 @@ import java.io.Closeable; import java.io.DataInput; import java.io.DataOutput; +import java.io.EOFException; import java.io.IOException; import java.io.InputStreamReader; import java.nio.ByteBuffer; @@ -74,6 +75,32 @@ public interface DataHandle extends WrapperPlugin, */ void setLength(long length) throws IOException; + /** + * Verifies that the handle has sufficient bytes available to read, returning + * the actual number of bytes which will be possible to read, which might + * be less than the requested value. + * + * @param count Number of bytes to read. + * @return The actual number of bytes available to be read. + * @throws IOException If something goes wrong with the check. + */ + default long available(final long count) throws IOException { + final long remain = length() - offset(); + return remain < count ? remain : count; + } + + /** + * Ensures that the handle has sufficient bytes available to read. + * + * @param count Number of bytes to read. + * @see #available(long) + * @throws EOFException If there are insufficient bytes available. + * @throws IOException If something goes wrong with the check. + */ + default void ensureReadable(final long count) throws IOException { + if (available(count) < count) throw new EOFException(); + } + /** * Ensures that the handle has the correct length to be written to and extends * it as required. From 6c87eb06d5417e91c3eef92540c39f54318a17ae Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 15:49:50 -0500 Subject: [PATCH 045/741] DataHandle: add more default method impls --- .../org/scijava/io/handle/DataHandle.java | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index efdb804ae..52d27fe35 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -442,11 +442,68 @@ default int readUnsignedByte() throws IOException { return readByte() & 0xff; } + @Override + default short readShort() throws IOException { + final int ch1 = read(); + final int ch2 = read(); + if ((ch1 | ch2) < 0) throw new EOFException(); + return (short) ((ch1 << 8) + (ch2 << 0)); + } + @Override default int readUnsignedShort() throws IOException { return readShort() & 0xffff; } + @Override + default char readChar() throws IOException { + return (char) readShort(); + } + + @Override + default int readInt() throws IOException { + int ch1 = read(); + int ch2 = read(); + int ch3 = read(); + int ch4 = read(); + if ((ch1 | ch2 | ch3 | ch4) < 0) throw new EOFException(); + return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); + } + + @Override + default long readLong() throws IOException { + int ch1 = read(); + int ch2 = read(); + int ch3 = read(); + int ch4 = read(); + int ch5 = read(); + int ch6 = read(); + int ch7 = read(); + int ch8 = read(); + if ((ch1 | ch2 | ch3 | ch4 | ch5 | ch6 | ch7 | ch8) < 0) { + throw new EOFException(); + } + // TODO: Double check this inconsistent code. + return ((long) ch1 << 56) + // + ((long) (ch2 & 255) << 48) + // + ((long) (ch3 & 255) << 40) + // + ((long) (ch4 & 255) << 32) + // + ((long) (ch5 & 255) << 24) + // + ((ch6 & 255) << 16) + // + ((ch7 & 255) << 8) + // + ((ch8 & 255) << 0); + } + + @Override + default float readFloat() throws IOException { + return Float.intBitsToFloat(readInt()); + } + + @Override + default double readDouble() throws IOException { + return Double.longBitsToDouble(readLong()); + } + @Override default String readLine() throws IOException { // NB: Code adapted from java.io.RandomAccessFile.readLine(). @@ -511,6 +568,42 @@ default void writeByte(final int v) throws IOException { write(v); } + @Override + default void writeChar(final int v) throws IOException { + write((v >>> 8) & 0xFF); + write((v >>> 0) & 0xFF); + } + + @Override + default void writeInt(final int v) throws IOException { + write((v >>> 24) & 0xFF); + write((v >>> 16) & 0xFF); + write((v >>> 8) & 0xFF); + write((v >>> 0) & 0xFF); + } + + @Override + default void writeLong(final long v) throws IOException { + write((byte) (v >>> 56)); + write((byte) (v >>> 48)); + write((byte) (v >>> 40)); + write((byte) (v >>> 32)); + write((byte) (v >>> 24)); + write((byte) (v >>> 16)); + write((byte) (v >>> 8)); + write((byte) (v >>> 0)); + } + + @Override + default void writeFloat(final float v) throws IOException { + writeInt(Float.floatToIntBits(v)); + } + + @Override + default void writeDouble(final double v) throws IOException { + writeLong(Double.doubleToLongBits(v)); + } + @Override default void writeBytes(final String s) throws IOException { write(s.getBytes("UTF-8")); From fc853ff1acb6f15b7348ec9aaa503c44fb955dcb Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 15:51:10 -0500 Subject: [PATCH 046/741] DataHandle: improve class javadoc --- src/main/java/org/scijava/io/handle/DataHandle.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 52d27fe35..ae0143c26 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -44,8 +44,8 @@ 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}. + * A data handle is a plugin which provides both streaming and random + * access to bytes at a {@link Location} (e.g., files or arrays). * * @author Curtis Rueden * @see DataHandleInputStream From b948c95431cdd15ecd2dcf31db357d83ac749442 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 15:51:54 -0500 Subject: [PATCH 047/741] DataHandle: tweak getOrder() javadoc --- src/main/java/org/scijava/io/handle/DataHandle.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index ae0143c26..e6809d90f 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -120,11 +120,7 @@ default boolean ensureWritable(final long count) throws IOException { return true; } - /** - * Returns the current order of the stream. - * - * @return See above. - */ + /** Returns the byte order of the stream. */ ByteOrder getOrder(); /** Gets the endianness of the stream. */ From 9dabafa5be25ca92922378478e48998eeacb18f5 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 15:53:41 -0500 Subject: [PATCH 048/741] DataHandle: relocate the seek method It is the mutator which goes with offset(), so should be adjacent to it. --- src/main/java/org/scijava/io/handle/DataHandle.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index e6809d90f..545580da0 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -64,6 +64,12 @@ public interface DataHandle extends WrapperPlugin, /** Returns the current offset in the stream. */ long offset() throws IOException; + /** + * Sets the stream offset, measured from the beginning of the stream, at which + * the next read or write occurs. + */ + void seek(long pos) throws IOException; + /** Returns the length of the stream. */ long length() throws IOException; @@ -175,12 +181,6 @@ default int read(final ByteBuffer buf, final int len) throws IOException { return n; } - /** - * Sets the stream 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. From 90f2d418f76d1b6dcaa87d967f8cc5ffb4ad3604 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 15:55:07 -0500 Subject: [PATCH 049/741] DataHandle: relocate the isLittleEndian() method It belongs immediately before the (to be renamed) setOrder(boolean). --- src/main/java/org/scijava/io/handle/DataHandle.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 545580da0..779923800 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -129,11 +129,6 @@ default boolean ensureWritable(final long count) throws IOException { /** Returns the byte order of the stream. */ ByteOrder getOrder(); - /** Gets the endianness of the stream. */ - default boolean isLittleEndian() { - return getOrder() == ByteOrder.LITTLE_ENDIAN; - } - /** * Sets the byte order of the stream. * @@ -141,6 +136,11 @@ default boolean isLittleEndian() { */ void setOrder(ByteOrder order); + /** Gets the endianness of the stream. */ + default boolean isLittleEndian() { + return getOrder() == ByteOrder.LITTLE_ENDIAN; + } + /** Sets the endianness of the stream. */ default void setOrder(final boolean little) { setOrder(little ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); From d6a4271687c02d2d2132d9f2097b0a8400fe3606 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 15:56:23 -0500 Subject: [PATCH 050/741] DataHandle: fix up the endianness API Now we have both isLittleEndian() and isBigEndian() accessors, and the setOrder(boolean) method is now setLittleEndian(boolean). --- .../org/scijava/io/handle/DataHandle.java | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 779923800..dd87f5a67 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -136,13 +136,32 @@ default boolean ensureWritable(final long count) throws IOException { */ void setOrder(ByteOrder order); - /** Gets the endianness of the stream. */ + /** + * Returns true iff the stream's order is {@link ByteOrder#BIG_ENDIAN}. + * + * @see #getOrder() + */ + default boolean isBigEndian() { + return getOrder() == ByteOrder.BIG_ENDIAN; + } + + /** + * Returns true iff the stream's order is {@link ByteOrder#LITTLE_ENDIAN}. + * + * @see #getOrder() + */ default boolean isLittleEndian() { return getOrder() == ByteOrder.LITTLE_ENDIAN; } - /** Sets the endianness of the stream. */ - default void setOrder(final boolean little) { + /** + * Sets the endianness of the stream. + * + * @param little If true, sets the order to {@link ByteOrder#LITTLE_ENDIAN}; + * otherwise, sets the order to {@link ByteOrder#BIG_ENDIAN}. + * @see #setOrder(ByteOrder) + */ + default void setLittleEndian(final boolean little) { setOrder(little ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN); } From aa6f96237927bec4ba678c016ca423e7c6332712 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 16:07:13 -0500 Subject: [PATCH 051/741] DataHandle: tweak comment --- src/main/java/org/scijava/io/handle/DataHandle.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index dd87f5a67..52c8c1971 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -521,7 +521,7 @@ default double readDouble() throws IOException { @Override default String readLine() throws IOException { - // NB: Code adapted from java.io.RandomAccessFile.readLine(). + // NB: Adapted from java.io.RandomAccessFile.readLine(). final StringBuffer input = new StringBuffer(); int c = -1; From b2e9316cfb7e7ebf7cec2e124dffb6cf8f2a9668 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 16:07:43 -0500 Subject: [PATCH 052/741] DataHandle: tweak readString, skip and skipBytes They can lean on the available(long) method. --- .../org/scijava/io/handle/DataHandle.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 52c8c1971..81a217a49 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -234,10 +234,9 @@ default String readCString() throws IOException { } /** Reads a string of up to length n. */ - default String readString(int n) throws IOException { - final long avail = length() - offset(); - if (n > avail) n = (int) avail; - final byte[] b = new byte[n]; + default String readString(final int n) throws IOException { + final int r = (int) available(n); + final byte[] b = new byte[r]; readFully(b); return new String(b, getEncoding()); } @@ -433,11 +432,10 @@ default int read(byte[] b) throws IOException { * @throws IOException - if an I/O error occurs. */ default 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; + final long skip = available(n); + if (skip <= 0) return 0; + seek(offset() + skip); + return skip; } // -- DataInput methods -- @@ -560,10 +558,11 @@ default String readUTF() throws IOException { @Override default int skipBytes(final int n) throws IOException { - final int skipped = (int) Math.min(n, length() - offset()); - if (skipped < 0) return 0; - seek(offset() + skipped); - return skipped; + // NB: Cast here is safe since the value of n bounds the result to an int. + final int skip = (int) available(n); + if (skip < 0) return 0; + seek(offset() + skip); + return skip; } // -- DataOutput methods -- From 6f2994fd7fa4c00ac5a3166708cede1a1d3d5e6a Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 16:10:02 -0500 Subject: [PATCH 053/741] DataHandle: relocate DataInput methods Now they match their declared order in DataInput. --- .../org/scijava/io/handle/DataHandle.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 81a217a49..6cf3147da 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -441,13 +441,22 @@ default long skip(final long n) throws IOException { // -- DataInput methods -- @Override - default boolean readBoolean() throws IOException { - return readByte() != 0; + default void readFully(final byte[] b) throws IOException { + readFully(b, 0, b.length); } @Override - default void readFully(final byte[] b) throws IOException { - readFully(b, 0, b.length); + default int skipBytes(final int n) throws IOException { + // NB: Cast here is safe since the value of n bounds the result to an int. + final int skip = (int) available(n); + if (skip < 0) return 0; + seek(offset() + skip); + return skip; + } + + @Override + default boolean readBoolean() throws IOException { + return readByte() != 0; } @Override @@ -556,15 +565,6 @@ default String readUTF() throws IOException { return new String(b, "UTF-8"); } - @Override - default int skipBytes(final int n) throws IOException { - // NB: Cast here is safe since the value of n bounds the result to an int. - final int skip = (int) available(n); - if (skip < 0) return 0; - seek(offset() + skip); - return skip; - } - // -- DataOutput methods -- @Override From daa914594be635b6d176dd6e412aa53d1e6b17bb Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 16:12:48 -0500 Subject: [PATCH 054/741] DataHandle: add readFully(byte[], int, int) impl --- .../java/org/scijava/io/handle/DataHandle.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 6cf3147da..9b697b90d 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -33,6 +33,7 @@ import java.io.Closeable; import java.io.DataInput; +import java.io.DataInputStream; import java.io.DataOutput; import java.io.EOFException; import java.io.IOException; @@ -445,6 +446,20 @@ default void readFully(final byte[] b) throws IOException { readFully(b, 0, b.length); } + @Override + default void readFully(final byte[] b, final int off, final int len) + throws IOException + { + // NB: Adapted from java.io.DataInputStream.readFully(byte[], int, int). + if (len < 0) throw new IndexOutOfBoundsException(); + int n = 0; + while (n < len) { + int count = read(b, off + n, len - n); + if (count < 0) throw new EOFException(); + n += count; + } + } + @Override default int skipBytes(final int n) throws IOException { // NB: Cast here is safe since the value of n bounds the result to an int. From 2e11aff8993812cb94e450c35d00bfbdcc1eb207 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 16:29:38 -0500 Subject: [PATCH 055/741] DataHandle: improve readUTF implementation Better to lean on the Java standard library here. --- src/main/java/org/scijava/io/handle/DataHandle.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 9b697b90d..0fea60ecc 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -574,10 +574,7 @@ default String readLine() throws IOException { @Override default String readUTF() throws IOException { - final int length = readUnsignedShort(); - final byte[] b = new byte[length]; - read(b); - return new String(b, "UTF-8"); + return DataInputStream.readUTF(this); } // -- DataOutput methods -- From 21881a9f13b62199803ec670b03bb7bc0e73ab1a Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 16:31:11 -0500 Subject: [PATCH 056/741] DataHandle: add more default DataOutput methods --- .../java/org/scijava/io/handle/DataHandle.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 0fea60ecc..c5d0e86cb 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -594,6 +594,12 @@ default void writeByte(final int v) throws IOException { write(v); } + @Override + default void writeShort(final int v) throws IOException { + write((v >>> 8) & 0xFF); + write((v >>> 0) & 0xFF); + } + @Override default void writeChar(final int v) throws IOException { write((v >>> 8) & 0xFF); @@ -635,6 +641,16 @@ default void writeBytes(final String s) throws IOException { write(s.getBytes("UTF-8")); } + @Override + default void writeChars(final String s) throws IOException { + final int len = s.length(); + for (int i = 0 ; i < len ; i++) { + final int v = s.charAt(i); + write((v >>> 8) & 0xFF); + write((v >>> 0) & 0xFF); + } + } + @Override default void writeUTF(final String str) throws IOException { final byte[] b = str.getBytes("UTF-8"); From 02b18b9fd303995c59ccd9579b0091c7c760d5ac Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 16:48:38 -0500 Subject: [PATCH 057/741] Add a utility class which knows how to write UTF The whole thing exists only to work around the fact that DataOutputStream.writeUTF(String, DataOutput), which is the method we really need, has package-protected access. This utility class grabs that method via reflection, makes it accessible, and caches the reference. --- .../org/scijava/io/handle/DataHandles.java | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 src/main/java/org/scijava/io/handle/DataHandles.java diff --git a/src/main/java/org/scijava/io/handle/DataHandles.java b/src/main/java/org/scijava/io/handle/DataHandles.java new file mode 100644 index 000000000..47be40ebc --- /dev/null +++ b/src/main/java/org/scijava/io/handle/DataHandles.java @@ -0,0 +1,110 @@ +/* + * #%L + * SciJava Common shared library for SciJava software. + * %% + * Copyright (C) 2009 - 2017 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.handle; + +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Utility methods for working with {@link DataHandle}s. + * + * @author Curtis Rueden + */ +public final class DataHandles { + + private static Method utfMethod; + + private DataHandles() { + // Prevent instantiation of utility class. + } + + /** + * Writes a string to the specified DataOutput using modified UTF-8 encoding + * in a machine-independent manner. + *

+ * First, two bytes are written to out as if by the {@code writeShort} method + * giving the number of bytes to follow. This value is the number of bytes + * actually written out, not the length of the string. Following the length, + * each character of the string is output, in sequence, using the modified + * UTF-8 encoding for the character. If no exception is thrown, the counter + * {@code written} is incremented by the total number of bytes written to the + * output stream. This will be at least two plus the length of {@code str}, + * and at most two plus thrice the length of {@code str}. + *

+ * + * @param str a string to be written. + * @param out destination to write to + * @return The number of bytes written out. + * @throws IOException if an I/O error occurs. + */ + public static int writeUTF(final String str, final DataOutput out) + throws IOException + { + // HACK: Strangely, DataOutputStream.writeUTF(String, DataOutput) + // has package-private access. We work around it via reflection. + try { + return (Integer) utfMethod().invoke(null, str, out); + } + catch (final IllegalAccessException | IllegalArgumentException + | InvocationTargetException exc) + { + throw new IllegalStateException( + "Cannot invoke DataOutputStream.writeUTF(String, DataOutput)", exc); + } + } + + // -- Helper methods -- + + /** Gets the {@link #utfMethod} field, initializing if needed. */ + private static Method utfMethod() { + if (utfMethod == null) initUTFMethod(); + return utfMethod; + } + + /** Initializes the {@link #utfMethod} field. */ + private static synchronized void initUTFMethod() { + if (utfMethod != null) return; + try { + final Method m = DataOutputStream.class.getDeclaredMethod("writeUTF", + String.class, DataOutput.class); + m.setAccessible(true); + utfMethod = m; + } + catch (final NoSuchMethodException | SecurityException exc) { + throw new IllegalStateException( + "No usable DataOutputStream.writeUTF(String, DataOutput)", exc); + } + } +} From e702e9195d878477eae011f70fdaff708ecf125c Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 16 Jun 2017 16:52:35 -0500 Subject: [PATCH 058/741] DataHandle: simplify writeUTF method Now it leans on the Java standard library (indirectly). --- src/main/java/org/scijava/io/handle/DataHandle.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index c5d0e86cb..7f7f39f03 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -653,9 +653,7 @@ default void writeChars(final String s) throws IOException { @Override default void writeUTF(final String str) throws IOException { - final byte[] b = str.getBytes("UTF-8"); - writeShort(b.length); - write(b); + DataHandles.writeUTF(str, this); } } From 5b1d44f591b232a8c8560de344af9fbd088f2c04 Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Tue, 14 Mar 2017 14:33:27 +0100 Subject: [PATCH 059/741] DataHandle: improve javadoc --- .../org/scijava/io/handle/DataHandle.java | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 7f7f39f03..7aa5fe255 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -71,7 +71,7 @@ public interface DataHandle extends WrapperPlugin, */ void seek(long pos) throws IOException; - /** Returns the length of the stream. */ + /** Returns the length of the data in bytes. */ long length() throws IOException; /** @@ -83,12 +83,31 @@ public interface DataHandle extends WrapperPlugin, void setLength(long length) throws IOException; /** - * Verifies that the handle has sufficient bytes available to read, returning - * the actual number of bytes which will be possible to read, which might - * be less than the requested value. + * Gets the number of bytes which can be safely read from, or written to, the + * data handle, bounded by the specified number of bytes. + *

+ * In the case of reading, attempting to read the returned number of bytes is + * guaranteed not to throw {@link EOFException}. However, be aware that the + * following methods might still process fewer bytes than indicated + * by this method: + *

+ *
    + *
  • {@link #read(ByteBuffer)}
  • + *
  • {@link #read(ByteBuffer, int)}
  • + *
  • {@link #read(byte[])}
  • + *
  • {@link #read(byte[], int, int)}
  • + *
  • {@link #skip(long)}
  • + *
  • {@link #skipBytes(int)}
  • + *
+ *

+ * In the case of writing, attempting to write the returned number of bytes is + * guaranteed not to expand the length of the handle; i.e., the write will + * only overwrite bytes already within the handle's bounds. + *

* - * @param count Number of bytes to read. - * @return The actual number of bytes available to be read. + * @param count Desired number of bytes to read/write. + * @return The actual number of bytes which could be safely read/written, + * which might be less than the requested value. * @throws IOException If something goes wrong with the check. */ default long available(final long count) throws IOException { @@ -109,8 +128,8 @@ default void ensureReadable(final long count) throws IOException { } /** - * Ensures that the handle has the correct length to be written to and extends - * it as required. + * Ensures that the handle has the correct length to be written to, and + * extends it as required. * * @param count Number of bytes to write. * @return {@code true} if the handle's length was sufficient, or @@ -202,7 +221,7 @@ default int read(final ByteBuffer buf, final int len) throws IOException { } /** - * Writes up to {@code buf.remaining()} bytes of data from the given + * Writes {@code buf.remaining()} bytes of data from the given * {@link ByteBuffer} to the stream. */ default void write(final ByteBuffer buf) throws IOException { @@ -210,7 +229,8 @@ default void write(final ByteBuffer buf) throws IOException { } /** - * Writes up to len bytes of data from the given ByteBuffer to the stream. + * Writes {@code len} bytes of data from the given {@link ByteBuffer} to the + * stream. */ default void write(final ByteBuffer buf, final int len) throws IOException @@ -227,7 +247,6 @@ default void write(final ByteBuffer buf, final int len) } } - /** Reads a string of arbitrary length, terminated by a null char. */ default String readCString() throws IOException { final String line = findString("\0"); From 795295b64a09c647a24363b2144a4a2c72061948 Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Mon, 20 Mar 2017 15:53:50 +0100 Subject: [PATCH 060/741] DataHandle: add writeLine(String) method This is analogous to readLine(). --- src/main/java/org/scijava/io/handle/DataHandle.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 7aa5fe255..f71b3df28 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -411,6 +411,17 @@ default String findString(final boolean saveString, final int blockSize, return saveString ? out.toString() : null; } + /** + * Writes the provided string, followed by a newline character. + * + * @param string The string to write. + * @throws IOException If an I/O error occurs. + */ + default void writeLine(final String string) throws IOException { + writeBytes(string); + writeBytes("\n"); + } + // -- InputStream look-alikes -- /** From d978412359ffabb27d8989e797b77bae0e565637 Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Fri, 7 Jul 2017 14:15:48 +0200 Subject: [PATCH 061/741] DataHandle: improve javadoc --- .../org/scijava/io/handle/DataHandle.java | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index f71b3df28..9f2afe5ba 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -83,7 +83,7 @@ public interface DataHandle extends WrapperPlugin, void setLength(long length) throws IOException; /** - * Gets the number of bytes which can be safely read from, or written to, the + * Gets the number of bytes which can be read from, or written to, the * data handle, bounded by the specified number of bytes. *

* In the case of reading, attempting to read the returned number of bytes is @@ -104,9 +104,9 @@ public interface DataHandle extends WrapperPlugin, * guaranteed not to expand the length of the handle; i.e., the write will * only overwrite bytes already within the handle's bounds. *

- * + * * @param count Desired number of bytes to read/write. - * @return The actual number of bytes which could be safely read/written, + * @return The actual number of bytes which could be read/written, * which might be less than the requested value. * @throws IOException If something goes wrong with the check. */ @@ -151,14 +151,14 @@ default boolean ensureWritable(final long count) throws IOException { /** * Sets the byte order of the stream. - * + * * @param order Order to set. */ void setOrder(ByteOrder order); /** * Returns true iff the stream's order is {@link ByteOrder#BIG_ENDIAN}. - * + * * @see #getOrder() */ default boolean isBigEndian() { @@ -167,7 +167,7 @@ default boolean isBigEndian() { /** * Returns true iff the stream's order is {@link ByteOrder#LITTLE_ENDIAN}. - * + * * @see #getOrder() */ default boolean isLittleEndian() { @@ -176,7 +176,7 @@ default boolean isLittleEndian() { /** * Sets the endianness of the stream. - * + * * @param little If true, sets the order to {@link ByteOrder#LITTLE_ENDIAN}; * otherwise, sets the order to {@link ByteOrder#BIG_ENDIAN}. * @see #setOrder(ByteOrder) @@ -290,7 +290,7 @@ default String findString(final 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 offset to * the terminating bytes, and return it. If false, returns null. * @param terminators The strings for which to search. @@ -309,7 +309,7 @@ default String findString(final boolean saveString, /** * 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 @@ -325,9 +325,9 @@ default String findString(final int blockSize, final String... terminators) /** * 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 offset - * to the terminating bytes, and return it. If false, returns null. + * + * @param saveString Whether to collect the string from the current offset 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 @@ -354,8 +354,8 @@ default String findString(final boolean saveString, final int blockSize, } @SuppressWarnings("resource") - final InputStreamReader in = - new InputStreamReader(new DataHandleInputStream<>(this), getEncoding()); + final InputStreamReader in = new InputStreamReader( + new DataHandleInputStream<>(this), getEncoding()); final char[] buf = new char[blockSize]; long loc = 0; while (loc < maxLen && offset() < length() - 1) { @@ -426,7 +426,7 @@ default void writeLine(final String string) throws IOException { /** * 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. */ @@ -436,7 +436,7 @@ default 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. */ default int read(byte[] b) throws IOException { @@ -445,7 +445,7 @@ default 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; @@ -457,7 +457,7 @@ default int read(byte[] b) throws IOException { * 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. @@ -591,7 +591,7 @@ default String readLine() throws IOException { if (read() != '\n') seek(cur); break; default: - input.append((char)c); + input.append((char) c); break; } } @@ -674,7 +674,7 @@ default void writeBytes(final String s) throws IOException { @Override default void writeChars(final String s) throws IOException { final int len = s.length(); - for (int i = 0 ; i < len ; i++) { + for (int i = 0; i < len; i++) { final int v = s.charAt(i); write((v >>> 8) & 0xFF); write((v >>> 0) & 0xFF); From 4251229c24d4f7e05c7be011c5d318b656800383 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Fri, 7 Jul 2017 15:41:28 -0500 Subject: [PATCH 062/741] DataHandle: make some minor style tweaks --- src/main/java/org/scijava/io/handle/DataHandle.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index 9f2afe5ba..e184a5232 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -439,12 +439,13 @@ default int read() throws IOException { * * @return the total number of bytes read into the buffer. */ - default int read(byte[] b) throws IOException { + default int read(final byte[] b) throws IOException { return read(b, 0, b.length); } /** - * Reads up to len bytes of data from the stream into an array of bytes. + * Reads up to {@code len} bytes of data from the stream into an array of + * bytes. * * @return the total number of bytes read into the buffer. */ From 8b065baff3a41c919f6a862f5cef602c12fa06ba Mon Sep 17 00:00:00 2001 From: Gabriel Einsdorf Date: Wed, 5 Jul 2017 17:08:08 +0200 Subject: [PATCH 063/741] DataHandle: remove built-in references to java.nio The java.nio package is not available on all JVMs, so its use must be optional on top of the base API layer. --- .../scijava/io/handle/AbstractDataHandle.java | 2 - .../org/scijava/io/handle/DataHandle.java | 64 ++----------------- .../org/scijava/io/handle/DataHandleTest.java | 9 +-- 3 files changed, 5 insertions(+), 70 deletions(-) diff --git a/src/main/java/org/scijava/io/handle/AbstractDataHandle.java b/src/main/java/org/scijava/io/handle/AbstractDataHandle.java index b072df9ad..ac0e4b7a8 100644 --- a/src/main/java/org/scijava/io/handle/AbstractDataHandle.java +++ b/src/main/java/org/scijava/io/handle/AbstractDataHandle.java @@ -31,8 +31,6 @@ package org.scijava.io.handle; -import java.nio.ByteOrder; - import org.scijava.io.location.Location; import org.scijava.plugin.AbstractWrapperPlugin; diff --git a/src/main/java/org/scijava/io/handle/DataHandle.java b/src/main/java/org/scijava/io/handle/DataHandle.java index e184a5232..4c3b845d8 100644 --- a/src/main/java/org/scijava/io/handle/DataHandle.java +++ b/src/main/java/org/scijava/io/handle/DataHandle.java @@ -38,8 +38,6 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStreamReader; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import org.scijava.io.location.Location; import org.scijava.plugin.WrapperPlugin; @@ -56,6 +54,10 @@ public interface DataHandle extends WrapperPlugin, DataInput, DataOutput, Closeable { + public enum ByteOrder { + LITTLE_ENDIAN, BIG_ENDIAN + } + /** Default block size to use when searching through the stream. */ int DEFAULT_BLOCK_SIZE = 256 * 1024; // 256 KB @@ -92,8 +94,6 @@ public interface DataHandle extends WrapperPlugin, * by this method: *

*