From 0f1b46ff78b27c3da3ce5616ac0a1a28fbec6679 Mon Sep 17 00:00:00 2001 From: "andremoniy@gmail.com" Date: Sun, 3 Mar 2019 19:25:37 +0100 Subject: [PATCH] IsUnmodifiableCollection matcher feature #249 (cherry picked from commit 0ba3f59b81ca6c886a2064b993b6025f84bf9c57) --- .../collection/IsUnmodifiableCollection.java | 176 ++++++++++++++++++ .../IsUnmodifiableCollectionTest.java | 140 ++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 hamcrest/src/main/java/org/hamcrest/collection/IsUnmodifiableCollection.java create mode 100644 hamcrest/src/test/java/org/hamcrest/collection/IsUnmodifiableCollectionTest.java diff --git a/hamcrest/src/main/java/org/hamcrest/collection/IsUnmodifiableCollection.java b/hamcrest/src/main/java/org/hamcrest/collection/IsUnmodifiableCollection.java new file mode 100644 index 00000000..ecc6184d --- /dev/null +++ b/hamcrest/src/main/java/org/hamcrest/collection/IsUnmodifiableCollection.java @@ -0,0 +1,176 @@ +package org.hamcrest.collection; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Matches if collection is truly unmodifiable + */ +public class IsUnmodifiableCollection extends TypeSafeDiagnosingMatcher> { + + private static final Map DEFAULT_COLLECTIONS = new HashMap<>(); + + static { + final List list = Arrays.asList("a", "b", "c"); + DEFAULT_COLLECTIONS.put(Collection.class, list); + DEFAULT_COLLECTIONS.put(List.class, list); + DEFAULT_COLLECTIONS.put(Set.class, new HashSet<>(list)); + } + + @SuppressWarnings("unchecked") + @Override + protected boolean matchesSafely(final Collection collection, final Description mismatchDescription) { + final Class collectionClass = collection.getClass(); + final Collection item = getInstanceOfType(collectionClass); + if (item == null) { + throw failedToInstantiateItem(collectionClass, null); + } + final Object testObject = new Object(); + final Set singletonList = Collections.singleton(testObject); + + try { + item.add(testObject); + mismatchDescription.appendText("was able to add a value into the collection"); + return false; + } catch (Exception ignore) { + } + + try { + item.addAll(singletonList); + mismatchDescription.appendText("was able to perform addAll on the collection"); + return false; + } catch (Exception ignore) { + } + + try { + item.remove(testObject); + mismatchDescription.appendText("was able to remove a value from the collection"); + return false; + } catch (Exception ignore) { + } + + try { + item.removeAll(singletonList); + mismatchDescription.appendText("was able to perform removeAll on the collection"); + return false; + } catch (Exception ignore) { + } + + try { + item.retainAll(singletonList); + mismatchDescription.appendText("was able to perform retainAll on the collection"); + return false; + } catch (Exception ignore) { + } + + try { + item.clear(); + mismatchDescription.appendText("was able to clear the collection"); + return false; + } catch (Exception ignore) { + } + + return true; + } + + + @SuppressWarnings("unchecked") + private T getInstanceOfType(final Class clazz) { + Exception lastException = null; + + if (clazz.isArray()) { + return (T) Array.newInstance(clazz, 0); + } + + if (clazz.isPrimitive()) { + if (Byte.TYPE.isAssignableFrom(clazz)) { + return (T) Byte.valueOf((byte) 1); + } + if (Short.TYPE.isAssignableFrom(clazz)) { + return (T) Short.valueOf((short) 1); + } + if (Integer.TYPE.isAssignableFrom(clazz)) { + return (T) Integer.valueOf(1); + } + if (Long.TYPE.isAssignableFrom(clazz)) { + return (T) Long.valueOf(1L); + } + if (Float.TYPE.isAssignableFrom(clazz)) { + return (T) Float.valueOf(1L); + } + if (Double.TYPE.isAssignableFrom(clazz)) { + return (T) Double.valueOf(1L); + } + if (Boolean.TYPE.isAssignableFrom(clazz)) { + return (T) Boolean.valueOf(true); + } + if (Character.TYPE.isAssignableFrom(clazz)) { + return (T) Character.valueOf(' '); + } + } + + if (clazz.isInterface()) { + Object defaultCollection = DEFAULT_COLLECTIONS.get(clazz); + if (defaultCollection != null) { + return (T) defaultCollection; + } + return null; + } + + // For the most part of implementations there probably won't be any default constructor + final Constructor[] declaredConstructors = clazz.getDeclaredConstructors(); + // First take constructor with fewer number of arguments + Arrays.sort(declaredConstructors, new Comparator>() { + @Override + public int compare(Constructor o1, Constructor o2) { + return Integer.compare(o2.getParameterTypes().length, o1.getParameterTypes().length); + } + }); + for (Constructor declaredConstructor : declaredConstructors) { + declaredConstructor.setAccessible(true); + final int parametersNumber = declaredConstructor.getParameterTypes().length; + + Object[] arguments = new Object[parametersNumber]; + for (int argumentIndex = 0; argumentIndex < arguments.length; argumentIndex++) { + arguments[argumentIndex] = getInstanceOfType(declaredConstructor.getParameterTypes()[argumentIndex]); + } + try { + return (T) declaredConstructor.newInstance(arguments); + } catch (Exception e) { + lastException = e; + } + + } + throw failedToInstantiateItem(clazz, lastException); + } + + private IllegalStateException failedToInstantiateItem(Class clazz, Exception e) { + return new IllegalStateException("Failed to create an instance of <" + clazz + "> class.", e); + } + + @Override + public void describeTo(Description description) { + description.appendText("Expected to be unmodifiable collection, but "); + } + + /** + * Creates matcher that matches when collection is truly unmodifiable + */ + public static Matcher> isUnmodifiable() { + return new IsUnmodifiableCollection<>(); + } + +} diff --git a/hamcrest/src/test/java/org/hamcrest/collection/IsUnmodifiableCollectionTest.java b/hamcrest/src/test/java/org/hamcrest/collection/IsUnmodifiableCollectionTest.java new file mode 100644 index 00000000..2d822ab9 --- /dev/null +++ b/hamcrest/src/test/java/org/hamcrest/collection/IsUnmodifiableCollectionTest.java @@ -0,0 +1,140 @@ +package org.hamcrest.collection; + +import org.hamcrest.AbstractMatcherTest; +import org.hamcrest.Matcher; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; + +import static org.hamcrest.collection.IsUnmodifiableCollection.isUnmodifiable; + +public class IsUnmodifiableCollectionTest extends AbstractMatcherTest { + + @Override + protected Matcher createMatcher() { + return isUnmodifiable(); + } + + public void testMatchesUnmodifiableList() { + assertMatches("truly unmodifiable list", isUnmodifiable(), Collections.unmodifiableList(Collections.emptyList())); + } + + public void testMatchesUnmodifiableSet() { + assertMatches("truly unmodifiable set", isUnmodifiable(), Collections.unmodifiableSet(Collections.emptySet())); + } + + public void testMatchesUnmodifiableCollection() { + assertMatches("truly unmodifiable collection", isUnmodifiable(), Collections.unmodifiableCollection(Arrays.asList(1,2,3))); + } + + public void testMismatchesArrayList() { + assertMismatchDescription("was able to add a value into the collection", isUnmodifiable(), new ArrayList<>()); + } + + public void testMismatchesArraysList() { + assertMismatchDescription("was able to remove a value from the collection", isUnmodifiable(), Arrays.asList(1,2,3)); + } + + public void testMismatchesHashSet() { + assertMismatchDescription("was able to add a value into the collection", isUnmodifiable(), new HashSet<>()); + } + + public void testMismatchesPartiallyUnmodifiableListAllowingAddAll() { + assertMismatchDescription("was able to perform addAll on the collection", isUnmodifiable(), new ArrayList() { + @Override + public boolean add(String s) { + throw new UnsupportedOperationException(); + } + }); + } + + public void testMismatchesPartiallyUnmodifiableListAllowingRemove() { + assertMismatchDescription("was able to remove a value from the collection", isUnmodifiable(), new ArrayList() { + @Override + public boolean add(String s) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + }); + } + + public void testMismatchesPartiallyUnmodifiableListAllowingRemoveAll() { + assertMismatchDescription("was able to perform removeAll on the collection", isUnmodifiable(), new ArrayList() { + @Override + public boolean add(String s) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + }); + } + + public void testMismatchesPartiallyUnmodifiableListAllowingRetainAll() { + assertMismatchDescription("was able to perform retainAll on the collection", isUnmodifiable(), new ArrayList() { + @Override + public boolean add(String s) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + }); + } + + public void testMismatchesPartiallyUnmodifiableListAllowingClear() { + assertMismatchDescription("was able to clear the collection", isUnmodifiable(), new ArrayList() { + @Override + public boolean add(String s) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean addAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean removeAll(Collection c) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean retainAll(Collection c) { + throw new UnsupportedOperationException(); + } + }); + } + +}