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 }