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: * $Header:$
018: */
019: package org.apache.beehive.netui.script.el.tokens;
020:
021: import java.lang.reflect.Array;
022: import java.lang.reflect.Method;
023: import java.util.List;
024: import java.util.Map;
025:
026: import org.apache.beehive.netui.util.internal.cache.PropertyCache;
027: import org.apache.beehive.netui.util.logging.Logger;
028: import org.apache.beehive.netui.script.el.util.ParseUtils;
029:
030: /**
031: *
032: */
033: public abstract class ExpressionToken {
034:
035: private static final Logger LOGGER = Logger
036: .getInstance(ArrayIndexToken.class);
037: private static final PropertyCache PROPERTY_CACHE = new PropertyCache();
038:
039: public abstract Object read(Object object);
040:
041: public abstract void write(Object object, Object value);
042:
043: public abstract String getTokenString();
044:
045: /**
046: * Lookup the <code>key</code> in the <code>map</code>.
047: * @param map the map
048: * @param key the key
049: * @return the value found at <code>map.get(key)</code> or <code>null</code> if no value was found
050: */
051: protected final Object mapLookup(Map map, Object key) {
052: LOGGER.trace("get value from Map");
053: return map.get(key);
054: }
055:
056: /**
057: * Get a JavaBean property from the given <code>value</code>
058: * @param value the JavaBean
059: * @param propertyName the property name
060: * @return the value of the property from the object <code>value</code>
061: */
062: protected final Object beanLookup(Object value, Object propertyName) {
063: LOGGER.trace("get JavaBean property : " + propertyName);
064: return ParseUtils.getProperty(value, propertyName.toString(),
065: PROPERTY_CACHE);
066: }
067:
068: /**
069: * Get the value in a {@link List} at <code>index</code>.
070: * @param list the List
071: * @param index the index
072: * @return the value returned from <code>list.get(index)</code>
073: */
074: protected final Object listLookup(List list, int index) {
075: LOGGER.trace("get value in List index " + index);
076: return list.get(index);
077: }
078:
079: /**
080: * Get the value from <code>array</code> at <code>index</code>
081: * @param array the array
082: * @param index the index
083: * @return the value returned from <code>Array.get(array, index)</code>
084: */
085: protected final Object arrayLookup(Object array, int index) {
086: LOGGER.trace("get value from array index " + index);
087: return Array.get(array, index);
088: }
089:
090: /**
091: * Update the value of <code>key</code> in <code>map</code>
092: * @param map the map
093: * @param key the key
094: * @param value the value
095: */
096: protected final void mapUpdate(Map map, Object key, Object value) {
097: Object o = map.get(key);
098: /*
099: If a value exists in map.get(key), convert the "value" parameter into
100: the type of map.get(key). It's a best guess as to what the type of the
101: Map _should_ be without any further reflective information about the
102: types contained in the map.
103: */
104: if (o != null) {
105: Class type = o.getClass();
106: value = ParseUtils.convertType(value, type);
107: }
108:
109: map.put(key, value);
110: }
111:
112: protected final void arrayUpdate(Object array, int index,
113: Object value) {
114: Object converted = value;
115:
116: Class elementType = array.getClass().getComponentType();
117: if (!elementType.isAssignableFrom(value.getClass())) {
118: converted = ParseUtils.convertType(value, elementType);
119: }
120:
121: try {
122: Array.set(array, index, converted);
123: } catch (Exception e) {
124: String msg = "An error occurred setting a value at index \""
125: + index
126: + "\" on an array with component types \""
127: + elementType + "\". Cause: " + e.toString();
128: LOGGER.error(msg);
129: throw new RuntimeException(msg);
130: }
131: }
132:
133: /**
134: * Update a {@link List} with the Object <code>value</code> at <code>index</code>.
135: * @param list the List
136: * @param index the index
137: * @param value the new value
138: */
139: protected final void listUpdate(List list, int index, Object value) {
140: Object converted = value;
141:
142: if (list.size() > index) {
143: Object o = list.get(index);
144: // can only convert types when there is an item in the currently requested place
145: if (o != null) {
146: Class itemType = o.getClass();
147: converted = ParseUtils.convertType(value, itemType);
148: }
149:
150: list.set(index, value);
151: } else {
152: // @note: not sure that this is the right thing. Question is whether or not to insert nulls here to fill list up to "index"
153: // @update: List doesn't guarantee that implementations will accept nulls. So, we can't rely on that as a solution.
154: // @update: this is an unfortunate but necessary solution...unless the List has enough elements to
155: // accomodate the new item at a particular index, this must be an error case. The reasons are this:
156: // 1) can't fill the list with nulls, List implementations are allowed to disallow them
157: // 2) can't just do an "add" to the list -- in processing [0] and [1] on an empty list, [1] may get processed first.
158: // this will go into list slot [0]. then, [0] gets processed and simply overwrites the previous because it's
159: // already in the list
160: // 3) can't go to a mixed approach because there's no metadata about what has been done and no time to build
161: // something that is apt to be complicated and exposed to the user
162: // so...
163: // the ultimate 8.1sp2 functionality is to simply disallow updating a value in a list that doesn't exist. that
164: // being said, it is still possible to simply add to the list. if {actionForm.list[42]} inserts into the 42'nd
165: // item, {actionForm.list} will just do an append on POST since there is no index specified. this fix does
166: // not break backwards compatability because it will work on full lists and is completely broken now on empty
167: // lists, so changing this just gives a better exception message that "ArrayIndexOutOfBounds". :)
168: //
169: // September 2, 2003
170: // ekoneil@apache.com
171: //
172: String msg = "An error occurred setting a value at index \""
173: + index
174: + "\" because the list is "
175: + (list != null ? (" of size " + list.size())
176: : "null")
177: + ". "
178: + "Be sure to allocate enough items in the List to accomodate any updates which may occur against the list.";
179:
180: LOGGER.error(msg);
181: throw new RuntimeException(msg);
182: }
183: }
184:
185: /**
186: * Update a JavaBean property named <code>identifier</code> with the given <code>value</code>.
187: * @param bean the JavaBean
188: * @param identifier the property name
189: * @param value the new value
190: */
191: protected final void beanUpdate(Object bean, Object identifier,
192: Object value) {
193: LOGGER.trace("Update bean \"" + bean + "\" property \""
194: + identifier + "\"");
195:
196: String propertyName = identifier.toString();
197:
198: Class beanType = bean.getClass();
199: Class propType = PROPERTY_CACHE.getPropertyType(beanType,
200: propertyName);
201: // Get the type of the JavaBean property given reflected information from the JavaBean's type
202: if (propType != null) {
203: try {
204: // The type of the JavaBean property is a List. To update it, get the List and
205: // append the value to the end of the List.
206: if (List.class.isAssignableFrom(propType)) {
207: Method listGetter = PROPERTY_CACHE
208: .getPropertyGetter(beanType, propertyName);
209: if (listGetter != null) {
210: List list = (List) listGetter.invoke(bean,
211: (Object[]) null);
212: applyValuesToList(value, list);
213: return;
214: }
215: }
216: // The JavaBean is an Object, so set the Bean's property with the given value
217: else {
218: Method setter = PROPERTY_CACHE.getPropertySetter(
219: beanType, propertyName);
220:
221: if (setter != null) {
222: LOGGER
223: .trace("Set property via setter method: ["
224: + setter + "]");
225:
226: Class targetType = setter.getParameterTypes()[0];
227: Object converted = ParseUtils.convertType(
228: value, targetType);
229:
230: setter.invoke(bean, new Object[] { converted });
231: return;
232: }
233: }
234: } catch (Exception e) {
235: String msg = "Could not update proprety named \""
236: + propertyName + "\" on bean of type \""
237: + beanType + "\". Cause: " + e;
238: LOGGER.error(msg, e);
239: throw new RuntimeException(msg, e);
240: }
241: }
242:
243: String msg = "Could not update expression because a public JavaBean setter for the property \""
244: + identifier + "\" could not be found.";
245: LOGGER.error(msg);
246: throw new RuntimeException(msg);
247: }
248:
249: /**
250: * Attempt to convert a String indexString into an integer index.
251: * @param indexString the index string
252: * @return the converted integer
253: */
254: protected final int parseIndex(String indexString) {
255: try {
256: return Integer.parseInt(indexString);
257: } catch (Exception e) {
258: String msg = "Error converting \"" + indexString
259: + "\" into an integer. Cause: " + e;
260: LOGGER.error(msg, e);
261: throw new RuntimeException(msg, e);
262: }
263: }
264:
265: /**
266: * Set a list of values on a {@link List}. The behavior of this method is different given
267: * the type of <code>value</code>:<br/>
268: * - value is java.lang.String[]: add each item in the String[] to the list
269: * - value is a String: add the value to the list
270: * - otherwise, add the value to the end of the list
271: * @param value the value to apply to a list
272: * @param list the {@link List}
273: */
274: private static void applyValuesToList(Object value, List list) {
275: if (list == null) {
276: String msg = "Can not add a value to a null java.util.List";
277: LOGGER.error(msg);
278: throw new RuntimeException(msg);
279: }
280:
281: if (value instanceof String[]) {
282: String[] ary = (String[]) value;
283: for (int i = 0; i < ary.length; i++)
284: list.add(ary[i]);
285: } else if (value instanceof String)
286: list.add(value);
287: // types that are not String[] or String are just set on the object
288: else
289: list.add(value);
290: }
291: }
|