From d09c4f4ec2aa50a7d4e958ce31f6ca068e6d54da Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 11 Oct 2014 20:11:46 +0200 Subject: [PATCH 01/16] Fix OptionsTest's assumption that bar was not persisted previously Once the test ran successfully, bar is distinctively different from 0... Signed-off-by: Johannes Schindelin --- src/test/java/org/scijava/options/OptionsTest.java | 1 + 1 file changed, 1 insertion(+) 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 From 13bf97f2ce7650f74ecd3c29ef172b9dfe81c033 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 27 Jun 2014 16:49:41 -0500 Subject: [PATCH 02/16] Fix Javadocs Signed-off-by: Johannes Schindelin --- src/main/java/org/scijava/util/Prefs.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) { From b3215739d8727ad164a7779d7dad4b15898101ce Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 30 Jun 2014 22:27:03 -0500 Subject: [PATCH 03/16] Avoid compiler warning Signed-off-by: Johannes Schindelin --- src/test/java/org/scijava/script/ScriptEngineTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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; + } } From 6470e08835de3ca7b8ecf121ea02bdba301944ef Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 27 Jun 2014 16:50:30 -0500 Subject: [PATCH 04/16] PrefService: Provide an API for Iterables The most generic way to provide a bunch of Strings to persist is by passing an Iterable for writing and handling an Iterable when reading. Signed-off-by: Johannes Schindelin --- .../org/scijava/prefs/DefaultPrefService.java | 76 +++++++++++++++++++ .../java/org/scijava/prefs/PrefService.java | 20 +++++ 2 files changed, 96 insertions(+) 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); } From 71ca10e5a26a9cad1cb880128b2b579da8cc2a90 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 2 Jul 2014 09:36:43 -0500 Subject: [PATCH 05/16] Add a data structure keeping n unique last-recently-used items This is similar to the LinkedHashMap structure, except that an already-existing item can be easily put to the front of the list, or the back. Signed-off-by: Johannes Schindelin --- .../org/scijava/util/LastRecentlyUsed.java | 300 ++++++++++++++++++ .../scijava/util/LastRecentlyUsedTest.java | 69 ++++ 2 files changed, 369 insertions(+) create mode 100644 src/main/java/org/scijava/util/LastRecentlyUsed.java create mode 100644 src/test/java/org/scijava/util/LastRecentlyUsedTest.java 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..678429fcb --- /dev/null +++ b/src/main/java/org/scijava/util/LastRecentlyUsed.java @@ -0,0 +1,300 @@ +/* + * #%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.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 { + 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; + } + } + + /** + * 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/test/java/org/scijava/util/LastRecentlyUsedTest.java b/src/test/java/org/scijava/util/LastRecentlyUsedTest.java new file mode 100644 index 000000000..2051b9616 --- /dev/null +++ b/src/test/java/org/scijava/util/LastRecentlyUsedTest.java @@ -0,0 +1,69 @@ +/* + * #%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 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); + } +} From e89f212da17afa1ca69e6b67977ba9fc9e14accf Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 2 Jul 2014 09:38:22 -0500 Subject: [PATCH 06/16] Add a history class for the upcoming script interpreters The newly-introduced History class maintains the history of most recently executed statements and has functionality to persist and retrieve the list from the preferences. Signed-off-by: Johannes Schindelin --- src/main/java/org/scijava/script/History.java | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 src/main/java/org/scijava/script/History.java 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..0879fceef --- /dev/null +++ b/src/main/java/org/scijava/script/History.java @@ -0,0 +1,140 @@ +/* + * #%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 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; + } + + public boolean replace(final String currentCommand) { + if (position < 0) 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 ? null : 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 ? null : 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(); + } +} From b7904e0dca18559af875f005d65de3378ff583bc Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 2 Jul 2014 09:40:34 -0500 Subject: [PATCH 07/16] Add the UI-agnostic part of the script interpreters The newly-introduced ScriptInterpreter class provides all the functionality of a script interpreter sans the graphical user interface. It can execute statements and persists a history of most recently executed statements. Signed-off-by: Johannes Schindelin --- .../script/DefaultScriptInterpreter.java | 104 ++++++++++++++++++ .../org/scijava/script/ScriptInterpreter.java | 78 +++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 src/main/java/org/scijava/script/DefaultScriptInterpreter.java create mode 100644 src/main/java/org/scijava/script/ScriptInterpreter.java 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..56dc00553 --- /dev/null +++ b/src/main/java/org/scijava/script/DefaultScriptInterpreter.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.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 ScriptEngine engine; + private History history; + private String currentCommand = ""; + + /** + * Constructs a new {@link DefaultScriptInterpreter}. + * + * @param scriptService the script service + * @param engine the script engine + */ + public DefaultScriptInterpreter(final PrefService prefs, final ScriptService scriptService, final ScriptEngine engine) { + this.engine = engine; + 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, boolean forward) { + this.currentCommand = currentCommand; + if (history == null) return currentCommand; + history.replace(currentCommand); + return forward ? history.next() : history.previous(); + } + + /** + * Evaluates a command. + * + * @param command the command to evaluate + * @throws ScriptException + */ + @Override + public void eval(String command) throws ScriptException { + if (history != null) history.add(command); + if (engine == null) throw new java.lang.IllegalArgumentException(); + engine.eval(command); + } + + /** + * Returns the current script engine. + * + * @return + */ + @Override + public ScriptEngine getEngine() { + return engine; + } + +} 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..3b8e3bb26 --- /dev/null +++ b/src/main/java/org/scijava/script/ScriptInterpreter.java @@ -0,0 +1,78 @@ +/* + * #%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. + */ + public void readHistory(); + + /** + * Persists the history of the current script interpreter. + */ + public 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 + */ + public String walkHistory(final String currentCommand, boolean forward); + + /** + * Evaluates a command. + * + * @param command the command to evaluate + * @throws ScriptException + */ + public void eval(String command) throws ScriptException; + + /** + * Returns the associated {@link ScriptEngine} + * + * @return the script engine + */ + public ScriptEngine getEngine(); +} From 7e52d8fbcf4f79c5bb14ebcd8e8ea5058be26c12 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 26 Jul 2014 16:01:25 -0500 Subject: [PATCH 08/16] Let LastRecentlyUsed implement the full Collection contract Signed-off-by: Johannes Schindelin --- .../org/scijava/util/LastRecentlyUsed.java | 94 ++++++++++++++++++- 1 file changed, 93 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/util/LastRecentlyUsed.java b/src/main/java/org/scijava/util/LastRecentlyUsed.java index 678429fcb..c3fb19e9d 100644 --- a/src/main/java/org/scijava/util/LastRecentlyUsed.java +++ b/src/main/java/org/scijava/util/LastRecentlyUsed.java @@ -31,6 +31,8 @@ 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; @@ -42,7 +44,7 @@ * * @author Johannes Schindelin */ -public class LastRecentlyUsed implements Iterable { +public class LastRecentlyUsed implements Iterable, Collection { private final Object[] entries; private final Map map; /** @@ -161,6 +163,96 @@ public void clear() { } } + @Override + public boolean addAll(final Collection 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}. * From 64e577ca95607c1a99cfa03c8b110d317d85d065 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 26 Jul 2014 16:24:52 -0500 Subject: [PATCH 09/16] Test LastRecentlyUsed#remove(Object) Signed-off-by: Johannes Schindelin --- .../scijava/util/LastRecentlyUsedTest.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/test/java/org/scijava/util/LastRecentlyUsedTest.java b/src/test/java/org/scijava/util/LastRecentlyUsedTest.java index 2051b9616..50dc5980a 100644 --- a/src/test/java/org/scijava/util/LastRecentlyUsedTest.java +++ b/src/test/java/org/scijava/util/LastRecentlyUsedTest.java @@ -32,6 +32,9 @@ package org.scijava.util; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.util.Iterator; import org.junit.Test; @@ -66,4 +69,36 @@ public void test() { 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()); + } } From b7eb86ae641521b76825f0273dff4b0fed12a743 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 6 Oct 2014 17:00:14 -0500 Subject: [PATCH 10/16] ScriptInterpreter: remove irrelevant modifiers --- .../java/org/scijava/script/ScriptInterpreter.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/scijava/script/ScriptInterpreter.java b/src/main/java/org/scijava/script/ScriptInterpreter.java index 3b8e3bb26..63af267a8 100644 --- a/src/main/java/org/scijava/script/ScriptInterpreter.java +++ b/src/main/java/org/scijava/script/ScriptInterpreter.java @@ -44,12 +44,12 @@ public interface ScriptInterpreter { /** * Reads the persisted history of the current script interpreter. */ - public void readHistory(); + void readHistory(); /** * Persists the history of the current script interpreter. */ - public void writeHistory(); + void writeHistory(); /** * Obtains the next/previous command in the command history. @@ -59,7 +59,7 @@ public interface ScriptInterpreter { * if false, the previous one * @return the next/previous command */ - public String walkHistory(final String currentCommand, boolean forward); + String walkHistory(String currentCommand, boolean forward); /** * Evaluates a command. @@ -67,12 +67,12 @@ public interface ScriptInterpreter { * @param command the command to evaluate * @throws ScriptException */ - public void eval(String command) throws ScriptException; + void eval(String command) throws ScriptException; /** * Returns the associated {@link ScriptEngine} * * @return the script engine */ - public ScriptEngine getEngine(); + ScriptEngine getEngine(); } From e7b80c6662ad3e1b3162aab28f937136b2dd0e41 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 6 Oct 2014 17:00:43 -0500 Subject: [PATCH 11/16] DefaultScriptInterpreter: remove redundant javadoc --- .../org/scijava/script/DefaultScriptInterpreter.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java index 56dc00553..56cdd8b19 100644 --- a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java +++ b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java @@ -78,12 +78,6 @@ public synchronized String walkHistory(final String currentCommand, boolean forw return forward ? history.next() : history.previous(); } - /** - * Evaluates a command. - * - * @param command the command to evaluate - * @throws ScriptException - */ @Override public void eval(String command) throws ScriptException { if (history != null) history.add(command); @@ -91,11 +85,6 @@ public void eval(String command) throws ScriptException { engine.eval(command); } - /** - * Returns the current script engine. - * - * @return - */ @Override public ScriptEngine getEngine() { return engine; From 52acdad77e7bf7399f523d5feef38f6bbfe735ca Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 6 Oct 2014 17:01:53 -0500 Subject: [PATCH 12/16] DefaultScriptInterpreter: add final keywords --- .../java/org/scijava/script/DefaultScriptInterpreter.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java index 56cdd8b19..ac5c620d0 100644 --- a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java +++ b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java @@ -42,8 +42,8 @@ */ public class DefaultScriptInterpreter implements ScriptInterpreter { - private ScriptEngine engine; - private History history; + private final ScriptEngine engine; + private final History history; private String currentCommand = ""; /** @@ -52,7 +52,9 @@ public class DefaultScriptInterpreter implements ScriptInterpreter { * @param scriptService the script service * @param engine the script engine */ - public DefaultScriptInterpreter(final PrefService prefs, final ScriptService scriptService, final ScriptEngine engine) { + public DefaultScriptInterpreter(final PrefService prefs, + final ScriptService scriptService, final ScriptEngine engine) + { this.engine = engine; history = new History(prefs, engine.getClass().getName()); readHistory(); From f998ed8386c879fbc9771f668630f75003b88123 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 6 Oct 2014 17:02:33 -0500 Subject: [PATCH 13/16] ScriptInterpreter: add missing punctuation --- src/main/java/org/scijava/script/ScriptInterpreter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/scijava/script/ScriptInterpreter.java b/src/main/java/org/scijava/script/ScriptInterpreter.java index 63af267a8..568dc7a55 100644 --- a/src/main/java/org/scijava/script/ScriptInterpreter.java +++ b/src/main/java/org/scijava/script/ScriptInterpreter.java @@ -70,7 +70,7 @@ public interface ScriptInterpreter { void eval(String command) throws ScriptException; /** - * Returns the associated {@link ScriptEngine} + * Returns the associated {@link ScriptEngine}. * * @return the script engine */ From 6b186e35c90e5c93de9dbd17fee46d0d5b366bf1 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 6 Oct 2014 17:02:46 -0500 Subject: [PATCH 14/16] ScriptInterpreter: remember the associated language It simplifies things downstream to be able to query the associated ScriptLanguage later, since it is known at construction time anyway. This does *not* persist the most recently used interpreter language! --- .../scijava/script/DefaultScriptInterpreter.java | 15 ++++++++++----- .../org/scijava/script/ScriptInterpreter.java | 5 +++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java index ac5c620d0..febac424a 100644 --- a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java +++ b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java @@ -42,20 +42,21 @@ */ public class DefaultScriptInterpreter implements ScriptInterpreter { + private final ScriptLanguage language; private final ScriptEngine engine; private final History history; - private String currentCommand = ""; /** * Constructs a new {@link DefaultScriptInterpreter}. * * @param scriptService the script service - * @param engine the script engine + * @param language the script language */ public DefaultScriptInterpreter(final PrefService prefs, - final ScriptService scriptService, final ScriptEngine engine) + final ScriptService scriptService, final ScriptLanguage language) { - this.engine = engine; + this.language = language; + engine = language.getScriptEngine(); history = new History(prefs, engine.getClass().getName()); readHistory(); } @@ -74,7 +75,6 @@ public synchronized void writeHistory() { @Override public synchronized String walkHistory(final String currentCommand, boolean forward) { - this.currentCommand = currentCommand; if (history == null) return currentCommand; history.replace(currentCommand); return forward ? history.next() : history.previous(); @@ -87,6 +87,11 @@ public void eval(String command) throws ScriptException { engine.eval(command); } + @Override + public ScriptLanguage getLanguage() { + return language; + } + @Override public ScriptEngine getEngine() { return engine; diff --git a/src/main/java/org/scijava/script/ScriptInterpreter.java b/src/main/java/org/scijava/script/ScriptInterpreter.java index 568dc7a55..936f211af 100644 --- a/src/main/java/org/scijava/script/ScriptInterpreter.java +++ b/src/main/java/org/scijava/script/ScriptInterpreter.java @@ -69,6 +69,11 @@ public interface ScriptInterpreter { */ void eval(String command) throws ScriptException; + /** + * Returns the associated {@link ScriptLanguage}. + */ + ScriptLanguage getLanguage(); + /** * Returns the associated {@link ScriptEngine}. * From b91b7034fc806e5c97546d89b05f6eae0b048101 Mon Sep 17 00:00:00 2001 From: Curtis Rueden Date: Mon, 6 Oct 2014 17:18:45 -0500 Subject: [PATCH 15/16] DefaultScriptInterpreter: add more final keywords --- .../java/org/scijava/script/DefaultScriptInterpreter.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java index febac424a..6f94198fe 100644 --- a/src/main/java/org/scijava/script/DefaultScriptInterpreter.java +++ b/src/main/java/org/scijava/script/DefaultScriptInterpreter.java @@ -74,14 +74,16 @@ public synchronized void writeHistory() { } @Override - public synchronized String walkHistory(final String currentCommand, boolean forward) { + 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(String command) throws ScriptException { + 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); From f9c3057dd28fc077ec3849d903aa3ac0326b5415 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 14 Oct 2014 16:14:31 -0500 Subject: [PATCH 16/16] Script Interpreter: keep a record of a 'current command' The 'current command' is the command that is not yet in the history. It is very convenient to be able to go back in history, just to have a look, before continuing to craft the current command (and it would be annoying if it was lost when going back in history). Of course, if the user decides to execute a different command from the history instead, the edits to the 'current command' are lost. This behavior is most in line with the Unix shell behavior power users might be used to. Signed-off-by: Johannes Schindelin --- src/main/java/org/scijava/script/History.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/scijava/script/History.java b/src/main/java/org/scijava/script/History.java index 0879fceef..cb0f181cb 100644 --- a/src/main/java/org/scijava/script/History.java +++ b/src/main/java/org/scijava/script/History.java @@ -49,6 +49,7 @@ class History { private final PrefService prefs; private final String name; private final LastRecentlyUsed entries = new LastRecentlyUsed(MAX_ENTRIES); + private String currentCommand = ""; private int position = -1; /** @@ -88,10 +89,14 @@ public void write() { public void add(final String command) { entries.add(command); position = -1; + currentCommand = ""; } public boolean replace(final String currentCommand) { - if (position < 0) return false; + if (position < 0) { + this.currentCommand = currentCommand; + return false; + } return entries.replace(position, currentCommand); } @@ -106,7 +111,7 @@ public boolean replace(final String currentCommand) { */ public String next() { position = entries.next(position); - return position < 0 ? null : entries.get(position); + return position < 0 ? currentCommand : entries.get(position); } /** @@ -120,7 +125,7 @@ public String next() { */ public String previous() { position = entries.previous(position); - return position < 0 ? null : entries.get(position); + return position < 0 ? currentCommand : entries.get(position); } @Override