001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.betwixt.expression;
018
019 import java.lang.reflect.Array;
020 import java.lang.reflect.Method;
021 import java.util.Collection;
022
023 import org.apache.commons.logging.Log;
024 import org.apache.commons.logging.LogFactory;
025
026 /** <p><code>MapEntryAdder</code> is used to add entries to a map.</p>
027 *
028 * <p>
029 * <code>MapEntryAdder</code> supplies two updaters:
030 * <ul>
031 * <li>{@link #getKeyUpdater()} which allows the entry key to be updated</li>
032 * <li>{@link #getValueUpdater()} which allows the entry value to be updated</li>
033 * </ul>
034 * When both of these updaters have been called, the entry adder method is called.
035 * Once this has happened then the values can be updated again.
036 * Note that only the <code>Context</code> passed by the last update will be used.
037 * </p>
038 *
039 * @author <a href="mailto:rdonkin@apache.org">Robert Burrell Donkin</a>
040 * @since 0.5
041 */
042 public class MapEntryAdder {
043
044
045 // Class Attributes
046 //-------------------------------------------------------------------------
047
048 /** Log used by this class */
049 private static Log log = LogFactory.getLog( MapEntryAdder.class );
050
051
052 // Class Methods
053 //-------------------------------------------------------------------------
054
055 /**
056 * Sets the logger used by this class.
057 *
058 * @param newLog log to this
059 */
060 public static void setLog(Log newLog) {
061 log = newLog;
062 }
063
064 // Attributes
065 //-------------------------------------------------------------------------
066
067 /** The method to be called to add a new map entry */
068 private Method adderMethod;
069
070 /** Has the entry key been updated? */
071 private boolean keyUpdated = false;
072 /** The entry key */
073 private Object key;
074
075 /** Has the entry value been updated? */
076 private boolean valueUpdated = false;
077 /** The entry value */
078 private Object value;
079
080
081 // Constructors
082 //-------------------------------------------------------------------------
083
084 /**
085 * Construct a <code>MapEntryAdder</code> which adds entries to given method.
086 *
087 * @param method the <code>Method</code> called to add a key-value entry
088 * @throws IllegalArgumentException if the given method does not take two parameters
089 */
090 public MapEntryAdder(Method method) {
091
092 Class[] types = method.getParameterTypes();
093 if ( types == null || types.length != 2) {
094 throw new IllegalArgumentException(
095 "Method used to add entries to maps must have two parameter.");
096 }
097 this.adderMethod = method;
098 }
099
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 }