GP-134: Mainline changes cherry-picked from Debugger branch

This commit is contained in:
ghidravore 2020-09-16 13:20:45 -04:00
parent 192aba987a
commit 724df5a44c
60 changed files with 3855 additions and 1770 deletions

View file

@ -0,0 +1,72 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.generic.util.datastruct;
import java.util.List;
/**
* An interface for sorted lists
*
* <p>
* This might be better described as a NavigableMultiset; however, I wish for the elements to be
* retrievable by index, though insertion and mutation is not permitted by index. This implies that
* though unordered, the underlying implementation has sorted the elements in some way and wishes to
* expose that ordering to its clients.
*
* @param <E> the type of elements in this list
*/
public interface SortedList<E> extends List<E> {
/**
* Returns the greatest index in this list whose element is strictly less than the specified
* element
*
* @param element the element to search for
* @return the index of the found element, or -1
*/
int lowerIndex(E element);
/**
* Returns the greatest index in this list whose element is less than or equal to the specified
* element
*
* <p>
* If multiples of the specified element exist, this returns the least index of that element.
*
* @param element the element to search for
* @return the index of the found element, or -1
*/
int floorIndex(E element);
/**
* Returns the least index in this list whose element is greater than or equal to the specified
* element
*
* <p>
* If multiples of the specified element exist, this returns the greatest index of that element.
*
* @param element the element to search for
* @return the index of the found element, or -1
*/
int ceilingIndex(E element);
/**
* Returns the least index in this list whose element is strictly greater the specified element
*
* @param element the element to search for
* @return the index of the found element, or -1
*/
int higherIndex(E element);
}

View file

@ -15,14 +15,13 @@
*/
package ghidra.generic.util.datastruct;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.*;
import org.apache.commons.collections4.multimap.AbstractSetValuedMap;
/**
* A multi-valued dictionary using a tree map of tree sets
* A multi-valued map using a tree map of tree sets
*
* @param <K> the type of key
* @param <V> the type of value
*/

View file

