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