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 }