@ -22,36 +22,43 @@ import org.apache.commons.collections4.comparators.ComparableComparator;
import ghidra.util.ReversedListIterator;
/**
* A map that is sorted by value.
*
* This is an implementation of {@link Map} where entries are sorted by value, rather than by key.
* Such a tree may be useful as a priority queue where the cost of an entry may change over time.
* As such, the collections returned by {@link #entrySet()}, {@link #keySet()}, and
* {@link #values()} all implement {@link Deque}. The order of the entries will be updated on any
* call to {@link #put(Object, Object)}, or a call to {@link Collection#add(Object)} on the entry
* set. Additionally, if the values are mutable objects, whose costs may change, there is an
* {@link #update(Object)} method, which notifies the map that the given key may need to be
* repositioned. The associated collections also implement the {@link List} interface, providing
* fairly efficient implementations of {@link List#get(int)} and {@link List#indexOf(Object)}.
* Sequential access is best performed via {@link Collection#iterator()}, since this will use a
* linked list.
*
* A tree-based implementation of a value-sorted map
*
* The underlying implementation is currently an unbalanced binary tree whose nodes also comprise a
* doubly-linked list. Currently, it is not thread safe.
* TODO Consider changing to an AVL tree implementation
* TODO Consider implementing the {@link NavigableMap} interface
* TODO Consider making the implementation thread-safe
*
* Note this implementation isn't terribly smart, as it makes no efforts to balance the tree. It is
* also not thread safe.
*
* @param <K> the type of the keys
* @param <V> the type of the values
*/
public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
public class TreeValueSortedMap<K, V> extends AbstractMap<K, V> implements ValueSortedMap<K, V> {
/**
* Create a tree using the values' natural ordering
*/
public static <K, V extends Comparable<V>> TreeValueSortedMap<K, V> createWithNaturalOrder() {
Comparator<V> natural = Comparator.naturalOrder();
return new TreeValueSortedMap<>(natural);
}
/**
* Create a tree using a custom comparator to order the values
*
* @param comparator the comparator, providing a total ordering of the values
*/
public static <K, V> TreeValueSortedMap<K, V> createWithComparator(Comparator<V> comparator) {
return new TreeValueSortedMap<>(comparator);
}
/**
* An iterator of the entries
*/
protected class EntryListIterator implements ListIterator<Entry<K, V>> {
private boolean atEnd = false;
private Node next;
private Node cur;
/**
* Construct a list iterator over the entries
@ -88,7 +95,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
if (!hasNext()) {
throw new NoSuchElementException();
}
Node cur = next;
cur = next;
next = next.next;
atEnd = next == null;
return cur;
@ -109,9 +116,9 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
atEnd = tail == null;
}
else {
next = next.prev;
cur = next = next.prev;
}
return next;
return cur;
}
@Override
@ -124,8 +131,12 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public void remove() {
nodeMap.remove(next.key);
next.remove();
if (cur == null) {
throw new IllegalStateException();
}
nodeMap.remove(cur.key);
cur.remove();
cur = null;
}
@Override
@ -204,18 +215,18 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
*/
protected class Node implements Entry<K, V> {
// Node key and data
private final K key;
private V val;
protected final K key;
protected V val;
// Tree-related fields
private Node parent;
private Node lChild;
private Node rChild;
private int sizeLeft;
protected Node parent;
protected Node lChild;
protected Node rChild;
protected int sizeLeft;
// Linked list-related fields
private Node next;
private Node prev;
protected Node next;
protected Node prev;
@Override
public String toString() {
@ -224,10 +235,11 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
/**
* Construct a new node
*
* @param key the key
* @param val the data
*/
private Node(K key, V val) {
protected Node(K key, V val) {
this.key = key;
this.val = val;
}
@ -237,17 +249,24 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
try {
@SuppressWarnings("unchecked")
Entry<K, V> that = (Entry<K, V>) obj;
return eq(this.key, that.getKey()) && eq(this.val, that.getValue());
return Objects.equals(this.key, that.getKey()) &&
Objects.equals(this.val, that.getValue());
}
catch (ClassCastException e) {
return false;
}
}
@Override
public int hashCode() {
return Objects.hash(this.key, this.val);
}
/**
* Compute this node's index.
*
* This uses the {@link #sizeLeft} field to compute the index in O(log n) on average.
*
* @return the index
*/
public int computeIndex() {
@ -268,7 +287,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
*
* This really only makes sense at the root
*
* @param index the index
* @param index the index
* @return the node at the given index
*/
private Node getByIndex(int index) {
@ -310,6 +329,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
/**
* Insert a node into this subtree and the linked list
*
* @param item the node to insert
*/
void insert(Node item) {
@ -340,6 +360,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
/**
* Insert a node as a successor to this node in the linked list
*
* NOTE: Called only after the node is inserted into the tree
*/
private void insertAfter(Node item) {
@ -357,6 +378,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
/**
* Insert a node as a predecessor to this node in the linked list
*
* NOTE: Called only after the node is inserted into the tree
*/
private void insertBefore(Node item) {
@ -456,34 +478,57 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
/**
* Find the given value in this subtree
*
* @param val the value to find
* @param value the value to find
* @param mode when the value occurs multiple times, identifies which instance to find
* @return the node containing the given value, or null if not found
*/
private Node searchValue(V val, SearchMode mode) {
private Node searchValue(V value, SearchMode mode) {
Node cur = this;
Node eq = null;
int c;
while (true) {
int c = comparator.compare(val, cur.val);
c = comparator.compare(value, cur.val);
if (c == 0) {
eq = cur;
}
if (c < 0 || (c == 0 && mode == SearchMode.FIRST)) {
if (c < 0 || (c == 0 && mode.inEq == Comp.LT)) {
if (cur.lChild == null) {
return eq;
break;
}
cur = cur.lChild;
}
else if (c > 0 || (c == 0 && mode == SearchMode.LAST)) {
else if (c > 0 || (c == 0 && mode.inEq == Comp.GT)) {
if (cur.rChild == null) {
return eq;
break;
}
cur = cur.rChild;
}
else { // c == 0 && mode == SearchMode.ANY
return eq;
else {
break;
}
}
if (eq != null) {
if (mode.allowEq == BoundType.CLOSED) {
return eq;
}
if (mode.comp == Comp.LT) {
return eq.prev;
}
return eq.next;
}
if (mode.comp == Comp.LT) {
if (c < 0) {
return cur.prev; // May be null;
}
return cur; // c != 0 here, so c > 0
}
if (mode.comp == Comp.GT) {
if (c < 0) {
return cur;
}
return cur.next;
}
return null; // Other search modes require exact match
}
@Override
@ -495,19 +540,42 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
}
}
private enum BoundType {
CLOSED, OPEN
}
private enum Comp {
NONE, LT, GT
}
/**
* When searching for values, identifies which instance to find
*
* TODO When/if implementing {@link NavigableMap}, this seems an appropriate place to put
* FLOOR, CEILING, etc.
*/
private enum SearchMode {
/** Find any occurrence */
ANY,
ANY(BoundType.CLOSED, Comp.NONE, Comp.NONE),
/** Find the first occurrence */
FIRST,
FIRST(BoundType.CLOSED, Comp.LT, Comp.NONE),
/** Find the last occurrence */
LAST;
LAST(BoundType.CLOSED, Comp.GT, Comp.NONE),
/** Find the nearest match less than */
LOWER(BoundType.OPEN, Comp.NONE, Comp.LT),
/** Find the nearest match less than or equal */
FLOOR(BoundType.CLOSED, Comp.LT, Comp.LT),
/** Find the nearest match greater than or equal */
CEILING(BoundType.CLOSED, Comp.GT, Comp.GT),
/** Find the nearest match greater than */
HIGHER(BoundType.OPEN, Comp.NONE, Comp.GT);
final BoundType allowEq;
final Comp inEq;
final Comp comp;
SearchMode(BoundType allowEq, Comp inEq, Comp comp) {
this.allowEq = allowEq;
this.inEq = inEq;
this.comp = comp;
}
}
/**
@ -577,13 +645,13 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
* A public view of the map as a set of entries
*
* In addition to {@link Set}, this view implements {@link List} and {@link Deque}, since an
* ordered set ought to behave like a list, and since this implementation is meant to be used
* as a dynamic-cost priority queue.
* ordered set ought to behave like a list, and since this implementation is meant to be used as
* a dynamic-cost priority queue.
*
* Generally, all of the mutation methods are supported.
*/
public class ValueSortedTreeMapEntrySet extends AbstractSet<Entry<K, V>>
implements List<Entry<K, V>>, Deque<Entry<K, V>> {
protected class ValueSortedTreeMapEntrySet extends AbstractSet<Entry<K, V>>
implements ValueSortedMapEntryList<K, V> {
private ValueSortedTreeMapEntrySet() {
}
@ -633,7 +701,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public void clear() {
DynamicValueSortedTreeMap.this.clear();
TreeValueSortedMap.this.clear();
}
@Override
@ -645,7 +713,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@SuppressWarnings("unchecked")
Node n = (Node) o;
Node m = nodeMap.get(n.key);
return eq(n.val, m.val);
return Objects.equals(n.val, m.val);
}
catch (ClassCastException e) {
return false;
@ -658,18 +726,18 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
}
@Override
public Node element() {
public Entry<K, V> element() {
return getFirst();
}
@Override
public Node get(int index) {
public Entry<K, V> get(int index) {
return root.getByIndex(index);
}
@Override
public Node getFirst() {
Node ret = peekFirst();
public Entry<K, V> getFirst() {
Entry<K, V> ret = peekFirst();
if (ret == null) {
throw new NoSuchElementException();
}
@ -677,8 +745,8 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
}
@Override
public Node getLast() {
Node ret = peekLast();
public Entry<K, V> getLast() {
Entry<K, V> ret = peekLast();
if (ret == null) {
throw new NoSuchElementException();
}
@ -722,6 +790,9 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public ListIterator<Entry<K, V>> listIterator(int index) {
if (root == null) {
return Collections.emptyListIterator();
}
return new EntryListIterator(root.getByIndex(index));
}
@ -747,27 +818,27 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
}
@Override
public Node peek() {
public Entry<K, V> peek() {
return peekFirst();
}
@Override
public Node peekFirst() {
public Entry<K, V> peekFirst() {
return head;
}
@Override
public Node peekLast() {
public Entry<K, V> peekLast() {
return tail;
}
@Override
public Node poll() {
public Entry<K, V> poll() {
return pollFirst();
}
@Override
public Node pollFirst() {
public Entry<K, V> pollFirst() {
if (head == null) {
return null;
}
@ -778,7 +849,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
}
@Override
public Node pollLast() {
public Entry<K, V> pollLast() {
if (tail == null) {
return tail;
}
@ -789,7 +860,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
}
@Override
public Node pop() {
public Entry<K, V> pop() {
return removeFirst();
}
@ -799,12 +870,12 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
}
@Override
public Node remove() {
public Entry<K, V> remove() {
return removeFirst();
}
@Override
public Node remove(int index) {
public Entry<K, V> remove(int index) {
Node n = root.getByIndex(index);
n.remove();
nodeMap.remove(n.key);
@ -822,7 +893,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
nodeMap.remove(n.key);
return true;
}
if (eq(n.val, rm.val)) {
if (Objects.equals(n.val, rm.val)) {
nodeMap.remove(rm.key);
rm.remove();
return true;
@ -835,8 +906,8 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
}
@Override
public Node removeFirst() {
Node ret = pollFirst();
public Entry<K, V> removeFirst() {
Entry<K, V> ret = pollFirst();
if (ret == null) {
throw new NoSuchElementException();
}
@ -849,8 +920,8 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
}
@Override
public Node removeLast() {
Node ret = pollLast();
public Entry<K, V> removeLast() {
Entry<K, V> ret = pollLast();
if (ret == null) {
throw new NoSuchElementException();
}
@ -865,13 +936,13 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
/**
* Modify the entry (key and value) at index
*
* Because the map is sorted by value, the index of the given entry may not remain the
* same after it is modified. In fact, this is equivalent to removing the entry at the
* given index, and then inserting the given entry at its sorted position.
* Because the map is sorted by value, the index of the given entry may not remain the same
* after it is modified. In fact, this is equivalent to removing the entry at the given
* index, and then inserting the given entry at its sorted position.
*/
@Override
public Node set(int index, Entry<K, V> element) {
Node result = remove(index);
public Entry<K, V> set(int index, Entry<K, V> element) {
Entry<K, V> result = remove(index);
add(element);
return result;
}
@ -881,11 +952,6 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
return nodeMap.size();
}
@Override
public Spliterator<Entry<K, V>> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED | Spliterator.DISTINCT);
}
/**
* This operation is not supported
*/
@ -899,12 +965,13 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
* A public view of the map as a set of keys
*
* In addition to {@link Set}, this view implements {@link List} and {@link Deque}, since an
* ordered set ought to behave like a list, and since this implementation is meant to be used
* as a dynamic-cost priority queue.
* ordered set ought to behave like a list, and since this implementation is meant to be used as
* a dynamic-cost priority queue.
*
* Generally, only the removal mutation methods are supported, all others are not supported.
*/
public class ValueSortedTreeMapKeySet extends AbstractSet<K> implements List<K>, Deque<K> {
protected class ValueSortedTreeMapKeySet extends AbstractSet<K>
implements ValueSortedMapKeyList<K> {
private ValueSortedTreeMapKeySet() {
}
@ -940,7 +1007,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public void clear() {
DynamicValueSortedTreeMap.this.clear();
TreeValueSortedMap.this.clear();
}
@Override
@ -960,17 +1027,17 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public K get(int index) {
return entrySet.get(index).key;
return entrySet.get(index).getKey();
}
@Override
public K getFirst() {
return entrySet.getFirst().key;
return entrySet.getFirst().getKey();
}
@Override
public K getLast() {
return entrySet.getLast().key;
return entrySet.getLast().getKey();
}
@Override
@ -1029,20 +1096,20 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public K peekFirst() {
Node n = entrySet.peekFirst();
Entry<K, V> n = entrySet.peekFirst();
if (n == null) {
return null;
}
return n.key;
return n.getKey();
}
@Override
public K peekLast() {
Node n = entrySet.peekLast();
Entry<K, V> n = entrySet.peekLast();
if (n == null) {
return null;
}
return n.key;
return n.getKey();
}
@Override
@ -1052,20 +1119,20 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public K pollFirst() {
Node n = entrySet.pollFirst();
Entry<K, V> n = entrySet.pollFirst();
if (n == null) {
return null;
}
return n.key;
return n.getKey();
}
@Override
public K pollLast() {
Node n = entrySet.pollLast();
Entry<K, V> n = entrySet.pollLast();
if (n == null) {
return null;
}
return n.key;
return n.getKey();
}
@Override
@ -1085,32 +1152,32 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public K remove(int index) {
return entrySet.remove(index).key;
return entrySet.remove(index).getKey();
}
@Override
public boolean remove(Object o) {
return DynamicValueSortedTreeMap.this.remove(o) != null;
return TreeValueSortedMap.this.remove(o) != null;
}
@Override
public K removeFirst() {
return entrySet.removeFirst().key;
return entrySet.removeFirst().getKey();
}
@Override
public boolean removeFirstOccurrence(Object o) {
return DynamicValueSortedTreeMap.this.remove(o) != null;
return TreeValueSortedMap.this.remove(o) != null;
}
@Override
public K removeLast() {
return entrySet.removeLast().key;
return entrySet.removeLast().getKey();
}
@Override
public boolean removeLastOccurrence(Object o) {
return DynamicValueSortedTreeMap.this.remove(o) != null;
return TreeValueSortedMap.this.remove(o) != null;
}
@Override
@ -1123,11 +1190,6 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
return nodeMap.size();
}
@Override
public Spliterator<K> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED | Spliterator.DISTINCT);
}
/**
* This operation is not supported
*/
@ -1140,14 +1202,14 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
/**
* A public view of the map as a list of values
*
* This view implements {@link List} and {@link Deque}, since an ordered collection ought to
* behave like a list, and since this implementation is meant to be used as a dynamic-cost
* This view implements {@link SortedList} and {@link Deque}, since an ordered collection ought
* to behave like a list, and since this implementation is meant to be used as a dynamic-cost
* priority queue.
*
* Generally, only the removal mutation methods are supported, all others are not supported.
*/
public class ValueSortedTreeMapValues extends AbstractCollection<V>
implements List<V>, Deque<V> {
protected class ValueSortedTreeMapValues extends AbstractCollection<V>
implements SortedList<V>, Deque<V> {
private ValueSortedTreeMapValues() {
}
@ -1183,7 +1245,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public void clear() {
DynamicValueSortedTreeMap.this.clear();
TreeValueSortedMap.this.clear();
}
@Override
@ -1210,17 +1272,17 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public V get(int index) {
return entrySet.get(index).val;
return entrySet.get(index).getValue();
}
@Override
public V getFirst() {
return entrySet.getFirst().val;
return entrySet.getFirst().getValue();
}
@Override
public V getLast() {
return entrySet.getLast().val;
return entrySet.getLast().getValue();
}
@Override
@ -1228,7 +1290,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
try {
@SuppressWarnings("unchecked")
V val = (V) o;
Node n = root.searchValue(val, SearchMode.FIRST);
Node n = searchValue(val, SearchMode.FIRST);
if (n == null) {
return -1;
}
@ -1239,6 +1301,45 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
}
}
@Override
public int lowerIndex(V element) {
if (root == null) {
return -1;
}
Node n = searchValue(element, SearchMode.LOWER);
if (n == null) {
return -1;
}
return n.computeIndex();
}
@Override
public int floorIndex(V element) {
Node n = searchValue(element, SearchMode.FLOOR);
if (n == null) {
return -1;
}
return n.computeIndex();
}
@Override
public int ceilingIndex(V element) {
Node n = searchValue(element, SearchMode.CEILING);
if (n == null) {
return -1;
}
return n.computeIndex();
}
@Override
public int higherIndex(V element) {
Node n = searchValue(element, SearchMode.HIGHER);
if (n == null) {
return -1;
}
return n.computeIndex();
}
@Override
public boolean isEmpty() {
return root == null;
@ -1297,20 +1398,20 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public V peekFirst() {
Node n = entrySet.peekFirst();
Entry<K, V> n = entrySet.peekFirst();
if (n == null) {
return null;
}
return n.val;
return n.getValue();
}
@Override
public V peekLast() {
Node n = entrySet.peekLast();
Entry<K, V> n = entrySet.peekLast();
if (n == null) {
return null;
}
return n.val;
return n.getValue();
}
@Override
@ -1320,20 +1421,20 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public V pollFirst() {
Node n = entrySet.pollFirst();
Entry<K, V> n = entrySet.pollFirst();
if (n == null) {
return null;
}
return n.val;
return n.getValue();
}
@Override
public V pollLast() {
Node n = entrySet.pollLast();
Entry<K, V> n = entrySet.pollLast();
if (n == null) {
return null;
}
return n.val;
return n.getValue();
}
@Override
@ -1353,7 +1454,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public V remove(int index) {
return entrySet.remove(index).val;
return entrySet.remove(index).getValue();
}
@Override
@ -1363,7 +1464,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public V removeFirst() {
return entrySet.removeFirst().val;
return entrySet.removeFirst().getValue();
}
@Override
@ -1386,7 +1487,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
@Override
public V removeLast() {
return entrySet.removeLast().val;
return entrySet.removeLast().getValue();
}
@Override
@ -1426,53 +1527,61 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
}
}
/**
* A convenience for null-safe comparison
*/
protected static boolean eq(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
// The user-provided comparator
private final Comparator<V> comparator;
protected final Comparator<V> comparator;
// A hash map to locate entries by key
private final Map<K, Node> nodeMap = new HashMap<>();
/* Remember, the tree is indexed by *value*, not by key, and more specifically, they are
* indexed by the comparator, so an entry's cost may change at any time. Thus, this map
* provides an index by key. This is especially important during an update, since we need to
* locate the affected node, given that it's most likely not in its correct position at the
* moment. We also use it to ensure each key occurs at most once. */
protected final Map<K, Node> nodeMap = new HashMap<>();
/*
* Remember, the tree is indexed by *value*, not by key, and more specifically, they are indexed
* by the comparator, so an entry's cost may change at any time. Thus, this map provides an
* index by key. This is especially important during an update, since we need to locate the
* affected node, given that it's most likely not in its correct position at the moment. We also
* use it to ensure each key occurs at most once.
*/
// Pre-constructed views. Unlike Java's stock collections, I create these outright
// At least one ought to be accessed for this implementation to be useful
private transient final ValueSortedTreeMapEntrySet entrySet = new ValueSortedTreeMapEntrySet();
private transient final ValueSortedTreeMapKeySet keySet = new ValueSortedTreeMapKeySet();
private transient final ValueSortedTreeMapValues values = new ValueSortedTreeMapValues();
private transient final ValueSortedTreeMapEntrySet entrySet = createEntrySet();
private transient final ValueSortedTreeMapKeySet keySet = createKeySet();
private transient final ValueSortedTreeMapValues values = createValues();
// Pointers into the data structure
private Node root; // The root of the binary tree
private Node head; // The node with the least value
private Node tail; // The node with the greatest value
protected Node root; // The root of the binary tree
protected Node head; // The node with the least value
protected Node tail; // The node with the greatest value
/**
* Construct a dynamic value-sorted tree map using the values' natural ordering
*
* If the values do not have a natural ordering, you will eventually encounter a
* {@link ClassCastException}. This condition is not checked at construction.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public DynamicValueSortedTreeMap() {
protected TreeValueSortedMap() {
this(new ComparableComparator());
}
/**
* Construct a dynamic value-sorted tree map using a custom comparator to order the values
* @param comparator the comparator, providing a total ordering of the values
*/
public DynamicValueSortedTreeMap(Comparator<V> comparator) {
protected TreeValueSortedMap(Comparator<V> comparator) {
this.comparator = comparator;
}
protected ValueSortedTreeMapEntrySet createEntrySet() {
return new ValueSortedTreeMapEntrySet();
}
protected ValueSortedTreeMapKeySet createKeySet() {
return new ValueSortedTreeMapKeySet();
}
protected ValueSortedTreeMapValues createValues() {
return new ValueSortedTreeMapValues();
}
protected Node createNode(K key, V value) {
return new Node(key, value);
}
protected Node searchValue(V value, SearchMode mode) {
if (root == null) {
return null;
}
return root.searchValue(value, mode);
}
@Override
public void clear() {
nodeMap.clear();
@ -1491,7 +1600,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
try {
@SuppressWarnings("unchecked")
V val = (V) value;
return root.searchValue(val, SearchMode.ANY) != null;
return searchValue(val, SearchMode.ANY) != null;
}
catch (ClassCastException e) {
return false;
@ -1517,6 +1626,26 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
return n.val;
}
@Override
public Entry<K, V> lowerEntryByValue(V value) {
return searchValue(value, SearchMode.LOWER);
}
@Override
public Entry<K, V> floorEntryByValue(V value) {
return searchValue(value, SearchMode.FLOOR);
}
@Override
public Entry<K, V> ceilingEntryByValue(V value) {
return searchValue(value, SearchMode.CEILING);
}
@Override
public Entry<K, V> higherEntryByValue(V value) {
return searchValue(value, SearchMode.HIGHER);
}
@Override
public boolean isEmpty() {
return root == null;
@ -1524,6 +1653,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
/**
* Check if a node is correctly positioned relative to its immediate neighbors
*
* @param n the node
* @return true if the node need not be moved
*/
@ -1557,7 +1687,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
if (n != null) {
return n.setValue(value);
}
n = new Node(key, value);
n = createNode(key, value);
nodeMap.put(key, n);
if (root == null) {
root = n;
@ -1592,15 +1722,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
return nodeMap.size();
}
/**
* Notify the map of an external change to the cost of a key's associated value
*
* This is meant to update the entry's position after a change in cost. The position may not
* necessarily change, however, if the cost did not change significantly.
*
* @param key the key whose associated value has changed in cost
* @return true if the entry's position changed
*/
@Override
public boolean update(K key) {
Node n = nodeMap.get(key);
if (n == null) {
@ -1615,6 +1737,7 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
* This ought to be called any time the value of a node is modified, whether internall or
* externally. The only way we know of external changes is if the user calls
* {@link #update(Object)}.
*
* @param n the node whose position to check and update
* @return true if the node's position changed
*/
@ -1636,4 +1759,24 @@ public class DynamicValueSortedTreeMap<K, V> extends AbstractMap<K, V> {
public ValueSortedTreeMapValues values() {
return values;
}
@Override
public ValueSortedMap<K, V> subMapByValue(V fromValue, boolean fromInclusive, V toValue,
boolean toInclusive) {
return new RestrictedValueSortedMap<>(this, comparator, true, fromValue, fromInclusive,
true, toValue, toInclusive);
}
@Override
// TODO: Test this implementation and related others
public ValueSortedMap<K, V> headMapByValue(V toValue, boolean inclusive) {
return new RestrictedValueSortedMap<>(this, comparator, false, null, false, true, toValue,
inclusive);
}
@Override
public ValueSortedMap<K, V> tailMapByValue(V fromValue, boolean inclusive) {
return new RestrictedValueSortedMap<>(this, comparator, true, fromValue, inclusive, false,
null, false);
}
}

View file

@ -0,0 +1,143 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.generic.util.datastruct;
import java.util.*;
/**
* A map that is sorted by value.
*
* <p>
* This is an extension of {@link Map} where entries are sorted by value, rather than by key. Such a
* map may be useful as a priority queue where the cost of an entry may change over time. As such,
* the collections returned by {@link #entrySet()}, {@link #keySet()}, and {@link #values()} all
* extend {@link Deque}. The order of the entries will be updated on any call to {@link #put(Object,
* Object))}, or a call to {@link Collection#add(Object)} on the entry set. Additionally, if the
* values are mutable objects, whose order may change, there is an {@link #update(Object)} method,
* which notifies the map that the given key may need to be repositioned. The associated collections
* also extend the {@link List} interface, providing fairly efficient implementations of
* {@link List#get(int)} and {@link List#indexOf(Object)}. Sequential access is best performed via
* {@link Collection#iterator()}, since this will use a linked list.
*
* @param <K> the type of the keys
* @param <V> the type of the values
*/
public interface ValueSortedMap<K, V> extends Map<K, V> {
public interface ValueSortedMapEntryList<K, V>
extends Set<Entry<K, V>>, List<Entry<K, V>>, Deque<Entry<K, V>> {
@Override
default Spliterator<Entry<K, V>> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED | Spliterator.DISTINCT);
}
}
public interface ValueSortedMapKeyList<K> extends Set<K>, List<K>, Deque<K> {
@Override
default Spliterator<K> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED | Spliterator.DISTINCT);
}
}
@Override
ValueSortedMapEntryList<K, V> entrySet();
/**
* Returns a key-value mapping associated with the greatest value strictly less than the given
* value, or {@code null} if there is no such value.
*
* @param value the value
* @return the found entry, or {@code null}
*/
Entry<K, V> lowerEntryByValue(V value);
/**
* Returns a key-value mapping associated with the greatest value less than or equal to the
* given value, or {@code null} if there is no such value.
*
* @param value the value
* @return the found entry, or {@code null}
*/
Entry<K, V> floorEntryByValue(V value);
/**
* Returns a key-value mapping associated with the least value greater than or equal to the
* given value, or {@code null} if there is no such value.
*
* @param value the value
* @return the found entry, or {@code null}
*/
Entry<K, V> ceilingEntryByValue(V value);
/**
* Returns a key-value mapping associated with the least value strictly greater than the given
* value, or {@code null} if there is no such value.
*
* @param value the value
* @return the found entry, or {@code null}
*/
Entry<K, V> higherEntryByValue(V value);
/**
* Returns a view of the portion of this map whose values range from {@code fromValue} to
* {@code toValue}. The returned map is an unmodifiable view.
*
* @param fromValue low endpoint of the values in the returned map
* @param fromInclusive {@code true} if the low endpoint is to be included in the returned view
* @param toValue high endpoint of the values in the returned map
* @param toInclusive {@code true} if the high endpoint is to be included in the returned view
* @return the view
*/
ValueSortedMap<K, V> subMapByValue(V fromValue, boolean fromInclusive, V toValue,
boolean toInclusive);
/**
* Returns a view of the portion of this map whose values are less than (or equal to, if
* {@code inclusive} is true) {@code toValue}. The returned map is an unmodifiable view.
*
* @param toValue high endpoint of the values in the returned map
* @param inclusive {@code true} if the high endpoint is to be included in the returned view
* @return the view
*/
ValueSortedMap<K, V> headMapByValue(V toValue, boolean inclusive);
/**
* Returns a view of the portion of this map whose values are greater than (or equal to, if
* {@code inclusive} is true) {@code toValue}. The returned map is an unmodifiable view.
*
* @param fromValue low endpoint of the values in the returned map
* @param inclusive {@code true} if the low endpoint is to be included in the returned view
* @return the view
*/
ValueSortedMap<K, V> tailMapByValue(V fromValue, boolean inclusive);
@Override
ValueSortedMapKeyList<K> keySet();
/**
* Notify the map of an external change to the cost of a key's associated value
*
* <p>
* This is meant to update the entry's position after a change in cost. The position may not
* necessarily change, however, if the cost did not change significantly.
*
* @param key the key whose associated value has changed in cost
* @return true if the entry's position changed
*/
boolean update(K key);
@Override
SortedList<V> values();
}

View file

@ -1,320 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util;
import java.util.*;
import ghidra.generic.util.datastruct.DynamicValueSortedTreeMap;
/**
* A set where the ordering of elements may change over time, based on an alternative comparator
*
* This is an implementation of {@link Set} where elements may be sorted by an alternative
* comparator (usually by "cost"), rather than by the natural ordering. It may seem odd, but the
* natural ordering is still used to determine the uniqueness of keys. That is, two elements that
* are unequal -- but are considered equal by the alternative comparator -- may co-exist in the
* set. (Note: in such cases, the two elements are ordered first-in first-out). Additionally, if
* the elements are mutable, then their ordering may change over time. This mode of operation is
* enabled by the {@link #update(Object)} method, which must be called to notify the set of any
* change to an element that may affect its order. This set also implements the {@link List} and
* {@link Deque} interfaces. Since the set is ordered, it makes sense to treat it as a list. It
* provides fairly efficient implementations of {@link #get(int)} and {@link #indexOf(Object)}.
* Sequential access is best performed via {@link #iterator()}, since this will use a linked list.
*
* The underlying implementation is backed by {@link DynamicValueSortedTreeMap}. Currently, it is
* not thread safe.
*
* @param <E> the type of the elements
*/
public class DynamicSortedTreeSet<E> extends AbstractSet<E> implements List<E>, Deque<E> {
private final transient DynamicValueSortedTreeMap<E, E>.ValueSortedTreeMapKeySet keys;
private final transient DynamicValueSortedTreeMap<E, E> map;
/**
* Construct a dynamic sorted tree set using the elements' natural ordering
*
* Other than, perhaps, a more convenient interface, this offers few if any benefits over the
* stock {@link Set}.
*/
public DynamicSortedTreeSet() {
map = new DynamicValueSortedTreeMap<>();
keys = map.keySet();
}
/**
* Construct a dynamic sorted tree set using a custom comparator to order the elements
* @param comparator the comparator, providing a total ordering of the values
*/
public DynamicSortedTreeSet(Comparator<E> comparator) {
map = new DynamicValueSortedTreeMap<>(comparator);
keys = map.keySet();
}
@Override
public boolean add(E e) {
return map.put(e, e) == null;
}
/**
* Inserts the element, ignoring index
*
* @param index ignore since the set is sorted
*/
@Override
public void add(int index, E element) {
add(element);
}
/**
* Inserts all elements from the given collection, ignoring index
*
* @param index ignore since the set is sorted
*/
@Override
public boolean addAll(int index, Collection<? extends E> c) {
return addAll(c);
}
/**
* Inserts the element, not necessarily first
*/
@Override
public void addFirst(E e) {
add(e);
}
/**
* Inserts the element, not necessarily last
*/
@Override
public void addLast(E e) {
add(e);
}
@Override
public void clear() {
map.clear();
}
@Override
public boolean contains(Object o) {
return map.containsKey(o);
}
@Override
public Iterator<E> descendingIterator() {
return keys.descendingIterator();
}
@Override
public E element() {
return keys.element();
}
@Override
public E get(int index) {
return keys.get(index);
}
@Override
public E getFirst() {
return keys.getFirst();
}
@Override
public E getLast() {
return keys.getLast();
}
@Override
public int indexOf(Object o) {
return keys.indexOf(o);
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public Iterator<E> iterator() {
return keys.iterator();
}
@Override
public int lastIndexOf(Object o) {
return keys.lastIndexOf(o);
}
@Override
public ListIterator<E> listIterator() {
return keys.listIterator();
}
@Override
public ListIterator<E> listIterator(int index) {
return keys.listIterator(index);
}
@Override
public boolean offer(E e) {
return add(e);
}
/**
* Inserts the element, not necessarily first
*/
@Override
public boolean offerFirst(E e) {
return add(e);
}
/**
* Inserts the element, not necessarily last
*/
@Override
public boolean offerLast(E e) {
return add(e);
}
@Override
public E peek() {
return keys.peek();
}
@Override
public E peekFirst() {
return keys.peekFirst();
}
@Override
public E peekLast() {
return keys.peekLast();
}
@Override
public E poll() {
return keys.poll();
}
@Override
public E pollFirst() {
return keys.pollFirst();
}
@Override
public E pollLast() {
return keys.pollLast();
}
@Override
public E pop() {
return keys.pop();
}
@Override
public void push(E e) {
add(e);
}
@Override
public E remove() {
return keys.remove();
}
@Override
public E remove(int index) {
return keys.remove(index);
}
@Override
public boolean remove(Object o) {
return keys.remove(o);
}
@Override
public boolean removeAll(Collection<?> c) {
return keys.removeAll(c);
}
@Override
public E removeFirst() {
return keys.removeFirst();
}
@Override
public boolean removeFirstOccurrence(Object o) {
return keys.removeFirstOccurrence(o);
}
@Override
public E removeLast() {
return keys.removeLast();
}
@Override
public boolean removeLastOccurrence(Object o) {
return keys.removeLastOccurrence(o);
}
@Override
public boolean retainAll(Collection<?> c) {
return keys.retainAll(c);
}
/**
* Replace the element at the given index with the given element
*
* Because the set is sorted, the index of the given element may not be the same as
* {@code index}. In fact, this is equivalent to removing the element at the given index, and
* then inserting the given element at its sorted position.
*/
@Override
public E set(int index, E element) {
E result = remove(index);
add(element);
return result;
}
@Override
public int size() {
return map.size();
}
@Override
public Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED | Spliterator.DISTINCT);
}
/**
* This operation is not supported
*/
@Override
public List<E> subList(int fromIndex, int toIndex) {
throw new UnsupportedOperationException();
}
/**
* Notify the queue of a change to an elements cost.
*
* This may cause the element's index to change.
* @param e the element whose cost may have changed
* @return true if the index changed
*/
public boolean update(E e) {
return map.update(e);
}
}

View file

@ -21,8 +21,8 @@ public class MathUtilities {
}
/**
* Perform unsigned division. Provides proper handling of all 64-bit unsigned
* values.
* Perform unsigned division. Provides proper handling of all 64-bit unsigned values.
*
* @param numerator unsigned numerator
* @param denominator positive divisor
* @return result of unsigned division
@ -47,8 +47,8 @@ public class MathUtilities {
}
/**
* Perform unsigned modulo. Provides proper handling of all 64-bit unsigned
* values.
* Perform unsigned modulo. Provides proper handling of all 64-bit unsigned values.
*
* @param numerator unsigned numerator
* @param denominator positive divisor
* @return result of unsigned modulo (i.e., remainder)
@ -97,4 +97,111 @@ public class MathUtilities {
}
}
/**
* Compute the minimum, treating the inputs as unsigned
*
* @param a the first value to consider
* @param b the second value to consider
* @return the minimum
*/
public static long unsignedMin(long a, long b) {
return (Long.compareUnsigned(a, b) < 0) ? a : b;
}
/**
* Compute the minimum, treating the inputs as unsigned
*
* @param a the first value to consider
* @param b the second value to consider
* @return the minimum
*/
public static int unsignedMin(int a, int b) {
return (Integer.compareUnsigned(a, b) < 0) ? a : b;
}
/**
* Compute the minimum, treating the inputs as unsigned
*
* <p>
* This method is overloaded to prevent accidental signed-extension on one of the inputs. This
* method will correctly zero-extend the {@code int} parameter before performing any comparison.
* Also note the return type is {@code int}, since b would never be selected if it overflows an
* {@code int}.
*
* @param a the first value to consider
* @param b the second value to consider
* @return the minimum
*/
public static int unsignedMin(int a, long b) {
return (Long.compareUnsigned(a & 0x0ffffffffL, b) < 0) ? a : (int) b;
}
/**
* Compute the minimum, treating the inputs as unsigned
*
* <p>
* This method is overloaded to prevent accidental signed-extension on one of the inputs. This
* method will correctly zero-extend the {@code int} parameter before performing any comparison.
* Also note the return type is {@code int}, since b would never be selected if it overflows an
* {@code int}.
*
* @param a the first value to consider
* @param b the second value to consider
* @return the minimum
*/
public static int unsignedMin(long a, int b) {
return (Long.compareUnsigned(a, b & 0x0ffffffffL) < 0) ? (int) a : b;
}
/**
* Compute the maximum, treating the inputs as unsigned
*
* @param a the first value to consider
* @param b the second value to consider
* @return the maximum
*/
public static long unsignedMax(long a, long b) {
return (Long.compareUnsigned(a, b) > 0) ? a : b;
}
/**
* Compute the maximum, treating the inputs as unsigned
*
* @param a the first value to consider
* @param b the second value to consider
* @return the maximum
*/
public static int unsignedMax(int a, int b) {
return (Integer.compareUnsigned(a, b) > 0) ? a : b;
}
/**
* Compute the maximum, treating the inputs as unsigned
*
* <p>
* This method is overloaded to prevent accidental signed-extension on one of the inputs. This
* method will correctly zero-extend the {@code int} parameter before performing any comparison.
*
* @param a the first value to consider
* @param b the second value to consider
* @return the maximum
*/
public static long unsignedMax(int a, long b) {
return (Long.compareUnsigned(a & 0x0ffffffffL, b) > 0) ? a : b;
}
/**
* Compute the maximum, treating the inputs as unsigned
*
* <p>
* This method is overloaded to prevent accidental signed-extension on one of the inputs. This
* method will correctly zero-extend the {@code int} parameter before performing any comparison.
*
* @param a the first value to consider
* @param b the second value to consider
* @return the maximum
*/
public static long unsignedMax(long a, int b) {
return (Long.compareUnsigned(a, b & 0x0ffffffffL) > 0) ? a : b;
}
}

View file

@ -57,9 +57,8 @@ public final class NumericUtilities {
}
/**
* parses the given string as a numeric value, detecting whether
* or not it begins with a Hex prefix, and if not, parses as a
* long int value.
* parses the given string as a numeric value, detecting whether or not it begins with a Hex
* prefix, and if not, parses as a long int value.
*/
public static long parseNumber(String numStr) {
long value = 0;
@ -85,9 +84,8 @@ public final class NumericUtilities {
}
/**
* parses the given string as a numeric value, detecting whether
* or not it begins with a Hex prefix, and if not, parses as a
* long int value.
* parses the given string as a numeric value, detecting whether or not it begins with a Hex
* prefix, and if not, parses as a long int value.
*/
public static long parseLong(String numStr) {
String origStr = numStr;
@ -132,9 +130,8 @@ public final class NumericUtilities {
}
/**
* parses the given string as a numeric value, detecting whether
* or not it begins with a Hex prefix, and if not, parses as a
* long int value.
* parses the given string as a numeric value, detecting whether or not it begins with a Hex
* prefix, and if not, parses as a long int value.
*/
public static long parseOctLong(String numStr) {
@ -193,8 +190,9 @@ public final class NumericUtilities {
}
/**
* returns the value of the specified long as hexadecimal, prefixing
* with the HEX_PREFIX_x string.
* returns the value of the specified long as hexadecimal, prefixing with the HEX_PREFIX_x
* string.
*
* @param value the long value to convert
*/
public final static String toHexString(long value) {
@ -202,8 +200,9 @@ public final class NumericUtilities {
}
/**
* returns the value of the specified long as hexadecimal, prefixing
* with the HEX_PREFIX_x string.
* returns the value of the specified long as hexadecimal, prefixing with the HEX_PREFIX_x
* string.
*
* @param value the long value to convert
* @param size number of bytes to be represented
*/
@ -215,8 +214,9 @@ public final class NumericUtilities {
}
/**
* returns the value of the specified long as signed hexadecimal, prefixing
* with the HEX_PREFIX_x string.
* returns the value of the specified long as signed hexadecimal, prefixing with the
* HEX_PREFIX_x string.
*
* @param value the long value to convert
*/
public final static String toSignedHexString(long value) {
@ -256,8 +256,9 @@ public final class NumericUtilities {
}
/**
* Get an unsigned aligned value corresponding to the specified unsigned value
* which will be greater than or equal the specified value.
* Get an unsigned aligned value corresponding to the specified unsigned value which will be
* greater than or equal the specified value.
*
* @param unsignedValue value to be aligned
* @param alignment alignment
* @return aligned value
@ -280,19 +281,38 @@ public final class NumericUtilities {
/**
* Convert a masked value into a hexadecimal-ish string.
*
* Converts the data to hexadecimal, placing an X where a nibble is unknown. Where a nibble
* is partially defined, it is displayed as four bits in brackets []. Bits are displayed
* as x, or the defined value.
* Converts the data to hexadecimal, placing an X where a nibble is unknown. Where a nibble is
* partially defined, it is displayed as four bits in brackets []. Bits are displayed as x, or
* the defined value.
*
* For example, consider the mask 00001111:01011100, and the value 00001001:00011000. This
* will display as {@code X8:[x0x1][10xx]}. To see the correlation, consider the table:
* <table><caption></caption>
* <tr><th>Display</th><th>{@code X}</th> <th>{@code 8}</th> <th>{@code :}</th>
* <th>{@code [x0x1]}</th><th>{@code [10xx]}</th></tr>
* <tr><th>Mask</th> <td>{@code 0000}</td><td>{@code 1111}</td><td>{@code :}</td>
* <td>{@code 0101}</td> <td>{@code 1100}</td> </tr>
* <tr><th>Value</th> <td>{@code 0000}</td><td>{@code 1000}</td><td>{@code :}</td>
* <td>{@code 0001}</td> <td>{@code 1000}</td> </tr>
* For example, consider the mask 00001111:01011100, and the value 00001001:00011000. This will
* display as {@code X8:[x0x1][10xx]}. To see the correlation, consider the table:
* <table>
* <caption></caption>
* <tr>
* <th>Display</th>
* <th>{@code X}</th>
* <th>{@code 8}</th>
* <th>{@code :}</th>
* <th>{@code [x0x1]}</th>
* <th>{@code [10xx]}</th>
* </tr>
* <tr>
* <th>Mask</th>
* <td>{@code 0000}</td>
* <td>{@code 1111}</td>
* <td>{@code :}</td>
* <td>{@code 0101}</td>
* <td>{@code 1100}</td>
* </tr>
* <tr>
* <th>Value</th>
* <td>{@code 0000}</td>
* <td>{@code 1000}</td>
* <td>{@code :}</td>
* <td>{@code 0001}</td>
* <td>{@code 1000}</td>
* </tr>
* </table>
*
* @param msk the mask
@ -364,6 +384,7 @@ public final class NumericUtilities {
* Philosophically, it is hexadecimal, but the only valid digits are 0 and F. Any
* partially-included nibble will be broken down into bracketed bits. Displaying masks in this
* way is convenient when shown proximal to related masked values.
*
* @param msk the mask
* @param n the number of nibbles, starting at the right
* @param truncate true if leading Xs may be truncated
@ -420,6 +441,7 @@ public final class NumericUtilities {
/**
* The reverse of {@link #convertMaskedValueToHexString(long, long, int, boolean, int, String)}
*
* @param msk an object to receive the resulting mask
* @param val an object to receive the resulting value
* @param hex the input string to parse
@ -509,7 +531,8 @@ public final class NumericUtilities {
/**
* Render <code>number</code> in different bases using the default signedness mode.
* <p>This invokes {@linkplain #formatNumber(long, int, SignednessFormatMode)} with a
* <p>
* This invokes {@linkplain #formatNumber(long, int, SignednessFormatMode)} with a
* <code>mode</code> parameter of <code>{@linkplain SignednessFormatMode#DEFAULT}</code>.
*
* @param number The number to represent
@ -524,30 +547,129 @@ public final class NumericUtilities {
/**
* Provide renderings of <code>number</code> in different bases:
* <ul>
* <li> <code>0</code> - renders <code>number</code> as an escaped character sequence</li>
* <li> <code>2</code> - renders <code>number</code> as a <code>base-2</code> integer</li>
* <li> <code>8</code> - renders <code>number</code> as a <code>base-8</code> integer</li>
* <li> <code>10</code> - renders <code>number</code> as a <code>base-10</code> integer</li>
* <li> <code>16</code> (default) - renders <code>number</code> as a <code>base-16</code> integer</li>
* <li><code>0</code> - renders <code>number</code> as an escaped character sequence</li>
* <li><code>2</code> - renders <code>number</code> as a <code>base-2</code> integer</li>
* <li><code>8</code> - renders <code>number</code> as a <code>base-8</code> integer</li>
* <li><code>10</code> - renders <code>number</code> as a <code>base-10</code> integer</li>
* <li><code>16</code> (default) - renders <code>number</code> as a <code>base-16</code>
* integer</li>
* </ul>
* <table><caption></caption>
* <tr><th>Number</th><th>Radix</th><th>DEFAULT Mode Alias</th><th style="text-align:center"><i>UNSIGNED</i> Mode Value</th><th><i>SIGNED</i> Mode Value</th></tr>
* <tr><td>&nbsp;</td><td></td><td><i></i></td><td></td><td></td></tr>
* <tr style="text-align:right;font-family: monospace"><td>100</td><td>2</td><td><i>UNSIGNED</i></td><td>1100100b</td><td>1100100b</td></tr>
* <tr style="text-align:right;font-family: monospace"><td>100</td><td>8</td><td><i>UNSIGNED</i></td><td>144o</td><td>144o</td></tr>
* <tr style="text-align:right;font-family: monospace"><td>100</td><td>10</td><td><i>SIGNED</i></td><td>100</td><td>100</td></tr>
* <tr style="text-align:right;font-family: monospace"><td>100</td><td>16</td><td><i>UNSIGNED</i></td><td>64h</td><td>64h</td></tr>
* <tr><td>&nbsp;</td><td></td><td><i></i></td><td></td><td></td></tr>
* <tr style="text-align:right;font-family: monospace"><td>-1</td><td>2</td><td><i>UNSIGNED</i></td><td>1111111111111111111111111111111111111111111111111111111111111111b</td><td>-1b</td></tr>
* <tr style="text-align:right;font-family: monospace"><td>-1</td><td>8</td><td><i>UNSIGNED</i></td><td>1777777777777777777777o</td><td>-1o</td></tr>
* <tr style="text-align:right;font-family: monospace"><td>-1</td><td>10</td><td><i>SIGNED</i></td><td>18446744073709551615</td><td>-1</td></tr>
* <tr style="text-align:right;font-family: monospace"><td>-1</td><td>16</td><td><i>UNSIGNED</i></td><td>ffffffffffffffffh</td><td>-1h</td></tr>
*<tr><td>&nbsp;</td><td></td><td><i></i></td><td></td><td></td></tr>
* <tr style="text-align:right;font-family: monospace"><td>-100</td><td>2</td><td><i>UNSIGNED</i></td><td>1111111111111111111111111111111111111111111111111111111110011100b</td><td>-1100100b</td></tr>
* <tr style="text-align:right;font-family: monospace"><td>-100</td><td>8</td><td><i>UNSIGNED</i></td><td>1777777777777777777634o</td><td>-144o</td></tr>
* <tr style="text-align:right;font-family: monospace"><td>-100</td><td>10</td><td><i>SIGNED</i></td><td>18446744073709551516</td><td>-100</td></tr>
* <tr style="text-align:right;font-family: monospace"><td>-100</td><td>16</td><td><i>UNSIGNED</i></td><td>ffffffffffffff9ch</td><td>-64h</td></tr>
* <table>
* <caption></caption>
* <tr>
* <th>Number</th>
* <th>Radix</th>
* <th>DEFAULT Mode Alias</th>
* <th style="text-align:center"><i>UNSIGNED</i> Mode Value</th>
* <th><i>SIGNED</i> Mode Value</th>
* </tr>
* <tr>
* <td>&nbsp;</td>
* <td></td>
* <td><i></i></td>
* <td></td>
* <td></td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>100</td>
* <td>2</td>
* <td><i>UNSIGNED</i></td>
* <td>1100100b</td>
* <td>1100100b</td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>100</td>
* <td>8</td>
* <td><i>UNSIGNED</i></td>
* <td>144o</td>
* <td>144o</td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>100</td>
* <td>10</td>
* <td><i>SIGNED</i></td>
* <td>100</td>
* <td>100</td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>100</td>
* <td>16</td>
* <td><i>UNSIGNED</i></td>
* <td>64h</td>
* <td>64h</td>
* </tr>
* <tr>
* <td>&nbsp;</td>
* <td></td>
* <td><i></i></td>
* <td></td>
* <td></td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>-1</td>
* <td>2</td>
* <td><i>UNSIGNED</i></td>
* <td>1111111111111111111111111111111111111111111111111111111111111111b</td>
* <td>-1b</td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>-1</td>
* <td>8</td>
* <td><i>UNSIGNED</i></td>
* <td>1777777777777777777777o</td>
* <td>-1o</td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>-1</td>
* <td>10</td>
* <td><i>SIGNED</i></td>
* <td>18446744073709551615</td>
* <td>-1</td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>-1</td>
* <td>16</td>
* <td><i>UNSIGNED</i></td>
* <td>ffffffffffffffffh</td>
* <td>-1h</td>
* </tr>
* <tr>
* <td>&nbsp;</td>
* <td></td>
* <td><i></i></td>
* <td></td>
* <td></td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>-100</td>
* <td>2</td>
* <td><i>UNSIGNED</i></td>
* <td>1111111111111111111111111111111111111111111111111111111110011100b</td>
* <td>-1100100b</td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>-100</td>
* <td>8</td>
* <td><i>UNSIGNED</i></td>
* <td>1777777777777777777634o</td>
* <td>-144o</td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>-100</td>
* <td>10</td>
* <td><i>SIGNED</i></td>
* <td>18446744073709551516</td>
* <td>-100</td>
* </tr>
* <tr style="text-align:right;font-family: monospace">
* <td>-100</td>
* <td>16</td>
* <td><i>UNSIGNED</i></td>
* <td>ffffffffffffff9ch</td>
* <td>-64h</td>
* </tr>
* </table>
*
* @param number The number to represent
* @param radix The base in which <code>number</code> is represented
* @param mode Specifies how the number is formatted with respect to its signed-ness
@ -637,8 +759,7 @@ public final class NumericUtilities {
}
/**
* Convert the given byte into a two character String, padding with a leading 0 if
* needed.
* Convert the given byte into a two character String, padding with a leading 0 if needed.
*
* @param b the byte
* @return the byte string
@ -728,6 +849,7 @@ public final class NumericUtilities {
/**
* Determine if the provided Number is an integer type -- Byte, Short, Integer, or Long.
*
* @param number the object to check for for integer-type
* @return true if the provided number is an integer-type, false otherwise
*/
@ -738,6 +860,7 @@ public final class NumericUtilities {
/**
* Determine if the provided Number class is an integer type.
*
* @param numClass Class of an object
* @return true if the class parameter is a integer type, false otherwise
*/
@ -747,6 +870,7 @@ public final class NumericUtilities {
/**
* Determine if the provided Number is a floating-point type -- Float or Double.
*
* @param number the object to check for for floating-point-type
* @return true if the provided number is a floating-point-type, false otherwise
*/
@ -757,6 +881,7 @@ public final class NumericUtilities {
/**
* Determine if the provided Number class is a floating-point type.
*
* @param numClass Class of an object
* @return true if the class parameter is a floating-point type, false otherwise
*/
@ -765,12 +890,12 @@ public final class NumericUtilities {
}
/**
* Provides the protocol for rendering integer-type numbers in different
* signed-ness modes.
* Provides the protocol for rendering integer-type numbers in different signed-ness modes.
*/
private static interface IntegerRadixRenderer {
/**
* Format the given number in the provided radix base.
*
* @param number the number to render
* @param radix the base in which to render
* @return a string representing the provided number in the given base
@ -836,8 +961,8 @@ public final class NumericUtilities {
/**
* {@inheritDoc}
* <p>
* Values to be rendered in binary, octal, or hexadecimal bases are rendered
* as unsigned, numbers rendered in decimal are rendered as signed.
* Values to be rendered in binary, octal, or hexadecimal bases are rendered as unsigned,
* numbers rendered in decimal are rendered as signed.
*/
@Override
public String toString(long number, int radix) {

View file

@ -0,0 +1,216 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.*;
/**
* Class to provide a map with weak values, backed by a given map
*
* @param <K> the type of keys
* @param <V> the type of values
*/
public abstract class AbstractWeakValueMap<K, V> implements Map<K, V> {
protected ReferenceQueue<V> refQueue;
/**
* Constructs a new weak map
*/
protected AbstractWeakValueMap() {
refQueue = new ReferenceQueue<>();
}
/**
* Returns the backing map
*
* @return the map
*/
protected abstract Map<K, WeakValueRef<K, V>> getRefMap();
@Override
public V put(K key, V value) {
processQueue();
WeakValueRef<K, V> ref = new WeakValueRef<>(key, value, refQueue);
WeakValueRef<K, V> oldRef = getRefMap().put(key, ref);
if (oldRef != null) {
return oldRef.get();
}
return null;
}
@Override
public V get(Object key) {
processQueue();
WeakValueRef<K, V> ref = getRefMap().get(key);
if (ref != null) {
return ref.get();
}
return null;
}
@Override
public int size() {
processQueue();
return getRefMap().size();
}
@Override
public void clear() {
getRefMap().clear();
refQueue = new ReferenceQueue<>();
}
@Override
public boolean isEmpty() {
processQueue();
return getRefMap().isEmpty();
}
@Override
public boolean containsKey(Object key) {
processQueue();
return getRefMap().containsKey(key);
}
@Override
public boolean containsValue(Object value) {
processQueue();
Iterator<WeakValueRef<K, V>> it = getRefMap().values().iterator();
while (it.hasNext()) {
WeakValueRef<K, V> ref = it.next();
if (value.equals(ref.get())) {
return true;
}
}
return false;
}
@Override
public Collection<V> values() {
ArrayList<V> list = new ArrayList<>(getRefMap().size());
Iterator<WeakValueRef<K, V>> it = getRefMap().values().iterator();
while (it.hasNext()) {
WeakValueRef<K, V> ref = it.next();
V value = ref.get();
if (value != null) {
list.add(value);
}
}
return list;
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
Iterator<? extends K> it = map.keySet().iterator();
while (it.hasNext()) {
K key = it.next();
V value = map.get(key);
if (value != null) {
put(key, value);
}
}
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
processQueue();
Set<Map.Entry<K, V>> list = new HashSet<>();
Set<Map.Entry<K, WeakValueRef<K, V>>> entrySet = getRefMap().entrySet();
Iterator<Map.Entry<K, WeakValueRef<K, V>>> it = entrySet.iterator();
while (it.hasNext()) {
Map.Entry<K, WeakValueRef<K, V>> next = it.next();
WeakValueRef<K, V> valueRef = next.getValue();
V value = valueRef.get();
if (value != null) {
list.add(new GeneratedEntry(next.getKey(), value));
}
}
return list;
}
@Override
public Set<K> keySet() {
processQueue();
return getRefMap().keySet();
}
@Override
public V remove(Object key) {
WeakValueRef<K, V> ref = getRefMap().remove(key);
if (ref != null) {
return ref.get();
}
return null;
}
@SuppressWarnings("unchecked")
protected void processQueue() {
WeakValueRef<K, V> ref;
while ((ref = (WeakValueRef<K, V>) refQueue.poll()) != null) {
getRefMap().remove(ref.key);
}
}
/**
* An entry for the "entrySet" method, since internally, entries are of weak-referenced values.
*/
protected class GeneratedEntry implements Map.Entry<K, V> {
K key;
V value;
GeneratedEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
this.value = value;
return put(key, value);
}
}
/**
* A weak value ref that also knows its key in the map.
*
* <p>
* Used for processing the reference queue, so we know which keys to remove.
*
* @param <K> the type of key
* @param <V> the type of value
*/
protected static class WeakValueRef<K, V> extends WeakReference<V> {
K key;
WeakValueRef(K key, V value, ReferenceQueue<V> refQueue) {
super(value, refQueue);
this.key = key;
}
}
}

View file

@ -0,0 +1,216 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import java.util.*;
/**
* Class to provide a navigable, e.g., tree-, map with weak values
*
* @param <K> the type of keys
* @param <V> the type of values
*/
public abstract class AbstractWeakValueNavigableMap<K, V> extends AbstractWeakValueMap<K, V>
implements NavigableMap<K, V> {
/**
* A view of this same map that limits or changes the order of the keys
*
* <p>
* TODO: By virtue of extending (indirectly) {@link AbstractWeakValueMap}, this view inherits a
* unique, but totally unused, {@link AbstractWeakValueMap#refQueue}. This is a small and
* harmless, but unnecessary waste.
*
* @param <K> the type of keys
* @param <V> the type of values
*/
protected static class NavigableView<K, V> extends AbstractWeakValueNavigableMap<K, V> {
protected final AbstractWeakValueNavigableMap<K, V> map;
protected final NavigableMap<K, WeakValueRef<K, V>> mod;
public NavigableView(AbstractWeakValueNavigableMap<K, V> map,
NavigableMap<K, WeakValueRef<K, V>> sub) {
this.map = map;
this.mod = Collections.unmodifiableNavigableMap(sub);
}
@Override
protected NavigableMap<K, WeakValueRef<K, V>> getRefMap() {
map.processQueue();
return mod;
}
}
@Override
protected abstract NavigableMap<K, WeakValueRef<K, V>> getRefMap();
@Override
public Comparator<? super K> comparator() {
return getRefMap().comparator();
}
@Override
public K firstKey() {
processQueue();
return getRefMap().firstKey();
}
@Override
public K lastKey() {
processQueue();
return getRefMap().lastKey();
}
/**
* Construct a generated (wrapper) entry, for the entry-retrieval methods.
*
* <p>
* This handles the null case in one place.
*
* @param ent the entry to wrap, possibly null
* @return the generated entry, or null
*/
protected GeneratedEntry generateEntry(Entry<K, WeakValueRef<K, V>> ent) {
if (ent == null) {
return null;
}
return new GeneratedEntry(ent.getKey(), ent.getValue().get());
}
@Override
public Entry<K, V> lowerEntry(K key) {
processQueue();
return generateEntry(getRefMap().lowerEntry(key));
}
@Override
public K lowerKey(K key) {
processQueue();
return getRefMap().lowerKey(key);
}
@Override
public Entry<K, V> floorEntry(K key) {
processQueue();
return generateEntry(getRefMap().floorEntry(key));
}
@Override
public K floorKey(K key) {
processQueue();
return getRefMap().floorKey(key);
}
@Override
public Entry<K, V> ceilingEntry(K key) {
processQueue();
return generateEntry(getRefMap().ceilingEntry(key));
}
@Override
public K ceilingKey(K key) {
processQueue();
return getRefMap().ceilingKey(key);
}
@Override
public Entry<K, V> higherEntry(K key) {
processQueue();
return generateEntry(getRefMap().higherEntry(key));
}
@Override
public K higherKey(K key) {
processQueue();
return getRefMap().higherKey(key);
}
@Override
public Entry<K, V> firstEntry() {
processQueue();
return generateEntry(getRefMap().firstEntry());
}
@Override
public Entry<K, V> lastEntry() {
processQueue();
return generateEntry(getRefMap().lastEntry());
}
@Override
public Entry<K, V> pollFirstEntry() {
processQueue();
return generateEntry(getRefMap().pollFirstEntry());
}
@Override
public Entry<K, V> pollLastEntry() {
processQueue();
return generateEntry(getRefMap().pollLastEntry());
}
@Override
public NavigableMap<K, V> descendingMap() {
processQueue();
return new NavigableView<>(this, getRefMap().descendingMap());
}
@Override
public NavigableSet<K> navigableKeySet() {
return getRefMap().navigableKeySet();
}
@Override
public NavigableSet<K> descendingKeySet() {
return getRefMap().descendingKeySet();
}
@Override
public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey,
boolean toInclusive) {
processQueue();
return new NavigableView<>(this,
getRefMap().subMap(fromKey, fromInclusive, toKey, toInclusive));
}
@Override
public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
processQueue();
return new NavigableView<>(this, getRefMap().headMap(toKey, inclusive));
}
@Override
public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
processQueue();
return new NavigableView<>(this, getRefMap().tailMap(fromKey, inclusive));
}
@Override
public SortedMap<K, V> subMap(K fromKey, K toKey) {
processQueue();
return subMap(fromKey, true, toKey, false);
}
@Override
public SortedMap<K, V> headMap(K toKey) {
return headMap(toKey, false);
}
@Override
public SortedMap<K, V> tailMap(K fromKey) {
return tailMap(fromKey, true);
}
}

View file

@ -15,192 +15,36 @@
*/
package ghidra.util.datastruct;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
/**
* Class to provide a hash map with weak values.
*/
public class WeakValueHashMap<K, V> implements Map<K, V> {
private HashMap<K, WeakValueRef<K, V>> hashMap;
private ReferenceQueue<V> refQueue;
public class WeakValueHashMap<K, V> extends AbstractWeakValueMap<K, V> {
private Map<K, WeakValueRef<K, V>> refMap;
/**
* Constructs a new weak map
*/
public WeakValueHashMap() {
hashMap = new HashMap<>();
refQueue = new ReferenceQueue<>();
super();
refMap = new HashMap<>();
}
/**
* Constructs a new weak map with the given initial size
*
* @param initialSize the initial size of the backing map
*/
public WeakValueHashMap(int initialSize) {
hashMap = new HashMap<>(initialSize);
refQueue = new ReferenceQueue<>();
super();
refMap = new HashMap<>(initialSize);
}
@Override
public V put(K key, V value) {
processQueue();
WeakValueRef<K, V> ref = new WeakValueRef<>(key, value, refQueue);
WeakValueRef<K, V> oldRef = hashMap.put(key, ref);
if (oldRef != null) {
return oldRef.get();
}
return null;
protected Map<K, WeakValueRef<K, V>> getRefMap() {
return refMap;
}
@Override
public V get(Object key) {
processQueue();
WeakValueRef<K, V> ref = hashMap.get(key);
if (ref != null) {
return ref.get();
}
return null;
}
@Override
public int size() {
processQueue();
return hashMap.size();
}
@Override
public void clear() {
hashMap.clear();
refQueue = new ReferenceQueue<>();
}
@Override
public boolean isEmpty() {
processQueue();
return hashMap.isEmpty();
}
@Override
public boolean containsKey(Object key) {
processQueue();
return hashMap.containsKey(key);
}
@Override
public boolean containsValue(Object value) {
processQueue();
Iterator<WeakValueRef<K, V>> it = hashMap.values().iterator();
while (it.hasNext()) {
WeakValueRef<K, V> ref = it.next();
if (value.equals(ref.get())) {
return true;
}
}
return false;
}
@Override
public Collection<V> values() {
ArrayList<V> list = new ArrayList<>(hashMap.size());
Iterator<WeakValueRef<K, V>> it = hashMap.values().iterator();
while (it.hasNext()) {
WeakValueRef<K, V> ref = it.next();
V value = ref.get();
if (value != null) {
list.add(value);
}
}
return list;
}
@Override
public void putAll(Map<? extends K, ? extends V> map) {
Iterator<? extends K> it = map.keySet().iterator();
while (it.hasNext()) {
K key = it.next();
V value = map.get(key);
if (value != null) {
put(key, value);
}
}
}
@Override
public Set<Map.Entry<K, V>> entrySet() {
processQueue();
Set<Map.Entry<K, V>> list = new HashSet<>();
Set<Map.Entry<K, WeakValueRef<K, V>>> entrySet = hashMap.entrySet();
Iterator<Map.Entry<K, WeakValueRef<K, V>>> it = entrySet.iterator();
while (it.hasNext()) {
Map.Entry<K, WeakValueRef<K, V>> next = it.next();
WeakValueRef<K, V> valueRef = next.getValue();
V value = valueRef.get();
if (value != null) {
list.add(new GeneratedEntry(next.getKey(), value));
}
}
return list;
}
@Override
public Set<K> keySet() {
processQueue();
return hashMap.keySet();
}
@Override
public V remove(Object key) {
WeakValueRef<K, V> ref = hashMap.remove(key);
if (ref != null) {
return ref.get();
}
return null;
}
@SuppressWarnings("unchecked")
private void processQueue() {
WeakValueRef<K, V> ref;
while ((ref = (WeakValueRef<K, V>) refQueue.poll()) != null) {
hashMap.remove(ref.key);
}
}
class GeneratedEntry implements Map.Entry<K, V> {
K key;
V value;
GeneratedEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() {
return key;
}
@Override
public V getValue() {
return value;
}
@Override
public V setValue(V value) {
return put(key, value);
}
}
static class WeakValueRef<K, V> extends WeakReference<V> {
K key;
WeakValueRef(K key, V value, ReferenceQueue<V> refQueue) {
super(value, refQueue);
this.key = key;
}
}
}

View file

@ -0,0 +1,48 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util.datastruct;
import java.util.*;
/**
* Class to provide a tree map with weak values.
*/
public class WeakValueTreeMap<K, V> extends AbstractWeakValueNavigableMap<K, V> {
protected final NavigableMap<K, WeakValueRef<K, V>> refMap;
/**
* Constructs a new weak map
*/
public WeakValueTreeMap() {
super();
refMap = new TreeMap<>();
}
/**
* Constructs a new weak map with keys ordered according to the given comparator
*
* @param comparator the comparator, or {@code null} for the natural ordering
*/
public WeakValueTreeMap(Comparator<K> comparator) {
super();
refMap = new TreeMap<>(comparator);
}
@Override
protected NavigableMap<K, WeakValueRef<K, V>> getRefMap() {
return refMap;
}
}

View file

@ -13,35 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util;
package ghidra.generic.util.datastruct;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.*;
import java.util.Map.Entry;
import java.util.Random;
import java.util.Set;
import org.apache.commons.collections4.comparators.ReverseComparator;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.Test;
import ghidra.generic.util.datastruct.DynamicValueSortedTreeMap;
public class DynamicValueSortedTreeMapTest {
public static class NonComparable {
}
import ghidra.generic.util.datastruct.TreeValueSortedMap;
import ghidra.generic.util.datastruct.ValueSortedMap;
public class TreeValueSortedMapTest {
@Test
public void testNaturalOrder() {
DynamicValueSortedTreeMap<String, Integer> queue = new DynamicValueSortedTreeMap<>();
ValueSortedMap<String, Integer> queue = TreeValueSortedMap.createWithNaturalOrder();
queue.put("2nd", 2);
queue.put("1st", 1);
queue.put("3rd", 3);
@ -49,17 +38,10 @@ public class DynamicValueSortedTreeMapTest {
assertEquals(Arrays.asList(new String[] { "1st", "2nd", "3rd" }), ordered);
}
@Test(expected = ClassCastException.class)
public void testUnorderedError() {
DynamicValueSortedTreeMap<String, NonComparable> queue = new DynamicValueSortedTreeMap<>();
queue.put("2nd", new NonComparable());
queue.put("1st", new NonComparable());
}
@Test
public void testExplicitOrdered() {
DynamicValueSortedTreeMap<String, Integer> queue =
new DynamicValueSortedTreeMap<>(new ReverseComparator<>());
ValueSortedMap<String, Integer> queue =
TreeValueSortedMap.createWithComparator(new ReverseComparator<>());
queue.put("2nd", 2);
queue.put("1st", 1);
queue.put("3rd", 3);
@ -67,15 +49,101 @@ public class DynamicValueSortedTreeMapTest {
assertEquals(Arrays.asList(new String[] { "3rd", "2nd", "1st" }), ordered);
}
@Test
public void testBoundsSearches() {
ValueSortedMap<String, Integer> queue = TreeValueSortedMap.createWithNaturalOrder();
assertNull(queue.lowerEntryByValue(4));
assertNull(queue.floorEntryByValue(4));
assertNull(queue.ceilingEntryByValue(4));
assertNull(queue.higherEntryByValue(4));
assertEquals(-1, queue.values().lowerIndex(4));
assertEquals(-1, queue.values().floorIndex(4));
assertEquals(-1, queue.values().ceilingIndex(4));
assertEquals(-1, queue.values().higherIndex(4));
queue.put("4th", 4);
assertNull(queue.lowerEntryByValue(3));
assertEquals(-1, queue.values().lowerIndex(3));
assertNull(queue.lowerEntryByValue(4));
assertEquals(-1, queue.values().lowerIndex(4));
assertEquals("4th", queue.lowerEntryByValue(5).getKey());
assertEquals(0, queue.values().lowerIndex(5));
assertNull(queue.floorEntryByValue(3));
assertEquals(-1, queue.values().floorIndex(3));
assertEquals("4th", queue.floorEntryByValue(4).getKey());
assertEquals(0, queue.values().floorIndex(4));
assertEquals("4th", queue.floorEntryByValue(5).getKey());
assertEquals(0, queue.values().floorIndex(5));
assertEquals("4th", queue.ceilingEntryByValue(3).getKey());
assertEquals(0, queue.values().ceilingIndex(3));
assertEquals("4th", queue.ceilingEntryByValue(4).getKey());
assertEquals(0, queue.values().ceilingIndex(4));
assertNull(queue.ceilingEntryByValue(5));
assertEquals(-1, queue.values().ceilingIndex(5));
assertEquals("4th", queue.higherEntryByValue(3).getKey());
assertEquals(0, queue.values().higherIndex(3));
assertNull(queue.higherEntryByValue(4));
assertEquals(-1, queue.values().higherIndex(4));
assertNull(queue.higherEntryByValue(5));
assertEquals(-1, queue.values().higherIndex(5));
queue.put("2nd", 2);
queue.put("6th", 6);
assertNull(queue.lowerEntryByValue(1));
assertNull(queue.lowerEntryByValue(2));
assertEquals("2nd", queue.lowerEntryByValue(3).getKey());
assertEquals("2nd", queue.lowerEntryByValue(4).getKey());
assertEquals("4th", queue.lowerEntryByValue(5).getKey());
assertEquals("4th", queue.lowerEntryByValue(6).getKey());
assertEquals("6th", queue.lowerEntryByValue(7).getKey());
assertEquals(2, queue.values().lowerIndex(7)); // Only this once
assertNull(queue.floorEntryByValue(1));
assertEquals("2nd", queue.floorEntryByValue(2).getKey());
assertEquals("2nd", queue.floorEntryByValue(3).getKey());
assertEquals("4th", queue.floorEntryByValue(4).getKey());
assertEquals("4th", queue.floorEntryByValue(5).getKey());
assertEquals("6th", queue.floorEntryByValue(6).getKey());
assertEquals("6th", queue.floorEntryByValue(7).getKey());
assertEquals("2nd", queue.ceilingEntryByValue(1).getKey());
assertEquals("2nd", queue.ceilingEntryByValue(2).getKey());
assertEquals("4th", queue.ceilingEntryByValue(3).getKey());
assertEquals("4th", queue.ceilingEntryByValue(4).getKey());
assertEquals("6th", queue.ceilingEntryByValue(5).getKey());
assertEquals("6th", queue.ceilingEntryByValue(6).getKey());
assertNull(queue.ceilingEntryByValue(7));
assertEquals("2nd", queue.higherEntryByValue(1).getKey());
assertEquals("4th", queue.higherEntryByValue(2).getKey());
assertEquals("4th", queue.higherEntryByValue(3).getKey());
assertEquals("6th", queue.higherEntryByValue(4).getKey());
assertEquals("6th", queue.higherEntryByValue(5).getKey());
assertNull(queue.higherEntryByValue(6));
assertNull(queue.higherEntryByValue(7));
}
@Test
public void testIsEmpty() {
DynamicValueSortedTreeMap<String, Integer> queue = new DynamicValueSortedTreeMap<>();
ValueSortedMap<String, Integer> queue = TreeValueSortedMap.createWithNaturalOrder();
assertTrue(queue.isEmpty());
assertFalse(queue.containsKey("1st"));
assertFalse(queue.containsValue(1));
assertEquals(-1, queue.values().indexOf(1));
queue.put("1st", 1);
assertFalse(queue.isEmpty());
}
protected <K, V> void checkConsistent(DynamicValueSortedTreeMap<K, V> queue) {
protected <K, V> void checkConsistent(ValueSortedMap<K, V> queue) {
Iterator<Entry<K, V>> it = queue.entrySet().iterator();
V last = null;
Set<K> seen = new HashSet<>();
@ -119,7 +187,7 @@ public class DynamicValueSortedTreeMapTest {
final int COUNT = 1000;
final int ROUNDS = 5;
Random rand = new Random();
DynamicValueSortedTreeMap<String, Integer> queue = new DynamicValueSortedTreeMap<>();
ValueSortedMap<String, Integer> queue = TreeValueSortedMap.createWithNaturalOrder();
for (int r = 0; r < ROUNDS; r++) {
for (int i = 0; i < COUNT; i++) {
queue.put("Element" + i, rand.nextInt(50));
@ -133,7 +201,7 @@ public class DynamicValueSortedTreeMapTest {
public void testRemoveRandomly() {
final int COUNT = 100;
Random rand = new Random();
DynamicValueSortedTreeMap<String, Integer> queue = new DynamicValueSortedTreeMap<>();
ValueSortedMap<String, Integer> queue = TreeValueSortedMap.createWithNaturalOrder();
HashSet<String> all = new HashSet<>();
for (int i = 0; i < COUNT; i++) {
queue.put("Element" + i, rand.nextInt(50));
@ -157,7 +225,7 @@ public class DynamicValueSortedTreeMapTest {
public void testUpdateRandomly() {
final int COUNT = 100;
Random rand = new Random();
DynamicValueSortedTreeMap<String, Integer> queue = new DynamicValueSortedTreeMap<>();
ValueSortedMap<String, Integer> queue = TreeValueSortedMap.createWithNaturalOrder();
for (int i = 0; i < COUNT; i++) {
queue.put("Element" + i, rand.nextInt(50));
}
@ -176,7 +244,7 @@ public class DynamicValueSortedTreeMapTest {
public void testValueIndices() {
final int ROUNDS = 1000;
Random rand = new Random();
DynamicValueSortedTreeMap<String, Integer> queue = new DynamicValueSortedTreeMap<>();
ValueSortedMap<String, Integer> queue = TreeValueSortedMap.createWithNaturalOrder();
int[] vals = // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
new int[] { 0, 0, 1, 1, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6, 8, 8, 10 };
for (int r = 0; r < ROUNDS; r++) {
@ -216,7 +284,7 @@ public class DynamicValueSortedTreeMapTest {
public void testAsMonotonicQueue() {
final int COUNT = 1000;
Random rand = new Random();
DynamicValueSortedTreeMap<String, Integer> queue = new DynamicValueSortedTreeMap<>();
ValueSortedMap<String, Integer> queue = TreeValueSortedMap.createWithNaturalOrder();
for (int i = 0; i < COUNT; i++) {
queue.put("ElementA" + i, rand.nextInt(50));
}
@ -238,4 +306,46 @@ public class DynamicValueSortedTreeMapTest {
assertEquals(0, queue.size());
assertTrue(queue.isEmpty());
}
@Test
public void testClearViaKeyIterator() {
ValueSortedMap<String, Integer> queue = TreeValueSortedMap.createWithNaturalOrder();
for (int i = 0; i < 10; i++) {
queue.put("Element" + i, i);
}
Iterator<String> kit = queue.keySet().iterator();
while (kit.hasNext()) {
kit.next();
kit.remove();
}
assertTrue(queue.isEmpty());
}
@Test
public void testRemoveOddsViaValueIterator() {
ValueSortedMap<String, Integer> queue = TreeValueSortedMap.createWithNaturalOrder();
for (int i = 0; i < 10; i++) {
queue.put("Element" + i, i);
}
Iterator<Integer> vit = queue.values().iterator();
while (vit.hasNext()) {
int val = vit.next();
if (val % 2 == 1) {
vit.remove();
}
}
for (int val : queue.values()) {
assertEquals(0, val % 2);
}
}
@Test(expected = IllegalStateException.class)
public void testNominalBehaviorIteratorRemoveBeforeNext() {
Set<Integer> set = new HashSet<>();
set.add(5);
Iterator<Integer> it = set.iterator();
it.remove();
}
}

View file

@ -1,211 +0,0 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.Test;
public class DynamicSortedTreeSetTest {
public static class NonComparable {
public NonComparable(String key, int cost) {
this.key = key;
this.cost = cost;
}
@Override
public String toString() {
return key + "=" + cost;
}
protected String key;
protected int cost;
}
public static class TestElem extends NonComparable implements Comparable<TestElem> {
public TestElem(String key, int cost) {
super(key, cost);
}
@Override
public int compareTo(TestElem that) {
return key.compareTo(that.key);
}
}
public static class CostComparator implements Comparator<TestElem> {
@Override
public int compare(TestElem a, TestElem b) {
return a.cost - b.cost;
}
}
@Test
public void testNaturalOrder() {
DynamicSortedTreeSet<String> queue = new DynamicSortedTreeSet<>();
queue.add("2nd");
queue.add("1st");
queue.add("3rd");
List<String> ordered = new ArrayList<>(queue);
assertEquals(Arrays.asList(new String[] { "1st", "2nd", "3rd" }), ordered);
}
@Test(expected = ClassCastException.class)
public void testUnorderedError() {
DynamicSortedTreeSet<NonComparable> queue = new DynamicSortedTreeSet<>();
queue.add(new NonComparable("2nd", 2));
queue.add(new NonComparable("1st", 1));
}
@Test
public void testExplicitOrdered() {
DynamicSortedTreeSet<TestElem> queue = new DynamicSortedTreeSet<>(new CostComparator());
queue.add(new TestElem("2ndB", 2));
queue.add(new TestElem("2ndA", 2));
queue.add(new TestElem("1st", 1));
queue.add(new TestElem("3rd", 3));
List<String> ordered = new ArrayList<>();
for (TestElem elem : queue) {
ordered.add(elem.key);
}
assertEquals(Arrays.asList(new String[] { "1st", "2ndB", "2ndA", "3rd" }), ordered);
}
@Test
public void testIsEmpty() {
DynamicSortedTreeSet<TestElem> queue = new DynamicSortedTreeSet<>(new CostComparator());
assertTrue(queue.isEmpty());
queue.add(new TestElem("1st", 1));
assertFalse(queue.isEmpty());
}
protected <E> void checkConsistent(DynamicSortedTreeSet<E> queue, Comparator<E> comp) {
Iterator<E> it = queue.iterator();
E last = null;
Set<E> seen = new HashSet<>();
for (int i = 0; i < queue.size(); i++) {
E e = it.next();
assertTrue("Indices and iterator did not give same order", queue.get(i) == e);
assertEquals("Incorrect computed index", i, queue.indexOf(e));
if (!seen.add(e)) {
fail("Unique index did not give unique element");
}
if (last != null && comp.compare(last, e) > 0) {
fail("Costs should be monotonic");
}
last = e;
}
for (int i = queue.size(); i < queue.size() * 2; i++) {
try {
queue.get(i);
fail();
}
catch (IndexOutOfBoundsException e) {
// pass
}
}
for (int i = -queue.size(); i < 0; i++) {
try {
queue.get(i);
fail();
}
catch (IndexOutOfBoundsException e) {
// pass
}
}
}
@Test
public void testAddRandomly() {
final int COUNT = 1000;
final int ROUNDS = 10;
Random rand = new Random();
CostComparator comp = new CostComparator();
DynamicSortedTreeSet<TestElem> queue = new DynamicSortedTreeSet<>(comp);
for (int r = 0; r < ROUNDS; r++) {
for (int i = 0; i < COUNT; i++) {
queue.add(new TestElem("Element" + i, rand.nextInt(50)));
}
checkConsistent(queue, comp);
queue.clear();
}
}
@Test
public void testRemoveRandomly() {
final int COUNT = 100;
Random rand = new Random();
CostComparator comp = new CostComparator();
DynamicSortedTreeSet<TestElem> queue = new DynamicSortedTreeSet<>(comp);
HashSet<TestElem> all = new HashSet<>();
for (int i = 0; i < COUNT; i++) {
TestElem e = new TestElem("Element" + i, rand.nextInt(50));
queue.add(e);
all.add(e);
}
checkConsistent(queue, comp);
TestElem[] shuffled = all.toArray(new TestElem[all.size()]);
for (int i = 0; i < shuffled.length; i++) {
ArrayUtils.swap(shuffled, i, i + rand.nextInt(shuffled.length - i));
}
for (TestElem e : shuffled) {
queue.remove(e);
checkConsistent(queue, comp);
}
assertTrue(queue.isEmpty());
assertTrue(queue.size() == 0);
}
@Test
public void testUpdateRandomly() {
final int COUNT = 100;
Random rand = new Random();
CostComparator comp = new CostComparator();
DynamicSortedTreeSet<TestElem> queue = new DynamicSortedTreeSet<>(comp);
for (int i = 0; i < COUNT; i++) {
queue.add(new TestElem("Element" + i, rand.nextInt(50)));
}
checkConsistent(queue, comp);
for (int i = 0; i < COUNT; i++) {
TestElem e = queue.get(rand.nextInt(queue.size()));
int oldCost = e.cost;
if (rand.nextInt(2) == 0) {
e.cost = rand.nextInt(50);
}
boolean result = queue.update(e);
if (oldCost == e.cost) {
assertEquals(false, result);
}
// NOTE: A different cost does not necessarily promote the updated element
checkConsistent(queue, comp);
}
}
}