diff --git a/pom.xml b/pom.xml
index 90b21a764..cde8d72fd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,12 +5,12 @@
org.scijava
pom-scijava
- 3.6
+ 5.1
scijava-common
- 2.33.1-SNAPSHOT
+ 2.35.2-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 both ImageJ and SCIFIO.
diff --git a/src/main/java/org/scijava/AbstractUIDetails.java b/src/main/java/org/scijava/AbstractUIDetails.java
index ec90ebc82..fb0b78493 100644
--- a/src/main/java/org/scijava/AbstractUIDetails.java
+++ b/src/main/java/org/scijava/AbstractUIDetails.java
@@ -105,7 +105,8 @@ public String getTitle() {
// use the unique identifier, if available
if (this instanceof Identifiable) {
- return ((Identifiable) this).getIdentifier();
+ final String id = ((Identifiable) this).getIdentifier();
+ if (id != null) return id;
}
// use class name as a last resort
@@ -235,7 +236,7 @@ public int compareTo(final Prioritized that) {
// compare titles
final String thisTitle = getTitle();
final String thatTitle = uiDetails.getTitle();
- return thisTitle.compareTo(thatTitle);
+ return MiscUtils.compare(thisTitle, thatTitle);
}
}
diff --git a/src/main/java/org/scijava/annotations/AbstractIndexWriter.java b/src/main/java/org/scijava/annotations/AbstractIndexWriter.java
index 9266d6d26..52104745b 100644
--- a/src/main/java/org/scijava/annotations/AbstractIndexWriter.java
+++ b/src/main/java/org/scijava/annotations/AbstractIndexWriter.java
@@ -45,6 +45,8 @@
import java.util.Map.Entry;
import java.util.TreeMap;
+import javax.lang.model.element.AnnotationValue;
+
/**
* Writes annotations as JSON-formatted files.
*
@@ -170,6 +172,9 @@ protected Object adapt(final Object o) {
if (o instanceof Annotation) {
return adapt((Annotation) o);
}
+ else if (o instanceof AnnotationValue) {
+ return adapt(((AnnotationValue) o).getValue());
+ }
else if (o instanceof Enum) {
return adapt((Enum>) o);
}
diff --git a/src/main/java/org/scijava/annotations/AnnotationProcessor.java b/src/main/java/org/scijava/annotations/AnnotationProcessor.java
index 778d909fa..fe0a5c26b 100644
--- a/src/main/java/org/scijava/annotations/AnnotationProcessor.java
+++ b/src/main/java/org/scijava/annotations/AnnotationProcessor.java
@@ -32,7 +32,9 @@
package org.scijava.annotations;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -62,6 +64,7 @@
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic.Kind;
+import javax.tools.FileObject;
import javax.tools.StandardLocation;
import org.scijava.annotations.AbstractIndexWriter.StreamFactory;
@@ -90,14 +93,14 @@ public boolean process(final Set extends TypeElement> elements,
try {
writer.write(writer);
}
- catch (IOException e) {
+ catch (final IOException e) {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
e.printStackTrace(new PrintStream(out));
try {
out.close();
processingEnv.getMessager().printMessage(Kind.ERROR, out.toString());
}
- catch (IOException e2) {
+ catch (final IOException e2) {
processingEnv.getMessager().printMessage(Kind.ERROR,
e2.getMessage() + " while printing " + e.getMessage());
}
@@ -152,8 +155,9 @@ public void add(final TypeElement element) {
}
@SuppressWarnings("unchecked")
- private Map adapt(List extends AnnotationMirror> mirrors,
- TypeMirror annotationType)
+ private Map adapt(
+ final List extends AnnotationMirror> mirrors,
+ final TypeMirror annotationType)
{
final Map result = new TreeMap();
for (final AnnotationMirror mirror : mirrors) {
@@ -208,7 +212,8 @@ else if (o instanceof VariableElement) {
}
private AnnotationMirror getMirror(final TypeElement element) {
- for (AnnotationMirror candidate : utils.getAllAnnotationMirrors(element))
+ for (final AnnotationMirror candidate : utils
+ .getAllAnnotationMirrors(element))
{
final Name binaryName =
utils.getBinaryName((TypeElement) candidate.getAnnotationType()
@@ -221,7 +226,9 @@ private AnnotationMirror getMirror(final TypeElement element) {
}
@Override
- public InputStream openInput(String annotationName) throws IOException {
+ public InputStream openInput(final String annotationName)
+ throws IOException
+ {
try {
return filer.getResource(StandardLocation.CLASS_OUTPUT, "",
Index.INDEX_PREFIX + annotationName).openInputStream();
@@ -232,16 +239,37 @@ public InputStream openInput(String annotationName) throws IOException {
}
@Override
- public OutputStream openOutput(String annotationName) throws IOException {
+ public OutputStream openOutput(final String annotationName)
+ throws IOException
+ {
final List originating = originatingElements.get(annotationName);
- return filer.createResource(StandardLocation.CLASS_OUTPUT, "",
- Index.INDEX_PREFIX + annotationName,
- originating.toArray(new Element[originating.size()]))
- .openOutputStream();
+ final String path = Index.INDEX_PREFIX + annotationName;
+ final FileObject fileObject =
+ filer.createResource(StandardLocation.CLASS_OUTPUT, "", path,
+ originating.toArray(new Element[originating.size()]));
+
+ /*
+ * Verify that the generated file is in the META-INF/json/ subdirectory;
+ * Despite our asking for it explicitly, the DefaultFileManager will
+ * strip out the directory if javac was called without an explicit
+ * output directory (i.e. without -d option).
+ */
+ final String uri = fileObject.toUri().toString();
+ if (uri != null && uri.endsWith("/" + path)) {
+ return fileObject.openOutputStream();
+ }
+ final String prefix =
+ uri.substring(0, uri.length() - annotationName.length());
+ final File file = new File(prefix + path);
+ final File parent = file.getParentFile();
+ if (parent != null && !parent.isDirectory() && !parent.mkdirs()) {
+ throw new IOException("Could not create directory: " + parent);
+ }
+ return new FileOutputStream(file);
}
@Override
- public boolean isClassObsolete(String className) {
+ public boolean isClassObsolete(final String className) {
return false;
}
diff --git a/src/main/java/org/scijava/menu/ShadowMenu.java b/src/main/java/org/scijava/menu/ShadowMenu.java
index 6c65355a1..b117f1e0c 100644
--- a/src/main/java/org/scijava/menu/ShadowMenu.java
+++ b/src/main/java/org/scijava/menu/ShadowMenu.java
@@ -529,14 +529,15 @@ private ShadowMenu addChild(final ModuleInfo info, final int depth) {
if (!leaf) child.addChild(info, depth + 1);
else if (existingChild != null) {
if (log != null) {
- if (info.getPriority() == existingChild.getModuleInfo().getPriority()) {
+ final ModuleInfo childInfo = existingChild.getModuleInfo();
+ if (childInfo != null && info.getPriority() == childInfo.getPriority())
+ {
log.warn("ShadowMenu: menu item already exists:\n\texisting: " +
- existingChild.getModuleInfo() + "\n\t ignored: " + info);
+ childInfo + "\n\t ignored: " + info);
}
else {
log.debug("ShadowMenu: higher-priority menu item already exists:\n" +
- "\texisting: " + existingChild.getModuleInfo() + "\n\t ignored: " +
- info);
+ "\texisting: " + childInfo + "\n\t ignored: " + info);
}
}
}
diff --git a/src/main/java/org/scijava/prefs/DefaultPrefService.java b/src/main/java/org/scijava/prefs/DefaultPrefService.java
index f5bb0529b..df189324f 100644
--- a/src/main/java/org/scijava/prefs/DefaultPrefService.java
+++ b/src/main/java/org/scijava/prefs/DefaultPrefService.java
@@ -378,6 +378,28 @@ public List getList(final Class> prefClass) {
return getList(preferences);
}
+ @Override
+ public Iterable getIterable(final String key) {
+ return getIterable((Class>) null, key);
+ }
+
+ @Override
+ public Iterable getIterable(final Class> prefClass, final String key) {
+ final Preferences preferences = prefs(prefClass);
+ return getIterable(preferences.node(key));
+ }
+
+ @Override
+ public void putIterable(final Iterable iterable, final String key) {
+ putIterable((Class>) null, iterable, key);
+ }
+
+ @Override
+ public void putIterable(final Class> prefClass, final Iterable iterable, final String key) {
+ final Preferences preferences = prefs(prefClass);
+ putIterable(preferences.node(key), iterable);
+ }
+
// -- Helper methods --
private void clear(final Preferences preferences, final String key) {
@@ -460,6 +482,60 @@ private List getList(final Preferences preferences) {
return list;
}
+ private void putIterable(final Preferences preferences,
+ final Iterable iterable)
+ {
+ if (preferences == null) {
+ throw new IllegalArgumentException("Preferences not set.");
+ }
+ int index = 0;
+ for (final String value : iterable) {
+ preferences.put("" + index++, value == null ? null : value.toString());
+ }
+ }
+
+ private Iterable getIterable(final Preferences preferences)
+ {
+ if (preferences == null) {
+ throw new IllegalArgumentException("Preferences not set.");
+ }
+ return new Iterable() {
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ private String value;
+ private int index;
+ {
+ findNext();
+ }
+
+ @Override
+ public String next() {
+ final String result = value;
+ findNext();
+ return result;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return value != null;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ private void findNext() {
+ if (index < 0) return;
+ value = preferences.get("" + index, null);
+ index = value == null ? -1 : index + 1;
+ }
+ };
+ }
+ };
+ }
+
private Preferences prefs(final Class> c) {
return Preferences.userNodeForPackage(c == null ? PrefService.class : c);
}
diff --git a/src/main/java/org/scijava/prefs/PrefService.java b/src/main/java/org/scijava/prefs/PrefService.java
index 590f8259c..21d4ac537 100644
--- a/src/main/java/org/scijava/prefs/PrefService.java
+++ b/src/main/java/org/scijava/prefs/PrefService.java
@@ -194,4 +194,24 @@ public interface PrefService extends SciJavaService {
* prefs.
*/
List getList(Class> prefClass);
+
+ /**
+ * Puts an iterable into the preferences.
+ */
+ void putIterable(Iterable iterable, String key);
+
+ /**
+ * Puts an iterable into the preferences.
+ */
+ void putIterable(Class> prefClass, Iterable iterable, String key);
+
+ /**
+ * Gets an iterable from the preferences.
+ */
+ Iterable getIterable(String key);
+
+ /**
+ * Gets an iterable from the preferences.
+ */
+ Iterable getIterable(Class> prefClass, String key);
}
diff --git a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java
new file mode 100644
index 000000000..6f94198fe
--- /dev/null
+++ b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java
@@ -0,0 +1,102 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2014 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 javax.script.ScriptEngine;
+import javax.script.ScriptException;
+
+import org.scijava.prefs.PrefService;
+
+/**
+ * The default implementation of a {@link ScriptInterpreter}.
+ *
+ * @author Johannes Schindelin
+ */
+public class DefaultScriptInterpreter implements ScriptInterpreter {
+
+ private final ScriptLanguage language;
+ private final ScriptEngine engine;
+ private final History history;
+
+ /**
+ * Constructs a new {@link DefaultScriptInterpreter}.
+ *
+ * @param scriptService the script service
+ * @param language the script language
+ */
+ public DefaultScriptInterpreter(final PrefService prefs,
+ final ScriptService scriptService, final ScriptLanguage language)
+ {
+ this.language = language;
+ engine = language.getScriptEngine();
+ history = new History(prefs, engine.getClass().getName());
+ readHistory();
+ }
+
+ @Override
+ public synchronized void readHistory() {
+ if (history == null) return;
+ history.read();
+ }
+
+ @Override
+ public synchronized void writeHistory() {
+ if (history == null) return;
+ history.write();
+ }
+
+ @Override
+ public synchronized String walkHistory(final String currentCommand,
+ final boolean forward)
+ {
+ if (history == null) return currentCommand;
+ history.replace(currentCommand);
+ return forward ? history.next() : history.previous();
+ }
+
+ @Override
+ public void eval(final String command) throws ScriptException {
+ if (history != null) history.add(command);
+ if (engine == null) throw new java.lang.IllegalArgumentException();
+ engine.eval(command);
+ }
+
+ @Override
+ public ScriptLanguage getLanguage() {
+ return language;
+ }
+
+ @Override
+ public ScriptEngine getEngine() {
+ return engine;
+ }
+
+}
diff --git a/src/main/java/org/scijava/script/History.java b/src/main/java/org/scijava/script/History.java
new file mode 100644
index 000000000..cb0f181cb
--- /dev/null
+++ b/src/main/java/org/scijava/script/History.java
@@ -0,0 +1,145 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2014 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 {@link Prefs}
+ */
+ 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 currentCommand) {
+ if (position < 0) {
+ this.currentCommand = currentCommand;
+ return false;
+ }
+ return entries.replace(position, currentCommand);
+ }
+
+ /**
+ * 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 position = -1;
+ for (;;) {
+ position = entries.previous(position);
+ if (position < 0) break;
+ if (builder.length() > 0) builder.append(" -> ");
+ if (this.position == position) builder.append("[");
+ builder.append(entries.get(position));
+ if (this.position == position) builder.append("]");
+ }
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/org/scijava/script/ScriptInterpreter.java b/src/main/java/org/scijava/script/ScriptInterpreter.java
new file mode 100644
index 000000000..936f211af
--- /dev/null
+++ b/src/main/java/org/scijava/script/ScriptInterpreter.java
@@ -0,0 +1,83 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2014 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 javax.script.ScriptEngine;
+import javax.script.ScriptException;
+
+/**
+ * The contract for script interpreters.
+ *
+ * @author Johannes Schindelin
+ */
+public interface ScriptInterpreter {
+
+ /**
+ * Reads the persisted history of the current script interpreter.
+ */
+ void readHistory();
+
+ /**
+ * Persists the history of the current script interpreter.
+ */
+ void writeHistory();
+
+ /**
+ * Obtains the next/previous command in the command history.
+ *
+ * @param currentCommand the current command (will be stored in the history)
+ * @param forward if true, the next history entry is returned (more recent),
+ * if false, the previous one
+ * @return the next/previous command
+ */
+ String walkHistory(String currentCommand, boolean forward);
+
+ /**
+ * Evaluates a command.
+ *
+ * @param command the command to evaluate
+ * @throws ScriptException
+ */
+ void eval(String command) throws ScriptException;
+
+ /**
+ * Returns the associated {@link ScriptLanguage}.
+ */
+ ScriptLanguage getLanguage();
+
+ /**
+ * Returns the associated {@link ScriptEngine}.
+ *
+ * @return the script engine
+ */
+ ScriptEngine getEngine();
+}
diff --git a/src/main/java/org/scijava/util/LastRecentlyUsed.java b/src/main/java/org/scijava/util/LastRecentlyUsed.java
new file mode 100644
index 000000000..c3fb19e9d
--- /dev/null
+++ b/src/main/java/org/scijava/util/LastRecentlyUsed.java
@@ -0,0 +1,392 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2014 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.util;
+
+import java.lang.reflect.Array;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A simple container for {@code N} last-recently-used items.
+ *
+ * @author Johannes Schindelin
+ */
+public class LastRecentlyUsed implements Iterable, Collection {
+ private final Object[] entries;
+ private final Map map;
+ /**
+ * The double-linked list pointers.
+ *
+ * The {@code top} variable points to the most recently added, the
+ * {@code bottom} variable to the oldest entry. The {@code next} and
+ * {@code previous} arrays point to the next newer/next older entry.
+ *
+ *
+ * For initialization performance, all of {@code next}, {@code previous},
+ * {@code top} and {@code bottom} are initialized to {@code 0}, meaning that
+ * you need to decrement the values by one in order to obtain the entry index.
+ * Example: the index of the most recently added entry is {@code top -1}, and
+ * {@code next[top - 1]} is {@code 0} because there is no newer entry than the
+ * newest entry.
+ *
+ */
+ private final int[] next, previous;
+ private int top, bottom;
+
+ public LastRecentlyUsed(int size) {
+ entries = new Object[2 * size];
+ next = new int[2 * size];
+ previous = new int[2 * size];
+ map = new HashMap();
+ }
+
+ /**
+ * Given the index of an entry, returns the index of the next newer entry.
+ *
+ * @param index the index of the current entry, or -1 to wrap around to the oldest entry.
+ * @return the index of the next newer entry, or -1 when there is no such entry.
+ */
+ public int next(int index) {
+ return index < 0 ? bottom - 1 : next[index] - 1;
+ }
+
+ /**
+ * Given the index of an entry, returns the index of the next older entry.
+ *
+ * @param index the index of the current entry, or -1 to wrap around to the newest entry.
+ * @return the index of the next older entry, or -1 when there is no such entry.
+ */
+ public int previous(int index) {
+ return index < 0 ? top - 1 : previous[index] - 1;
+ }
+
+ /**
+ * Returns the entry for the given index.
+ *
+ * @param index the index of the entry
+ * @return the entry
+ */
+ @SuppressWarnings("unchecked")
+ public T get(int index) {
+ return (T) entries[index];
+ }
+
+ /**
+ * Looks up the index for a given entry.
+ *
+ * @param value the value of the entry to find
+ * @return the corresponding index, or {@code -1} if the entry was not found
+ */
+ public int lookup(final T value) {
+ final Integer result = map.get(value);
+ return result == null ? -1 : (int) result;
+ }
+
+ /**
+ * Add a new newest entry.
+ *
+ * @param value the value of the entry
+ * @return whether the entry was added
+ */
+ public boolean add(final T value) {
+ return add(value, false);
+ }
+
+ /**
+ * Add a new oldest entry.
+ *
+ * This method helps recreating {@link LastRecentlyUsed} instances given the
+ * entries in the order newest first, oldest last.
+ *
+ *
+ * @param value the value of the entry to add
+ */
+ public void addToEnd(final T value) {
+ add(value, true);
+ }
+
+ public boolean replace(final int index, T newValue) {
+ final Object previousValue = get(index);
+ if (previousValue == null) {
+ throw new IllegalArgumentException("No current entry at position " +
+ index);
+ }
+ if (newValue.equals(previous)) return false;
+ map.remove(previousValue);
+ map.put(newValue, index);
+ entries[index] = newValue;
+ return true;
+ }
+
+ /**
+ * Empties the data structure.
+ */
+ public void clear() {
+ top = bottom = 0;
+ map.clear();
+ for (int i = 0; i < entries.length; i++) {
+ entries[i] = null;
+ next[i] = previous[i] = 0;
+ }
+ }
+
+ @Override
+ public boolean addAll(final Collection extends T> values) {
+ for (final T value : values) {
+ add(value);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean contains(final Object value) {
+ return map.containsKey(value);
+ }
+
+ @Override
+ public boolean containsAll(final Collection> values) {
+ return map.keySet().containsAll(values);
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return top == 0;
+ }
+
+ @Override
+ public boolean remove(Object value) {
+ final Integer index = map.get(value);
+ if (index == null) return false;
+ remove(index.intValue());
+ return true;
+ }
+
+ @Override
+ public boolean removeAll(Collection> values) {
+ boolean result = true;
+ for (final Object value : values) {
+ result = remove(value) && result;
+ }
+ return result;
+ }
+
+ @Override
+ public boolean retainAll(Collection> values) {
+ for (int index = top - 1; index >= 0; ) {
+ final int prev = previous[index] - 1;
+ if (!values.contains(get(index))) {
+ remove(index);
+ }
+ index = prev;
+ }
+ return containsAll(values);
+ }
+
+ @Override
+ public int size() {
+ return map.size();
+ }
+
+ @Override
+ public Object[] toArray() {
+ final Object[] result = new Object[size()];
+ for (int i = 0, index = top - 1; index >= 0; i++, index =
+ previous[index] - 1)
+ {
+ result[i] = get(index);
+ }
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public S[] toArray(final S[] array) {
+ final int size = size();
+ if (array.length >= size) {
+ for (int i = 0, index = top - 1; index >= 0; i++, index =
+ previous[index] - 1)
+ {
+ array[i] = (S) get(index);
+ }
+ return array;
+ }
+ final S[] result =
+ (S[]) Array.newInstance(array.getClass().getComponentType(), size);
+ for (int i = 0, index = top - 1; index >= 0; i++, index =
+ previous[index] - 1)
+ {
+ result[i] = (S) get(index);
+ }
+ return result;
+ }
+
+ /**
+ * Returns an {@link Iterator}.
+ *
+ * @return the iterator
+ */
+ public Iterator iterator() {
+ return new Iterator() {
+
+ private int position = top - 1;
+
+ @Override
+ public boolean hasNext() {
+ return position >= 0;
+ }
+
+ @Override
+ public T next() {
+ @SuppressWarnings("unchecked")
+ final T result = (T) entries[position];
+ position = previous[position] - 1;
+ return result;
+ }
+
+ @Override
+ public void remove() {
+ LastRecentlyUsed.this.remove(position == 0 ? top - 1 : next[position] - 1);
+ }
+
+ };
+ }
+
+ // -- private methods
+
+ private void remove(int position) {
+ assert(entries[position] != null);
+ map.remove(entries[position]);
+ entries[position] = null;
+ if (next[position] == 0) {
+ top = previous[position];
+ }
+ else {
+ previous[next[position] - 1] = previous[position];
+ }
+ if (previous[position] == 0) {
+ bottom = next[position];
+ }
+ else {
+ next[previous[position] - 1] = next[position];
+ }
+ next[position] = previous[position] = 0;
+ }
+
+ private boolean add(final T value, boolean addAtEnd) {
+ final Integer existing = map.get(value);
+ int insert;
+ if (existing != null) {
+ insert = existing;
+ remove(insert);
+ }
+ else if (map.size() == entries.length / 2) {
+ insert = bottom - 1;
+ remove(insert);
+ }
+ else {
+ insert = value.hashCode() % entries.length;
+ if (insert < 0) insert += entries.length;
+ while (insert < entries.length && entries[insert] != null) insert++;
+ }
+ add(insert, value, addAtEnd);
+ return existing == null;
+ }
+
+ private void add(int position, T value, boolean atEnd) {
+ assert(next[position] == 0);
+ assert(previous[position] == 0);
+ assert(entries[position] == null);
+
+ map.put(value, position);
+ entries[position] = value;
+ if (atEnd) {
+ next[position] = bottom;
+ if (bottom > 0) previous[bottom - 1] = position + 1;
+ bottom = position + 1;
+ if (top == 0) top = bottom;
+ }
+ else {
+ previous[position] = top;
+ if (top > 0) {
+ next[top - 1] = position + 1;
+ }
+ top = position + 1;
+ if (bottom == 0) bottom = top;
+ }
+ }
+
+ // For testing
+ protected void assertConsistency() {
+ if (top == 0) {
+ assert(bottom == 0);
+ assert(map.size() == 0);
+ for (int i = 0; i < entries.length; i++) {
+ assert(entries[i] == null);
+ assert(next[i] == 0);
+ assert(previous[i] == 0);
+ }
+ return;
+ }
+ assert(bottom != 0);
+ final Set indices = new HashSet(map.values());
+ assert(indices.size() == map.size());
+ for (int i = 0; i < entries.length; i++) {
+ if (indices.contains(i)) {
+ assert(entries[i] != null);
+ assert(map.get(entries[i]) == i);
+ if (i == top - 1 || top == bottom) {
+ assert(next[i] == 0);
+ }
+ else {
+ assert(next[i] > 0);
+ assert(previous[next[i] - 1] == i + 1);
+ }
+ if (i == bottom - 1 || top == bottom) {
+ assert(previous[i] == 0);
+ }
+ else {
+ assert(previous[i] > 0);
+ assert(next[previous[i] - 1] == i + 1);
+ }
+ }
+ else {
+ assert(entries[i] == null);
+ assert(next[i] == 0);
+ assert(previous[i] == 0);
+ }
+ }
+ }
+}
diff --git a/src/main/java/org/scijava/util/Prefs.java b/src/main/java/org/scijava/util/Prefs.java
index 7dd12f503..5cc620a12 100644
--- a/src/main/java/org/scijava/util/Prefs.java
+++ b/src/main/java/org/scijava/util/Prefs.java
@@ -223,7 +223,7 @@ public static void remove(final Preferences preferences, final String key) {
service().remove(preferences.absolutePath(), key);
}
- /** Puts a list into the preferences. */
+ /** Puts a map into the preferences. */
public static void putMap(final Map map, final String key) {
service().putMap(map, key);
}
@@ -234,7 +234,7 @@ public static void putMap(final Preferences preferences,
service().putMap(preferences.absolutePath(), map, key);
}
- /** Puts a list into the preferences. */
+ /** Puts a map into the preferences. */
public static void putMap(final Preferences preferences,
final Map map)
{
diff --git a/src/main/java/org/scijava/util/ReflectException.java b/src/main/java/org/scijava/util/ReflectException.java
new file mode 100644
index 000000000..d9814bf4a
--- /dev/null
+++ b/src/main/java/org/scijava/util/ReflectException.java
@@ -0,0 +1,58 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2014 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.util;
+
+/**
+ * An exception thrown when something goes wrong performing a reflected
+ * operation with {@link ReflectedUniverse}.
+ *
+ * @author Curtis Rueden
+ */
+public class ReflectException extends Exception {
+
+ public ReflectException() {
+ super();
+ }
+
+ public ReflectException(final String s) {
+ super(s);
+ }
+
+ public ReflectException(final String s, final Throwable cause) {
+ super(s, cause);
+ }
+
+ public ReflectException(final Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/src/main/java/org/scijava/util/ReflectedUniverse.java b/src/main/java/org/scijava/util/ReflectedUniverse.java
new file mode 100644
index 000000000..20ecc1ee2
--- /dev/null
+++ b/src/main/java/org/scijava/util/ReflectedUniverse.java
@@ -0,0 +1,489 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2014 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.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+import java.util.StringTokenizer;
+
+/**
+ * A general-purpose reflection wrapper class.
+ *
+ * Note: use of this class is discouraged in favor of compile-time (i.e.,
+ * type-safe) dependency linkages. However, there are times when it proves very
+ * useful for writing blocks of reflected code in a more succinct way than you
+ * can do with the vanilla {@link java.lang.reflect} API. Of course, debugging
+ * such reflected code becomes much more difficult—caveat emptor!
+ *
+ *
+ * @author Curtis Rueden
+ */
+public class ReflectedUniverse {
+
+ // -- Fields --
+
+ /** Hashtable containing all variables present in the universe. */
+ private final HashMap variables;
+
+ /** Class loader for imported classes. */
+ private final ClassLoader loader;
+
+ /** Whether to force our way past restrictive access modifiers. */
+ private boolean force;
+
+ // -- Constructors --
+
+ /** Constructs a new reflected universe. */
+ public ReflectedUniverse() {
+ this((ClassLoader) null);
+ }
+
+ /**
+ * Constructs a new reflected universe, with the given URLs representing
+ * additional search paths for imported classes (in addition to the
+ * CLASSPATH).
+ */
+ public ReflectedUniverse(final URL[] urls) {
+ this(urls == null ? null : new URLClassLoader(urls));
+ }
+
+ /** Constructs a new reflected universe that uses the given class loader. */
+ public ReflectedUniverse(final ClassLoader loader) {
+ variables = new HashMap();
+ this.loader = loader == null ? getClass().getClassLoader() : loader;
+ }
+
+ // -- Utility methods --
+
+ /**
+ * Returns whether the given object is compatible with the specified class for
+ * the purposes of reflection.
+ */
+ public static boolean isInstance(final Class> c, final Object o) {
+ return (o == null || c.isInstance(o) ||
+ (c == byte.class && o instanceof Byte) ||
+ (c == short.class && o instanceof Short) ||
+ (c == int.class && o instanceof Integer) ||
+ (c == long.class && o instanceof Long) ||
+ (c == float.class && o instanceof Float) ||
+ (c == double.class && o instanceof Double) ||
+ (c == boolean.class && o instanceof Boolean) || (c == char.class && o instanceof Character));
+ }
+
+ // -- ReflectedUniverse API methods --
+
+ /**
+ * Executes a command in the universe. The following syntaxes are valid:
+ *
+ * - import fully.qualified.package.ClassName
+ * - var = new ClassName(param1, ..., paramN)
+ * - var.method(param1, ..., paramN)
+ * - var2 = var.method(param1, ..., paramN)
+ * - ClassName.method(param1, ..., paramN)
+ * - var2 = ClassName.method(param1, ..., paramN)
+ * - var2 = var
+ *
+ * Important guidelines:
+ *
+ * - Any referenced class must be imported first using "import".
+ * - Variables can be exported from the universe with getVar().
+ * - Variables can be imported to the universe with setVar().
+ * - Each parameter must be either:
+ *
+ * - a variable in the universe
+ * - a static or instance field (i.e., no nested methods)
+ * - a string literal (remember to escape the double quotes)
+ * - an integer literal
+ * - a long literal (ending in L)
+ * - a double literal (containing a decimal point)
+ * - a boolean literal (true or false)
+ * - the null keyword
+ *
+ *
+ *
+ */
+ public Object exec(String command) throws ReflectException {
+ command = command.trim();
+ if (command.startsWith("import ")) {
+ // command is an import statement
+ command = command.substring(7).trim();
+ final int dot = command.lastIndexOf(".");
+ final String varName = dot < 0 ? command : command.substring(dot + 1);
+ Class> c;
+ try {
+ c = Class.forName(command, true, loader);
+ }
+ catch (final NoClassDefFoundError err) {
+ throw new ReflectException("No such class: " + command, err);
+ }
+ catch (final ClassNotFoundException exc) {
+ throw new ReflectException("No such class: " + command, exc);
+ }
+ catch (final RuntimeException exc) {
+ // HACK: workaround for bug in Apache Axis2
+ final String msg = exc.getMessage();
+ if (msg != null && msg.indexOf("ClassNotFound") < 0) throw exc;
+ throw new ReflectException("No such class: " + command, exc);
+ }
+ setVar(varName, c);
+ return null;
+ }
+
+ // get variable where results of command should be stored
+ final int eqIndex = command.indexOf("=");
+ String target = null;
+ if (eqIndex >= 0) {
+ target = command.substring(0, eqIndex).trim();
+ command = command.substring(eqIndex + 1).trim();
+ }
+
+ Object result = null;
+
+ // parse parentheses
+ final int leftParen = command.indexOf("(");
+ if (leftParen < 0) {
+ // command is a simple assignment
+ result = getVar(command);
+ if (target != null) setVar(target, result);
+ return result;
+ }
+ else if (leftParen != command.lastIndexOf("(") ||
+ command.indexOf(")") != command.length() - 1)
+ {
+ throw new ReflectException("Invalid parentheses");
+ }
+
+ // parse arguments
+ final String arglist = command.substring(leftParen + 1);
+ final StringTokenizer st = new StringTokenizer(arglist, "(,)");
+ final int len = st.countTokens();
+ final Object[] args = new Object[len];
+ for (int i = 0; i < len; i++) {
+ final String arg = st.nextToken().trim();
+ args[i] = getVar(arg);
+ }
+ command = command.substring(0, leftParen);
+
+ if (command.startsWith("new ")) {
+ // command is a constructor call
+ final String className = command.substring(4).trim();
+ final Object var = getVar(className);
+ if (var == null) {
+ throw new ReflectException("Class not found: " + className);
+ }
+ else if (!(var instanceof Class>)) {
+ throw new ReflectException("Not a class: " + className);
+ }
+ final Class> cl = (Class>) var;
+
+ // Search for a constructor that matches the arguments. Unfortunately,
+ // calling cl.getConstructor(argClasses) does not work, because
+ // getConstructor() is not flexible enough to detect when the arguments
+ // are subclasses of the constructor argument classes, making a brute
+ // force search through all public constructors necessary.
+ Constructor> constructor = null;
+ final Constructor>[] c = cl.getConstructors();
+ for (int i = 0; i < c.length; i++) {
+ if (force) c[i].setAccessible(true);
+ final Class>[] params = c[i].getParameterTypes();
+ if (params.length == args.length) {
+ boolean match = true;
+ for (int j = 0; j < params.length; j++) {
+ if (!isInstance(params[j], args[j])) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ constructor = c[i];
+ break;
+ }
+ }
+ }
+ if (constructor == null) {
+ final StringBuffer sb = new StringBuffer(command);
+ for (int i = 0; i < args.length; i++) {
+ sb.append(i == 0 ? "(" : ", ");
+ sb.append(args[i].getClass().getName());
+ }
+ sb.append(")");
+ throw new ReflectException("No such constructor: " + sb.toString());
+ }
+
+ // invoke constructor
+ Exception exc = null;
+ try {
+ result = constructor.newInstance(args);
+ }
+ catch (final InstantiationException e) {
+ exc = e;
+ }
+ catch (final IllegalAccessException e) {
+ exc = e;
+ }
+ catch (final InvocationTargetException e) {
+ exc = e;
+ }
+ if (exc != null) {
+ throw new ReflectException("Cannot instantiate object", exc);
+ }
+ }
+ else {
+ // command is a method call
+ final int dot = command.indexOf(".");
+ if (dot < 0) throw new ReflectException("Syntax error");
+ final String varName = command.substring(0, dot).trim();
+ final String methodName = command.substring(dot + 1).trim();
+ final Object var = getVar(varName);
+ if (var == null) {
+ throw new ReflectException("No such variable: " + varName);
+ }
+ final Class> varClass =
+ var instanceof Class> ? (Class>) var : var.getClass();
+
+ // Search for a method that matches the arguments. Unfortunately,
+ // calling varClass.getMethod(methodName, argClasses) does not work,
+ // because getMethod() is not flexible enough to detect when the
+ // arguments are subclasses of the method argument classes, making a
+ // brute force search through all public methods necessary.
+ Method method = null;
+ final Method[] m = varClass.getMethods();
+ for (int i = 0; i < m.length; i++) {
+ if (force) m[i].setAccessible(true);
+ if (methodName.equals(m[i].getName())) {
+ final Class>[] params = m[i].getParameterTypes();
+ if (params.length == args.length) {
+ boolean match = true;
+ for (int j = 0; j < params.length; j++) {
+ if (!isInstance(params[j], args[j])) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ method = m[i];
+ break;
+ }
+ }
+ }
+ }
+ if (method == null) {
+ throw new ReflectException("No such method: " + methodName);
+ }
+
+ // invoke method
+ Exception exc = null;
+ try {
+ result = method.invoke(var, args);
+ }
+ catch (final IllegalAccessException e) {
+ exc = e;
+ }
+ catch (final InvocationTargetException e) {
+ exc = e;
+ }
+ if (exc != null) {
+ throw new ReflectException("Cannot execute method: " + methodName, exc);
+ }
+ }
+
+ // assign result to proper variable
+ if (target != null) setVar(target, result);
+ return result;
+ }
+
+ /** Registers a variable in the universe. */
+ public void setVar(final String varName, final Object obj) {
+ if (obj == null) variables.remove(varName);
+ else variables.put(varName, obj);
+ }
+
+ /** Registers a variable of primitive type boolean in the universe. */
+ public void setVar(final String varName, final boolean b) {
+ setVar(varName, new Boolean(b));
+ }
+
+ /** Registers a variable of primitive type byte in the universe. */
+ public void setVar(final String varName, final byte b) {
+ setVar(varName, new Byte(b));
+ }
+
+ /** Registers a variable of primitive type char in the universe. */
+ public void setVar(final String varName, final char c) {
+ setVar(varName, Character.valueOf(c));
+ }
+
+ /** Registers a variable of primitive type double in the universe. */
+ public void setVar(final String varName, final double d) {
+ setVar(varName, new Double(d));
+ }
+
+ /** Registers a variable of primitive type float in the universe. */
+ public void setVar(final String varName, final float f) {
+ setVar(varName, new Float(f));
+ }
+
+ /** Registers a variable of primitive type int in the universe. */
+ public void setVar(final String varName, final int i) {
+ setVar(varName, Integer.valueOf(i));
+ }
+
+ /** Registers a variable of primitive type long in the universe. */
+ public void setVar(final String varName, final long l) {
+ setVar(varName, Long.valueOf(l));
+ }
+
+ /** Registers a variable of primitive type short in the universe. */
+ public void setVar(final String varName, final short s) {
+ setVar(varName, Short.valueOf(s));
+ }
+
+ /**
+ * Returns the value of a variable or field in the universe. Primitive types
+ * will be wrapped in their Java Object wrapper classes.
+ */
+ public Object getVar(final String varName) throws ReflectException {
+ if (varName.equals("null")) {
+ // variable is a null value
+ return null;
+ }
+ else if (varName.equals("true")) {
+ // variable is a boolean literal
+ return Boolean.TRUE;
+ }
+ else if (varName.equals("false")) {
+ // variable is a boolean literal
+ return Boolean.FALSE;
+ }
+ else if (varName.startsWith("\"") && varName.endsWith("\"")) {
+ // variable is a string literal
+ return varName.substring(1, varName.length() - 1);
+ }
+ try {
+ if (varName.matches("-?\\d+")) {
+ // variable is an int literal
+ return new Integer(varName);
+ }
+ else if (varName.matches("-?\\d+L")) {
+ // variable is a long literal
+ return new Long(varName);
+ }
+ else if (varName.matches("-?\\d*\\.\\d*")) {
+ // variable is a double literal
+ return new Double(varName);
+ }
+ }
+ catch (final NumberFormatException exc) {
+ throw new ReflectException("Invalid literal: " + varName, exc);
+ }
+ final int dot = varName.indexOf(".");
+ if (dot >= 0) {
+ // get field value of variable
+ final String className = varName.substring(0, dot).trim();
+ final Object var = variables.get(className);
+ if (var == null) {
+ throw new ReflectException("No such class: " + className);
+ }
+ final Class> varClass =
+ var instanceof Class> ? (Class>) var : var.getClass();
+ final String fieldName = varName.substring(dot + 1).trim();
+ Field field;
+ try {
+ field = varClass.getField(fieldName);
+ if (force) field.setAccessible(true);
+ }
+ catch (final NoSuchFieldException exc) {
+ throw new ReflectException("No such field: " + varName, exc);
+ }
+ Object fieldVal;
+ try {
+ fieldVal = field.get(var);
+ }
+ catch (final IllegalAccessException exc) {
+ throw new ReflectException("Cannot get field value: " + varName, exc);
+ }
+ return fieldVal;
+ }
+ // get variable
+ final Object var = variables.get(varName);
+ return var;
+ }
+
+ /** Sets whether access modifiers (protected, private, etc.) are ignored. */
+ public void setAccessibilityIgnored(final boolean ignore) {
+ force = ignore;
+ }
+
+ /** Gets whether access modifiers (protected, private, etc.) are ignored. */
+ public boolean isAccessibilityIgnored() {
+ return force;
+ }
+
+ // -- Main method --
+
+ /**
+ * Allows exploration of a reflected universe in an interactive environment.
+ */
+ public static void main(final String[] args) throws IOException {
+ final ReflectedUniverse r = new ReflectedUniverse();
+ System.out.println("Reflected universe test environment. "
+ + "Type commands, or press ^D to quit.");
+ if (args.length > 0) {
+ r.setAccessibilityIgnored(true);
+ System.out.println("Ignoring accessibility modifiers.");
+ }
+ final BufferedReader in =
+ new BufferedReader(new InputStreamReader(System.in, "UTF-8"));
+ while (true) {
+ System.out.print("> ");
+ final String line = in.readLine();
+ if (line == null) break;
+ try {
+ r.exec(line);
+ }
+ catch (final ReflectException exc) {
+ System.err.println("Could not execute '" + line + "':");
+ exc.printStackTrace();
+ }
+ }
+ System.out.println();
+ }
+
+}
diff --git a/src/test/java/org/scijava/options/OptionsTest.java b/src/test/java/org/scijava/options/OptionsTest.java
index 6681ac4d3..d70feaac3 100644
--- a/src/test/java/org/scijava/options/OptionsTest.java
+++ b/src/test/java/org/scijava/options/OptionsTest.java
@@ -94,6 +94,7 @@ public void testPersistence() {
final FooOptions fooOptions = optionsService.getOptions(FooOptions.class);
// bar should initially be 0
+ fooOptions.setBar(0);
assertEquals(0, fooOptions.getBar());
// verify that we can set bar to a desired value at all
diff --git a/src/test/java/org/scijava/script/ScriptEngineTest.java b/src/test/java/org/scijava/script/ScriptEngineTest.java
index 081def361..3c75f1297 100644
--- a/src/test/java/org/scijava/script/ScriptEngineTest.java
+++ b/src/test/java/org/scijava/script/ScriptEngineTest.java
@@ -138,5 +138,7 @@ public Object eval(Reader reader) throws ScriptException {
}
}
- private static class Rot13Bindings extends HashMap implements Bindings { }
+ private static class Rot13Bindings extends HashMap implements Bindings {
+ private static final long serialVersionUID = 1L;
+ }
}
diff --git a/src/test/java/org/scijava/util/LastRecentlyUsedTest.java b/src/test/java/org/scijava/util/LastRecentlyUsedTest.java
new file mode 100644
index 000000000..50dc5980a
--- /dev/null
+++ b/src/test/java/org/scijava/util/LastRecentlyUsedTest.java
@@ -0,0 +1,104 @@
+/*
+ * #%L
+ * SciJava Common shared library for SciJava software.
+ * %%
+ * Copyright (C) 2009 - 2014 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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.util.Iterator;
+
+import org.junit.Test;
+
+/**
+ * Tests the {@link LastRecentlyUsed} data structure.
+ *
+ * @author Johannes Schindelin
+ */
+public class LastRecentlyUsedTest {
+
+ @Test
+ public void test() {
+ int count = 3;
+ final LastRecentlyUsed lru = new LastRecentlyUsed(count);
+
+ for (int i = 1; i <= count; i++) {
+ lru.add("" + i);
+ }
+
+ int position = -1;
+ for (int i = 1; i <= count; i++) {
+ position = lru.next(position);
+ assertEquals("" + i, lru.get(position));
+ }
+ position = lru.next(position);
+ assertEquals(-1, position);
+
+ for (int i = count; i >= 1; i--) {
+ position = lru.previous(position);
+ assertEquals("" + i, lru.get(position));
+ }
+ position = lru.previous(position);
+ assertEquals(-1, position);
+ }
+
+ @Test
+ public void testRemove() {
+ final LastRecentlyUsed lru = new LastRecentlyUsed(3);
+ lru.add("a");
+ lru.add("b");
+ lru.add("c");
+
+ lru.remove("b");
+
+ Iterator iter = lru.iterator();
+ assertEquals("c", iter.next());
+ assertEquals("a", iter.next());
+ assertFalse(iter.hasNext());
+
+ lru.remove("a");
+
+ iter = lru.iterator();
+ assertEquals("c", iter.next());
+ assertFalse(iter.hasNext());
+
+ lru.remove("a");
+
+ iter = lru.iterator();
+ assertEquals("c", iter.next());
+ assertFalse(iter.hasNext());
+
+ lru.remove("c");
+
+ iter = lru.iterator();
+ assertFalse(iter.hasNext());
+ }
+}