diff options
| author | Benjamin J. Culkin <bjculkin@mix.wvu.edu> | 2017-10-08 22:39:59 -0300 |
|---|---|---|
| committer | Benjamin J. Culkin <bjculkin@mix.wvu.edu> | 2017-10-08 22:39:59 -0300 |
| commit | c82e3b3b2de0633317ec8fc85925e91422820597 (patch) | |
| tree | 96567416ce23c5ce85601f9cedc3a94bb1c55cba /base/src/main/java/bjc/utils/esodata | |
| parent | b3ac1c8690c3e14c879913e5dcc03a5f5e14876e (diff) | |
Start splitting into maven modules
Diffstat (limited to 'base/src/main/java/bjc/utils/esodata')
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/AbbrevMap.java | 227 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/Directory.java | 106 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/DoubleTape.java | 258 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/PushdownMap.java | 148 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/QueueStack.java | 88 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/SimpleDirectory.java | 95 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/SimpleStack.java | 88 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/SingleTape.java | 255 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/SpaghettiStack.java | 99 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/Stack.java | 459 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/Tape.java | 126 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/TapeChanger.java | 363 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/TapeLibrary.java | 340 | ||||
| -rw-r--r-- | base/src/main/java/bjc/utils/esodata/UnifiedDirectory.java | 105 |
14 files changed, 2757 insertions, 0 deletions
diff --git a/base/src/main/java/bjc/utils/esodata/AbbrevMap.java b/base/src/main/java/bjc/utils/esodata/AbbrevMap.java new file mode 100644 index 0000000..0d54471 --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/AbbrevMap.java @@ -0,0 +1,227 @@ +package bjc.utils.esodata; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.SetMultimap; + +import bjc.utils.funcdata.FunctionalMap; +import bjc.utils.funcdata.IMap; + +/** + * Represents a mapping from a set of strings to a mapping of all unambiguous + * prefixes of their respective strings. + * + * This works the same as Ruby's Abbrev. + * + * @author EVE + * + */ +public class AbbrevMap { + /* + * All of the words we have abbreviations for. + */ + private final Set<String> wrds; + + /* + * Maps abbreviations to their strings. + */ + private IMap<String, String> abbrevMap; + + /* + * Counts how many times we've seen a substring. + */ + private Set<String> seen; + + /* + * Maps ambiguous abbreviations to the strings they could be. + */ + private SetMultimap<String, String> ambMap; + + /** + * Create a new abbreviation map. + * + * @param words + * The initial set of words to put in the map. + */ + public AbbrevMap(final String... words) { + wrds = new HashSet<>(Arrays.asList(words)); + + recalculate(); + } + + /** + * Recalculate all the abbreviations in this map. + */ + public void recalculate() { + abbrevMap = new FunctionalMap<>(); + + ambMap = HashMultimap.create(); + + seen = new HashSet<>(); + + for (final String word : wrds) { + /* + * A word always abbreviates to itself. + */ + abbrevMap.put(word, word); + + intAddWord(word); + } + } + + /** + * Adds words to the abbreviation map. + * + * @param words + * The words to add to the abbreviation map. + */ + public void addWords(final String... words) { + wrds.addAll(Arrays.asList(words)); + + for (final String word : words) { + /* + * A word always abbreviates to itself. + */ + abbrevMap.put(word, word); + + intAddWord(word); + } + } + + /* + * Actually add abbreviations of a word. + */ + private void intAddWord(final String word) { + /* + * Skip blank words. + */ + if (word.equals("")) return; + + /* + * Handle each possible abbreviation. + */ + for (int i = word.length(); i > 0; i--) { + final String subword = word.substring(0, i); + + if (seen.contains(subword)) { + /* + * Remove a mapping if its ambiguous and not a + * whole word. + */ + if (abbrevMap.containsKey(subword) && !wrds.contains(subword)) { + final String oldword = abbrevMap.remove(subword); + + ambMap.put(subword, oldword); + ambMap.put(subword, word); + } else if (!wrds.contains(subword)) { + ambMap.put(subword, word); + } + } else { + seen.add(subword); + + abbrevMap.put(subword, word); + } + } + } + + /** + * Removes words from the abbreviation map. + * + * NOTE: There may be inconsistent behavior after removing a word from + * the map. Use {@link AbbrevMap#recalculate()} to fix it if it occurs. + * + * @param words + * The words to remove. + */ + public void removeWords(final String... words) { + wrds.removeAll(Arrays.asList(words)); + + for (final String word : words) { + intRemoveWord(word); + } + } + + /* + * Actually remove a word. + */ + private void intRemoveWord(final String word) { + /* + * Skip blank words. + */ + if (word.equals("")) return; + + /* + * Handle each possible abbreviation. + */ + for (int i = word.length(); i > 0; i--) { + final String subword = word.substring(0, i); + + if (abbrevMap.containsKey(subword)) { + abbrevMap.remove(subword); + } else { + ambMap.remove(subword, word); + + final Set<String> possWords = ambMap.get(subword); + + if (possWords.size() == 0) { + seen.remove(subword); + } else if (possWords.size() == 1) { + final String newWord = possWords.iterator().next(); + + abbrevMap.put(subword, newWord); + ambMap.remove(subword, newWord); + } + } + } + } + + /** + * Convert an abbreviation into all the strings it could abbreviate + * into. + * + * @param abbrev + * The abbreviation to convert. + * + * @return All the expansions for the provided abbreviation. + */ + public String[] deabbrev(final String abbrev) { + if (abbrevMap.containsKey(abbrev)) + return new String[] { abbrevMap.get(abbrev) }; + else return ambMap.get(abbrev).toArray(new String[0]); + } + + @Override + public int hashCode() { + final int prime = 31; + + int result = 1; + result = prime * result + (wrds == null ? 0 : wrds.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof AbbrevMap)) return false; + + final AbbrevMap other = (AbbrevMap) obj; + + if (wrds == null) { + if (other.wrds != null) return false; + } else if (!wrds.equals(other.wrds)) return false; + + return true; + } + + @Override + public String toString() { + final String fmt = "AbbrevMap [wrds=%s, abbrevMap=%s, seen=%s, ambMap=%s]"; + + return String.format(fmt, wrds, abbrevMap, seen, ambMap); + } +} diff --git a/base/src/main/java/bjc/utils/esodata/Directory.java b/base/src/main/java/bjc/utils/esodata/Directory.java new file mode 100644 index 0000000..17b70f5 --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/Directory.java @@ -0,0 +1,106 @@ +package bjc.utils.esodata;
+
+/**
+ * Represents a hierarchical map.
+ *
+ * What's useful about this is that you can hand sub-directories to people and
+ * be able to ensure that they can't write outside of it.
+ *
+ * @param <K>
+ * The key type of the map.
+ * @param <V>
+ * The value type of the map.
+ */
+public interface Directory<K, V> {
+ /**
+ * Retrieves a given sub-directory.
+ *
+ * @param key
+ * The key to retrieve the sub-directory for.
+ *
+ * @return The sub-directory under that name.
+ *
+ * @throws IllegalArgumentException
+ * If the given sub-directory doesn't exist.
+ */
+ Directory<K, V> getSubdirectory(K key);
+
+ /**
+ * Check if a given sub-directory exists.
+ *
+ * @param key
+ * The key to look for the sub-directory under.
+ *
+ * @return Whether or not a sub-directory of that name exists.
+ */
+ boolean hasSubdirectory(K key);
+
+ /**
+ * Insert a sub-directory into the dictionary.
+ *
+ * @param key
+ * The name of the new sub-directory
+ * @param value
+ * The sub-directory to insert
+ *
+ * @return The old sub-directory attached to this key, or null if such a
+ * sub-directory didn't exist
+ */
+ Directory<K, V> putSubdirectory(K key, Directory<K, V> value);
+
+ /**
+ * Create a new sub-directory.
+ *
+ * Will fail if a sub-directory of that name already exists.
+ *
+ * @param key
+ * The name of the new sub-directory.
+ *
+ * @return The new sub-directory, or null if one by that name already
+ * exists.
+ */
+ default Directory<K, V> newSubdirectory(final K key) {
+ if (hasSubdirectory(key)) return null;
+
+ final Directory<K, V> dir = new SimpleDirectory<>();
+
+ putSubdirectory(key, dir);
+
+ return dir;
+ }
+
+ /**
+ * Check if the directory contains a data-item under the given key.
+ *
+ * @param key
+ * The key to check for.
+ *
+ * @return Whether or not there is a data item for the given key.
+ */
+ boolean containsKey(K key);
+
+ /**
+ * Retrieve a given data-item from the directory.
+ *
+ * @param key
+ * The key to retrieve data for.
+ *
+ * @return The value for the given key.
+ *
+ * @throws IllegalArgumentException
+ * If no value exists for the given key.
+ */
+ V getKey(K key);
+
+ /**
+ * Insert a data-item into the directory.
+ *
+ * @param key
+ * The key to insert into.
+ * @param val
+ * The value to insert.
+ *
+ * @return The old value of key, or null if such a value didn't exist.
+ */
+ V putKey(K key, V val);
+}
\ No newline at end of file diff --git a/base/src/main/java/bjc/utils/esodata/DoubleTape.java b/base/src/main/java/bjc/utils/esodata/DoubleTape.java new file mode 100644 index 0000000..5c463c6 --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/DoubleTape.java @@ -0,0 +1,258 @@ +package bjc.utils.esodata; + +/** + * Double-sided tape is essentially two tapes stuck together with a shared + * cursor. + * + * The main way a double-sided tape differs is that it can be flipped, allowing + * access to another set of data. + * + * However, there is only one cursor, and the position of the cursor on one side + * is the inverse of the position on the other side. + * + * When one side is extended, a null will be inserted into the inactive side + * regardless of the auto-extension policy of the tape. The policy will still be + * respected for the active side. + * + * All operations that refer to the tape refer to the currently active side of + * the tape, except for flip. + * + * Flip refers to the entire tape for 'obvious' reasons. + * + * @param <T> + * The element type of the tape. + * @author bjculkin + */ +public class DoubleTape<T> implements Tape<T> { + private Tape<T> front; + private Tape<T> back; + + /** + * Create a new empty double-sided tape that doesn't autoextend. + */ + public DoubleTape() { + this(false); + } + + /** + * Create a new empty double-sided tape that follows the specified + * auto-extension policy. + * + * @param autoExtnd + * Whether or not to auto-extend the tape to the right w/ + * nulls. + */ + public DoubleTape(final boolean autoExtnd) { + front = new SingleTape<>(autoExtnd); + back = new SingleTape<>(autoExtnd); + } + + /** + * Get the item the tape is currently on. + * + * @return The item the tape is on. + */ + @Override + public T item() { + return front.item(); + } + + /** + * Set the item the tape is currently on. + * + * @param itm + * The new value for the tape item. + */ + @Override + public void item(final T itm) { + front.item(itm); + } + + /** + * Get the current number of elements in the tape. + * + * @return The current number of elements in the tape. + */ + @Override + public int size() { + return front.size(); + } + + @Override + public int position() { + return front.position(); + } + + /** + * Insert an element before the current item. + * + * @param itm + * The item to add. + */ + @Override + public void insertBefore(final T itm) { + front.insertBefore(itm); + back.insertAfter(null); + } + + /** + * Insert an element after the current item. + */ + @Override + public void insertAfter(final T itm) { + front.insertAfter(itm); + back.insertBefore(itm); + } + + /** + * Remove the current element. + * + * Also moves the cursor back one step if possible to maintain relative + * position, and removes the corresponding item from the non-active side + * + * @return The removed item from the active side. + */ + @Override + public T remove() { + back.remove(); + + return front.remove(); + } + + /** + * Move the cursor to the left-most position. + */ + @Override + public void first() { + front.first(); + back.last(); + } + + /** + * Move the cursor the right-most position. + */ + @Override + public void last() { + front.last(); + back.first(); + } + + /** + * Move the cursor one space left. + * + * The cursor can't go past zero. + * + * @return True if the cursor was moved left. + */ + @Override + public boolean left() { + return left(1); + } + + /** + * Move the cursor the specified amount left. + * + * The cursor can't go past zero. Attempts to move the cursor by amounts + * that would exceed zero don't move the cursor at all. + * + * @param amt + * The amount to attempt to move the cursor left. + * + * @return True if the cursor was moved left. + */ + @Override + public boolean left(final int amt) { + final boolean succ = front.left(amt); + + if (succ) { + back.right(amt); + } + + return succ; + } + + /** + * Move the cursor one space right. + * + * Moving the cursor right will auto-extend the tape if that is enabled. + * + * @return Whether the cursor was moved right. + */ + @Override + public boolean right() { + return right(1); + } + + /** + * Move the cursor the specified amount right. + * + * Moving the cursor right will auto-extend the tape if that is enabled. + * + * @param amt + * The amount to move the cursor right by. + * + * @return Whether the cursor was moved right. + */ + @Override + public boolean right(final int amt) { + final boolean succ = front.right(amt); + + if (succ) { + back.left(amt); + } + + return succ; + } + + /** + * Flips the tape. + * + * The active side becomes inactive, and the inactive side becomes + * active. + */ + public void flip() { + final Tape<T> tmp = front; + + front = back; + + back = tmp; + } + + @Override + public boolean isDoubleSided() { + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (back == null ? 0 : back.hashCode()); + result = prime * result + (front == null ? 0 : front.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof DoubleTape<?>)) return false; + + final DoubleTape<?> other = (DoubleTape<?>) obj; + + if (back == null) { + if (other.back != null) return false; + } else if (!back.equals(other.back)) return false; + + if (front == null) { + if (other.front != null) return false; + } else if (!front.equals(other.front)) return false; + + return true; + } + + @Override + public String toString() { + return String.format("DoubleTape [front=%s, back=%s]", front, back); + } +} diff --git a/base/src/main/java/bjc/utils/esodata/PushdownMap.java b/base/src/main/java/bjc/utils/esodata/PushdownMap.java new file mode 100644 index 0000000..a631704 --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/PushdownMap.java @@ -0,0 +1,148 @@ +package bjc.utils.esodata; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; + +import bjc.utils.funcdata.FunctionalMap; +import bjc.utils.funcdata.IList; +import bjc.utils.funcdata.IMap; + +/** + * A variant of a map where inserting a duplicate key shadows the existing value + * instead of replacing it. + * + * @author EVE + * + * @param <KeyType> + * The key of the map. + * @param <ValueType> + * The values in the map. + */ +public class PushdownMap<KeyType, ValueType> implements IMap<KeyType, ValueType> { + private final IMap<KeyType, Stack<ValueType>> backing; + + /** + * Create a new empty stack-based map. + */ + public PushdownMap() { + backing = new FunctionalMap<>(); + } + + private PushdownMap(final IMap<KeyType, Stack<ValueType>> back) { + backing = back; + } + + @Override + public void clear() { + backing.clear(); + } + + @Override + public boolean containsKey(final KeyType key) { + return backing.containsKey(key); + } + + @Override + public IMap<KeyType, ValueType> extend() { + return new PushdownMap<>(backing.extend()); + } + + @Override + public void forEach(final BiConsumer<KeyType, ValueType> action) { + backing.forEach((key, stk) -> action.accept(key, stk.top())); + } + + @Override + public void forEachKey(final Consumer<KeyType> action) { + backing.forEachKey(action); + } + + @Override + public void forEachValue(final Consumer<ValueType> action) { + backing.forEachValue(stk -> action.accept(stk.top())); + } + + @Override + public ValueType get(final KeyType key) { + return backing.get(key).top(); + } + + @Override + public int size() { + return backing.size(); + } + + @Override + public IList<KeyType> keyList() { + return backing.keyList(); + } + + @Override + public <V2> IMap<KeyType, V2> transform(final Function<ValueType, V2> transformer) { + throw new UnsupportedOperationException("Cannot transform pushdown maps."); + } + + @Override + public ValueType put(final KeyType key, final ValueType val) { + if (backing.containsKey(key)) { + final Stack<ValueType> stk = backing.get(key); + + final ValueType vl = stk.top(); + + stk.push(val); + + return vl; + } else { + final Stack<ValueType> stk = new SimpleStack<>(); + + stk.push(val); + + return null; + } + } + + @Override + public ValueType remove(final KeyType key) { + final Stack<ValueType> stk = backing.get(key); + + if (stk.size() > 1) + return stk.pop(); + else return backing.remove(key).top(); + } + + @Override + public IList<ValueType> valueList() { + return backing.valueList().map(stk -> stk.top()); + } + + @Override + public int hashCode() { + final int prime = 31; + + int result = 1; + result = prime * result + (backing == null ? 0 : backing.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof PushdownMap<?, ?>)) return false; + + final PushdownMap<?, ?> other = (PushdownMap<?, ?>) obj; + + if (backing == null) { + if (other.backing != null) return false; + } else if (!backing.equals(other.backing)) return false; + + return true; + } + + @Override + public String toString() { + return String.format("PushdownMap [backing=%s]", backing); + } +} diff --git a/base/src/main/java/bjc/utils/esodata/QueueStack.java b/base/src/main/java/bjc/utils/esodata/QueueStack.java new file mode 100644 index 0000000..850598a --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/QueueStack.java @@ -0,0 +1,88 @@ +package bjc.utils.esodata; + +import java.util.Deque; +import java.util.LinkedList; + +/** + * A FIFO implementation of a stack. + * + * @param <T> + * The datatype stored in the stack. + * @author Ben Culkin + */ +public class QueueStack<T> extends Stack<T> { + private final Deque<T> backing; + + /** + * Create a new empty stack queue. + * + */ + public QueueStack() { + backing = new LinkedList<>(); + } + + @Override + public void push(final T elm) { + backing.add(elm); + } + + @Override + public T pop() { + if (backing.isEmpty()) throw new StackUnderflowException(); + + return backing.remove(); + } + + @Override + public T top() { + if (backing.isEmpty()) throw new StackUnderflowException(); + + return backing.peek(); + } + + @Override + public int size() { + return backing.size(); + } + + @Override + public boolean empty() { + return backing.size() == 0; + } + + @SuppressWarnings("unchecked") + @Override + public T[] toArray() { + return (T[]) backing.toArray(); + } + + @Override + public String toString() { + return String.format("QueueStack [backing=%s]", backing); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + + result = prime * result + (backing == null ? 0 : backing.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof QueueStack<?>)) return false; + + final QueueStack<?> other = (QueueStack<?>) obj; + + if (backing == null) { + if (other.backing != null) return false; + } else if (!backing.equals(other.backing)) return false; + + return true; + } +} diff --git a/base/src/main/java/bjc/utils/esodata/SimpleDirectory.java b/base/src/main/java/bjc/utils/esodata/SimpleDirectory.java new file mode 100644 index 0000000..69fd019 --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/SimpleDirectory.java @@ -0,0 +1,95 @@ +package bjc.utils.esodata; + +import bjc.utils.funcdata.FunctionalMap; +import bjc.utils.funcdata.IMap; + +/** + * Simple implementation of {@link Directory}. + * + * Has a split namespace for data and children. + * + * @author EVE + * + * @param <K> + * The key type of the directory. + * @param <V> + * The value type of the directory. + */ +public class SimpleDirectory<K, V> implements Directory<K, V> { + private final IMap<K, Directory<K, V>> children; + + private final IMap<K, V> data; + + /** + * Create a new directory. + */ + public SimpleDirectory() { + children = new FunctionalMap<>(); + data = new FunctionalMap<>(); + } + + @Override + public Directory<K, V> getSubdirectory(final K key) { + return children.get(key); + } + + @Override + public boolean hasSubdirectory(final K key) { + return children.containsKey(key); + } + + @Override + public Directory<K, V> putSubdirectory(final K key, final Directory<K, V> val) { + return children.put(key, val); + } + + @Override + public boolean containsKey(final K key) { + return data.containsKey(key); + } + + @Override + public V getKey(final K key) { + return data.get(key); + } + + @Override + public V putKey(final K key, final V val) { + return data.put(key, val); + } + + @Override + public int hashCode() { + final int prime = 31; + + int result = 1; + result = prime * result + (children == null ? 0 : children.hashCode()); + result = prime * result + (data == null ? 0 : data.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof SimpleDirectory<?, ?>)) return false; + + final SimpleDirectory<?, ?> other = (SimpleDirectory<?, ?>) obj; + + if (children == null) { + if (other.children != null) return false; + } else if (!children.equals(other.children)) return false; + + if (data == null) { + if (other.data != null) return false; + } else if (!data.equals(other.data)) return false; + + return true; + } + + @Override + public String toString() { + return String.format("SimpleDirectory [children=%s, data=%s]", children, data); + } +}
\ No newline at end of file diff --git a/base/src/main/java/bjc/utils/esodata/SimpleStack.java b/base/src/main/java/bjc/utils/esodata/SimpleStack.java new file mode 100644 index 0000000..fdb3300 --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/SimpleStack.java @@ -0,0 +1,88 @@ +package bjc.utils.esodata; + +import java.util.Deque; +import java.util.LinkedList; + +/** + * Simple implementation of a stack. + * + * @param <T> + * The datatype stored in the stack. + * @author Ben Culkin + */ +public class SimpleStack<T> extends Stack<T> { + private final Deque<T> backing; + + /** + * Create a new empty stack. + * + */ + public SimpleStack() { + backing = new LinkedList<>(); + } + + @Override + public void push(final T elm) { + backing.push(elm); + } + + @Override + public T pop() { + if (backing.isEmpty()) throw new StackUnderflowException(); + + return backing.pop(); + } + + @Override + public T top() { + if (backing.isEmpty()) throw new StackUnderflowException(); + + return backing.peek(); + } + + @Override + public int size() { + return backing.size(); + } + + @Override + public boolean empty() { + return backing.size() == 0; + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray() { + return (T[]) backing.toArray(); + } + + @Override + public int hashCode() { + final int prime = 31; + + int result = 1; + result = prime * result + (backing == null ? 0 : backing.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof SimpleStack<?>)) return false; + + final SimpleStack<?> other = (SimpleStack<?>) obj; + + if (backing == null) { + if (other.backing != null) return false; + } else if (!backing.equals(other.backing)) return false; + + return true; + } + + @Override + public String toString() { + return String.format("SimpleStack [backing=%s]", backing); + } +} diff --git a/base/src/main/java/bjc/utils/esodata/SingleTape.java b/base/src/main/java/bjc/utils/esodata/SingleTape.java new file mode 100644 index 0000000..c50be92 --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/SingleTape.java @@ -0,0 +1,255 @@ +package bjc.utils.esodata; + +import java.util.ArrayList; + +/** + * A tape is a one-dimensional array that can only be accessed in one position + * at a time. + * + * A tape is essentially a 1D array with a cursor attached to it, and you can + * only affect elements at that cursor. The size of the array is theoretically + * unbounded to the right, but in practice bounded by available memory. + * + * You can choose whether or not you want the tape to automatically extend + * itself to the right with null elements by specifying its auto-extension + * policy. + * + * @param <T> + * The element type of the tape. + * + * @author bjculkin + */ +public class SingleTape<T> implements Tape<T> { + protected ArrayList<T> backing; + protected int pos; + + protected boolean autoExtend; + + /** + * Create a new tape with the specified contents that doesn't + * autoextend. + */ + public SingleTape(T... vals) { + autoExtend = false; + + backing = new ArrayList(vals.length); + + for(T val : vals) { + backing.add(val); + } + } + /** + * Create a new empty tape that doesn't autoextend. + */ + public SingleTape() { + this(false); + } + + /** + * Create a new empty tape that follows the specified auto-extension + * policy. + * + * @param autoExtnd + * Whether or not to auto-extend the tape to the right w/ + * nulls. + */ + public SingleTape(final boolean autoExtnd) { + autoExtend = autoExtnd; + + backing = new ArrayList<>(); + } + + /** + * Get the item the tape is currently on. + * + * @return The item the tape is on. + */ + @Override + public T item() { + return backing.get(pos); + } + + /** + * Set the item the tape is currently on. + * + * @param itm + * The new value for the tape item. + */ + @Override + public void item(final T itm) { + backing.set(pos, itm); + } + + /** + * Get the current number of elements in the tape. + * + * @return The current number of elements in the tape. + */ + @Override + public int size() { + return backing.size(); + } + + @Override + public int position() { + return pos; + } + + /** + * Insert an element before the current item. + * + * @param itm + * The item to add. + */ + @Override + public void insertBefore(final T itm) { + backing.add(pos, itm); + } + + /** + * Insert an element after the current item. + */ + @Override + public void insertAfter(final T itm) { + if (pos == backing.size() - 1) { + backing.add(itm); + } else { + backing.add(pos + 1, itm); + } + } + + /** + * Remove the current element. + * + * Also moves the cursor back one step if possible to maintain relative + * position. + * + * @return The removed item. + */ + @Override + public T remove() { + final T res = backing.remove(pos); + if (pos != 0) { + pos -= 1; + } + return res; + } + + /** + * Move the cursor to the left-most position. + */ + @Override + public void first() { + pos = 0; + } + + /** + * Move the cursor the right-most position. + */ + @Override + public void last() { + pos = backing.size() - 1; + } + + /** + * Move the cursor one space left. + * + * The cursor can't go past zero. + * + * @return True if the cursor was moved left. + */ + @Override + public boolean left() { + return left(1); + } + + /** + * Move the cursor the specified amount left. + * + * The cursor can't go past zero. Attempts to move the cursor by amounts + * that would exceed zero don't move the cursor at all. + * + * @param amt + * The amount to attempt to move the cursor left. + * + * @return True if the cursor was moved left. + */ + @Override + public boolean left(final int amt) { + if (pos - amt < 0) return false; + + pos -= amt; + return true; + } + + /** + * Move the cursor one space right. + * + * Moving the cursor right will auto-extend the tape if that is enabled. + * + * @return Whether the cursor was moved right. + */ + @Override + public boolean right() { + return right(1); + } + + /** + * Move the cursor the specified amount right. + * + * Moving the cursor right will auto-extend the tape if that is enabled. + * + * @param amt + * The amount to move the cursor right by. + * + * @return Whether the cursor was moved right. + */ + @Override + public boolean right(final int amt) { + if (pos + amt >= backing.size() - 1) { + if (autoExtend) { + while (pos + amt >= backing.size() - 1) { + backing.add(null); + } + } else return false; + } + + pos += amt; + return true; + } + + @Override + public boolean isDoubleSided() { + return false; + } + + @Override + public int hashCode() { + final int prime = 31; + + int result = 1; + result = prime * result + (backing == null ? 0 : backing.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof SingleTape<?>)) return false; + + final SingleTape<?> other = (SingleTape<?>) obj; + + if (backing == null) { + if (other.backing != null) return false; + } else if (!backing.equals(other.backing)) return false; + + return true; + } + + @Override + public String toString() { + return String.format("SingleTape [backing=%s, pos=%s, autoExtend=%s]", backing, pos, autoExtend); + } +} diff --git a/base/src/main/java/bjc/utils/esodata/SpaghettiStack.java b/base/src/main/java/bjc/utils/esodata/SpaghettiStack.java new file mode 100644 index 0000000..7c8c757 --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/SpaghettiStack.java @@ -0,0 +1,99 @@ +package bjc.utils.esodata; + +import java.util.Arrays; +import java.util.stream.Stream; + +/* + * Implements a spaghetti stack, which is a stack that is branched off of a + * parent stack. + * + * @param T The datatype stored in the stack. + * @author Ben Culkin + */ +class SpaghettiStack<T> extends Stack<T> { + private final Stack<T> backing; + + private final Stack<T> parent; + + /** + * Create a new empty spaghetti stack, off of the specified parent. + * + * @param par + * The parent stack + */ + public SpaghettiStack(final Stack<T> par) { + backing = new SimpleStack<>(); + + parent = par; + } + + @Override + public void push(final T elm) { + backing.push(elm); + } + + @Override + public T pop() { + if (backing.empty()) return parent.pop(); + + return backing.pop(); + } + + @Override + public T top() { + if (backing.empty()) return parent.top(); + + return backing.top(); + } + + @Override + public int size() { + return parent.size() + backing.size(); + } + + @Override + public boolean empty() { + return backing.empty() && parent.empty(); + } + + @SuppressWarnings("unchecked") + @Override + public T[] toArray() { + return (T[]) Stream.concat(Arrays.stream(parent.toArray()), Arrays.stream(backing.toArray())).toArray(); + } + + @Override + public int hashCode() { + final int prime = 31; + + int result = 1; + result = prime * result + (backing == null ? 0 : backing.hashCode()); + result = prime * result + (parent == null ? 0 : parent.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof SpaghettiStack<?>)) return false; + + final SpaghettiStack<?> other = (SpaghettiStack<?>) obj; + + if (backing == null) { + if (other.backing != null) return false; + } else if (!backing.equals(other.backing)) return false; + + if (parent == null) { + if (other.parent != null) return false; + } else if (!parent.equals(other.parent)) return false; + + return true; + } + + @Override + public String toString() { + return String.format("SpaghettiStack [backing=%s, parent=%s]", backing, parent); + } +} diff --git a/base/src/main/java/bjc/utils/esodata/Stack.java b/base/src/main/java/bjc/utils/esodata/Stack.java new file mode 100644 index 0000000..9d74e9a --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/Stack.java @@ -0,0 +1,459 @@ +package bjc.utils.esodata; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * A stack, with support for combinators. + * + * A FILO stack with support for forth/factor style combinators. + * + * <p> + * <h2>Stack underflow</h2> + * <p> + * NOTE: In general, using any operation that attempts to remove more data from + * the stack than exists will cause a {@link StackUnderflowException} to be + * thrown. Check the size of the stack if you want to avoid this. + * <p> + * </p> + * + * @param <T> + * The datatype stored in the stack. + * + * @author Ben Culkin + */ +public abstract class Stack<T> { + /** + * The exception thrown when attempting to access an element from the + * stack that isn't there. + * + * @author EVE + * + */ + public static class StackUnderflowException extends RuntimeException { + + /** + * + */ + private static final long serialVersionUID = 1423867176204571539L; + } + + /** + * Push an element onto the stack. + * + * @param elm + * The element to insert. + */ + public abstract void push(T elm); + + /** + * Pop an element off of the stack. + * + * @return The element on top of the stack. + */ + public abstract T pop(); + + /** + * Retrieve the top element of this stack without removing it from the + * stack. + * + * @return The top element of this stack. + */ + public abstract T top(); + + /** + * Get the number of elements in the stack. + * + * @return the number of elements in the stack. + */ + public abstract int size(); + + /** + * Check if the stack is empty. + * + * @return Whether or not the stack is empty. + */ + public abstract boolean empty(); + + /** + * Create a spaghetti stack branching off of this one. + * + * @return A spaghetti stack with this stack as a parent. + */ + public Stack<T> spaghettify() { + return new SpaghettiStack<>(this); + } + + /* + * Basic combinators + */ + + /** + * Drop n items from the stack. + * + * @param n + * The number of items to drop. + */ + public void drop(final int n) { + for (int i = 0; i < n; i++) { + pop(); + } + } + + /** + * Drop one item from the stack. + */ + public void drop() { + drop(1); + } + + /** + * Delete n items below the current one. + * + * @param n + * The number of items below the top to delete. + */ + public void nip(final int n) { + final T elm = pop(); + + drop(n); + + push(elm); + } + + /** + * Delete the second element in the stack. + */ + public void nip() { + nip(1); + } + + /** + * Replicate the top n items of the stack m times. + * + * @param n + * The number of items to duplicate. + * @param m + * The number of times to duplicate items. + */ + public void multidup(final int n, final int m) { + final List<T> lst = new ArrayList<>(n); + + for (int i = n; i > 0; i--) { + lst.set(i - 1, pop()); + } + + for (int i = 0; i < m; i++) { + for (final T elm : lst) { + push(elm); + } + } + } + + /** + * Duplicate the top n items of the stack. + * + * @param n + * The number of items to duplicate. + */ + public void dup(final int n) { + multidup(n, 2); + } + + /** + * Duplicate the top item on the stack. + */ + public void dup() { + dup(1); + } + + /** + * Replicate the n elements below the top one m times. + * + * @param n + * The number of items to duplicate. + * @param m + * The number of times to duplicate items. + */ + public void multiover(final int n, final int m) { + final List<T> lst = new ArrayList<>(n); + + final T elm = pop(); + + for (int i = n; i > 0; i--) { + lst.set(i - 1, pop()); + } + + for (final T nelm : lst) { + push(nelm); + } + push(elm); + + for (int i = 1; i < m; i++) { + for (final T nelm : lst) { + push(nelm); + } + } + } + + /** + * Duplicate the n elements below the top one. + * + * @param n + * The number of items to duplicate. + */ + public void over(final int n) { + multiover(n, 2); + } + + /** + * Duplicate the second item in the stack. + */ + public void over() { + over(1); + } + + /** + * Duplicate the third item in the stack. + */ + public void pick() { + final T z = pop(); + final T y = pop(); + final T x = pop(); + + push(x); + push(y); + push(z); + push(x); + } + + /** + * Swap the top two items on the stack. + */ + public void swap() { + final T y = pop(); + final T x = pop(); + + push(y); + push(x); + } + + /** + * Duplicate the second item below the first item. + */ + public void deepdup() { + final T y = pop(); + final T x = pop(); + + push(x); + push(x); + push(y); + } + + /** + * Swap the second and third items in the stack. + */ + public void deepswap() { + final T z = pop(); + final T y = pop(); + final T x = pop(); + + push(y); + push(x); + push(z); + } + + /** + * Rotate the top three items on the stack + */ + public void rot() { + final T z = pop(); + final T y = pop(); + final T x = pop(); + + push(y); + push(z); + push(x); + } + + /** + * Inversely rotate the top three items on the stack + */ + public void invrot() { + final T z = pop(); + final T y = pop(); + final T x = pop(); + + push(z); + push(x); + push(y); + } + + /* + * Dataflow Combinators + */ + /** + * Hides the top n elements on the stack from cons. + * + * @param n + * The number of elements to hide. + * @param cons + * The action to hide the elements from + */ + public void dip(final int n, final Consumer<Stack<T>> cons) { + final List<T> elms = new ArrayList<>(n); + + for (int i = n; i > 0; i--) { + elms.set(i - 1, pop()); + } + + cons.accept(this); + + for (final T elm : elms) { + push(elm); + } + } + + /** + * Hide the top element of the stack from cons. + * + * @param cons + * The action to hide the top from + */ + public void dip(final Consumer<Stack<T>> cons) { + dip(1, cons); + } + + /** + * Copy the top n elements on the stack, replacing them once cons is + * done. + * + * @param n + * The number of elements to copy. + * @param cons + * The action to execute. + */ + public void keep(final int n, final Consumer<Stack<T>> cons) { + dup(n); + dip(n, cons); + } + + /** + * Apply all the actions in conses to the top n elements of the stack. + * + * @param n + * The number of elements to give to cons. + * @param conses + * The actions to execute. + */ + public void multicleave(final int n, final List<Consumer<Stack<T>>> conses) { + final List<T> elms = new ArrayList<>(n); + + for (int i = n; i > 0; i--) { + elms.set(i - 1, pop()); + } + + for (final Consumer<Stack<T>> cons : conses) { + for (final T elm : elms) { + push(elm); + } + + cons.accept(this); + } + } + + /** + * Apply all the actions in conses to the top element of the stack. + * + * @param conses + * The actions to execute. + */ + public void cleave(final List<Consumer<Stack<T>>> conses) { + multicleave(1, conses); + } + + /** + * Apply every action in cons to n arguments. + * + * @param n + * The number of parameters each action takes. + * @param conses + * The actions to execute. + */ + public void multispread(final int n, final List<Consumer<Stack<T>>> conses) { + final List<List<T>> nelms = new ArrayList<>(conses.size()); + + for (int i = conses.size(); i > 0; i--) { + final List<T> elms = new ArrayList<>(n); + + for (int j = n; j > 0; j--) { + elms.set(j, pop()); + } + + nelms.set(i, elms); + } + + int i = 0; + for (final List<T> elms : nelms) { + for (final T elm : elms) { + push(elm); + } + + conses.get(i).accept(this); + i += 1; + } + } + + /** + * Apply the actions in cons to corresponding elements from the stack. + * + * @param conses + * The actions to execute. + */ + public void spread(final List<Consumer<Stack<T>>> conses) { + multispread(1, conses); + } + + /** + * Apply the action in cons to the first m groups of n arguments. + * + * @param n + * The number of arguments cons takes. + * @param m + * The number of time to call cons. + * @param cons + * The action to execute. + */ + public void multiapply(final int n, final int m, final Consumer<Stack<T>> cons) { + final List<Consumer<Stack<T>>> conses = new ArrayList<>(m); + + for (int i = 0; i < m; i++) { + conses.add(cons); + } + + multispread(n, conses); + } + + /** + * Apply cons n times to the corresponding elements in the stack. + * + * @param n + * The number of times to execute cons. + * @param cons + * The action to execute. + */ + public void apply(final int n, final Consumer<Stack<T>> cons) { + multiapply(1, n, cons); + } + + /* + * Misc. functions + */ + /** + * Get an array representing this stack. + * + * @return The stack as an array. + */ + public abstract T[] toArray(); +} diff --git a/base/src/main/java/bjc/utils/esodata/Tape.java b/base/src/main/java/bjc/utils/esodata/Tape.java new file mode 100644 index 0000000..b6a2c01 --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/Tape.java @@ -0,0 +1,126 @@ +package bjc.utils.esodata; + +/** + * Interface for something that acts like a tape. + * + * A tape is essentially a 1D array with a cursor attached to it, and you can + * only affect elements at that cursor. The size of the array is theoretically + * unbounded to the right, but in practice bounded by available memory. + * + * @param <T> + * The element type of the tape. + * + * @author bjculkin + */ +public interface Tape<T> { + /** + * Get the item the tape is currently on. + * + * @return The item the tape is on. + */ + T item(); + + /** + * Set the item the tape is currently on. + * + * @param itm + * The new value for the tape item. + */ + void item(T itm); + + /** + * Get the current number of elements in the tape. + * + * @return The current number of elements in the tape. + */ + int size(); + + /** + * Get the position of the current item. + * + * @return The position of the current item. + */ + int position(); + + /** + * Insert an element before the current item. + * + * @param itm + * The item to add. + */ + void insertBefore(T itm); + + /** + * Insert an element after the current item. + * + * @param itm + * The item to insert. + */ + void insertAfter(T itm); + + /** + * Remove the current element. + * + * Also moves the cursor back one step if possible to maintain relative + * position. + * + * @return The removed item. + */ + T remove(); + + /** + * Move the cursor to the left-most position. + */ + void first(); + + /** + * Move the cursor the right-most position. + */ + void last(); + + /** + * Move the cursor one space left. + * + * The cursor can't go past zero. + * + * @return True if the cursor was moved left. + */ + boolean left(); + + /** + * Move the cursor the specified amount left. + * + * The cursor can't go past zero. Attempts to move the cursor by amounts + * that would exceed zero don't move the cursor at all. + * + * @param amt + * The amount to attempt to move the cursor left. + * + * @return True if the cursor was moved left. + */ + boolean left(int amt); + + /** + * Move the cursor one space right. + * + * @return Whether the cursor was moved right. + */ + boolean right(); + + /** + * Move the cursor the specified amount right. + * + * @param amt + * The amount to move the cursor right by. + * + * @return Whether the cursor was moved right. + */ + boolean right(int amt); + + /** + * Is this tape double sided? + * + * @return Whether or not this tape is double-sided. + */ + boolean isDoubleSided(); +} diff --git a/base/src/main/java/bjc/utils/esodata/TapeChanger.java b/base/src/main/java/bjc/utils/esodata/TapeChanger.java new file mode 100644 index 0000000..dc885bc --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/TapeChanger.java @@ -0,0 +1,363 @@ +package bjc.utils.esodata; + +/** + * A tape changer is essentially a tape of tapes. + * + * It has a current tape that you can do operations to, but also operations to + * add/remove other tapes. + * + * If there is no tape currently loaded into the changer, all the methods will + * either return null/false. + * + * @param <T> + * The element type of the tapes. + */ +public class TapeChanger<T> implements Tape<T> { + private Tape<Tape<T>> tapes; + private Tape<T> currentTape; + + /** + * Create a new empty tape changer. + */ + public TapeChanger() { + tapes = new SingleTape<>(); + } + + /** + * Create a new tape changer with the specified tapes. + * + * @param current + * The tape to mount first. + * @param others + * The tapes to put in this tape changer. + */ + @SafeVarargs + public TapeChanger(final Tape<T> current, final Tape<T>... others) { + this(); + + tapes.insertBefore(current); + + for (final Tape<T> tp : others) { + tapes.insertAfter(tp); + tapes.right(); + } + + tapes.first(); + currentTape = tapes.item(); + } + + /** + * Get the item the tape is currently on. + * + * @return The item the tape is on. + */ + @Override + public T item() { + if (currentTape == null) return null; + + return currentTape.item(); + } + + /** + * Set the item the tape is currently on. + * + * @param itm + * The new value for the tape item. + */ + @Override + public void item(final T itm) { + if (currentTape == null) return; + + currentTape.item(itm); + } + + /** + * Get the current number of elements in the tape. + * + * @return The current number of elements in the tape. + */ + @Override + public int size() { + if (currentTape == null) return 0; + + return currentTape.size(); + } + + @Override + public int position() { + if (currentTape == null) return 0; + + return currentTape.position(); + } + + /** + * Insert an element before the current item. + * + * @param itm + * The item to add. + */ + @Override + public void insertBefore(final T itm) { + if (currentTape == null) return; + + currentTape.insertBefore(itm); + } + + /** + * Insert an element after the current item. + */ + @Override + public void insertAfter(final T itm) { + if (currentTape == null) return; + + currentTape.insertAfter(itm); + } + + /** + * Remove the current element. + * + * Also moves the cursor back one step if possible to maintain relative + * position, and removes the corresponding item from the non-active side + * + * @return The removed item from the active side. + */ + @Override + public T remove() { + if (currentTape == null) return null; + + return currentTape.remove(); + } + + /** + * Move the cursor to the left-most position. + */ + @Override + public void first() { + if (currentTape == null) return; + + currentTape.first(); + } + + /** + * Move the cursor the right-most position. + */ + @Override + public void last() { + if (currentTape == null) return; + + currentTape.last(); + } + + /** + * Move the cursor one space left. + * + * The cursor can't go past zero. + * + * @return True if the cursor was moved left. + */ + @Override + public boolean left() { + return left(1); + } + + /** + * Move the cursor the specified amount left. + * + * The cursor can't go past zero. Attempts to move the cursor by amounts + * that would exceed zero don't move the cursor at all. + * + * @param amt + * The amount to attempt to move the cursor left. + * + * @return True if the cursor was moved left. + */ + @Override + public boolean left(final int amt) { + if (currentTape == null) return false; + + return currentTape.left(amt); + } + + /** + * Move the cursor one space right. + * + * Moving the cursor right will auto-extend the tape if that is enabled. + * + * @return Whether the cursor was moved right. + */ + @Override + public boolean right() { + return right(1); + } + + /** + * Move the cursor the specified amount right. + * + * Moving the cursor right will auto-extend the tape if that is enabled. + * + * @param amt + * The amount to move the cursor right by. + * + * @return Whether the cursor was moved right. + */ + @Override + public boolean right(final int amt) { + if (currentTape == null) return false; + + return currentTape.right(amt); + } + + /** + * Flips the tape. + * + * The active side becomes inactive, and the inactive side becomes + * active. + * + * If the current tape is not double-sided, does nothing. + */ + public void flip() { + if (currentTape == null) return; + + if (currentTape.isDoubleSided()) { + ((DoubleTape<T>) currentTape).flip(); + } + } + + @Override + public boolean isDoubleSided() { + if (currentTape == null) return false; + + return currentTape.isDoubleSided(); + } + + /** + * Check if a tape is currently loaded. + * + * @return Whether or not a tape is loaded. + */ + public boolean isLoaded() { + return currentTape != null; + } + + /** + * Move to the next tape in the changer. + * + * Attempting to load a tape that isn't there won't eject the current + * tape. + * + * @return Whether or not the next tape was loaded. + */ + public boolean nextTape() { + final boolean succ = tapes.right(); + + if (succ) { + currentTape = tapes.item(); + } + + return succ; + } + + /** + * Move to the previous tape in the changer. + * + * Attempting to load a tape that isn't there won't eject the current + * tape. + * + * @return Whether or not the previous tape was loaded. + */ + public boolean prevTape() { + final boolean succ = tapes.left(); + + if (succ) { + currentTape = tapes.item(); + } + + return succ; + } + + /** + * Inserts a tape into the tape changer. + * + * Any currently loaded tape is ejected, and becomes the previous tape. + * + * The specified tape is loaded. + * + * @param tp + * The tape to insert and load. + */ + public void insertTape(final Tape<T> tp) { + tapes.insertAfter(tp); + tapes.right(); + + currentTape = tapes.item(); + } + + /** + * Removes the current tape. + * + * Does nothing if there is not a tape loaded. + * + * Loads the previous tape, if there is one. + * + * @return The removed tape. + */ + public Tape<T> removeTape() { + if (currentTape == null) return null; + + final Tape<T> tp = tapes.remove(); + currentTape = tapes.item(); + + return tp; + } + + /** + * Ejects the current tape. + * + * Does nothing if no tape is loaded. + */ + public void eject() { + currentTape = null; + } + + /** + * Get how many tapes are currently in the changer. + * + * @return How many tapes are currently in the changer. + */ + public int tapeCount() { + return tapes.size(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (currentTape == null ? 0 : currentTape.hashCode()); + result = prime * result + (tapes == null ? 0 : tapes.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof TapeChanger<?>)) return false; + + final TapeChanger<?> other = (TapeChanger<?>) obj; + + if (currentTape == null) { + if (other.currentTape != null) return false; + } else if (!currentTape.equals(other.currentTape)) return false; + + if (tapes == null) { + if (other.tapes != null) return false; + } else if (!tapes.equals(other.tapes)) return false; + + return true; + } + + @Override + public String toString() { + return String.format("TapeChanger [tapes=%s, currentTape='%s']", tapes, currentTape); + } +} diff --git a/base/src/main/java/bjc/utils/esodata/TapeLibrary.java b/base/src/main/java/bjc/utils/esodata/TapeLibrary.java new file mode 100644 index 0000000..2dbc70b --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/TapeLibrary.java @@ -0,0 +1,340 @@ +package bjc.utils.esodata; + +import java.util.HashMap; +import java.util.Map; + +/** + * A tape changer is essentially a map of tapes. + * + * It has a current tape that you can do operations to, but also operations to + * add/remove other tapes. + * + * If there is no tape currently loaded into the changer, all the methods will + * either return null/false. + * + * @param <T> + * The element type of the tapes. + */ +public class TapeLibrary<T> implements Tape<T> { + private final Map<String, Tape<T>> tapes; + private Tape<T> currentTape; + + /** + * Create a new empty tape library. + */ + public TapeLibrary() { + tapes = new HashMap<>(); + } + + /** + * Get the item the tape is currently on. + * + * @return The item the tape is on. + */ + @Override + public T item() { + if (currentTape == null) return null; + + return currentTape.item(); + } + + /** + * Set the item the tape is currently on. + * + * @param itm + * The new value for the tape item. + */ + @Override + public void item(final T itm) { + if (currentTape == null) return; + + currentTape.item(itm); + } + + /** + * Get the current number of elements in the tape. + * + * @return The current number of elements in the tape. + */ + @Override + public int size() { + if (currentTape == null) return 0; + + return currentTape.size(); + } + + @Override + public int position() { + if (currentTape == null) return 0; + + return currentTape.position(); + } + /** + * Insert an element before the current item. + * + * @param itm + * The item to add. + */ + @Override + public void insertBefore(final T itm) { + if (currentTape == null) return; + + currentTape.insertBefore(itm); + } + + /** + * Insert an element after the current item. + */ + @Override + public void insertAfter(final T itm) { + if (currentTape == null) return; + + currentTape.insertAfter(itm); + } + + /** + * Remove the current element. + * + * Also moves the cursor back one step if possible to maintain relative + * position, and removes the corresponding item from the non-active side + * + * @return The removed item from the active side. + */ + @Override + public T remove() { + if (currentTape == null) return null; + + return currentTape.remove(); + } + + /** + * Move the cursor to the left-most position. + */ + @Override + public void first() { + if (currentTape == null) return; + + currentTape.first(); + } + + /** + * Move the cursor the right-most position. + */ + @Override + public void last() { + if (currentTape == null) return; + + currentTape.last(); + } + + /** + * Move the cursor one space left. + * + * The cursor can't go past zero. + * + * @return True if the cursor was moved left. + */ + @Override + public boolean left() { + return left(1); + } + + /** + * Move the cursor the specified amount left. + * + * The cursor can't go past zero. Attempts to move the cursor by amounts + * that would exceed zero don't move the cursor at all. + * + * @param amt + * The amount to attempt to move the cursor left. + * + * @return True if the cursor was moved left. + */ + @Override + public boolean left(final int amt) { + if (currentTape == null) return false; + + return currentTape.left(amt); + } + + /** + * Move the cursor one space right. + * + * Moving the cursor right will auto-extend the tape if that is enabled. + * + * @return Whether the cursor was moved right. + */ + @Override + public boolean right() { + return right(1); + } + + /** + * Move the cursor the specified amount right. + * + * Moving the cursor right will auto-extend the tape if that is enabled. + * + * @param amt + * The amount to move the cursor right by. + * + * @return Whether the cursor was moved right. + */ + @Override + public boolean right(final int amt) { + if (currentTape == null) return false; + + return currentTape.right(amt); + } + + /** + * Flips the tape. + * + * The active side becomes inactive, and the inactive side becomes + * active. + * + * If the current tape is not double-sided, does nothing. + */ + public void flip() { + if (currentTape == null) return; + + if (currentTape.isDoubleSided()) { + ((DoubleTape<T>) currentTape).flip(); + } + } + + @Override + public boolean isDoubleSided() { + if (currentTape == null) return false; + + return currentTape.isDoubleSided(); + } + + /** + * Check if a tape is currently loaded. + * + * @return Whether or not a tape is loaded. + */ + public boolean isLoaded() { + return currentTape != null; + } + + /** + * Move to the specified tape in the library. + * + * Attempting to load a tape that isn't there won't eject the current + * tape. + * + * @param label + * The label of the tape to load. + * + * @return Whether or not the next tape was loaded. + */ + public boolean switchTape(final String label) { + if (tapes.containsKey(label)) { + currentTape = tapes.get(label); + return true; + } + + return false; + } + + /** + * Inserts a tape into the tape library. + * + * Any currently loaded tape is ejected. + * + * The specified tape is loaded. + * + * Adding a duplicate tape will overwrite any existing types. + * + * @param label + * The label of the tape to add. + * + * @param tp + * The tape to insert and load. + */ + public void insertTape(final String label, final Tape<T> tp) { + tapes.put(label, tp); + + currentTape = tp; + } + + /** + * Remove a tape from the library. + * + * Does nothing if there is not a tape of that name loaded. + * + * @param label + * The tape to remove. + * + * @return The removed tape. + */ + public Tape<T> removeTape(final String label) { + return tapes.remove(label); + } + + /** + * Ejects the current tape. + * + * Does nothing if no tape is loaded. + */ + public void eject() { + currentTape = null; + } + + /** + * Get how many tapes are currently in the library. + * + * @return How many tapes are currently in the library. + */ + public int tapeCount() { + return tapes.size(); + } + + /** + * Check if a specific tape is loaded into the library. + * + * @param label + * The tape to check for. + * + * @return Whether or not a tape of that name exists + */ + public boolean hasTape(final String label) { + return tapes.containsKey(label); + } + + @Override + public int hashCode() { + final int prime = 31; + + int result = 1; + result = prime * result + (currentTape == null ? 0 : currentTape.hashCode()); + result = prime * result + (tapes == null ? 0 : tapes.hashCode()); + + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof TapeLibrary<?>)) return false; + + final TapeLibrary<?> other = (TapeLibrary<?>) obj; + + if (currentTape == null) { + if (other.currentTape != null) return false; + } else if (!currentTape.equals(other.currentTape)) return false; + + if (tapes == null) { + if (other.tapes != null) return false; + } else if (!tapes.equals(other.tapes)) return false; + + return true; + } + + @Override + public String toString() { + return String.format("TapeLibrary [tapes=%s, currentTape='%s']", tapes, currentTape); + } +} diff --git a/base/src/main/java/bjc/utils/esodata/UnifiedDirectory.java b/base/src/main/java/bjc/utils/esodata/UnifiedDirectory.java new file mode 100644 index 0000000..ffb639f --- /dev/null +++ b/base/src/main/java/bjc/utils/esodata/UnifiedDirectory.java @@ -0,0 +1,105 @@ +package bjc.utils.esodata; + +import bjc.utils.funcdata.FunctionalMap; +import bjc.utils.funcdata.IMap; + +/** + * Simple implementation of {@link Directory}. + * + * Has a unified namespace for data and children. + * + * @author EVE + * + * @param <K> + * The key type of the directory. + * @param <V> + * The value type of the directory. + */ +public class UnifiedDirectory<K, V> implements Directory<K, V> { + private final IMap<K, Directory<K, V>> children; + + private final IMap<K, V> data; + + /** + * Create a new directory. + */ + public UnifiedDirectory() { + children = new FunctionalMap<>(); + data = new FunctionalMap<>(); + } + + @Override + public Directory<K, V> getSubdirectory(final K key) { + return children.get(key); + } + + @Override + public boolean hasSubdirectory(final K key) { + return children.containsKey(key); + } + + @Override + public Directory<K, V> putSubdirectory(final K key, final Directory<K, V> val) { + if (data.containsKey(key)) { + final String msg = String.format("Key %s is already used for data", key); + + throw new IllegalArgumentException(msg); + } + + return children.put(key, val); + } + + @Override + public boolean containsKey(final K key) { + return data.containsKey(key); + } + + @Override + public V getKey(final K key) { + return data.get(key); + } + + @Override + public V putKey(final K key, final V val) { + if (children.containsKey(key)) { + final String msg = String.format("Key %s is already used for sub-directories.", key); + + throw new IllegalArgumentException(msg); + } + + return data.put(key, val); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (children == null ? 0 : children.hashCode()); + result = prime * result + (data == null ? 0 : data.hashCode()); + return result; + } + + @Override + public boolean equals(final Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (!(obj instanceof UnifiedDirectory<?, ?>)) return false; + + final UnifiedDirectory<?, ?> other = (UnifiedDirectory<?, ?>) obj; + + if (children == null) { + if (other.children != null) return false; + } else if (!children.equals(other.children)) return false; + + if (data == null) { + if (other.data != null) return false; + } else if (!data.equals(other.data)) return false; + + return true; + } + + @Override + public String toString() { + return String.format("UnifiedDirectory [children=%s, data=%s]", children, data); + } +}
\ No newline at end of file |
