001 package com.sptci.echo2;
002
003 import java.beans.PropertyChangeEvent;
004 import java.io.Serializable;
005 import java.lang.reflect.Field;
006 import java.lang.reflect.Method;
007 import java.lang.reflect.Modifier;
008
009 import java.util.ArrayList;
010 import java.util.List;
011 import java.util.Map;
012 import java.util.HashMap;
013 import java.util.Iterator;
014 import java.util.logging.Logger;
015
016 import nextapp.echo2.app.ListBox;
017 import nextapp.echo2.app.SelectField;
018 import nextapp.echo2.app.button.ToggleButton;
019 import nextapp.echo2.app.list.DefaultListModel;
020
021 import com.sptci.ReflectionUtility;
022
023 /**
024 * The <code>PropertyChangeListener</code> for change events fired
025 * by java beans to update <code>Echo2</code> UI container classes
026 * (represented by {@link #bean}).
027 *
028 * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
029 * @author Rakesh Vidyadharan 2006-01-25
030 * @version $Id: ViewPropertyChangeListener.java,v 1.2 2006/02/15 00:48:26 rakesh Exp $
031 */
032 public class ViewPropertyChangeListener extends PropertyChangeListener
033 implements Serializable
034 {
035 /**
036 * The logger used to log errors/warnings to.
037 */
038 private static transient final Logger logger =
039 Logger.getLogger( "com.sptci.echo2.ViewPropertyChangeListener" );
040
041 /**
042 * A map of all the <code>Field</code>s in the {@link #bean}.
043 */
044 private transient HashMap<String, Field> fields;
045
046 /**
047 * A map of the appropriate methods to be invoked for the elements
048 * in {@link #fields}.
049 */
050 private transient HashMap<Field, Method> methods;
051
052 /**
053 * Create a new instance of the listener for the specified object.
054 *
055 * @see #setBean
056 * @param bean The object whose fields are to be updated by
057 * change events.
058 */
059 public ViewPropertyChangeListener( Object bean )
060 {
061 super();
062 setBean( bean );
063 }
064
065 /**
066 * Implementation of the event handling method.
067 *
068 * @see #parseName
069 * @see #processList
070 * @see #processMap
071 * @param event A <code>PropertyChangeEvent</code> object describing
072 * the event source and the property that has changed.
073 * @throws BindingException If errors are encountered while
074 * attempting to modify the property in {@link #bean}.
075 */
076 public void propertyChange( PropertyChangeEvent event )
077 throws BindingException
078 {
079 try
080 {
081 String name = parseName( event.getPropertyName() );
082 Field field = fields.get( name );
083 if ( field == null )
084 {
085 throw new BindingException( "No field in bean " +
086 bean.getClass() + " with name " + name );
087 }
088
089 Object object = field.get( bean );
090 Method method = methods.get( field );
091 if ( method == null )
092 {
093 processList( object, event, field );
094 }
095 else
096 {
097 if ( object instanceof java.util.Map )
098 {
099 processMap( (Map) object, event );
100 }
101 else
102 {
103 method.invoke( object, event.getNewValue() );
104 }
105 }
106 }
107 catch ( Throwable t )
108 {
109 throw new BindingException( t.getMessage(), t );
110 }
111 }
112
113 /**
114 * Introspect the fields of {@link #bean} and populate the
115 * {@link #fields} and {@link #methods} fields.
116 *
117 * @throws BindingException If errors are encountered while
118 * introspecting the {@link #bean}.
119 */
120 private void introspect() throws BindingException
121 {
122 if ( bean == null ) return;
123 fields = new HashMap<String, Field>();
124 methods = new HashMap<Field, Method>();
125
126 try
127 {
128 for ( Field field : bean.getClass().getDeclaredFields() )
129 {
130 fields.put( field.getName(), field );
131
132 if ( ! Modifier.isPublic( field.getModifiers() ) )
133 {
134 field.setAccessible( true );
135 }
136
137 Object object = field.get( bean );
138 if ( object instanceof nextapp.echo2.app.text.TextComponent )
139 {
140 Method method = ReflectionUtility.fetchMethod( field,
141 "setText", new Class[] { java.lang.String.class } );
142 methods.put( field, method );
143 }
144 else if ( object instanceof
145 nextapp.echo2.app.button.ToggleButton )
146 {
147 Method method = ReflectionUtility.fetchMethod( field,
148 "setSelected", new Class[] { java.lang.String.class } );
149 methods.put( field, method );
150 }
151 else if ( object instanceof java.util.Map )
152 {
153 Method method = ReflectionUtility.fetchMethod( field,
154 "get", new Class[] { java.lang.Object.class } );
155 methods.put( field, method );
156 }
157 else
158 {
159 logger.fine( "No method for object type: " + field.getType() );
160 }
161 }
162 }
163 catch ( Throwable t )
164 {
165 throw new BindingException( t );
166 }
167 }
168
169 /**
170 * Update <code>AbstractList</code> UI components.
171 *
172 * @param object The UI component that is to be updated.
173 * @param event A <code>PropertyChangeEvent</code> object describing
174 * the event source and the property that has changed.
175 * @param field The field that represents the UI component that is
176 * to be updated.
177 * @throws BindingException If errors are encountered while
178 * attempting to modify the property in {@link #bean}.
179 */
180 protected void processList( Object object, PropertyChangeEvent event,
181 Field field ) throws BindingException
182 {
183 if ( object instanceof nextapp.echo2.app.SelectField )
184 {
185 updateSelectField( (SelectField) object, event );
186 }
187 else if ( object instanceof nextapp.echo2.app.ListBox )
188 {
189 updateListBox( (ListBox) object, event );
190 }
191 else
192 {
193 throw new BindingException( "No method found for field " + field );
194 }
195 }
196
197 /**
198 * Process a <code>SelectField</code> UI component.
199 *
200 * @param selectField The UI component that is to be updated.
201 * @param event A <code>PropertyChangeEvent</code> object describing
202 * the event source and the property that has changed.
203 */
204 protected void updateSelectField( SelectField selectField,
205 PropertyChangeEvent event )
206 {
207 DefaultListModel model = new DefaultListModel();
208 selectField.setSelectedIndex( 0 );
209 int count = 0;
210 for ( ListItem item : (List<ListItem>) event.getNewValue() )
211 {
212 model.add( count, item.getValue() );
213 if ( item.isSelected() )
214 {
215 selectField.setSelectedIndex( count );
216 }
217 ++count;
218 }
219
220 selectField.setModel( model );
221 }
222
223 /**
224 * Update a <code>ListBox</code> UI component.
225 *
226 * @param listBox The ListBox UI component that is to be updated.
227 * @param event A <code>PropertyChangeEvent</code> object describing
228 * the event source and the property that has changed.
229 */
230 protected void updateListBox( ListBox listBox,
231 PropertyChangeEvent event )
232 {
233 DefaultListModel model = new DefaultListModel();
234 int count = 0;
235 ArrayList<Integer> indices = new ArrayList<Integer>();
236
237 for ( ListItem item : (List<ListItem>) event.getNewValue() )
238 {
239 model.add( count, item.getValue() );
240 if ( item.isSelected() )
241 {
242 indices.add( count );
243 }
244 ++count;
245 }
246
247 listBox.setModel( model );
248
249 int[] selectedIndices = new int[ indices.size() ];
250 count = 0;
251 for ( int i : indices )
252 {
253 selectedIndices[ count++ ] = i;
254 }
255
256 listBox.setSelectedIndices( selectedIndices );
257 }
258
259 /**
260 * Update <code>ToggleButton</code> UI components.
261 *
262 * @param map The map that contains the name and selected state
263 * of the toggle button.
264 */
265 protected void processMap( Map map, PropertyChangeEvent event )
266 {
267 for ( Iterator iterator = map.entrySet().iterator();
268 iterator.hasNext(); )
269 {
270 Map.Entry entry = (Map.Entry) iterator.next();
271 Map newValue = (Map) event.getNewValue();
272 String key = (String) entry.getKey();
273
274 if ( key != null && key.indexOf( ">" ) != -1 )
275 {
276 key = key.substring( key.indexOf( ">" ) + 1 );
277 }
278
279 if ( newValue.containsKey( key ) )
280 {
281 if ( entry.getValue() instanceof
282 nextapp.echo2.app.button.ToggleButton )
283 {
284 ToggleButton button = (ToggleButton) entry.getValue();
285 button.setSelected(
286 ( (Boolean) newValue.get( key ) ).booleanValue() );
287 }
288 }
289 else
290 {
291 logger.info( newValue.toString() );
292 logger.info( "No key " + key + " found in map" );
293 }
294 }
295 }
296
297 /**
298 * Set {@link #bean}.
299 *
300 * @see #introspect
301 * @param bean The value to set.
302 */
303 public void setBean( Object bean )
304 {
305 boolean introspect = false;
306 if ( this.bean == null )
307 {
308 introspect = true;
309 }
310 else if ( ! this.bean.getClass().equals( bean.getClass() ) )
311 {
312 introspect = true;
313 }
314
315 this.bean = bean;
316 if ( introspect ) introspect();
317 }
318 }
319