View Javadoc

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