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: // Class Attributes
045: //-------------------------------------------------------------------------
046:
047: /** Log used by this class */
048: private static Log log = LogFactory.getLog(MapEntryAdder.class);
049:
050: // Class Methods
051: //-------------------------------------------------------------------------
052:
053: /**
054: * Sets the logger used by this class.
055: *
056: * @param newLog log to this
057: */
058: public static void setLog(Log newLog) {
059: log = newLog;
060: }
061:
062: // Attributes
063: //-------------------------------------------------------------------------
064:
065: /** The method to be called to add a new map entry */
066: private Method adderMethod;
067:
068: /** Has the entry key been updated? */
069: private boolean keyUpdated = false;
070: /** The entry key */
071: private Object key;
072:
073: /** Has the entry value been updated? */
074: private boolean valueUpdated = false;
075: /** The entry value */
076: private Object value;
077:
078: // Constructors
079: //-------------------------------------------------------------------------
080:
081: /**
082: * Construct a <code>MapEntryAdder</code> which adds entries to given method.
083: *
084: * @param method the <code>Method</code> called to add a key-value entry
085: * @throws IllegalArgumentException if the given method does not take two parameters
086: */
087: public MapEntryAdder(Method method) {
088:
089: Class[] types = method.getParameterTypes();
090: if (types == null || types.length != 2) {
091: throw new IllegalArgumentException(
092: "Method used to add entries to maps must have two parameter.");
093: }
094: this .adderMethod = method;
095: }
096:
097: // Properties
098: //-------------------------------------------------------------------------
099:
100: /**
101: * Gets the entry key <code>Updater</code>.
102: * This is used to update the entry key value to the read value.
103: * If {@link #getValueUpdater} has been called previously,
104: * then this trigger the updating of the adder method.
105: *
106: * @return the <code>Updater</code> which should be used to populate the entry key
107: */
108: public Updater getKeyUpdater() {
109:
110: return new Updater() {
111: public void update(Context context, Object keyValue) {
112: // might as well make sure that his can only be set once
113: if (!keyUpdated) {
114: keyUpdated = true;
115: key = keyValue;
116: if (log.isTraceEnabled()) {
117: log.trace("Setting entry key to " + key);
118: log.trace("Current entry value is " + value);
119: }
120: if (valueUpdated) {
121: callAdderMethod(context);
122: }
123: }
124: }
125: };
126: }
127:
128: /**
129: * Gets the entry value <code>Updater</code>.
130: * This is used to update the entry key value to the read value.
131: * If {@link #getKeyUpdater} has been called previously,
132: * then this trigger the updating of the adder method.
133: *
134: * @return the <code>Updater</code> which should be used to populate the entry value
135: */
136: public Updater getValueUpdater() {
137:
138: return new Updater() {
139: public void update(Context context, Object valueValue) {
140: // might as well make sure that his can only be set once
141: if (!valueUpdated) {
142: valueUpdated = true;
143: value = valueValue;
144: if (log.isTraceEnabled()) {
145: log.trace("Setting entry value to " + value);
146: log.trace("Current entry key is " + key);
147: }
148: if (keyUpdated) {
149: callAdderMethod(context);
150: }
151: }
152: }
153: };
154: }
155:
156: // Implementation methods
157: //-------------------------------------------------------------------------
158:
159: /**
160: * Call the adder method on the bean associated with the <code>Context</code>
161: * with the key, value entry values stored previously.
162: *
163: * @param context the Context against whose bean the adder method will be invoked
164: */
165: private void callAdderMethod(Context context) {
166: log.trace("Calling adder method");
167:
168: // this allows the same instance to be used multiple times.
169: keyUpdated = false;
170: valueUpdated = false;
171:
172: //
173: // XXX This is (basically) cut and pasted from the MethodUpdater code
174: // I haven't abstracted this code just yet since I think that adding
175: // handling for non-beans will mean adding quite a lot more structure
176: // and only once this is added will the proper position for this method
177: // become clear.
178: //
179:
180: Class[] types = adderMethod.getParameterTypes();
181: // key is first parameter
182: Class keyType = types[0];
183: // value is the second
184: Class valueType = types[1];
185:
186: Object bean = context.getBean();
187: if (bean != null) {
188: if (key instanceof String) {
189: // try to convert into primitive types
190: key = context.getObjectStringConverter()
191: .stringToObject((String) key, keyType, context);
192: }
193:
194: if (value instanceof String) {
195: // try to convert into primitive types
196: value = context.getObjectStringConverter()
197: .stringToObject((String) value, valueType,
198: context);
199: }
200:
201: // special case for collection objects into arrays
202: if (value instanceof Collection && valueType.isArray()) {
203: Collection valuesAsCollection = (Collection) value;
204: Class componentType = valueType.getComponentType();
205: if (componentType != null) {
206: Object[] valuesAsArray = (Object[]) Array
207: .newInstance(componentType,
208: valuesAsCollection.size());
209: value = valuesAsCollection.toArray(valuesAsArray);
210: }
211: }
212:
213: Object[] arguments = { key, value };
214: try {
215: if (log.isTraceEnabled()) {
216: log.trace("Calling adder method: "
217: + adderMethod.getName() + " on bean: "
218: + bean + " with key: " + key
219: + " and value: " + value);
220: }
221: adderMethod.invoke(bean, arguments);
222:
223: } catch (Exception e) {
224: log.warn("Cannot evaluate adder method: "
225: + adderMethod.getName() + " on bean: " + bean
226: + " of type: " + bean.getClass().getName()
227: + " with value: " + value + " of type: "
228: + valueType + " and key: " + key + " of type: "
229: + keyType);
230: log.debug(e);
231: }
232: }
233: }
234: }
|