001    package com.sptci.echo2;
002    
003    import java.beans.IntrospectionException;
004    import java.beans.PropertyChangeEvent;
005    import java.beans.PropertyDescriptor;
006    
007    import java.lang.reflect.Field;
008    import java.lang.reflect.Method;
009    import java.lang.reflect.Modifier;
010    
011    import java.util.List;
012    import java.util.Map;
013    import java.util.logging.Logger;
014    
015    import nextapp.echo2.app.ListBox;
016    import nextapp.echo2.app.SelectField;
017    import nextapp.echo2.app.button.ToggleButton;
018    
019    import com.sptci.ReflectionUtility;
020    
021    /**
022     * An implementation of <code>PropertyChangeListener</code> used
023     * to synchronise changes made to properties in UI components or the
024     * {@link #bean} java bean with each other.
025     *
026     * <p>Copyright 2006 Sans Pareil Technologies, Inc.</p>
027     * @author Rakesh Vidyadharan 2006-01-22
028     * @version $Id: PropertyChangeListener.java,v 1.4 2006/02/09 16:16:02 rakesh Exp $
029     */
030    public abstract class PropertyChangeListener 
031      extends com.sptci.PropertyChangeListener
032    {
033      /**
034       * The logger used to log errors to.
035       */
036      private static final Logger logger =
037        Logger.getLogger( "com.sptci.echo2.PropertyChangeListener" );
038    
039      /**
040       * Default constructor.  Just invokes super class constructor.
041       */
042      protected PropertyChangeListener() 
043      {
044        super();
045      }
046    
047      /**
048       * Create a new instance of the class using the specified java bean.
049       *
050       * @param bean The data object which is to be managed.
051       * @throws IntrospectionException If errors are encountered while
052       *   introspecting the {@link #bean} class.
053       */
054      public PropertyChangeListener( Object bean )
055        throws IntrospectionException
056      {
057        super( bean );
058      }
059    
060      /**
061       * Over-ridden to handle special events generated by the Echo2 
062       * framework.  Events that require special handling are usually
063       * <code>CheckBox</code>, <code>AbstractList</code> and 
064       * <code>RadioButton</code> components.  These components must
065       * be modelled as <code>Map</code> and <code>List</code> fields in
066       * the {@link #bean}.
067       *
068       * @see ReflectionUtility#fetchObject
069       * @see #processMap
070       * @see #processList
071       * @param event A <code>PropertyChangeEvent</code> object that
072       *   contains the property to change and the old and new values.
073       * @param descriptor A <code>PropertyDescriptor</code> object that
074       *   describes the property
075       * @throws BindingException If errors are encountered while trying
076       *   to invoke the appropriate method.
077       */
078      protected void modifyObject( PropertyChangeEvent event,
079          PropertyDescriptor descriptor ) throws BindingException
080      {
081        try
082        {
083          Object object = 
084            ReflectionUtility.fetchObject( descriptor.getName(), bean );
085    
086          if ( object instanceof java.util.Map )
087          {
088            processMap( (Map) object, event, descriptor );
089          }
090          else if ( object instanceof java.util.List )
091          {
092            processList( (List) object, event, descriptor );
093          }
094          else 
095          {
096            super.modifyObject( event, descriptor );
097          }
098        }
099        catch ( Throwable t1 )
100        {
101          throw new BindingException( t1 );
102        }
103      }
104    
105      /**
106       * Process a field with type <code>Map</code> in {@link #bean}.
107       * Adds an entry to the map using the name and value in the
108       * event.
109       *
110       * @param map The map that is to be updated.
111       * @param event A <code>PropertyChangeEvent</code> object that
112       *   contains the property to change and the old and new values.
113       * @param descriptor A <code>PropertyDescriptor</code> object that
114       *   describes the property
115       */
116      protected void processMap( Map map, PropertyChangeEvent event,
117          PropertyDescriptor descriptor )
118      {
119        if ( event.getSource() instanceof 
120            nextapp.echo2.app.button.ToggleButton )
121        {
122          ToggleButton button = (ToggleButton) event.getSource();
123          String name = button.getActionCommand();
124    
125          if ( name != null && name.indexOf( ">" ) != -1 )
126          {
127            name = name.substring( name.indexOf( ">" ) + 1 );
128          }
129          map.put( name, event.getNewValue() );
130        }
131        else
132        { 
133          logger.warning( "Map error name: " + 
134            descriptor.getName() + " object: " + map +
135            " source: " + event.getSource() ); 
136        }
137      }
138    
139      /**
140       * Process a field with type <code>List</code> in {@link #bean}.
141       * Sets the value of the list entry at the appropriate index using
142       * the values in the event.
143       *
144       * @param list The list that is to be updated.
145       * @param event A <code>PropertyChangeEvent</code> object that
146       *   contains the property to change and the old and new values.
147       * @param descriptor A <code>PropertyDescriptor</code> object that
148       *   describes the property
149       */
150      protected void processList( List list, PropertyChangeEvent event,
151          PropertyDescriptor descriptor )
152      {
153        if ( event.getSource() instanceof 
154            nextapp.echo2.app.ListBox )
155        {
156          ListBox listBox = (ListBox) event.getSource();
157          for ( int index : listBox.getSelectedIndices() )
158          {
159            String value = (String) listBox.getModel().get( index );
160            list.set( index, new ListItem( value, true ) );
161          }
162        }
163        else if ( event.getSource() instanceof 
164            nextapp.echo2.app.SelectField )
165        {
166          SelectField selectField = (SelectField) event.getSource();
167          if ( selectField.getSelectedIndex() != -1 )
168          {
169            list.set( selectField.getSelectedIndex(),
170                new ListItem( (String) selectField.getSelectedItem(), 
171                  true ) );
172          }
173        }
174        else 
175        { 
176          logger.warning( "List error name: " + 
177            descriptor.getName() + " object: " + list + 
178            " source: " + event.getSource() ); 
179        }
180      }
181    
182      /**
183       * Parse the field name specified, and remove any complex components
184       * used to set the name as <code>actionCommands</code>.
185       *
186       * @param name The name that is to be parsed.
187       * @return String The parsed and corrected (if necessary) name.
188       */
189      protected String parseName( String name )
190      {
191        if ( name != null && name.indexOf( ">" ) != -1 )
192        {
193          name = name.substring( 0, name.indexOf( ">" ) );
194        }
195    
196        return name;
197      }
198    }