1/*
2 * Copyright (C) 2007 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.common.collect;
18
19import static java.util.Arrays.asList;
20import static org.junit.contrib.truth.Truth.ASSERT;
21
22import com.google.common.annotations.GwtCompatible;
23import com.google.common.annotations.GwtIncompatible;
24import com.google.common.testing.SerializableTester;
25
26import junit.framework.TestCase;
27
28import java.util.AbstractCollection;
29import java.util.Collection;
30import java.util.Collections;
31import java.util.Iterator;
32import java.util.List;
33import java.util.ListIterator;
34import java.util.RandomAccess;
35import java.util.Set;
36import java.util.SortedSet;
37
38/**
39 * Tests for {@code Constraints}.
40 *
41 * @author Mike Bostock
42 * @author Jared Levy
43 */
44@GwtCompatible(emulated = true)
45public class ConstraintsTest extends TestCase {
46
47  private static final String TEST_ELEMENT = "test";
48
49  private static final class TestElementException
50      extends IllegalArgumentException {
51    private static final long serialVersionUID = 0;
52  }
53
54  private static final Constraint<String> TEST_CONSTRAINT
55      = new Constraint<String>() {
56          @Override
57          public String checkElement(String element) {
58            if (TEST_ELEMENT.equals(element)) {
59              throw new TestElementException();
60            }
61            return element;
62          }
63        };
64
65  public void testNotNull() {
66    Constraint<? super String> constraint = Constraints.notNull();
67    assertSame(TEST_ELEMENT, constraint.checkElement(TEST_ELEMENT));
68    try {
69      constraint.checkElement(null);
70      fail("NullPointerException expected");
71    } catch (NullPointerException expected) {}
72    assertEquals("Not null", constraint.toString());
73  }
74
75  public void testConstrainedCollectionLegal() {
76    Collection<String> collection = Lists.newArrayList("foo", "bar");
77    Collection<String> constrained = Constraints.constrainedCollection(
78        collection, TEST_CONSTRAINT);
79    collection.add(TEST_ELEMENT);
80    constrained.add("qux");
81    constrained.addAll(asList("cat", "dog"));
82    /* equals and hashCode aren't defined for Collection */
83    ASSERT.that(collection).hasContentsInOrder("foo", "bar", TEST_ELEMENT, "qux", "cat", "dog");
84    ASSERT.that(constrained).hasContentsInOrder("foo", "bar", TEST_ELEMENT, "qux", "cat", "dog");
85  }
86
87  public void testConstrainedCollectionIllegal() {
88    Collection<String> collection = Lists.newArrayList("foo", "bar");
89    Collection<String> constrained = Constraints.constrainedCollection(
90        collection, TEST_CONSTRAINT);
91    try {
92      constrained.add(TEST_ELEMENT);
93      fail("TestElementException expected");
94    } catch (TestElementException expected) {}
95    try {
96      constrained.addAll(asList("baz", TEST_ELEMENT));
97      fail("TestElementException expected");
98    } catch (TestElementException expected) {}
99    ASSERT.that(constrained).hasContentsInOrder("foo", "bar");
100    ASSERT.that(collection).hasContentsInOrder("foo", "bar");
101  }
102
103  public void testConstrainedSetLegal() {
104    Set<String> set = Sets.newLinkedHashSet(asList("foo", "bar"));
105    Set<String> constrained = Constraints.constrainedSet(set, TEST_CONSTRAINT);
106    set.add(TEST_ELEMENT);
107    constrained.add("qux");
108    constrained.addAll(asList("cat", "dog"));
109    assertTrue(set.equals(constrained));
110    assertTrue(constrained.equals(set));
111    assertEquals(set.toString(), constrained.toString());
112    assertEquals(set.hashCode(), constrained.hashCode());
113    ASSERT.that(set).hasContentsInOrder("foo", "bar", TEST_ELEMENT, "qux", "cat", "dog");
114    ASSERT.that(constrained).hasContentsInOrder("foo", "bar", TEST_ELEMENT, "qux", "cat", "dog");
115  }
116
117  public void testConstrainedSetIllegal() {
118    Set<String> set = Sets.newLinkedHashSet(asList("foo", "bar"));
119    Set<String> constrained = Constraints.constrainedSet(set, TEST_CONSTRAINT);
120    try {
121      constrained.add(TEST_ELEMENT);
122      fail("TestElementException expected");
123    } catch (TestElementException expected) {}
124    try {
125      constrained.addAll(asList("baz", TEST_ELEMENT));
126      fail("TestElementException expected");
127    } catch (TestElementException expected) {}
128    ASSERT.that(constrained).hasContentsInOrder("foo", "bar");
129    ASSERT.that(set).hasContentsInOrder("foo", "bar");
130  }
131
132  public void testConstrainedSortedSetLegal() {
133    SortedSet<String> sortedSet = Sets.newTreeSet(asList("foo", "bar"));
134    SortedSet<String> constrained = Constraints.constrainedSortedSet(
135        sortedSet, TEST_CONSTRAINT);
136    sortedSet.add(TEST_ELEMENT);
137    constrained.add("qux");
138    constrained.addAll(asList("cat", "dog"));
139    assertTrue(sortedSet.equals(constrained));
140    assertTrue(constrained.equals(sortedSet));
141    assertEquals(sortedSet.toString(), constrained.toString());
142    assertEquals(sortedSet.hashCode(), constrained.hashCode());
143    ASSERT.that(sortedSet).hasContentsInOrder("bar", "cat", "dog", "foo", "qux", TEST_ELEMENT);
144    ASSERT.that(constrained).hasContentsInOrder("bar", "cat", "dog", "foo", "qux", TEST_ELEMENT);
145    assertNull(constrained.comparator());
146    assertEquals("bar", constrained.first());
147    assertEquals(TEST_ELEMENT, constrained.last());
148  }
149
150  public void testConstrainedSortedSetIllegal() {
151    SortedSet<String> sortedSet = Sets.newTreeSet(asList("foo", "bar"));
152    SortedSet<String> constrained = Constraints.constrainedSortedSet(
153        sortedSet, TEST_CONSTRAINT);
154    try {
155      constrained.add(TEST_ELEMENT);
156      fail("TestElementException expected");
157    } catch (TestElementException expected) {}
158    try {
159      constrained.subSet("bar", "foo").add(TEST_ELEMENT);
160      fail("TestElementException expected");
161    } catch (TestElementException expected) {}
162    try {
163      constrained.headSet("bar").add(TEST_ELEMENT);
164      fail("TestElementException expected");
165    } catch (TestElementException expected) {}
166    try {
167      constrained.tailSet("foo").add(TEST_ELEMENT);
168      fail("TestElementException expected");
169    } catch (TestElementException expected) {}
170    try {
171      constrained.addAll(asList("baz", TEST_ELEMENT));
172      fail("TestElementException expected");
173    } catch (TestElementException expected) {}
174    ASSERT.that(constrained).hasContentsInOrder("bar", "foo");
175    ASSERT.that(sortedSet).hasContentsInOrder("bar", "foo");
176  }
177
178  public void testConstrainedListLegal() {
179    List<String> list = Lists.newArrayList("foo", "bar");
180    List<String> constrained = Constraints.constrainedList(
181        list, TEST_CONSTRAINT);
182    list.add(TEST_ELEMENT);
183    constrained.add("qux");
184    constrained.addAll(asList("cat", "dog"));
185    constrained.add(1, "cow");
186    constrained.addAll(4, asList("box", "fan"));
187    constrained.set(2, "baz");
188    assertTrue(list.equals(constrained));
189    assertTrue(constrained.equals(list));
190    assertEquals(list.toString(), constrained.toString());
191    assertEquals(list.hashCode(), constrained.hashCode());
192    ASSERT.that(list).hasContentsInOrder(
193        "foo", "cow", "baz", TEST_ELEMENT, "box", "fan", "qux", "cat", "dog");
194    ASSERT.that(constrained).hasContentsInOrder(
195        "foo", "cow", "baz", TEST_ELEMENT, "box", "fan", "qux", "cat", "dog");
196    ListIterator<String> iterator = constrained.listIterator();
197    iterator.next();
198    iterator.set("sun");
199    constrained.listIterator(2).add("sky");
200    ASSERT.that(list).hasContentsInOrder(
201        "sun", "cow", "sky", "baz", TEST_ELEMENT, "box", "fan", "qux", "cat", "dog");
202    ASSERT.that(constrained).hasContentsInOrder(
203        "sun", "cow", "sky", "baz", TEST_ELEMENT, "box", "fan", "qux", "cat", "dog");
204    assertTrue(constrained instanceof RandomAccess);
205  }
206
207  public void testConstrainedListRandomAccessFalse() {
208    List<String> list = Lists.newLinkedList(asList("foo", "bar"));
209    List<String> constrained = Constraints.constrainedList(
210        list, TEST_CONSTRAINT);
211    list.add(TEST_ELEMENT);
212    constrained.add("qux");
213    assertFalse(constrained instanceof RandomAccess);
214  }
215
216  public void testConstrainedListIllegal() {
217    List<String> list = Lists.newArrayList("foo", "bar");
218    List<String> constrained = Constraints.constrainedList(
219        list, TEST_CONSTRAINT);
220    try {
221      constrained.add(TEST_ELEMENT);
222      fail("TestElementException expected");
223    } catch (TestElementException expected) {}
224    try {
225      constrained.listIterator().add(TEST_ELEMENT);
226      fail("TestElementException expected");
227    } catch (TestElementException expected) {}
228    try {
229      constrained.listIterator(1).add(TEST_ELEMENT);
230      fail("TestElementException expected");
231    } catch (TestElementException expected) {}
232    try {
233      constrained.listIterator().set(TEST_ELEMENT);
234      fail("TestElementException expected");
235    } catch (TestElementException expected) {}
236    try {
237      constrained.listIterator(1).set(TEST_ELEMENT);
238      fail("TestElementException expected");
239    } catch (TestElementException expected) {}
240    try {
241      constrained.subList(0, 1).add(TEST_ELEMENT);
242      fail("TestElementException expected");
243    } catch (TestElementException expected) {}
244    try {
245      constrained.add(1, TEST_ELEMENT);
246      fail("TestElementException expected");
247    } catch (TestElementException expected) {}
248    try {
249      constrained.set(1, TEST_ELEMENT);
250      fail("TestElementException expected");
251    } catch (TestElementException expected) {}
252    try {
253      constrained.addAll(asList("baz", TEST_ELEMENT));
254      fail("TestElementException expected");
255    } catch (TestElementException expected) {}
256    try {
257      constrained.addAll(1, asList("baz", TEST_ELEMENT));
258      fail("TestElementException expected");
259    } catch (TestElementException expected) {}
260    ASSERT.that(constrained).hasContentsInOrder("foo", "bar");
261    ASSERT.that(list).hasContentsInOrder("foo", "bar");
262  }
263
264  public void testConstrainedMultisetLegal() {
265    Multiset<String> multiset = HashMultiset.create(asList("foo", "bar"));
266    Multiset<String> constrained = Constraints.constrainedMultiset(
267        multiset, TEST_CONSTRAINT);
268    multiset.add(TEST_ELEMENT);
269    constrained.add("qux");
270    constrained.addAll(asList("cat", "dog"));
271    constrained.add("cow", 2);
272    assertTrue(multiset.equals(constrained));
273    assertTrue(constrained.equals(multiset));
274    assertEquals(multiset.toString(), constrained.toString());
275    assertEquals(multiset.hashCode(), constrained.hashCode());
276    ASSERT.that(multiset).hasContentsAnyOrder(
277        "foo", "bar", TEST_ELEMENT, "qux", "cat", "dog", "cow", "cow");
278    ASSERT.that(constrained).hasContentsAnyOrder(
279        "foo", "bar", TEST_ELEMENT, "qux", "cat", "dog", "cow", "cow");
280    assertEquals(1, constrained.count("foo"));
281    assertEquals(1, constrained.remove("foo", 3));
282    assertEquals(2, constrained.setCount("cow", 0));
283    ASSERT.that(multiset).hasContentsAnyOrder("bar", TEST_ELEMENT, "qux", "cat", "dog");
284    ASSERT.that(constrained).hasContentsAnyOrder("bar", TEST_ELEMENT, "qux", "cat", "dog");
285  }
286
287  public void testConstrainedMultisetIllegal() {
288    Multiset<String> multiset = HashMultiset.create(asList("foo", "bar"));
289    Multiset<String> constrained = Constraints.constrainedMultiset(
290        multiset, TEST_CONSTRAINT);
291    try {
292      constrained.add(TEST_ELEMENT);
293      fail("TestElementException expected");
294    } catch (TestElementException expected) {}
295    try {
296      constrained.add(TEST_ELEMENT, 2);
297      fail("TestElementException expected");
298    } catch (TestElementException expected) {}
299    try {
300      constrained.addAll(asList("baz", TEST_ELEMENT));
301      fail("TestElementException expected");
302    } catch (TestElementException expected) {}
303    ASSERT.that(constrained).hasContentsAnyOrder("foo", "bar");
304    ASSERT.that(multiset).hasContentsAnyOrder("foo", "bar");
305  }
306
307  public void testNefariousAddAll() {
308    List<String> list = Lists.newArrayList("foo", "bar");
309    List<String> constrained = Constraints.constrainedList(
310        list, TEST_CONSTRAINT);
311    Collection<String> onceIterable = onceIterableCollection("baz");
312    constrained.addAll(onceIterable);
313    ASSERT.that(constrained).hasContentsInOrder("foo", "bar", "baz");
314    ASSERT.that(list).hasContentsInOrder("foo", "bar", "baz");
315  }
316
317  /**
318   * Returns a "nefarious" collection, which permits only one call to
319   * iterator(). This verifies that the constrained collection uses a defensive
320   * copy instead of potentially checking the elements in one snapshot and
321   * adding the elements from another.
322   *
323   * @param element the element to be contained in the collection
324   */
325  static <E> Collection<E> onceIterableCollection(final E element) {
326    return new AbstractCollection<E>() {
327      boolean iteratorCalled;
328      @Override public int size() {
329        /*
330         * We could make the collection empty, but that seems more likely to
331         * trigger special cases (so maybe we should test both empty and
332         * nonempty...).
333         */
334        return 1;
335      }
336      @Override public Iterator<E> iterator() {
337        assertFalse("Expected only one call to iterator()", iteratorCalled);
338        iteratorCalled = true;
339        return Collections.singleton(element).iterator();
340      }
341    };
342  }
343
344  @GwtIncompatible("SerializableTester")
345  public void testSerialization() {
346    // TODO: Test serialization of constrained collections.
347    assertSame(Constraints.notNull(),
348        SerializableTester.reserialize(Constraints.notNull()));
349  }
350}
351