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 }