1   /*
2    * Copyright 2002-2004 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * 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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package org.springframework.binding.value.support;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.Comparator;
22  import java.util.HashSet;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Random;
26  import java.util.Set;
27  import java.util.SortedSet;
28  import java.util.TreeSet;
29  
30  import org.springframework.beans.BeanUtils;
31  import org.springframework.binding.support.TestPropertyChangeListener;
32  import org.springframework.binding.value.CommitTrigger;
33  import org.springframework.binding.value.ValueModel;
34  import org.springframework.richclient.test.SpringRichTestCase;
35  
36  /**
37   * Test cases for {@link BufferedCollectionValueModel}
38   *
39   * @author oliverh
40   */
41  public class BufferedCollectionValueModelTests extends SpringRichTestCase {
42  
43      private Class[] supportedIterfaces = new Class[] {Collection.class, List.class, Set.class, SortedSet.class,};
44  
45      private Class[] supportedClasses = new Class[] {ArrayList.class, HashSet.class, TreeSet.class,};
46  
47      public void testCreating() {
48          try {
49              getBufferedCollectionValueModel(null, null);
50              fail("NULL wrappedType should not be supported");
51          }
52          catch (IllegalArgumentException e) {
53              // expected
54          }
55          try {
56              getBufferedCollectionValueModel(null, Object.class);
57              fail("wrappedType can only be an instance Collection or an array");
58          }
59          catch (IllegalArgumentException e) {
60              // expected
61          }
62          try {
63              getBufferedCollectionValueModel(null, int[].class);
64              fail("wrappedType can not be a primitive array");
65          }
66          catch (IllegalArgumentException e) {
67              // expected
68          }
69          for (int i = 0; i < supportedIterfaces.length; i++) {
70              getBufferedCollectionValueModel(null, supportedIterfaces[i]);
71          }
72          try {
73              getBufferedCollectionValueModel(null, CustomCollectionInterface.class);
74              fail("if wrappedType is an interface it must one of the standard JDK Collection interfaces");
75          }
76          catch (IllegalArgumentException e) {
77              // expected
78          }
79          getBufferedCollectionValueModel(null, Object[].class);
80          getBufferedCollectionValueModel(null, CustomCollectionClass.class);
81          for (int i = 0; i < supportedClasses.length; i++) {
82              getBufferedCollectionValueModel(null, supportedClasses[i]);
83          }
84      }
85  
86      public void testGetAfterBackingObjectChange() {
87          Object[] backingArray = getArray(1);
88          BufferedCollectionValueModel vm = getBufferedCollectionValueModel(backingArray);
89          assertHasSameStructure((ListListModel)vm.getValue(), backingArray);
90  
91          backingArray = getArray(2);
92          vm.getWrappedValueModel().setValue(backingArray);
93          assertHasSameStructure((ListListModel)vm.getValue(), backingArray);
94  
95          vm.getWrappedValueModel().setValue(null);
96          assertEquals("ListListModel must have no elements when backing collection is NULL",
97                  ((ListListModel)vm.getValue()).size(), 0);
98  
99          for (int i = 0; i < supportedClasses.length; i++) {
100             Collection backingCollection = getCollection(supportedClasses[i], i);
101             vm = getBufferedCollectionValueModel(backingCollection);
102             assertHasSameStructure((ListListModel)vm.getValue(), backingCollection);
103 
104             backingCollection = getCollection(supportedClasses[i], i + 1);
105             vm.getWrappedValueModel().setValue(backingCollection);
106             assertHasSameStructure((ListListModel)vm.getValue(), backingCollection);
107 
108             vm.getWrappedValueModel().setValue(null);
109             assertEquals("ListListModel must have no elements when backing collection is NULL",
110                     ((ListListModel)vm.getValue()).size(), 0);
111         }
112     }
113 
114     public void testCreateWithEmptyCollection() {
115         BufferedCollectionValueModel vm = new BufferedCollectionValueModel(new ValueHolder(null), Collection.class);
116         assertTrue(vm.getValue() instanceof ListListModel);
117         assertEquals(0, ((ListListModel)vm.getValue()).size());
118 
119         vm = new BufferedCollectionValueModel(new ValueHolder(new ArrayList()), Collection.class);
120         assertTrue(vm.getValue() instanceof ListListModel);
121         assertEquals(0, ((ListListModel)vm.getValue()).size());
122 
123         vm = new BufferedCollectionValueModel(new ValueHolder(null), Object[].class);
124         assertTrue(vm.getValue() instanceof ListListModel);
125         assertEquals(0, ((ListListModel)vm.getValue()).size());
126 
127         vm = new BufferedCollectionValueModel(new ValueHolder(new Object[0]), Object[].class);
128         assertTrue(vm.getValue() instanceof ListListModel);
129         assertEquals(0, ((ListListModel)vm.getValue()).size());
130     }
131 
132     public void testChangesToListListModelWithBackingArray() {
133         Object[] backingArray = getArray(100);
134         BufferedCollectionValueModel vm = getBufferedCollectionValueModel(backingArray);
135         ListListModel llm = (ListListModel)vm.getValue();
136         llm.clear();
137         assertEquals("changes to ListListModel should be not be made to backing array unless commit is called",
138                 vm.getWrappedValueModel().getValue(), backingArray);
139 
140         backingArray = getArray(101);
141         vm.getWrappedValueModel().setValue(backingArray);
142         Object newValue = new Double(1);
143         llm.set(1, newValue);
144         vm.commit();
145         Object[] newBackingArray = (Object[])vm.getWrappedValueModel().getValue();
146         assertNotSame("change should not have been committed back to original array", newBackingArray, backingArray);
147 
148         llm.add(newValue);
149         vm.commit();
150         newBackingArray = (Object[])vm.getWrappedValueModel().getValue();
151         assertNotSame("change should not have been committed back to original array", newBackingArray, backingArray);
152         assertTrue(newBackingArray.length == backingArray.length + 1);
153         assertEquals(newBackingArray[newBackingArray.length - 1], newValue);
154 
155         llm.clear();
156         vm.commit();
157         newBackingArray = (Object[])vm.getWrappedValueModel().getValue();
158         assertEquals(newBackingArray.length, 0);
159 
160         vm.getWrappedValueModel().setValue(null);
161         llm.clear();
162         vm.commit();
163         assertEquals("if backingCollection is NULL then a commit of an empty LLM should also be NULL",
164                 vm.getWrappedValueModel().getValue(), null);
165 
166         llm.add(newValue);
167         vm.commit();
168         newBackingArray = (Object[])vm.getWrappedValueModel().getValue();
169         assertEquals(newBackingArray.length, 1);
170         assertEquals(newBackingArray[0], newValue);
171     }
172 
173     public void testChangesToListListModelWithBackingCollection() {
174         for (int i = 0; i < supportedClasses.length; i++) {
175             Collection backingCollection = getCollection(supportedClasses[i], 200 + i);
176             BufferedCollectionValueModel vm = getBufferedCollectionValueModel(backingCollection);
177             ListListModel llm = (ListListModel)vm.getValue();
178             llm.clear();
179             assertEquals("changes to LLM should be not be made to backing collection unless commit is called",
180                     vm.getWrappedValueModel().getValue(), backingCollection);
181 
182             backingCollection = getCollection(supportedClasses[i], 201 + i);
183             vm.getWrappedValueModel().setValue(backingCollection);
184             Object newValue = new Integer(-1);
185             backingCollection.remove(newValue);
186             int orgSize = backingCollection.size();
187             llm.set(1, newValue);
188             vm.commit();
189             Collection newBackingCollection = (Collection)vm.getWrappedValueModel().getValue();
190             assertTrue("change should not have been committed back to original array",
191                     !backingCollection.contains(newValue));
192             assertTrue(newBackingCollection.contains(newValue));
193             assertTrue(orgSize == newBackingCollection.size());
194 
195             newValue = new Integer(-2);
196             backingCollection.remove(newValue);
197             orgSize = backingCollection.size();
198             llm.add(newValue);
199             vm.commit();
200             newBackingCollection = (Collection)vm.getWrappedValueModel().getValue();
201 
202             assertTrue(newBackingCollection.contains(newValue));
203             assertTrue(newBackingCollection.size() == orgSize + 1);
204 
205             llm.clear();
206             vm.commit();
207             assertEquals(((Collection)vm.getWrappedValueModel().getValue()).size(), 0);
208 
209             vm.getWrappedValueModel().setValue(null);
210             llm.clear();
211             vm.commit();
212             newBackingCollection = (Collection)vm.getWrappedValueModel().getValue();
213             assertEquals("if backingCollection is NULL then a commit of an empty LLM should also be NULL",
214                     newBackingCollection, null);
215 
216             llm.add(newValue);
217             vm.commit();
218             newBackingCollection = (Collection)vm.getWrappedValueModel().getValue();
219             assertTrue(supportedClasses[i].isAssignableFrom(newBackingCollection.getClass()));
220             assertEquals(newBackingCollection.size(), 1);
221             assertEquals(newBackingCollection.iterator().next(), newValue);
222         }
223     }
224 
225     public void testListListModelKeepsStuctureOfBackingObjectAfterCommit() {
226         Collection backingCollection = getCollection(HashSet.class, 500);
227         int origLength = backingCollection.size();
228         BufferedCollectionValueModel vm = getBufferedCollectionValueModel(backingCollection);
229         ListListModel llm = (ListListModel)vm.getValue();
230         llm.add(backingCollection.iterator().next());
231         assertTrue(llm.size() == origLength + 1);
232         vm.commit();
233         assertTrue("adding a duplicate item should not change the size of a set", llm.size() == origLength);
234         assertHasSameStructure(llm, backingCollection);
235 
236         backingCollection = getCollection(TreeSet.class, 501);
237         vm = getBufferedCollectionValueModel(backingCollection);
238         llm = (ListListModel)vm.getValue();
239         Collections.reverse(llm);
240         assertTrue(((Comparable)llm.get(0)).compareTo(llm.get(1)) > 0);
241         vm.commit();
242         assertTrue("LLM should be sorted the same way as backingCollection",
243                 ((Comparable)llm.get(0)).compareTo(llm.get(1)) < 0);
244         assertHasSameStructure(llm, backingCollection);
245 
246         backingCollection = new TreeSet(new Comparator() {
247 
248             public int compare(Object o1, Object o2) {
249                 return ((Comparable)o2).compareTo(o1);
250             }
251 
252         });
253         populateCollection(backingCollection, 502);
254         vm = getBufferedCollectionValueModel(backingCollection);
255         llm = (ListListModel)vm.getValue();
256         Collections.reverse(llm);
257         assertTrue(((Comparable)llm.get(0)).compareTo(llm.get(1)) < 0);
258         vm.commit();
259         assertTrue("LLM should be sorted the same way as backingCollection",
260                 ((Comparable)llm.get(0)).compareTo(llm.get(1)) > 0);
261         assertHasSameStructure(llm, backingCollection);
262     }
263 
264     public void testIncompatibleCollections() {
265         try {
266             getBufferedCollectionValueModel(new ArrayList(), Set.class);
267             fail("backing object must be assignable to backingCollectionClass");
268         }
269         catch (IllegalArgumentException e) {
270             // expected
271         }
272         try {
273             getBufferedCollectionValueModel(new Double[0], Integer[].class);
274             fail("backing object must be assignable to backingCollectionClass");
275         }
276         catch (IllegalArgumentException e) {
277             // expected
278         }
279     }
280 
281     public void testValueChangeNotification() {
282         Object[] backingArray = getArray(100);
283         BufferedCollectionValueModel vm = getBufferedCollectionValueModel(backingArray);
284         TestPropertyChangeListener vl = new TestPropertyChangeListener(ValueModel.VALUE_PROPERTY);
285         vm.addValueChangeListener(vl);
286 
287         ListListModel llm = (ListListModel)vm.getValue();
288         assertEquals(0, vl.eventCount());
289 
290         vl.reset();
291         llm.add(new Integer(100));
292         assertEquals(1, vl.eventCount());
293         llm.add(1, new Integer(102));
294         assertEquals(2, vl.eventCount());
295 
296         vl.reset();
297         llm.addAll(getCollection(ArrayList.class, 101));
298         assertEquals(1, vl.eventCount());
299         llm.addAll(1, getCollection(ArrayList.class, 101));
300         assertEquals(2, vl.eventCount());
301 
302         vl.reset();
303         llm.remove(1);
304         assertEquals(1, vl.eventCount());
305         llm.removeAll(getCollection(ArrayList.class, 101));
306         assertEquals(2, vl.eventCount());
307 
308         vl.reset();
309         llm.set(1, llm.get(1));
310         assertEquals(0, vl.eventCount());
311 
312         vl.reset();
313         llm.clear();
314         assertEquals(1, vl.eventCount());
315     }
316 
317     public void testRevert() {
318         CommitTrigger commitTriger = new CommitTrigger();
319 
320         Collection backingCollection = getCollection(HashSet.class, 700);
321         BufferedCollectionValueModel vm = getBufferedCollectionValueModel(backingCollection);
322         vm.setCommitTrigger(commitTriger);
323         ListListModel llm = (ListListModel)vm.getValue();
324         llm.clear();
325         commitTriger.revert();
326         assertHasSameStructure(llm, backingCollection);
327     }
328 
329     private void assertHasSameStructure(ListListModel c1, Object[] c2) {
330         assertEquals("collections must be the same size", c1.size(), c2.length);
331         for (int i = 0; i < c2.length; i++) {
332             assertEquals("collections must have the same items in the same order", c1.get(i), c2[i]);
333         }
334     }
335 
336     private void assertHasSameStructure(ListListModel c1, Collection c2) {
337         assertEquals("collections must be the same size", c2.size(), c1.size());
338         for (Iterator i = c1.iterator(), j = c2.iterator(); i.hasNext();) {
339             assertEquals("collections must have the same items in the same order", i.next(), j.next());
340         }
341     }
342 
343     private Object[] getArray(long randomSeed) {
344         Random random = new Random(randomSeed);
345         return new Number[] {new Integer(random.nextInt()), new Integer(random.nextInt()),
346                 new Integer(random.nextInt())};
347     }
348 
349     private Collection getCollection(Class collectionClass, long randomSeed) {
350         return populateCollection((Collection)BeanUtils.instantiateClass(collectionClass), randomSeed);
351     }
352 
353     private Collection populateCollection(Collection c, long randomSeed) {
354         Random random = new Random(randomSeed);
355         c.add(new Integer(random.nextInt()));
356         c.add(new Integer(random.nextInt()));
357         c.add(new Integer(random.nextInt()));
358         return c;
359     }
360 
361     private BufferedCollectionValueModel getBufferedCollectionValueModel(Object backingCollecton) {
362         return getBufferedCollectionValueModel(backingCollecton, backingCollecton.getClass());
363     }
364 
365     private BufferedCollectionValueModel getBufferedCollectionValueModel(Object backingCollecton,
366             Class backingCollectionClass) {
367         ValueModel vm = new ValueHolder(backingCollecton);
368         return new BufferedCollectionValueModel(vm, backingCollectionClass);
369     }
370 
371     interface CustomCollectionInterface extends Collection {
372 
373     }
374 
375     class CustomCollectionClass extends ArrayList implements CustomCollectionInterface {
376 
377     }
378 }