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 }