Skip to content
Snippets Groups Projects
Commit 26bb9fb7 authored by Hunter Schafer's avatar Hunter Schafer
Browse files

Add P2: Maps

parent 91588cbd
No related branches found
No related tags found
No related merge requests found
sourceSets.main.studentJava.files = [
'maps/ArrayMap.java',
'maps/ChainedHashMap.java',
]
package maps;
import java.util.AbstractMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Stack;
/**
* AVL-tree implementation of the map ADT.
*
* Does not allow null keys.
*/
public class AVLTreeMap<K extends Comparable<K>, V> extends AbstractIterableMap<K, V> {
private AVLNode<K, V> overallRoot;
private int size;
public AVLTreeMap() {
this.size = 0;
}
@Override
public V get(Object key) {
if (key == null) {
throw new NullPointerException();
}
AVLNode<K, V> node = getNode(key, this.overallRoot);
if (node == null) {
return null;
}
return node.value;
}
/**
* Returns the node with the given key or null if no such node is found.
*/
private AVLNode<K, V> getNode(Object key, AVLNode<K, V> current) {
if (current == null) {
return null;
} else if (compare(key, current.key) < 0) {
return getNode(key, current.left);
} else if (compare(key, current.key) > 0) {
return getNode(key, current.right);
}
return current;
}
/**
* Compares two keys; assumes that they have proper Comparable types.
*/
@SuppressWarnings("unchecked")
final int compare(Object k1, Object k2) {
return ((Comparable<? super K>) k1).compareTo((K) k2);
}
/**
* {@inheritDoc}
*
* @throws NullPointerException if the given key is null.
*/
@Override
public V put(K key, V value) {
if (key == null) {
throw new NullPointerException();
}
AVLNode<K, V> output = new AVLNode<>(null, null);
this.overallRoot = put(key, value, this.overallRoot, output);
return output.value;
}
private AVLNode<K, V> put(K key, V value, AVLNode<K, V> current, AVLNode<K, V> output) {
if (current == null) {
this.size++;
return new AVLNode<>(key, value);
}
if (key.compareTo(current.key) < 0) {
current.left = put(key, value, current.left, output);
} else if (key.compareTo(current.key) > 0) {
current.right = put(key, value, current.right, output);
} else {
output.value = current.value;
current.value = value;
return current;
}
updateHeight(current);
return balanceTree(current);
}
/**
* Maintains AVL balance invariant. Returns the balanced subtree.
*/
private AVLNode<K, V> balanceTree(AVLNode<K, V> root) {
int heightDiff = getHeightDiff(root);
if (heightDiff > 1) { // left-heavy, do right rotation
if (getHeightDiff(root.left) < 0) { // kink case, do left-right rotation
root.left = rotateLeft(root.left);
}
root = rotateRight(root);
} else if (heightDiff < -1) { // right-heavy, do left rotation
if (getHeightDiff(root.right) > 0) { // kink case, do right-left rotation
root.right = rotateRight(root.right);
}
root = rotateLeft(root);
}
return root;
}
/**
* Returns the difference in heights of the left and right subtrees of the given node.
*/
private int getHeightDiff(AVLNode<K, V> node) {
return getHeight(node.left) - getHeight(node.right);
}
/**
* Sets the given node's height to the maximum of its subtrees' heights plus 1.
*/
private void updateHeight(AVLNode<K, V> node) {
node.height = Math.max(getHeight(node.left), getHeight(node.right)) + 1;
}
/**
* Returns the height of the given node's subtree.
* Note: the height of an empty tree is -1, and the height of a tree with a single node is 0.
*/
private int getHeight(AVLNode<K, V> node) {
return node == null ? -1 : node.height;
}
/**
* Performs a right rotation on the given subtree. Returns the rotated subtree.
*/
private AVLNode<K, V> rotateRight(AVLNode<K, V> root) {
AVLNode<K, V> leftChild = root.left;
root.left = leftChild.right;
leftChild.right = root;
updateHeight(leftChild);
updateHeight(root);
return leftChild;
}
/**
* Performs a left rotation on the given subtree. Returns the rotated subtree.
*/
private AVLNode<K, V> rotateLeft(AVLNode<K, V> root) {
AVLNode<K, V> rightChild = root.right;
root.right = rightChild.left;
rightChild.left = root;
updateHeight(rightChild);
updateHeight(root);
return rightChild;
}
/**
* Remove has been left unimplemented.
*/
@Override
public V remove(Object key) {
throw new UnsupportedOperationException();
}
@Override
public boolean containsKey(Object key) {
return getNode(key, this.overallRoot) != null;
}
@Override
public int size() {
return this.size;
}
@Override
public boolean isEmpty() {
return this.overallRoot == null;
}
@Override
public Iterator<Map.Entry<K, V>> iterator() {
return new AVLIterator<>(this.overallRoot);
}
/**
* `AVLNode`s store a key and a value and have at most two children. Each node
* keeps track of its own height in the AVL tree. This is used to balance the tree.
*/
private static class AVLNode<K, V> {
final K key;
V value;
int height;
AVLNode<K, V> left;
AVLNode<K, V> right;
AVLNode(K key, V value) {
this.key = key;
this.value = value;
this.height = 0;
this.left = null;
this.right = null;
}
}
private static class AVLIterator<K, V> implements Iterator<Map.Entry<K, V>> {
private final Stack<Map.Entry<K, V>> stack;
public AVLIterator(AVLNode<K, V> overallRoot) {
this.stack = new Stack<>();
reverseOrderFill(overallRoot);
}
private void reverseOrderFill(AVLNode<K, V> root) {
if (root == null) {
return;
}
reverseOrderFill(root.right);
this.stack.push(new AbstractMap.SimpleEntry<>(root.key, root.value));
reverseOrderFill(root.left);
}
@Override
public boolean hasNext() {
return this.stack.size() > 0;
}
@Override
public Map.Entry<K, V> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return this.stack.pop();
}
}
}
package maps;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* A map that additionally implements the Iterable interface.
*
* Iterating over the map is equivalent to iterating over {@code map.entrySet()}, so having the map
* be Iterable is technically redundant, but we've chosen to specify the class this way because
* the iterator is simpler to implement than the entrySet method.
*
* This class inherits default method implementations for almost all Map methods, most of which
* delegate all work to the iterator. For better efficiency, implementers of this class should
* provide their own implementations of the basic Map methods included in this file.
*
* For the most part, the documentation in this class is copied from {@link Map}, but without a few
* extra exceptions and edge cases that you should not need to worry about in your implementations.
*/
public abstract class AbstractIterableMap<K, V> extends AbstractMap<K, V> implements Iterable<Map.Entry<K, V>> {
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that
* {@code Objects.equals(key, k)},
* then this method returns {@code v}; otherwise
* it returns {@code null}. (There can be at most one such mapping.)
*
* <p>A return value of {@code null} does not <i>necessarily</i> indicate that the map
* contains no mapping for the key; it's also possible that the map
* explicitly maps the key to {@code null}. The {@link #containsKey
* containsKey} operation may be used to distinguish these two cases.
*
* @param key the key whose associated value is to be returned
* @return the value to which the specified key is mapped, or
* {@code null} if this map contains no mapping for the key
*/
@Override
public V get(Object key) {
return super.get(key);
}
/**
* Associates the specified value with the specified key in this map
* (optional operation). If the map previously contained a mapping for
* the key, the old value is replaced by the specified value. (A map
* {@code m} is said to contain a mapping for a key {@code k} if and only
* if {@link #containsKey(Object) m.containsKey(k)} would return
* {@code true}.)
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with {@code key}, or
* {@code null} if there was no mapping for {@code key}.
* (A {@code null} return can also indicate that the map
* previously associated {@code null} with {@code key},
* if the implementation supports {@code null} values.)
*/
@Override
public V put(K key, V value) {
return super.put(key, value);
}
/**
* Removes the mapping for a key from this map if it is present
* (optional operation). More formally, if this map contains a mapping
* from key {@code k} to value {@code v} such that
* {@code Objects.equals(key, k)}, that mapping
* is removed. (The map can contain at most one such mapping.)
*
* <p>Returns the value to which this map previously associated the key,
* or {@code null} if the map contained no mapping for the key.
*
* <p>A return value of {@code null} does not <i>necessarily</i> indicate that the map
* contained no mapping for the key; it's also possible that the map
* explicitly mapped the key to {@code null}.
*
* <p>The map will not contain a mapping for the specified key once the
* call returns.
*
* @param key key whose mapping is to be removed from the map
* @return the previous value associated with {@code key}, or
* {@code null} if there was no mapping for {@code key}.
*/
@Override
public V remove(Object key) {
return super.remove(key);
}
/**
* Removes all of the mappings from this map (optional operation).
* The map will be empty after this call returns.
*/
@Override
public void clear() {
super.clear();
}
/**
* Returns {@code true} if this map contains a mapping for the specified
* key. More formally, returns {@code true} if and only if
* this map contains a mapping for a key {@code k} such that
* {@code Objects.equals(key, k)}. (There can be
* at most one such mapping.)
*
* @param key key whose presence in this map is to be tested
* @return {@code true} if this map contains a mapping for the specified
* key
*/
@Override
public boolean containsKey(Object key) {
return super.containsKey(key);
}
/**
* Returns the number of key-value mappings in this map.
*
* @return the number of key-value mappings in this map
*/
@Override
public int size() {
return super.size();
}
/**
* Returns an iterator that, when used, will yield all key-value mappings contained within this
* map.
*/
@Override
public abstract Iterator<Map.Entry<K, V>> iterator();
/**
* An entrySet implementation that delegates all work to the map's iterator.
*
* Contrary to the base Map interface, the set returned is not mutable, so it is not possible
* to remove items from the map through the entrySet.
*
* @return a set view of the mappings contained in this map
*/
@Override
public Set<Entry<K, V>> entrySet() {
return new AbstractSet<>() {
@Override
public Iterator<Entry<K, V>> iterator() {
return AbstractIterableMap.this.iterator();
}
@Override
public int size() {
return AbstractIterableMap.this.size();
}
};
}
}
package maps;
import java.util.Iterator;
import java.util.Map;
/**
* @see AbstractIterableMap
* @see Map
*/
public class ArrayMap<K, V> extends AbstractIterableMap<K, V> {
// TODO: define a reasonable default value for the following field
private static final int DEFAULT_INITIAL_CAPACITY = 0;
/*
Warning:
You may not rename this field or change its type.
We will be inspecting it in our secret tests.
*/
SimpleEntry<K, V>[] entries;
// You may add extra fields or helper methods though!
/**
* Constructs a new ArrayMap with default initial capacity.
*/
public ArrayMap() {
this(DEFAULT_INITIAL_CAPACITY);
}
/**
* Constructs a new ArrayMap with the given initial capacity (i.e., the initial
* size of the internal array).
*
* @param initialCapacity the initial capacity of the ArrayMap. Must be > 0.
*/
public ArrayMap(int initialCapacity) {
this.entries = this.createArrayOfEntries(initialCapacity);
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
/**
* This method will return a new, empty array of the given size that can contain
* {@code Entry<K, V>} objects.
*
* Note that each element in the array will initially be null.
*
* Note: You do not need to modify this method.
*/
@SuppressWarnings("unchecked")
private SimpleEntry<K, V>[] createArrayOfEntries(int arraySize) {
/*
It turns out that creating arrays of generic objects in Java is complicated due to something
known as "type erasure."
We've given you this helper method to help simplify this part of your assignment. Use this
helper method as appropriate when implementing the rest of this class.
You are not required to understand how this method works, what type erasure is, or how
arrays and generics interact.
*/
return (SimpleEntry<K, V>[]) (new SimpleEntry[arraySize]);
}
@Override
public V get(Object key) {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public V put(K key, V value) {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public V remove(Object key) {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public void clear() {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public boolean containsKey(Object key) {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public int size() {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public Iterator<Map.Entry<K, V>> iterator() {
// Note: You may or may not need to change this method, depending on whether you
// add any parameters to the ArrayMapIterator constructor.
return new ArrayMapIterator<>(this.entries);
}
// TODO: after you implement the iterator, remove this toString implementation
// Doing so will give you a better string representation for assertion errors the debugger.
@Override
public String toString() {
return super.toString();
}
private static class ArrayMapIterator<K, V> implements Iterator<Map.Entry<K, V>> {
private final SimpleEntry<K, V>[] entries;
// You may add more fields and constructor parameters
public ArrayMapIterator(SimpleEntry<K, V>[] entries) {
this.entries = entries;
}
@Override
public boolean hasNext() {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public Map.Entry<K, V> next() {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
}
}
package maps;
import java.util.Iterator;
import java.util.Map;
/**
* @see AbstractIterableMap
* @see Map
*/
public class ChainedHashMap<K, V> extends AbstractIterableMap<K, V> {
// TODO: define reasonable default values for each of the following three fields
private static final double DEFAULT_RESIZING_LOAD_FACTOR_THRESHOLD = 0;
private static final int DEFAULT_INITIAL_CHAIN_COUNT = 0;
private static final int DEFAULT_INITIAL_CHAIN_CAPACITY = 0;
/*
Warning:
You may not rename this field or change its type.
We will be inspecting it in our secret tests.
*/
AbstractIterableMap<K, V>[] chains;
// You're encouraged to add extra fields (and helper methods) though!
/**
* Constructs a new ChainedHashMap with default resizing load factor threshold,
* default initial chain count, and default initial chain capacity.
*/
public ChainedHashMap() {
this(DEFAULT_RESIZING_LOAD_FACTOR_THRESHOLD, DEFAULT_INITIAL_CHAIN_COUNT, DEFAULT_INITIAL_CHAIN_CAPACITY);
}
/**
* Constructs a new ChainedHashMap with the given parameters.
*
* @param resizingLoadFactorThreshold the load factor threshold for resizing. When the load factor
* exceeds this value, the hash table resizes. Must be > 0.
* @param initialChainCount the initial number of chains for your hash table. Must be > 0.
* @param chainInitialCapacity the initial capacity of each ArrayMap chain created by the map.
* Must be > 0.
*/
public ChainedHashMap(double resizingLoadFactorThreshold, int initialChainCount, int chainInitialCapacity) {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
/**
* This method will return a new, empty array of the given size that can contain
* {@code AbstractIterableMap<K, V>} objects.
*
* Note that each element in the array will initially be null.
*
* Note: You do not need to modify this method.
* @see ArrayMap createArrayOfEntries method for more background on why we need this method
*/
@SuppressWarnings("unchecked")
private AbstractIterableMap<K, V>[] createArrayOfChains(int arraySize) {
return (AbstractIterableMap<K, V>[]) new AbstractIterableMap[arraySize];
}
/**
* Returns a new chain.
*
* This method will be overridden by the grader so that your ChainedHashMap implementation
* is graded using our solution ArrayMaps.
*
* Note: You do not need to modify this method.
*/
protected AbstractIterableMap<K, V> createChain(int initialSize) {
return new ArrayMap<>(initialSize);
}
@Override
public V get(Object key) {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public V put(K key, V value) {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public V remove(Object key) {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public void clear() {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public boolean containsKey(Object key) {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public int size() {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public Iterator<Map.Entry<K, V>> iterator() {
// Note: you won't need to change this method (unless you add more constructor parameters)
return new ChainedHashMapIterator<>(this.chains);
}
// TODO: after you implement the iterator, remove this toString implementation
// Doing so will give you a better string representation for assertion errors the debugger.
@Override
public String toString() {
return super.toString();
}
/*
See the assignment webpage for tips and restrictions on implementing this iterator.
*/
private static class ChainedHashMapIterator<K, V> implements Iterator<Map.Entry<K, V>> {
private AbstractIterableMap<K, V>[] chains;
// You may add more fields and constructor parameters
public ChainedHashMapIterator(AbstractIterableMap<K, V>[] chains) {
this.chains = chains;
}
@Override
public boolean hasNext() {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
@Override
public Map.Entry<K, V> next() {
// TODO: replace this with your code
throw new UnsupportedOperationException("Not implemented yet.");
}
}
}
package maps;
import org.junit.jupiter.api.Test;
import java.util.Iterator;
import java.util.Map;
public class ArrayMapTests extends BaseMapTests {
@Override
protected <K, V> Map<K, V> createMap() {
return new ArrayMap<>();
}
protected <K, V> Map<K, V> createMap(int capacity) {
return new ArrayMap<>(capacity);
}
@Test
void iterator_hasNext_afterExhausted_whenArrayIsFull_returnsFalse() {
int capacity = 373;
Map<Integer, Integer> map = createMap(capacity);
// fill array to capacity - 1
for (int i = 0; i < capacity - 1; i++) {
map.put(i, i);
}
// make sure iterator works during capacity - 1
Iterator<Map.Entry<Integer, Integer>> iterator1 = map.entrySet().iterator();
exhaust(iterator1);
assertThat(iterator1).as("size == capacity - 1").isExhausted();
// makes sure iterator works during full capacity
map.put(-1, -1);
Iterator<Map.Entry<Integer, Integer>> iterator2 = map.entrySet().iterator();
exhaust(iterator2);
assertThat(iterator2).as("size == capacity").isExhausted();
}
}
package maps;
import edu.washington.cse373.BaseTest;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
public abstract class BaseMapTests extends BaseTest {
// some keys and values used across multiple tests
protected static final String KEY = "someKey";
protected static final String VAL = "someVal";
protected static final String OLD_VAL = "oldVal";
protected abstract <K, V> Map<K, V> createMap();
public <K, V> CustomMapAssert<K, V> assertThat(Map<K, V> map) {
return new CustomMapAssert<>(map);
}
// ---------------- Empty ----------------
@Test
void size_is0() {
Map<String, String> map = createMap();
assertThat(map).hasSize(0);
}
@Test
void containsKey_isFalse() {
Map<String, String> map = createMap();
assertThat(map).doesNotContainKey("foo");
}
@Test
void get_returnsNull() {
Map<String, String> map = createMap();
String output = map.get("foo");
assertThat(output).as("return value").isNull();
}
@Test
void put_returnsNull() {
Map<String, String> map = createMap();
String output = map.put("foo", "bar");
assertThat(output).as("return value").isNull();
}
@Test
void remove_returnsNull() {
Map<String, String> map = createMap();
String output = map.remove("foo");
assertThat(output).as("return value").isNull();
}
@Test
void iterator_hasNext_returnsFalse() {
Map<String, String> map = createMap();
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
assertThat(iterator).isExhausted();
}
@Test
void iterator_hasNext_twice_returnsFalse() {
Map<String, String> map = createMap();
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
iterator.hasNext();
assertThat(iterator).isExhausted();
}
@Test
void iterator_next_throwsNoSuchElement() {
Map<String, String> map = createMap();
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
assertThatThrownBy(iterator::next).isInstanceOf(NoSuchElementException.class);
}
// ---------------- 1 Entry ----------------
Map<String, String> createMapWith1Entry() {
Map<String, String> map = createMap();
map.put(KEY, VAL);
return map;
}
@Test
void size_is1() {
Map<String, String> map = createMapWith1Entry();
assertThat(map).hasSize(1);
}
@Test
void containsKey_returnsTrue() {
Map<String, String> map = createMapWith1Entry();
assertThat(map).containsKey(KEY);
}
@Test
void containsKey_withNewKey_returnsFalse() {
Map<String, String> map = createMapWith1Entry();
assertThat(map).doesNotContainKey("someOtherKey");
}
@Test
void get_returnsCorrectValue() {
Map<String, String> map = createMapWith1Entry();
String output = map.get(KEY);
assertThat(output).as("return value").isEqualTo(VAL);
}
@Test
void get_newKey_returnsNull() {
Map<String, String> map = createMapWith1Entry();
String output = map.get("someOtherKey");
assertThat(output).as("return value").isNull();
}
@Test
void put_newKey_returnsNull() {
Map<String, String> map = createMapWith1Entry();
String output = map.put("someOtherKey", VAL);
assertThat(output).as("return value").isNull();
}
@Test
void put_sameKey_returnsCorrectValue() {
Map<String, String> map = createMapWith1Entry();
String output = map.put(KEY, "someOtherVal");
assertThat(output).as("return value").isEqualTo(VAL);
}
@Test
void put_sameKeyAndValue_returnsCorrectValue() {
Map<String, String> map = createMapWith1Entry();
String output = map.put(KEY, VAL);
assertThat(output).as("return value").isEqualTo(VAL);
}
@Test
void remove_returnsCorrectValue() {
Map<String, String> map = createMapWith1Entry();
String output = map.remove(KEY);
assertThat(output).as("return value").isEqualTo(VAL);
}
@Test
void remove_newKey_returnsNull() {
Map<String, String> map = createMapWith1Entry();
String output = map.remove("someOtherKey");
assertThat(output).as("return value").isNull();
}
@Test
void size_afterRemove_newKey_is1() {
Map<String, String> map = createMapWith1Entry();
map.remove("someOtherKey");
assertThat(map).hasSize(1);
}
@Test
void iterator_hasNext_returnsTrue() {
Map<String, String> map = createMapWith1Entry();
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
assertThat(iterator).hasNext();
}
@Test
void iterator_hasNext_twice_returnsTrue() {
Map<String, String> map = createMapWith1Entry();
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
iterator.hasNext();
assertThat(iterator).hasNext();
}
@Test
void iterator_yieldsCorrectEntry() {
Map<String, String> map = createMapWith1Entry();
assertThat(map)
.as("entries yielded by iterator")
.containsExactly(entry(KEY, VAL));
}
@Test
void iterator_hasNext_afterExhausted_returnsFalse() {
Map<String, String> map = createMapWith1Entry();
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
exhaust(iterator);
assertThat(iterator).isExhausted();
}
@Test
void iterator_next_afterExhausted_throwsNoSuchElement() {
Map<String, String> map = createMapWith1Entry();
Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
exhaust(iterator);
assertThatThrownBy(iterator::next).isInstanceOf(NoSuchElementException.class);
}
// ---------------- Put And Remove Duplicate Key ----------------
Map<String, String> createMapAfterPutAndRemoveDuplicateKey() {
Map<String, String> map = createMap();
map.put(KEY, OLD_VAL);
map.put(KEY, VAL);
map.remove(KEY);
return map;
}
@Test
void containsKey_withRemovedKey_returnsFalse() {
Map<String, String> map = createMapAfterPutAndRemoveDuplicateKey();
assertThat(map).doesNotContainKey(KEY);
}
@Test
void get_removedKey_returnsNull() {
Map<String, String> map = createMapAfterPutAndRemoveDuplicateKey();
String output = map.get(KEY);
assertThat(output).as("return value").isNull();
}
@Test
void put_removedKey_returnsNull() {
Map<String, String> map = createMapAfterPutAndRemoveDuplicateKey();
String output = map.put(KEY, VAL);
assertThat(output).as("return value").isNull();
}
@Test
void remove_removedKey_returnsNull() {
Map<String, String> map = createMapAfterPutAndRemoveDuplicateKey();
String output = map.remove(KEY);
assertThat(output).as("return value").isNull();
}
// ---------------- 5 Entries ----------------
Map<Integer, Integer> createMapWith5Entries() {
Map<Integer, Integer> map = createMap();
for (int i = 0; i < 5; i++) {
map.put(i, i * i);
}
return map;
}
@Test
void size_5entries_is5() {
Map<Integer, Integer> map = createMapWith5Entries();
assertThat(map).hasSize(5);
}
@Test
void containsKey_onEachKey_5entries_returnsTrue() {
Map<Integer, Integer> map = createMapWith5Entries();
for (int i = 0; i < 5; i++) {
assertThat(map).as("key: " + i).containsKey(i);
}
}
@Test
void containsKey_withNewKey_5entries_returnsFalse() {
Map<Integer, Integer> map = createMapWith5Entries();
assertThat(map).doesNotContainKey(-1);
}
@Test
void get_5entries_returnsCorrectValue() {
Map<Integer, Integer> map = createMapWith5Entries();
Integer output = map.get(3);
assertThat(output).isEqualTo(9);
}
@Test
void get_newKey_5entries_returnsNull() {
Map<Integer, Integer> map = createMapWith5Entries();
Integer output = map.get(-1);
assertThat(output).isNull();
}
@Test
void put_newKey_5entries_returnsNull() {
Map<Integer, Integer> map = createMapWith5Entries();
Integer output = map.put(5, 25);
assertThat(output).isNull();
}
@Test
void put_sameKey_5entries_returnsCorrectValue() {
Map<Integer, Integer> map = createMapWith5Entries();
Integer output = map.put(2, -5);
assertThat(output).isEqualTo(4);
}
@Test
void remove_5entries_returnsCorrectValue() {
Map<Integer, Integer> map = createMapWith5Entries();
Integer output = map.remove(1);
assertThat(output).isEqualTo(1);
}
@Test
void iterator_5entries_yieldsCorrectEntries() {
Map<Integer, Integer> map = createMapWith5Entries();
assertThat(map)
.as("entries yielded by iterator")
.containsExactlyInAnyOrderEntriesOf(Map.of(
0, 0,
1, 1,
2, 4,
3, 9,
4, 16
));
}
@Test
void iterator_hasNext_afterExhausted_5entries_returnsFalse() {
Map<Integer, Integer> map = createMapWith5Entries();
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
exhaust(iterator);
assertThat(iterator).isExhausted();
}
@Test
void iterator_next_afterExhausted_5entries_throwsNoSuchElement() {
Map<Integer, Integer> map = createMapWith5Entries();
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
exhaust(iterator);
assertThatThrownBy(iterator::next).isInstanceOf(NoSuchElementException.class);
}
@Test
void iterator_afterRemoveOldestKey_5entries_yieldsCorrectEntries() {
Map<Integer, Integer> map = createMapWith5Entries();
map.remove(0);
assertThat(map)
.as("entries yielded by iterator")
.containsExactlyInAnyOrderEntriesOf(Map.of(
1, 1,
2, 4,
3, 9,
4, 16
));
}
@Test
void iterator_afterRemoveNewestKey_5entries_yieldsCorrectEntries() {
Map<Integer, Integer> map = createMapWith5Entries();
map.remove(4);
assertThat(map)
.as("entries yielded by iterator")
.containsExactlyInAnyOrderEntriesOf(Map.of(
0, 0,
1, 1,
2, 4,
3, 9
));
}
// ---------------- Custom Object Keys ----------------
@Test
void containsKey_wrapper_returnsTrue() {
Map<Wrapper<Integer>, Integer> map = createMap();
map.put(new Wrapper<>(1), 1);
assertThat(map).containsKey(new Wrapper<>(1));
}
@Test
void get_wrapper_returnsCorrectValue() {
Map<Wrapper<Integer>, Integer> map = createMap();
map.put(new Wrapper<>(1), 1);
assertThat(map).containsEntry(new Wrapper<>(1), 1);
}
@Test
void put_wrapper_sameKey_returnsCorrectValue() {
Map<Wrapper<Integer>, Integer> map = createMap();
map.put(new Wrapper<>(1), 1);
Integer output = map.put(new Wrapper<>(1), 15);
assertThat(output).as("return value").isEqualTo(1);
}
@Test
void remove_wrapper_returnsCorrectValue() {
Map<Wrapper<Integer>, Integer> map = createMap();
map.put(new Wrapper<>(1), 1);
Integer output = map.remove(new Wrapper<>(1));
assertThat(output).as("return value").isEqualTo(1);
}
@Test
void containsKey_afterPutNullValue_returnsTrue() {
Map<Integer, Integer> map = createMap();
map.put(100, null);
assertThat(map).containsKey(100);
}
@Test
void get_afterPutNullValue_returnsNull() {
Map<Integer, Integer> map = createMap();
map.put(100, null);
assertThat(map).containsEntry(100, null);
}
@Test
void containsKeyEach_afterPutMany_returnsCorrectValues() {
final int size = 100;
Map<Integer, Integer> map = createMap();
List<Integer> expectedKeys = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
map.put(i, i*i);
expectedKeys.add(i);
}
assertThat(map).containsKeys(expectedKeys);
}
@Test
void getEach_afterPutMany_returnsCorrectValues() {
final int size = 100;
Map<Integer, Integer> map = createMap();
Map<Integer, Integer> expected = new HashMap<>();
for (int i = 0; i < size; i++) {
map.put(i, i*i);
expected.put(i, i*i);
}
assertThat(map).containsAllEntriesOf(expected);
}
@Test
void iterator_afterPutMany_yieldsCorrectValues() {
final int size = 100;
Map<Integer, Integer> map = createMap();
Map<Integer, Integer> expected = new HashMap<>();
for (int i = 0; i < size; i++) {
map.put(i, i*i);
expected.put(i, i*i);
}
assertThat(map)
.as("entries yielded by iterator")
.containsExactlyInAnyOrderEntriesOf(expected);
}
@Test
void iterator_yieldsCorrectEntries_afterUsingAnotherIterator() {
final int size = 100;
Map<Integer, Integer> map = createMap();
Map<Integer, Integer> expected = new HashMap<>();
for (int i = 0; i < size; i++) {
map.put(i, i*i);
expected.put(i, i*i);
}
exhaust(map.keySet().iterator());
assertThat(map)
.as("entries yielded by iterator")
.containsExactlyInAnyOrderEntriesOf(expected);
}
@Test
void alternatingRemoveAndIterator_yieldsCorrectEntriesEachTime() {
final int size = 100;
Map<Integer, Integer> map = createMap();
Map<Integer, Integer> expected = new HashMap<>();
for (int i = 0; i < size; i++) {
map.put(i, i*i);
expected.put(i, i*i);
}
for (int i = 0; i < size; i++) {
map.remove(i);
expected.remove(i);
assertThat(map)
.as("entries yielded by iterator after removing %d/%d", i + 1, size)
.containsExactlyInAnyOrderEntriesOf(expected);
}
}
void exhaust(Iterator<?> iterator) {
while (iterator.hasNext()) {
iterator.next();
}
}
/**
* This is a wrapper class for arbitrary objects that additionally allows us to
* define a custom hash code.
*
* If no hash code is provided, the object's existing hash code is used instead.
*
* It is up to the user to make sure that the hash codes assigned are valid.
* (E.g., the user must ensure that two Wrapper objects with equal inner objects
* also have equal hash codes).
*/
protected static class Wrapper<T> {
private final T inner;
private final int hashCode;
public Wrapper(T inner) {
this(inner, inner == null ? 0 : inner.hashCode());
}
public Wrapper(T inner, int hashCode) {
this.inner = inner;
this.hashCode = hashCode;
}
@Override
public boolean equals(Object o) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
Wrapper<?> wrapper = (Wrapper<?>) o;
return Objects.equals(inner, wrapper.inner);
}
@Override
public int hashCode() {
return this.hashCode;
}
@Override
public String toString() {
return "Wrapper{" +
"inner=" + inner +
", hashCode=" + hashCode +
'}';
}
}
}
package maps;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
public class ChainedHashMapTests extends BaseMapTests {
@Override
protected <K, V> Map<K, V> createMap() {
return new ChainedHashMap<>();
}
protected <K, V> Map<K, V> createMap(double resizingLoadFactorThreshold,
int initialChainCount,
int chainInitialCapacity) {
return new ChainedHashMap<>(resizingLoadFactorThreshold, initialChainCount, chainInitialCapacity);
}
protected <K, V> AbstractIterableMap<K, V>[] extractChains(Map<K, V> map) {
return ((ChainedHashMap<K, V>) map).chains;
}
protected <K, V> Map.Entry<K, V>[] extractEntries(Map<K, V> map) {
return ((ArrayMap<K, V>) map).entries;
}
@Test
void constructor_withInitialChainCount_hasCorrectInitialChainCount() {
Map<String, Integer> map = createMap(100, 1, 10);
map.put("foo", 1);
Map<String, Integer>[] chains = extractChains(map);
assertThat(chains).hasSize(1);
}
@Test
void constructor_withInitialChainCapacity_hasCorrectInitialChainCapacity() {
Map<String, Integer> map = createMap(100, 1, 10);
map.put("foo", 1);
Map<String, Integer>[] chains = extractChains(map);
Map.Entry<String, Integer>[] chainEntries = extractEntries(chains[0]);
assertThat(chainEntries).hasSize(10);
}
@Test
void constructor_withResizeLoadFactorThreshold_resizesAtCorrectTime() {
Map<Integer, Integer> map = createMap(100, 1, 10);
for (int i = 0; i < 99; i++) {
map.put(i, i);
}
Map<Integer, Integer>[] chains = extractChains(map);
assertThat(chains).hasSize(1);
map.put(99, 99);
map.put(100, 100);
Map<Integer, Integer>[] newChains = extractChains(map);
assertThat(newChains).hasSizeGreaterThan(1);
}
@Test
void get_afterPutKeyWithLargeHashCode_returnsCorrectValue() {
Map<Wrapper<Integer>, Integer> map = createMap();
map.put(new Wrapper<>(1, 10000000), 5);
assertThat(map).containsEntry(new Wrapper<>(1, 10000000), 5);
}
@Test
void get_afterPutKeyWithNegativeHashCode_returnsCorrectValue() {
Map<Wrapper<Integer>, Integer> map = createMap();
map.put(new Wrapper<>(1, -100), 5);
assertThat(map).containsEntry(new Wrapper<>(1, -100), 5);
}
@Test
void getEach_afterPutManyKeysWithSameHashCode_returnsCorrectValues() {
Map<Wrapper<Integer>, Integer> map = createMap(2, 8, 16);
Map<Wrapper<Integer>, Integer> actual = new HashMap<>();
final int size = 100;
for (int i = 0; i < size; i++) {
map.put(new Wrapper<>(i, 3), i);
actual.put(new Wrapper<>(i, 3), i);
}
assertThat(map).containsAllEntriesOf(actual);
}
@Test
void ensureCreateChainMethodCanBeOverridden() {
// The grader will use similar code to override `createChain`, so make sure this compiles.
new ChainedHashMap<Integer, Integer>() {
@Override
protected AbstractIterableMap<Integer, Integer> createChain(int initialSize) {
return null;
}
};
}
}
package maps;
import org.assertj.core.api.AssertionInfo;
import org.assertj.core.api.MapAssert;
import org.assertj.core.error.BasicErrorMessageFactory;
import org.assertj.core.error.ErrorMessageFactory;
import org.assertj.core.error.GroupTypeDescription;
import org.assertj.core.internal.ComparisonStrategy;
import org.assertj.core.internal.Failures;
import org.assertj.core.internal.Objects;
import org.assertj.core.internal.StandardComparisonStrategy;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import static org.assertj.core.api.Assertions.entry;
import static org.assertj.core.error.GroupTypeDescription.getGroupTypeDescription;
/**
* Assertions for {@link Map}s.
*
* Includes slight changes from AssertJ's provided assertions to improve feedback
* provided by failure messages.
*/
public class CustomMapAssert<K, V> extends MapAssert<K, V> {
Failures failures = Failures.instance();
public CustomMapAssert(Map<K, V> actual) {
super(actual);
}
@Override
public MapAssert<K, V> containsAllEntriesOf(Map<? extends K, ? extends V> other) {
assertNotNull(info, actual);
Map<K, V> getValueMismatch = new LinkedHashMap<>();
Set<K> missingNullValuedEntries = new LinkedHashSet<>();
for (Map.Entry<? extends K, ? extends V> entry : other.entrySet()) {
if (entry.getValue() == null) {
if (!actual.containsKey(entry.getKey())) {
missingNullValuedEntries.add(entry.getKey());
continue;
}
}
Optional<Optional<V>> mismatchedGetValue = obtainGetValueMismatch(actual, entry);
mismatchedGetValue.ifPresent(v -> getValueMismatch.put(entry.getKey(), v.orElse(null)));
}
if (getValueMismatch.isEmpty() && missingNullValuedEntries.isEmpty()) {
return myself;
}
throw failures.failure(info, ShouldContainMultiple.shouldContain(
actual, other, getValueMismatch, missingNullValuedEntries));
}
@Override
public MapAssert<K, V> containsEntry(K key, V value) {
assertNotNull(info, actual);
Map.Entry<K, V> entry = entry(key, value);
if (value == null) {
// throw if value is null and key is not in actual
containsKey(key);
}
Optional<Optional<V>> mismatchedGetValue = obtainGetValueMismatch(actual, entry);
if (mismatchedGetValue.isPresent()) {
throw failures.failure(info, MismatchedGet.shouldMatch(actual, entry,
mismatchedGetValue.get().orElse(null)));
}
return myself;
}
/**
* returns an optional:
* - empty if get on the actual map matched the value of the entry;
* - containing an optional:
* - empty if the actual get value was null
* - containing the actual get value if it was non-null
* (this needs to be a nested Optional since a null-valued Optional is treated as empty,
* meaning that the Optional would always be empty if actual.get returned null.)
*/
private static <K, V> Optional<Optional<V>> obtainGetValueMismatch(Map<K, V> actual,
Map.Entry<? extends K, ? extends V> entry) {
V value = actual.get(entry.getKey());
if (java.util.Objects.deepEquals(value, entry.getValue())) {
return Optional.empty();
}
return Optional.of(Optional.ofNullable(value));
}
@Override
public MapAssert<K, V> containsKey(K key) {
assertNotNull(info, actual);
if (!actual.containsKey(key)) {
throw failures.failure(info, ContainsKeyFailure.shouldContainKey(actual, key));
}
return myself;
}
/**
* Verifies that the actual map contains the given keys.
* <p>
* Example :
* <pre><code class='java'> Map&lt;Ring, TolkienCharacter&gt; ringBearers = new HashMap&lt;&gt;();
* ringBearers.put(nenya, galadriel);
* ringBearers.put(narya, gandalf);
* ringBearers.put(oneRing, frodo);
*
* // assertions will pass
* assertThat(ringBearers).containsKeys(List.of(nenya, oneRing));
*
* // assertions will fail
* assertThat(ringBearers).containsKeys(List.of(vilya));
* assertThat(ringBearers).containsKeys(List.of(vilya, oneRing));</code></pre>
*
* @param keys the given keys
* @return {@code this} assertions object
* @throws AssertionError if the actual map is {@code null}.
* @throws AssertionError if the actual map does not contain the given key.
*/
public MapAssert<K, V> containsKeys(Collection<? extends K> keys) {
assertNotNull(info, actual);
Set<K> missingKeys = new LinkedHashSet<>();
for (K key : keys) {
if (!actual.containsKey(key)) {
missingKeys.add(key);
}
}
if (!missingKeys.isEmpty()) {
throw failures.failure(info, ContainsKeyFailure.shouldContainKeys(actual, missingKeys));
}
return myself;
}
@Override
public MapAssert<K, V> doesNotContainKey(K key) {
assertNotNull(info, actual);
if (actual.containsKey(key)) {
throw failures.failure(info, ContainsKeyFailure.shouldNotContainKey(actual, key));
}
return myself;
}
private void assertNotNull(AssertionInfo info, Map<?, ?> actual) {
Objects.instance().assertNotNull(info, actual);
}
public static final class ContainsKeyFailure extends BasicErrorMessageFactory {
private <K, V> ContainsKeyFailure(Map<K, V> actual, K key, boolean expectedBool, boolean actualBool) {
super("%nExpecting:%n"
+ " <%s>%n"
+ "to " + (expectedBool ? "contain" : "not contain") + " key:%n"
+ " <%s>%n"
+ "but containsKey returned " + actualBool,
actual, key);
}
private <K, V> ContainsKeyFailure(Map<K, V> actual, Set<K> keys, boolean expectedBool, boolean actualBool) {
super("%nExpecting:%n"
+ " <%s>%n"
+ "to " + (expectedBool ? "contain" : "not contain") + " keys:%n"
+ " <%s>%n"
+ "but containsKey returned " + actualBool + " for each",
actual, keys);
}
/**
* Creates a new {@code ContainsKeyFailure}.
*
* @param actual the actual value in the failed assertion.
* @param key the expected key
* @return the created {@code ErrorMessageFactory}.
*/
public static <K, V> ErrorMessageFactory shouldContainKey(Map<K, V> actual, K key) {
return new ContainsKeyFailure(actual, key, true, false);
}
/**
* Creates a new {@code ContainsKeyFailure}.
*
* @param actual the actual value in the failed assertion.
* @param keys the expected keys
* @return the created {@code ErrorMessageFactory}.
*/
public static <K, V> ErrorMessageFactory shouldContainKeys(Map<K, V> actual, Set<K> keys) {
if (keys.size() == 1) {
return new ContainsKeyFailure(actual, keys.iterator().next(), true, false);
}
return new ContainsKeyFailure(actual, keys, true, false);
}
/**
* Creates a new {@code ContainsKeyFailure}.
*
* @param actual the actual value in the failed assertion.
* @param key the unexpected key
* @return the created {@code ErrorMessageFactory}.
*/
public static <V, K> ErrorMessageFactory shouldNotContainKey(Map<K, V> actual, K key) {
return new ContainsKeyFailure(actual, key, false, true);
}
}
/**
* Creates an error message indicating that an assertion that verifies a map
* contains a given entry failed because the map returned an unexpected and non-null value.
* It also mentions the {@link ComparisonStrategy} used.
*/
public static final class MismatchedGet extends BasicErrorMessageFactory {
private <K, V> MismatchedGet(Map<K, V> actual,
Map.Entry<K, V> expected,
V actualGetValue,
ComparisonStrategy comparisonStrategy,
GroupTypeDescription groupTypeDescription) {
super("%nExpecting " + groupTypeDescription.getGroupTypeName() + ":%n"
+ " <%s>%n"
+ "to contain:%n"
+ " <%s>%n"
+ "but get returned:%n"
+ " <%s>%n"
+ "%s",
actual, expected, actualGetValue,
comparisonStrategy);
}
/**
* Creates a new {@code MismatchedGet}.
*
* @param actual the actual value in the failed assertion.
* @param expected entry expected to be in {@code actual}.
* @param actualGetValue the value returned by {@code actual.get}.
* @return the created {@code ErrorMessageFactory}.
*/
public static <K, V> ErrorMessageFactory shouldMatch(Map<K, V> actual,
Map.Entry<K, V> expected,
V actualGetValue) {
return shouldMatch(actual, expected, actualGetValue, StandardComparisonStrategy.instance());
}
/**
* Creates a new {@code MismatchedGet}.
*
* @param actual the actual value in the failed assertion.
* @param expected entry expected to be in {@code actual}.
* @param actualGetValue the value returned by {@code actual.get}.
* @param comparisonStrategy the {@link ComparisonStrategy} used to evaluate assertion.
* @return the created {@code ErrorMessageFactory}.
*/
public static <K, V> ErrorMessageFactory shouldMatch(Map<K, V> actual,
Map.Entry<K, V> expected,
V actualGetValue,
ComparisonStrategy comparisonStrategy) {
GroupTypeDescription groupTypeDescription = getGroupTypeDescription(actual);
return new MismatchedGet(actual, expected, actualGetValue, comparisonStrategy, groupTypeDescription);
}
}
/**
* Creates an error message indicating that an assertion that verifies a {@code Map}
* contains a given set of entries failed.
* It also mentions the {@link ComparisonStrategy} used.
*/
private static final class ShouldContainMultiple extends BasicErrorMessageFactory {
private <K, V> ShouldContainMultiple(Map<K, V> actual,
Map<? extends K, ? extends V> expected,
Map<K, V> mismatches,
Set<K> notFound,
ComparisonStrategy comparisonStrategy,
GroupTypeDescription groupTypeDescription) {
super("%nExpecting " + groupTypeDescription.getGroupTypeName() + ":%n"
+ " <%s>%n"
+ "to contain:%n"
+ " <%s>%n"
+ "but get calls returned unexpected values for the following:%n"
+ " <%s>%n"
+ "and some keys were not contained:%n"
+ " <%s>%n"
+ "%s",
actual, expected, mismatches, notFound, comparisonStrategy);
}
private <K, V> ShouldContainMultiple(Map<K, V> actual,
Map<? extends K, ? extends V> expected,
Map<K, V> mismatches,
ComparisonStrategy comparisonStrategy,
GroupTypeDescription groupTypeDescription) {
super("%nExpecting " + groupTypeDescription.getGroupTypeName() + ":%n"
+ " <%s>%n"
+ "to contain:%n"
+ " <%s>%n"
+ "but get calls returned unexpected values for the following:%n"
+ " <%s>%n"
+ "%s",
actual, expected, mismatches, comparisonStrategy);
}
private <K, V> ShouldContainMultiple(Map<K, V> actual,
Map<? extends K, ? extends V> expected,
Set<K> notFound,
ComparisonStrategy comparisonStrategy,
GroupTypeDescription groupTypeDescription) {
super("%nExpecting " + groupTypeDescription.getGroupTypeName() + ":%n"
+ " <%s>%n"
+ "to contain:%n"
+ " <%s>%n"
+ "but some keys were not contained:%n"
+ " <%s>%n"
+ "%s",
actual, expected, notFound, comparisonStrategy);
}
/**
* Creates a new {@code MismatchedGet}.
*
* @param actual the actual value in the failed assertion.
* @param expected map containing entries expected to be in {@code actual}.
* @param mismatches map containing mismatched values returned by {@code actual.get}.
* @param notFound set of expected keys not contained by the actual map
* (only includes keys which were expected to have null values)
* @return the created {@code ErrorMessageFactory}.
*/
public static <K, V> ErrorMessageFactory shouldContain(Map<K, V> actual,
Map<? extends K, ? extends V> expected,
Map<K, V> mismatches,
Set<K> notFound) {
return shouldContain(actual, expected, mismatches, notFound, StandardComparisonStrategy.instance());
}
/**
* Creates a new {@code MismatchedGet}.
*
* @param actual the actual value in the failed assertion.
* @param expected map containing entries expected to be in {@code actual}.
* @param mismatches map containing mismatched values returned by {@code actual.get}.
* @param notFound set of expected keys not contained by the actual map
* (only includes keys which were expected to have null values)
* @param comparisonStrategy the {@link ComparisonStrategy} used to evaluate assertion.
* @return the created {@code ErrorMessageFactory}.
*/
public static <K, V> ErrorMessageFactory shouldContain(Map<K, V> actual,
Map<? extends K, ? extends V> expected,
Map<K, V> mismatches,
Set<K> notFound,
ComparisonStrategy comparisonStrategy) {
GroupTypeDescription groupTypeDescription = getGroupTypeDescription(actual);
if (mismatches.isEmpty()) {
return new ShouldContainMultiple(
actual,
expected,
notFound,
comparisonStrategy,
groupTypeDescription);
}
if (notFound.isEmpty()) {
return new ShouldContainMultiple(
actual,
expected,
mismatches,
comparisonStrategy,
groupTypeDescription);
}
return new ShouldContainMultiple(
actual,
expected,
mismatches,
notFound,
comparisonStrategy,
groupTypeDescription);
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment