View Javadoc

1   /*
2    * Copyright 2001-2004 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */ 
16  package org.apache.commons.betwixt.expression;
17  
18  import java.lang.reflect.Array;
19  import java.lang.reflect.Method;
20  import java.util.Collection;
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  
25  /*** <p><code>MapEntryAdder</code> is used to add entries to a map.</p>
26    *
27    * <p>
28    * <code>MapEntryAdder</code> supplies two updaters:
29    * <ul>
30    *   <li>{@link #getKeyUpdater()} which allows the entry key to be updated</li>
31    *   <li>{@link #getValueUpdater()} which allows the entry value to be updated</li>
32    * </ul>
33    * When both of these updaters have been called, the entry adder method is called.
34    * Once this has happened then the values can be updated again.
35    * Note that only the <code>Context</code> passed by the last update will be used.
36    * </p>
37    *
38    * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
39    * @since 0.5
40    */
41  public class MapEntryAdder {
42  
43      
44      // Class Attributes
45      //-------------------------------------------------------------------------   
46      
47      /*** Log used by this class */
48      private static Log log = LogFactory.getLog( MapEntryAdder.class );
49      
50      
51      // Class Methods
52      //-------------------------------------------------------------------------      
53      
54      /*** 
55       * Sets the logger used by this class.
56       *
57       * @param newLog log to this
58       */
59      public static void setLog(Log newLog) {
60          log = newLog;
61      }
62      
63      // Attributes
64      //-------------------------------------------------------------------------    
65  
66      /*** The method to be called to add a new map entry */
67      private Method adderMethod;
68      
69      /*** Has the entry key been updated? */
70      private boolean keyUpdated = false;
71      /*** The entry key */
72      private Object key;
73      
74      /*** Has the entry value been updated? */
75      private boolean valueUpdated = false;
76      /*** The entry value */
77      private Object value;
78      
79          
80      // Constructors
81      //-------------------------------------------------------------------------    
82      
83      /***
84       * Construct a <code>MapEntryAdder</code> which adds entries to given method.
85       *
86       * @param method the <code>Method</code> called to add a key-value entry
87       * @throws IllegalArgumentException if the given method does not take two parameters 
88       */
89      public MapEntryAdder(Method method) {
90          
91          Class[] types = method.getParameterTypes();
92          if ( types == null || types.length != 2) {
93              throw new IllegalArgumentException(
94                  "Method used to add entries to maps must have two parameter.");
95          }
96          this.adderMethod = method;
97      }
98      
99      // Properties
100     //-------------------------------------------------------------------------    
101     
102     /***
103      * Gets the entry key <code>Updater</code>.
104      * This is used to update the entry key value to the read value.
105      * If {@link #getValueUpdater} has been called previously, 
106      * then this trigger the updating of the adder method.
107      *
108      * @return the <code>Updater</code> which should be used to populate the entry key
109      */
110     public Updater getKeyUpdater() {
111         
112         return new Updater() {
113             public void update( Context context, Object keyValue ) {
114                 // might as well make sure that his can only be set once
115                 if ( !keyUpdated ) {
116                     keyUpdated = true;
117                     key = keyValue;
118                     if ( log.isTraceEnabled() ) {
119                         log.trace( "Setting entry key to " + key );
120                         log.trace( "Current entry value is " + value );
121                     }
122                     if ( valueUpdated ) {
123                         callAdderMethod( context );
124                     }
125                 }
126             }
127         };
128     }
129     
130     /***
131      * Gets the entry value <code>Updater</code>.
132      * This is used to update the entry key value to the read value.
133      * If {@link #getKeyUpdater} has been called previously, 
134      * then this trigger the updating of the adder method.
135      *
136      * @return the <code>Updater</code> which should be used to populate the entry value
137      */
138     public Updater getValueUpdater() {
139         
140         return new Updater() {
141             public void update( Context context, Object valueValue ) {
142                 // might as well make sure that his can only be set once
143                 if ( !valueUpdated ) {
144                     valueUpdated = true;
145                     value = valueValue;
146                     if ( log.isTraceEnabled() ) {
147                         log.trace( "Setting entry value to " + value);
148                         log.trace( "Current entry key is " + key );
149                     }
150                     if ( keyUpdated ) {
151                         callAdderMethod( context );
152                     }
153                 }
154             }
155         };
156     }
157     
158     
159     
160     // Implementation methods
161     //-------------------------------------------------------------------------    
162 
163     /***
164      * Call the adder method on the bean associated with the <code>Context</code>
165      * with the key, value entry values stored previously.
166      *
167      * @param context the Context against whose bean the adder method will be invoked
168      */
169     private void callAdderMethod(Context context) {
170         log.trace("Calling adder method");
171         
172         // this allows the same instance to be used multiple times.
173         keyUpdated = false;
174         valueUpdated = false;
175         
176         //
177         // XXX This is (basically) cut and pasted from the MethodUpdater code
178         // I haven't abstracted this code just yet since I think that adding
179         // handling for non-beans will mean adding quite a lot more structure
180         // and only once this is added will the proper position for this method 
181         // become clear.
182         //
183         
184         Class[] types = adderMethod.getParameterTypes();
185         // key is first parameter
186         Class keyType = types[0];
187         // value is the second
188         Class valueType = types[1];
189         
190         Object bean = context.getBean();
191         if ( bean != null ) {
192             if ( key instanceof String ) {
193                 // try to convert into primitive types
194                 key = context.getObjectStringConverter()
195                         .stringToObject( (String) key, keyType, context );
196             }
197             
198             if ( value instanceof String ) {
199                 // try to convert into primitive types
200                 value = context.getObjectStringConverter()
201                         .stringToObject( (String) value, valueType, context );
202             }
203             
204             // special case for collection objects into arrays                    
205             if (value instanceof Collection && valueType.isArray()) {
206                 Collection valuesAsCollection = (Collection) value;
207                 Class componentType = valueType.getComponentType();
208                 if (componentType != null) {
209                     Object[] valuesAsArray = 
210                         (Object[]) Array.newInstance(componentType, valuesAsCollection.size());
211                     value = valuesAsCollection.toArray(valuesAsArray);
212                 }
213             }
214             
215                  
216             Object[] arguments = { key, value };
217             try {
218                 if ( log.isTraceEnabled() ) {
219                     log.trace( 
220                         "Calling adder method: " + adderMethod.getName() + " on bean: " + bean 
221                         + " with key: " + key + " and value: " + value
222                     );
223                 }
224                 adderMethod.invoke( bean, arguments );
225                 
226             } catch (Exception e) {
227                 log.warn( 
228                     "Cannot evaluate adder method: " + adderMethod.getName() + " on bean: " + bean 
229                     + " of type: " + bean.getClass().getName() + " with value: " + value 
230                     + " of type: " + valueType + " and key: " + key
231                     + " of type: " + keyType 
232                 );
233                 log.debug(e);
234             }
235         }
236     }
237 }