001 /*
002 * Copyright 2002-2005 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 javax.swing.DefaultListModel;
019 import javax.swing.event.EventListenerList;
020 import javax.swing.event.ListDataEvent;
021 import javax.swing.event.ListDataListener;
022
023 import ca.odell.glazedlists.EventList;
024 import ca.odell.glazedlists.TransformedList;
025 import ca.odell.glazedlists.event.ListEvent;
026 import org.springframework.binding.value.IndexAdapter;
027
028 /**
029 * This class provides an implementation of {@link EventList} that also implements the
030 * {@link ObservableList} interface so that it can be used by an AbstractForm as the list
031 * of editable objects.
032 *
033 * @author Larry Streepy
034 *
035 */
036 public class ObservableEventList extends TransformedList implements ObservableList {
037
038 private IndexAdapter indexAdapter;
039 protected EventListenerList listenerList = new EventListenerList();
040
041 /**
042 * Creates a {@link ObservableEventList} on the specified source event list.
043 */
044 public ObservableEventList(EventList source) {
045 super( source );
046
047 // listen for changes to the source list
048 source.addListEventListener(this);
049
050 }
051
052 /**
053 * Gets whether the source {@link EventList} is writable via this API.
054 *
055 * @return always true
056 */
057 protected boolean isWritable() {
058 return true;
059 }
060
061 /*
062 * (non-Javadoc)
063 *
064 * @see ca.odell.glazedlists.TransformedList#listChanged(ca.odell.glazedlists.event.ListEvent)
065 */
066 public void listChanged(ListEvent listChanges) {
067 // forward the event to interested listeners
068 updates.forwardEvent( listChanges );
069
070 // And then fire model events based on these changes
071 while( listChanges.nextBlock() ) {
072 // get the current change info
073 int startIndex = listChanges.getBlockStartIndex();
074 int endIndex = listChanges.getBlockEndIndex();
075 int changeType = listChanges.getType();
076
077 // create a table model event for this block
078 if( changeType == ListEvent.INSERT )
079 fireIntervalAdded(this, startIndex, endIndex);
080 else if( changeType == ListEvent.DELETE )
081 fireIntervalRemoved(this, startIndex, endIndex);
082 else if( changeType == ListEvent.UPDATE )
083 fireContentsChanged(this, startIndex, endIndex);
084 }
085
086 }
087
088 /*
089 * (non-Javadoc)
090 *
091 * @see org.springframework.binding.value.support.ObservableList#getIndexAdapter(int)
092 */
093 public IndexAdapter getIndexAdapter(int index) {
094 if( indexAdapter == null ) {
095 indexAdapter = new ThisIndexAdapter();
096 }
097 indexAdapter.setIndex( index );
098 return indexAdapter;
099 }
100
101 /*
102 * (non-Javadoc)
103 *
104 * @see javax.swing.ListModel#getSize()
105 */
106 public int getSize() {
107 return size();
108 }
109
110 /*
111 * (non-Javadoc)
112 *
113 * @see javax.swing.ListModel#getElementAt(int)
114 */
115 public Object getElementAt(int index) {
116 return get( index );
117 }
118
119 /*
120 * (non-Javadoc)
121 *
122 * @see javax.swing.ListModel#addListDataListener(javax.swing.event.ListDataListener)
123 */
124 public void addListDataListener(ListDataListener l) {
125 listenerList.add( ListDataListener.class, l );
126 }
127
128 /*
129 * (non-Javadoc)
130 *
131 * @see javax.swing.ListModel#removeListDataListener(javax.swing.event.ListDataListener)
132 */
133 public void removeListDataListener(ListDataListener l) {
134 listenerList.remove( ListDataListener.class, l );
135 }
136
137 /**
138 * <code>AbstractListModel</code> subclasses must call this method <b>after</b> one
139 * or more elements of the list change. The changed elements are specified by the
140 * closed interval index0, index1 -- the endpoints are included. Note that index0 need
141 * not be less than or equal to index1.
142 *
143 * @param source the <code>ListModel</code> that changed, typically "this"
144 * @param index0 one end of the new interval
145 * @param index1 the other end of the new interval
146 * @see EventListenerList
147 * @see DefaultListModel
148 */
149 protected void fireContentsChanged(Object source, int index0, int index1) {
150 Object[] listeners = listenerList.getListenerList();
151 ListDataEvent e = null;
152
153 for( int i = listeners.length - 2; i >= 0; i -= 2 ) {
154 if( listeners[i] == ListDataListener.class ) {
155 if( e == null ) {
156 e = new ListDataEvent( source, ListDataEvent.CONTENTS_CHANGED, index0, index1 );
157 }
158 ((ListDataListener) listeners[i + 1]).contentsChanged( e );
159 }
160 }
161 }
162
163 /**
164 * <code>AbstractListModel</code> subclasses must call this method <b>after</b> one
165 * or more elements are added to the model. The new elements are specified by a closed
166 * interval index0, index1 -- the enpoints are included. Note that index0 need not be
167 * less than or equal to index1.
168 *
169 * @param source the <code>ListModel</code> that changed, typically "this"
170 * @param index0 one end of the new interval
171 * @param index1 the other end of the new interval
172 * @see EventListenerList
173 * @see DefaultListModel
174 */
175 protected void fireIntervalAdded(Object source, int index0, int index1) {
176 Object[] listeners = listenerList.getListenerList();
177 ListDataEvent e = null;
178
179 for( int i = listeners.length - 2; i >= 0; i -= 2 ) {
180 if( listeners[i] == ListDataListener.class ) {
181 if( e == null ) {
182 e = new ListDataEvent( source, ListDataEvent.INTERVAL_ADDED, index0, index1 );
183 }
184 ((ListDataListener) listeners[i + 1]).intervalAdded( e );
185 }
186 }
187 }
188
189 /**
190 * <code>AbstractListModel</code> subclasses must call this method <b>after</b> one
191 * or more elements are removed from the model. <code>index0</code> and
192 * <code>index1</code> are the end points of the interval that's been removed. Note
193 * that <code>index0</code> need not be less than or equal to <code>index1</code>.
194 *
195 * @param source the <code>ListModel</code> that changed, typically "this"
196 * @param index0 one end of the removed interval, including <code>index0</code>
197 * @param index1 the other end of the removed interval, including <code>index1</code>
198 * @see EventListenerList
199 * @see DefaultListModel
200 */
201 protected void fireIntervalRemoved(Object source, int index0, int index1) {
202 Object[] listeners = listenerList.getListenerList();
203 ListDataEvent e = null;
204
205 for( int i = listeners.length - 2; i >= 0; i -= 2 ) {
206 if( listeners[i] == ListDataListener.class ) {
207 if( e == null ) {
208 e = new ListDataEvent( source, ListDataEvent.INTERVAL_REMOVED, index0, index1 );
209 }
210 ((ListDataListener) listeners[i + 1]).intervalRemoved( e );
211 }
212 }
213 }
214
215 /**
216 * Inner class to provide the index adapter for this list.
217 */
218 private class ThisIndexAdapter extends AbstractIndexAdapter {
219 private static final int NULL_INDEX = -1;
220
221 public Object getValue() {
222 if( getIndex() == NULL_INDEX ) {
223 return null;
224 }
225 return get( getIndex() );
226 }
227
228 public void setValue(Object value) {
229 if( getIndex() == NULL_INDEX ) {
230 throw new IllegalStateException( "Attempt to set value at null index; operation not allowed" );
231 }
232
233 // Propogate this operation onto our event list
234 set( getIndex(), value );
235 }
236
237 public void fireIndexedObjectChanged() {
238 // Not supported
239 }
240 }
241
242 }