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 }