001    /*
002     * Copyright 2002-2007 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.Iterator;
023    import java.util.List;
024    import java.util.ListIterator;
025    
026    import javax.swing.AbstractListModel;
027    
028    import org.springframework.binding.value.IndexAdapter;
029    import org.springframework.util.ObjectUtils;
030    
031    /**
032     * @author Keith Donald
033     */
034    public class ListListModel extends AbstractListModel implements ObservableList {
035        private List items;
036    
037        private Comparator sorter;
038    
039        private IndexAdapter indexAdapter;
040    
041        public ListListModel() {
042            this(null);
043        }
044    
045        public ListListModel(List items) {
046            this(items, null);
047        }
048    
049        public ListListModel(List items, Comparator sorter) {
050            if (items != null) {
051                this.items = new ArrayList(items);
052            }
053            else {
054                this.items = new ArrayList();
055            }
056            setComparator(sorter);
057            sort();
058        }
059    
060        public void setComparator(Comparator sorter) {
061            this.sorter = sorter;
062        }
063    
064        public void sort() {
065            if (sorter != null) {
066                Collections.sort(items, sorter);
067                fireContentsChanged(items, -1, -1);
068            }
069        }
070    
071        protected List getItems() {
072            return items;
073        }
074    
075        public int getSize() {
076            return items.size();
077        }
078    
079        public Object getElementAt(int index) {
080            return items.get(index);
081        }
082    
083        public void add(int index, Object o) {
084            items.add(index, o);
085            fireIntervalAdded(this, index, index);
086        }
087    
088        public IndexAdapter getIndexAdapter(int index) {
089            if (indexAdapter == null) {
090                this.indexAdapter = new ThisIndexAdapter();
091            }
092            indexAdapter.setIndex(index);
093            return indexAdapter;
094        }
095    
096        private class ThisIndexAdapter extends AbstractIndexAdapter {
097            private static final int NULL_INDEX = -1;
098    
099            public Object getValue() {
100                if (getIndex() == NULL_INDEX) {
101                    return null;
102                }
103                return get(getIndex());
104            }
105    
106            public void setValue(Object value) {
107                if (getIndex() == NULL_INDEX) {
108                    throw new IllegalStateException("Attempt to set value at null index; operation not allowed");
109                }
110                Object oldValue = items.set(getIndex(), value);
111                if (hasValueChanged(oldValue, value)) {
112                    fireContentsChanged(getIndex());
113                    fireValueChange(oldValue, value);
114                }
115            }
116    
117            public void fireIndexedObjectChanged() {
118                fireContentsChanged(getIndex());
119            }
120        }
121    
122        public boolean add(Object o) {
123            boolean result = items.add(o);
124            if (result) {
125                int end = items.size() - 1;
126                fireIntervalAdded(this, end, end);
127            }
128            return result;
129    
130        }
131    
132        public boolean addAll(Collection c) {
133            int firstIndex = items.size();
134            boolean result = items.addAll(c);
135            if (result) {
136                int lastIndex = items.size() - 1;
137                fireIntervalAdded(this, firstIndex, lastIndex);
138            }
139            return result;
140        }
141    
142        public boolean addAll(int index, Collection c) {
143            boolean result = items.addAll(index, c);
144            if (result) {
145                fireIntervalAdded(this, index, index + c.size() - 1);
146            }
147            return result;
148        }
149    
150        public void clear() {
151            if (items.size() > 0) {
152                int firstIndex = 0;
153                int lastIndex = items.size() - 1;
154                items.clear();
155                fireIntervalRemoved(this, firstIndex, lastIndex);
156            }
157        }
158    
159        public boolean contains(Object o) {
160            return items.contains(o);
161        }
162    
163        public boolean containsAll(Collection c) {
164            return items.containsAll(c);
165        }
166    
167        public Object get(int index) {
168            return items.get(index);
169        }
170    
171        public int indexOf(Object o) {
172            return items.indexOf(o);
173        }
174    
175        public boolean isEmpty() {
176            return items.isEmpty();
177        }
178    
179        public Iterator iterator() {
180            return items.iterator();
181        }
182    
183        public int lastIndexOf(Object o) {
184            return items.lastIndexOf(o);
185        }
186    
187        public ListIterator listIterator() {
188            return items.listIterator();
189        }
190    
191        public ListIterator listIterator(int index) {
192            return items.listIterator(index);
193        }
194    
195        public Object remove(int index) {
196            Object o = items.remove(index);
197            fireIntervalRemoved(this, index, index);
198            return o;
199        }
200    
201        public boolean remove(Object o) {
202            int index = indexOf(o);
203            if (index != -1) {
204                remove(index);
205                return true;
206            }
207            return false;
208        }
209    
210        public boolean removeAll(Collection c) {
211            boolean b = items.removeAll(c);
212            if (b) {
213                fireContentsChanged(this, -1, -1);
214            }
215            return b;
216        }
217    
218        public boolean retainAll(Collection c) {
219            boolean b = items.retainAll(c);
220                    if (b) {
221                            fireContentsChanged(this, -1, -1);
222                    }
223                    return b;
224        }
225    
226        /**
227             * Set the value of a list element at the specified index.
228             * @param index of element to set
229             * @param element New element value
230             * @return old element value
231             */
232        public Object set(int index, Object element) {
233            Object oldObject = items.set(index, element);
234            if (hasChanged(oldObject, element)) {
235                fireContentsChanged(index);
236            }
237            return oldObject;
238        }
239    
240        /**
241         * Determine if the provided objects are different (have changed).  This method essentially
242         * embodies the "change semantics" for elements in this list.  If list elements have an
243         * altered "equals" implementation, it may not be sufficient to detect changes in a pair of
244         * objects.  In that case, you can override this method and implement whatever change detection
245         * mechanism is appropriate.
246         * 
247         * @param oldElement Old (original) value to compare
248         * @param newElement New (updated) value to compare
249         * @return true if objects are different (have changed)
250         */
251        protected boolean hasChanged(Object oldElement, Object newElement) {
252            return !ObjectUtils.nullSafeEquals( oldElement, newElement );
253        }
254    
255        public int size() {
256            return items.size();
257        }
258    
259        public List subList(int fromIndex, int toIndex) {
260            return items.subList(fromIndex, toIndex);
261        }
262    
263        public Object[] toArray() {
264            return items.toArray();
265        }
266    
267        public Object[] toArray(Object[] a) {
268            return items.toArray(a);
269        }
270    
271        /**
272         * Notifies the list model that one of the list elements has changed.
273         */
274        protected void fireContentsChanged(int index) {
275            fireContentsChanged(index, index);
276        }
277    
278        /**
279         * Notifies the list model that one or more of the list elements have
280         * changed. The changed elements are specified by the range startIndex to
281         * endIndex inclusive.
282         */
283        protected void fireContentsChanged(int startIndex, int endIndex) {
284            fireContentsChanged(this, startIndex, endIndex);
285        }
286    
287        /**
288         * Replace this list model's items with the contents of the provided
289         * collection.
290         * 
291         * @param collection
292         *            The collection to replace with
293         */
294        public boolean replaceWith(Collection collection) {
295            boolean changed = false;
296            if (items.size() > 0) {
297                items.clear();
298                changed = true;
299            }
300            if (items.addAll(0, collection) && !changed) {
301                changed = true;
302            }
303            if (changed) {
304                fireContentsChanged(-1, -1);
305            }
306            return changed;
307        }
308    }