Skip to content

Commit 0ba3f59

Browse files
Andremoniynhojpatrick
authored andcommitted
IsUnmodifiableCollection matcher feature #249
1 parent 73d89b2 commit 0ba3f59

2 files changed

Lines changed: 316 additions & 0 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
package org.hamcrest.collection;
2+
3+
import org.hamcrest.Description;
4+
import org.hamcrest.Matcher;
5+
import org.hamcrest.TypeSafeDiagnosingMatcher;
6+
7+
import java.lang.reflect.Array;
8+
import java.lang.reflect.Constructor;
9+
import java.util.Arrays;
10+
import java.util.Collection;
11+
import java.util.Collections;
12+
import java.util.Comparator;
13+
import java.util.HashMap;
14+
import java.util.HashSet;
15+
import java.util.List;
16+
import java.util.Map;
17+
import java.util.Set;
18+
19+
/**
20+
* Matches if collection is truly unmodifiable
21+
*/
22+
public class IsUnmodifiableCollection<E> extends TypeSafeDiagnosingMatcher<Collection<? extends E>> {
23+
24+
private static final Map<Class, Object> DEFAULT_COLLECTIONS = new HashMap<>();
25+
26+
static {
27+
final List<String> list = Arrays.asList("a", "b", "c");
28+
DEFAULT_COLLECTIONS.put(Collection.class, list);
29+
DEFAULT_COLLECTIONS.put(List.class, list);
30+
DEFAULT_COLLECTIONS.put(Set.class, new HashSet<>(list));
31+
}
32+
33+
@SuppressWarnings("unchecked")
34+
@Override
35+
protected boolean matchesSafely(final Collection collection, final Description mismatchDescription) {
36+
final Class<? extends Collection> collectionClass = collection.getClass();
37+
final Collection item = getInstanceOfType(collectionClass);
38+
if (item == null) {
39+
throw failedToInstantiateItem(collectionClass, null);
40+
}
41+
final Object testObject = new Object();
42+
final Set<Object> singletonList = Collections.singleton(testObject);
43+
44+
try {
45+
item.add(testObject);
46+
mismatchDescription.appendText("was able to add a value into the collection");
47+
return false;
48+
} catch (Exception ignore) {
49+
}
50+
51+
try {
52+
item.addAll(singletonList);
53+
mismatchDescription.appendText("was able to perform addAll on the collection");
54+
return false;
55+
} catch (Exception ignore) {
56+
}
57+
58+
try {
59+
item.remove(testObject);
60+
mismatchDescription.appendText("was able to remove a value from the collection");
61+
return false;
62+
} catch (Exception ignore) {
63+
}
64+
65+
try {
66+
item.removeAll(singletonList);
67+
mismatchDescription.appendText("was able to perform removeAll on the collection");
68+
return false;
69+
} catch (Exception ignore) {
70+
}
71+
72+
try {
73+
item.retainAll(singletonList);
74+
mismatchDescription.appendText("was able to perform retainAll on the collection");
75+
return false;
76+
} catch (Exception ignore) {
77+
}
78+
79+
try {
80+
item.clear();
81+
mismatchDescription.appendText("was able to clear the collection");
82+
return false;
83+
} catch (Exception ignore) {
84+
}
85+
86+
return true;
87+
}
88+
89+
90+
@SuppressWarnings("unchecked")
91+
private <T> T getInstanceOfType(final Class<T> clazz) {
92+
Exception lastException = null;
93+
94+
if (clazz.isArray()) {
95+
return (T) Array.newInstance(clazz, 0);
96+
}
97+
98+
if (clazz.isPrimitive()) {
99+
if (Byte.TYPE.isAssignableFrom(clazz)) {
100+
return (T) Byte.valueOf((byte) 1);
101+
}
102+
if (Short.TYPE.isAssignableFrom(clazz)) {
103+
return (T) Short.valueOf((short) 1);
104+
}
105+
if (Integer.TYPE.isAssignableFrom(clazz)) {
106+
return (T) Integer.valueOf(1);
107+
}
108+
if (Long.TYPE.isAssignableFrom(clazz)) {
109+
return (T) Long.valueOf(1L);
110+
}
111+
if (Float.TYPE.isAssignableFrom(clazz)) {
112+
return (T) Float.valueOf(1L);
113+
}
114+
if (Double.TYPE.isAssignableFrom(clazz)) {
115+
return (T) Double.valueOf(1L);
116+
}
117+
if (Boolean.TYPE.isAssignableFrom(clazz)) {
118+
return (T) Boolean.valueOf(true);
119+
}
120+
if (Character.TYPE.isAssignableFrom(clazz)) {
121+
return (T) Character.valueOf(' ');
122+
}
123+
}
124+
125+
if (clazz.isInterface()) {
126+
Object defaultCollection = DEFAULT_COLLECTIONS.get(clazz);
127+
if (defaultCollection != null) {
128+
return (T) defaultCollection;
129+
}
130+
return null;
131+
}
132+
133+
// For the most part of implementations there probably won't be any default constructor
134+
final Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
135+
// First take constructor with fewer number of arguments
136+
Arrays.sort(declaredConstructors, new Comparator<Constructor<?>>() {
137+
@Override
138+
public int compare(Constructor<?> o1, Constructor<?> o2) {
139+
return Integer.compare(o2.getParameterTypes().length, o1.getParameterTypes().length);
140+
}
141+
});
142+
for (Constructor<?> declaredConstructor : declaredConstructors) {
143+
declaredConstructor.setAccessible(true);
144+
final int parametersNumber = declaredConstructor.getParameterTypes().length;
145+
146+
Object[] arguments = new Object[parametersNumber];
147+
for (int argumentIndex = 0; argumentIndex < arguments.length; argumentIndex++) {
148+
arguments[argumentIndex] = getInstanceOfType(declaredConstructor.getParameterTypes()[argumentIndex]);
149+
}
150+
try {
151+
return (T) declaredConstructor.newInstance(arguments);
152+
} catch (Exception e) {
153+
lastException = e;
154+
}
155+
156+
}
157+
throw failedToInstantiateItem(clazz, lastException);
158+
}
159+
160+
private <T> IllegalStateException failedToInstantiateItem(Class<T> clazz, Exception e) {
161+
return new IllegalStateException("Failed to create an instance of <" + clazz + "> class.", e);
162+
}
163+
164+
@Override
165+
public void describeTo(Description description) {
166+
description.appendText("Expected to be unmodifiable collection, but ");
167+
}
168+
169+
/**
170+
* Creates matcher that matches when collection is truly unmodifiable
171+
*/
172+
public static <E> Matcher<Collection<? extends E>> isUnmodifiable() {
173+
return new IsUnmodifiableCollection<>();
174+
}
175+
176+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package org.hamcrest.collection;
2+
3+
import org.hamcrest.AbstractMatcherTest;
4+
import org.hamcrest.Matcher;
5+
6+
import java.util.ArrayList;
7+
import java.util.Arrays;
8+
import java.util.Collection;
9+
import java.util.Collections;
10+
import java.util.HashSet;
11+
12+
import static org.hamcrest.collection.IsUnmodifiableCollection.isUnmodifiable;
13+
14+
public class IsUnmodifiableCollectionTest extends AbstractMatcherTest {
15+
16+
@Override
17+
protected Matcher<?> createMatcher() {
18+
return isUnmodifiable();
19+
}
20+
21+
public void testMatchesUnmodifiableList() {
22+
assertMatches("truly unmodifiable list", isUnmodifiable(), Collections.unmodifiableList(Collections.emptyList()));
23+
}
24+
25+
public void testMatchesUnmodifiableSet() {
26+
assertMatches("truly unmodifiable set", isUnmodifiable(), Collections.unmodifiableSet(Collections.emptySet()));
27+
}
28+
29+
public void testMatchesUnmodifiableCollection() {
30+
assertMatches("truly unmodifiable collection", isUnmodifiable(), Collections.unmodifiableCollection(Arrays.asList(1,2,3)));
31+
}
32+
33+
public void testMismatchesArrayList() {
34+
assertMismatchDescription("was able to add a value into the collection", isUnmodifiable(), new ArrayList<>());
35+
}
36+
37+
public void testMismatchesArraysList() {
38+
assertMismatchDescription("was able to remove a value from the collection", isUnmodifiable(), Arrays.asList(1,2,3));
39+
}
40+
41+
public void testMismatchesHashSet() {
42+
assertMismatchDescription("was able to add a value into the collection", isUnmodifiable(), new HashSet<>());
43+
}
44+
45+
public void testMismatchesPartiallyUnmodifiableListAllowingAddAll() {
46+
assertMismatchDescription("was able to perform addAll on the collection", isUnmodifiable(), new ArrayList<String>() {
47+
@Override
48+
public boolean add(String s) {
49+
throw new UnsupportedOperationException();
50+
}
51+
});
52+
}
53+
54+
public void testMismatchesPartiallyUnmodifiableListAllowingRemove() {
55+
assertMismatchDescription("was able to remove a value from the collection", isUnmodifiable(), new ArrayList<String>() {
56+
@Override
57+
public boolean add(String s) {
58+
throw new UnsupportedOperationException();
59+
}
60+
61+
@Override
62+
public boolean addAll(Collection<? extends String> c) {
63+
throw new UnsupportedOperationException();
64+
}
65+
});
66+
}
67+
68+
public void testMismatchesPartiallyUnmodifiableListAllowingRemoveAll() {
69+
assertMismatchDescription("was able to perform removeAll on the collection", isUnmodifiable(), new ArrayList<String>() {
70+
@Override
71+
public boolean add(String s) {
72+
throw new UnsupportedOperationException();
73+
}
74+
75+
@Override
76+
public boolean addAll(Collection<? extends String> c) {
77+
throw new UnsupportedOperationException();
78+
}
79+
80+
@Override
81+
public boolean remove(Object o) {
82+
throw new UnsupportedOperationException();
83+
}
84+
});
85+
}
86+
87+
public void testMismatchesPartiallyUnmodifiableListAllowingRetainAll() {
88+
assertMismatchDescription("was able to perform retainAll on the collection", isUnmodifiable(), new ArrayList<String>() {
89+
@Override
90+
public boolean add(String s) {
91+
throw new UnsupportedOperationException();
92+
}
93+
94+
@Override
95+
public boolean addAll(Collection<? extends String> c) {
96+
throw new UnsupportedOperationException();
97+
}
98+
99+
@Override
100+
public boolean remove(Object o) {
101+
throw new UnsupportedOperationException();
102+
}
103+
104+
@Override
105+
public boolean removeAll(Collection<?> c) {
106+
throw new UnsupportedOperationException();
107+
}
108+
});
109+
}
110+
111+
public void testMismatchesPartiallyUnmodifiableListAllowingClear() {
112+
assertMismatchDescription("was able to clear the collection", isUnmodifiable(), new ArrayList<String>() {
113+
@Override
114+
public boolean add(String s) {
115+
throw new UnsupportedOperationException();
116+
}
117+
118+
@Override
119+
public boolean addAll(Collection<? extends String> c) {
120+
throw new UnsupportedOperationException();
121+
}
122+
123+
@Override
124+
public boolean remove(Object o) {
125+
throw new UnsupportedOperationException();
126+
}
127+
128+
@Override
129+
public boolean removeAll(Collection<?> c) {
130+
throw new UnsupportedOperationException();
131+
}
132+
133+
@Override
134+
public boolean retainAll(Collection<?> c) {
135+
throw new UnsupportedOperationException();
136+
}
137+
});
138+
}
139+
140+
}

0 commit comments

Comments
 (0)