001    /*
002     * Copyright 2002-2004 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.binding.value.support;
017    
018    import java.util.ArrayList;
019    import java.util.Collection;
020    import java.util.Collections;
021    import java.util.Comparator;
022    import java.util.HashSet;
023    import java.util.Iterator;
024    import java.util.List;
025    import java.util.Random;
026    import java.util.Set;
027    import java.util.SortedSet;
028    import java.util.TreeSet;
029    
030    import org.springframework.beans.BeanUtils;
031    import org.springframework.binding.support.TestPropertyChangeListener;
032    import org.springframework.binding.value.CommitTrigger;
033    import org.springframework.binding.value.ValueModel;
034    import org.springframework.richclient.test.SpringRichTestCase;
035    
036    /**
037     * Test cases for {@link BufferedCollectionValueModel}
038     *
039     * @author oliverh
040     */
041    public class BufferedCollectionValueModelTests extends SpringRichTestCase {
042    
043        private Class[] supportedIterfaces = new Class[] {Collection.class, List.class, Set.class, SortedSet.class,};
044    
045        private Class[] supportedClasses = new Class[] {ArrayList.class, HashSet.class, TreeSet.class,};
046    
047        public void testCreating() {
048            try {
049                getBufferedCollectionValueModel(null, null);
050                fail("NULL wrappedType should not be supported");
051            }
052            catch (IllegalArgumentException e) {
053                // expected
054            }
055            try {
056                getBufferedCollectionValueModel(null, Object.class);
057                fail("wrappedType can only be an instance Collection or an array");
058            }
059            catch (IllegalArgumentException e) {
060                // expected
061            }
062            try {
063                getBufferedCollectionValueModel(null, int[].class);
064                fail("wrappedType can not be a primitive array");
065            }
066            catch (IllegalArgumentException e) {
067                // expected
068            }
069            for (int i = 0; i < supportedIterfaces.length; i++) {
070                getBufferedCollectionValueModel(null, supportedIterfaces[i]);
071            }
072            try {
073                getBufferedCollectionValueModel(null, CustomCollectionInterface.class);
074                fail("if wrappedType is an interface it must one of the standard JDK Collection interfaces");
075            }
076            catch (IllegalArgumentException e) {
077                // expected
078            }
079            getBufferedCollectionValueModel(null, Object[].class);
080            getBufferedCollectionValueModel(null, CustomCollectionClass.class);
081            for (int i = 0; i < supportedClasses.length; i++) {
082                getBufferedCollectionValueModel(null, supportedClasses[i]);
083            }
084        }
085    
086        public void testGetAfterBackingObjectChange() {
087            Object[] backingArray = getArray(1);
088            BufferedCollectionValueModel vm = getBufferedCollectionValueModel(backingArray);
089            assertHasSameStructure((ListListModel)vm.getValue(), backingArray);
090    
091            backingArray = getArray(2);
092            vm.getWrappedValueModel().setValue(backingArray);
093            assertHasSameStructure((ListListModel)vm.getValue(), backingArray);
094    
095            vm.getWrappedValueModel().setValue(null);
096            assertEquals("ListListModel must have no elements when backing collection is NULL",
097                    ((ListListModel)vm.getValue()).size(), 0);
098    
099            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    }