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 com.google.common.truth.Truth.assertThat;
20import static java.util.Arrays.asList;
21
22import com.google.common.annotations.GwtCompatible;
23
24import junit.framework.TestCase;
25
26import java.util.AbstractCollection;
27import java.util.Collection;
28import java.util.Collections;
29import java.util.Iterator;
30import java.util.List;
31import java.util.ListIterator;
32import java.util.RandomAccess;
33import java.util.Set;
34import java.util.SortedSet;
35
36/**
37 * Tests for {@code Constraints}.
38 *
39 * @author Mike Bostock
40 * @author Jared Levy
41 */
42@GwtCompatible
43public class ConstraintsTest extends TestCase {
44
45  private static final String TEST_ELEMENT = "test";
46
47  private static final class TestElementException
48      extends IllegalArgumentException {
49    private static final long serialVersionUID = 0;
50  }
51
52  private static final Constraint<String> TEST_CONSTRAINT
53      = new Constraint<String>() {
54          @Override
55          public String checkElement(String element) {
56            if (TEST_ELEMENT.equals(element)) {
57              throw new TestElementException();
58            }
59            return element;
60          }
61        };
62
63  public void testConstrainedCollectionLegal() {
64    Collection<String> collection = Lists.newArrayList("foo", "bar");
65    Collection<String> constrained = Constraints.constrainedCollection(
66        collection, TEST_CONSTRAINT);
67    collection.add(TEST_ELEMENT);
68    constrained.add("qux");
69    constrained.addAll(asList("cat", "dog"));
70    /* equals and hashCode aren't defined for Collection */
71    assertThat(collection).has()
72        .exactly("foo", "bar", TEST_ELEMENT, "qux", "cat", "dog").inOrder();
73    assertThat(constrained).has()
74        .exactly("foo", "bar", TEST_ELEMENT, "qux", "cat", "dog").inOrder();
75  }
76
77  public void testConstrainedCollectionIllegal() {
78    Collection<String> collection = Lists.newArrayList("foo", "bar");
79    Collection<String> constrained = Constraints.constrainedCollection(
80        collection, TEST_CONSTRAINT);
81    try {
82      constrained.add(TEST_ELEMENT);
83      fail("TestElementException expected");
84    } catch (TestElementException expected) {}
85    try {
86      constrained.addAll(asList("baz", TEST_ELEMENT));
87      fail("TestElementException expected");
88    } catch (TestElementException expected) {}
89    assertThat(constrained).has().exactly("foo", "bar").inOrder();
90    assertThat(collection).has().exactly("foo", "bar").inOrder();
91  }
92
93  public void testConstrainedSetLegal() {
94    Set<String> set = Sets.newLinkedHashSet(asList("foo", "bar"));
95    Set<String> constrained = Constraints.constrainedSet(set, TEST_CONSTRAINT);
96    set.add(TEST_ELEMENT);
97    constrained.add("qux");
98    constrained.addAll(asList("cat", "dog"));
99    assertTrue(set.equals(constrained));
100    assertTrue(constrained.equals(set));
101    assertEquals(set.toString(), constrained.toString());
102    assertEquals(set.hashCode(), constrained.hashCode());
103    assertThat(set).has().exactly("foo", "bar", TEST_ELEMENT, "qux", "cat", "dog").inOrder();
104    assertThat(constrained).has()
105        .exactly("foo", "bar", TEST_ELEMENT, "qux", "cat", "dog").inOrder();
106  }
107
108  public void testConstrainedSetIllegal() {
109    Set<String> set = Sets.newLinkedHashSet(asList("foo", "bar"));
110    Set<String> constrained = Constraints.constrainedSet(set, TEST_CONSTRAINT);
111    try {
112      constrained.add(TEST_ELEMENT);
113      fail("TestElementException expected");
114    } catch (TestElementException expected) {}
115    try {
116      constrained.addAll(asList("baz", TEST_ELEMENT));
117      fail("TestElementException expected");
118    } catch (TestElementException expected) {}
119    assertThat(constrained).has().exactly("foo", "bar").inOrder();
120    assertThat(set).has().exactly("foo", "bar").inOrder();
121  }
122
123  public void testConstrainedSortedSetLegal() {
124    SortedSet<String> sortedSet = Sets.newTreeSet(asList("foo", "bar"));
125    SortedSet<String> constrained = Constraints.constrainedSortedSet(
126        sortedSet, TEST_CONSTRAINT);
127    sortedSet.add(TEST_ELEMENT);
128    constrained.add("qux");
129    constrained.addAll(asList("cat", "dog"));
130    assertTrue(sortedSet.equals(constrained));
131    assertTrue(constrained.equals(sortedSet));
132    assertEquals(sortedSet.toString(), constrained.toString());
133    assertEquals(sortedSet.hashCode(), constrained.hashCode());
134    assertThat(sortedSet).has().exactly("bar", "cat", "dog", "foo", "qux", TEST_ELEMENT).inOrder();
135    assertThat(constrained).has()
136        .exactly("bar", "cat", "dog", "foo", "qux", TEST_ELEMENT).inOrder();
137    assertNull(constrained.comparator());
138    assertEquals("bar", constrained.first());
139    assertEquals(TEST_ELEMENT, constrained.last());
140  }
141
142  public void testConstrainedSortedSetIllegal() {
143    SortedSet<String> sortedSet = Sets.newTreeSet(asList("foo", "bar"));
144    SortedSet<String> constrained = Constraints.constrainedSortedSet(
145        sortedSet, TEST_CONSTRAINT);
146    try {
147      constrained.add(TEST_ELEMENT);
148      fail("TestElementException expected");
149    } catch (TestElementException expected) {}
150    try {
151      constrained.subSet("bar", "foo").add(TEST_ELEMENT);
152      fail("TestElementException expected");
153    } catch (TestElementException expected) {}
154    try {
155      constrained.headSet("bar").add(TEST_ELEMENT);
156      fail("TestElementException expected");
157    } catch (TestElementException expected) {}
158    try {
159      constrained.tailSet("foo").add(TEST_ELEMENT);
160      fail("TestElementException expected");
161    } catch (TestElementException expected) {}
162    try {
163      constrained.addAll(asList("baz", TEST_ELEMENT));
164      fail("TestElementException expected");
165    } catch (TestElementException expected) {}
166    assertThat(constrained).has().exactly("bar", "foo").inOrder();
167    assertThat(sortedSet).has().exactly("bar", "foo").inOrder();
168  }
169
170  public void testConstrainedListLegal() {
171    List<String> list = Lists.newArrayList("foo", "bar");
172    List<String> constrained = Constraints.constrainedList(
173        list, TEST_CONSTRAINT);
174    list.add(TEST_ELEMENT);
175    constrained.add("qux");
176    constrained.addAll(asList("cat", "dog"));
177    constrained.add(1, "cow");
178    constrained.addAll(4, asList("box", "fan"));
179    constrained.set(2, "baz");
180    assertTrue(list.equals(constrained));
181    assertTrue(constrained.equals(list));
182    assertEquals(list.toString(), constrained.toString());
183    assertEquals(list.hashCode(), constrained.hashCode());
184    assertThat(list).has().exactly(
185        "foo", "cow", "baz", TEST_ELEMENT, "box", "fan", "qux", "cat", "dog").inOrder();
186    assertThat(constrained).has().exactly(
187        "foo", "cow", "baz", TEST_ELEMENT, "box", "fan", "qux", "cat", "dog").inOrder();
188    ListIterator<String> iterator = constrained.listIterator();
189    iterator.next();
190    iterator.set("sun");
191    constrained.listIterator(2).add("sky");
192    assertThat(list).has().exactly(
193        "sun", "cow", "sky", "baz", TEST_ELEMENT, "box", "fan", "qux", "cat", "dog").inOrder();
194    assertThat(constrained).has().exactly(
195        "sun", "cow", "sky", "baz", TEST_ELEMENT, "box", "fan", "qux", "cat", "dog").inOrder();
196    assertTrue(constrained instanceof RandomAccess);
197  }
198
199  public void testConstrainedListRandomAccessFalse() {
200    List<String> list = Lists.newLinkedList(asList("foo", "bar"));
201    List<String> constrained = Constraints.constrainedList(
202        list, TEST_CONSTRAINT);
203    list.add(TEST_ELEMENT);
204    constrained.add("qux");
205    assertFalse(constrained instanceof RandomAccess);
206  }
207
208  public void testConstrainedListIllegal() {
209    List<String> list = Lists.newArrayList("foo", "bar");
210    List<String> constrained = Constraints.constrainedList(
211        list, TEST_CONSTRAINT);
212    try {
213      constrained.add(TEST_ELEMENT);
214      fail("TestElementException expected");
215    } catch (TestElementException expected) {}
216    try {
217      constrained.listIterator().add(TEST_ELEMENT);
218      fail("TestElementException expected");
219    } catch (TestElementException expected) {}
220    try {
221      constrained.listIterator(1).add(TEST_ELEMENT);
222      fail("TestElementException expected");
223    } catch (TestElementException expected) {}
224    try {
225      constrained.listIterator().set(TEST_ELEMENT);
226      fail("TestElementException expected");
227    } catch (TestElementException expected) {}
228    try {
229      constrained.listIterator(1).set(TEST_ELEMENT);
230      fail("TestElementException expected");
231    } catch (TestElementException expected) {}
232    try {
233      constrained.subList(0, 1).add(TEST_ELEMENT);
234      fail("TestElementException expected");
235    } catch (TestElementException expected) {}
236    try {
237      constrained.add(1, TEST_ELEMENT);
238      fail("TestElementException expected");
239    } catch (TestElementException expected) {}
240    try {
241      constrained.set(1, TEST_ELEMENT);
242      fail("TestElementException expected");
243    } catch (TestElementException expected) {}
244    try {
245      constrained.addAll(asList("baz", TEST_ELEMENT));
246      fail("TestElementException expected");
247    } catch (TestElementException expected) {}
248    try {
249      constrained.addAll(1, asList("baz", TEST_ELEMENT));
250      fail("TestElementException expected");
251    } catch (TestElementException expected) {}
252    assertThat(constrained).has().exactly("foo", "bar").inOrder();
253    assertThat(list).has().exactly("foo", "bar").inOrder();
254  }
255
256  public void testNefariousAddAll() {
257    List<String> list = Lists.newArrayList("foo", "bar");
258    List<String> constrained = Constraints.constrainedList(
259        list, TEST_CONSTRAINT);
260    Collection<String> onceIterable = onceIterableCollection("baz");
261    constrained.addAll(onceIterable);
262    assertThat(constrained).has().exactly("foo", "bar", "baz").inOrder();
263    assertThat(list).has().exactly("foo", "bar", "baz").inOrder();
264  }
265
266  /**
267   * Returns a "nefarious" collection, which permits only one call to
268   * iterator(). This verifies that the constrained collection uses a defensive
269   * copy instead of potentially checking the elements in one snapshot and
270   * adding the elements from another.
271   *
272   * @param element the element to be contained in the collection
273   */
274  static <E> Collection<E> onceIterableCollection(final E element) {
275    return new AbstractCollection<E>() {
276      boolean iteratorCalled;
277      @Override public int size() {
278        /*
279         * We could make the collection empty, but that seems more likely to
280         * trigger special cases (so maybe we should test both empty and
281         * nonempty...).
282         */
283        return 1;
284      }
285      @Override public Iterator<E> iterator() {
286        assertFalse("Expected only one call to iterator()", iteratorCalled);
287        iteratorCalled = true;
288        return Collections.singleton(element).iterator();
289      }
290    };
291  }
292
293  // TODO: Test serialization of constrained collections.
294}
295