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    }