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 }