001: /*
002: * Copyright 2004 Clinton Begin
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package com.ibatis.common.beans;
017:
018: import java.lang.reflect.Method;
019: import java.util.Map;
020: import java.util.StringTokenizer;
021:
022: import com.ibatis.sqlmap.engine.mapping.result.ResultObjectFactoryUtil;
023:
024: /**
025: * StaticBeanProbe provides methods that allow simple, reflective access to
026: * JavaBeans style properties. Methods are provided for all simple types as
027: * well as object types.
028: * <p/>
029: * Examples:
030: * <p/>
031: * StaticBeanProbe.setObject(object, propertyName, value);
032: * <P>
033: * Object value = StaticBeanProbe.getObject(object, propertyName);
034: */
035: public class ComplexBeanProbe extends BaseProbe {
036:
037: private static final Object[] NO_ARGUMENTS = new Object[0];
038:
039: protected ComplexBeanProbe() {
040: }
041:
042: /**
043: * Returns an array of the readable properties exposed by a bean
044: *
045: * @param object The bean
046: * @return The properties
047: */
048: public String[] getReadablePropertyNames(Object object) {
049: return ClassInfo.getInstance(object.getClass())
050: .getReadablePropertyNames();
051: }
052:
053: /**
054: * Returns an array of the writeable properties exposed by a bean
055: *
056: * @param object The bean
057: * @return The properties
058: */
059: public String[] getWriteablePropertyNames(Object object) {
060: return ClassInfo.getInstance(object.getClass())
061: .getWriteablePropertyNames();
062: }
063:
064: /**
065: * Returns the class that the setter expects to receive as a parameter when
066: * setting a property value.
067: *
068: * @param object The bean to check
069: * @param name The name of the property
070: * @return The type of the property
071: */
072: public Class getPropertyTypeForSetter(Object object, String name) {
073: Class type = object.getClass();
074:
075: if (object instanceof Class) {
076: type = getClassPropertyTypeForSetter((Class) object, name);
077: } else if (object instanceof Map) {
078: Map map = (Map) object;
079: Object value = map.get(name);
080: if (value == null) {
081: type = Object.class;
082: } else {
083: type = value.getClass();
084: }
085: } else {
086: if (name.indexOf('.') > -1) {
087: StringTokenizer parser = new StringTokenizer(name, ".");
088: while (parser.hasMoreTokens()) {
089: name = parser.nextToken();
090: type = ClassInfo.getInstance(type).getSetterType(
091: name);
092: }
093: } else {
094: type = ClassInfo.getInstance(type).getSetterType(name);
095: }
096: }
097:
098: return type;
099: }
100:
101: /**
102: * Returns the class that the getter will return when reading a property value.
103: *
104: * @param object The bean to check
105: * @param name The name of the property
106: * @return The type of the property
107: */
108: public Class getPropertyTypeForGetter(Object object, String name) {
109: Class type = object.getClass();
110:
111: if (object instanceof Class) {
112: type = getClassPropertyTypeForGetter((Class) object, name);
113: } else if (object instanceof Map) {
114: Map map = (Map) object;
115: Object value = map.get(name);
116: if (value == null) {
117: type = Object.class;
118: } else {
119: type = value.getClass();
120: }
121: } else {
122: if (name.indexOf('.') > -1) {
123: StringTokenizer parser = new StringTokenizer(name, ".");
124: while (parser.hasMoreTokens()) {
125: name = parser.nextToken();
126: type = ClassInfo.getInstance(type).getGetterType(
127: name);
128: }
129: } else {
130: type = ClassInfo.getInstance(type).getGetterType(name);
131: }
132: }
133:
134: return type;
135: }
136:
137: /**
138: * Returns the class that the getter will return when reading a property value.
139: *
140: * @param type The class to check
141: * @param name The name of the property
142: * @return The type of the property
143: */
144: private Class getClassPropertyTypeForGetter(Class type, String name) {
145:
146: if (name.indexOf('.') > -1) {
147: StringTokenizer parser = new StringTokenizer(name, ".");
148: while (parser.hasMoreTokens()) {
149: name = parser.nextToken();
150: type = ClassInfo.getInstance(type).getGetterType(name);
151: }
152: } else {
153: type = ClassInfo.getInstance(type).getGetterType(name);
154: }
155:
156: return type;
157: }
158:
159: /**
160: * Returns the class that the setter expects to receive as a parameter when
161: * setting a property value.
162: *
163: * @param type The class to check
164: * @param name The name of the property
165: * @return The type of the property
166: */
167: private Class getClassPropertyTypeForSetter(Class type, String name) {
168:
169: if (name.indexOf('.') > -1) {
170: StringTokenizer parser = new StringTokenizer(name, ".");
171: while (parser.hasMoreTokens()) {
172: name = parser.nextToken();
173: type = ClassInfo.getInstance(type).getSetterType(name);
174: }
175: } else {
176: type = ClassInfo.getInstance(type).getSetterType(name);
177: }
178:
179: return type;
180: }
181:
182: /**
183: * Gets an Object property from a bean
184: *
185: * @param object The bean
186: * @param name The property name
187: * @return The property value (as an Object)
188: */
189: public Object getObject(Object object, String name) {
190: if (name.indexOf('.') > -1) {
191: StringTokenizer parser = new StringTokenizer(name, ".");
192: Object value = object;
193: while (parser.hasMoreTokens()) {
194: value = getProperty(value, parser.nextToken());
195:
196: if (value == null) {
197: break;
198: }
199:
200: }
201: return value;
202: } else {
203: return getProperty(object, name);
204: }
205: }
206:
207: /**
208: * Sets the value of a bean property to an Object
209: *
210: * @param object The bean to change
211: * @param name The name of the property to set
212: * @param value The new value to set
213: */
214: public void setObject(Object object, String name, Object value) {
215: if (name.indexOf('.') > -1) {
216: StringTokenizer parser = new StringTokenizer(name, ".");
217: String property = parser.nextToken();
218: Object child = object;
219: while (parser.hasMoreTokens()) {
220: Class type = getPropertyTypeForSetter(child, property);
221: Object parent = child;
222: child = getProperty(parent, property);
223: if (child == null) {
224: if (value == null) {
225: return; // don't instantiate child path if value is null
226: } else {
227: try {
228: child = ResultObjectFactoryUtil
229: .createObjectThroughFactory(type);
230: setObject(parent, property, child);
231: } catch (Exception e) {
232: throw new ProbeException(
233: "Cannot set value of property '"
234: + name
235: + "' because '"
236: + property
237: + "' is null and cannot be instantiated on instance of "
238: + type.getName()
239: + ". Cause:" + e.toString(),
240: e);
241: }
242: }
243: }
244: property = parser.nextToken();
245: }
246: setProperty(child, property, value);
247: } else {
248: setProperty(object, name, value);
249: }
250: }
251:
252: /**
253: * Checks to see if a bean has a writable property be a given name
254: *
255: * @param object The bean to check
256: * @param propertyName The property to check for
257: * @return True if the property exists and is writable
258: */
259: public boolean hasWritableProperty(Object object,
260: String propertyName) {
261: boolean hasProperty = false;
262: if (object instanceof Map) {
263: hasProperty = true;//((Map) object).containsKey(propertyName);
264: } else {
265: if (propertyName.indexOf('.') > -1) {
266: StringTokenizer parser = new StringTokenizer(
267: propertyName, ".");
268: Class type = object.getClass();
269: while (parser.hasMoreTokens()) {
270: propertyName = parser.nextToken();
271: type = ClassInfo.getInstance(type).getGetterType(
272: propertyName);
273: hasProperty = ClassInfo.getInstance(type)
274: .hasWritableProperty(propertyName);
275: }
276: } else {
277: hasProperty = ClassInfo.getInstance(object.getClass())
278: .hasWritableProperty(propertyName);
279: }
280: }
281: return hasProperty;
282: }
283:
284: /**
285: * Checks to see if a bean has a readable property be a given name
286: *
287: * @param object The bean to check
288: * @param propertyName The property to check for
289: * @return True if the property exists and is readable
290: */
291: public boolean hasReadableProperty(Object object,
292: String propertyName) {
293: boolean hasProperty = false;
294: if (object instanceof Map) {
295: hasProperty = true;//((Map) object).containsKey(propertyName);
296: } else {
297: if (propertyName.indexOf('.') > -1) {
298: StringTokenizer parser = new StringTokenizer(
299: propertyName, ".");
300: Class type = object.getClass();
301: while (parser.hasMoreTokens()) {
302: propertyName = parser.nextToken();
303: type = ClassInfo.getInstance(type).getGetterType(
304: propertyName);
305: hasProperty = ClassInfo.getInstance(type)
306: .hasReadableProperty(propertyName);
307: }
308: } else {
309: hasProperty = ClassInfo.getInstance(object.getClass())
310: .hasReadableProperty(propertyName);
311: }
312: }
313: return hasProperty;
314: }
315:
316: protected Object getProperty(Object object, String name) {
317: ClassInfo classCache = ClassInfo.getInstance(object.getClass());
318: try {
319: Object value = null;
320: if (name.indexOf('[') > -1) {
321: value = getIndexedProperty(object, name);
322: } else {
323: if (object instanceof Map) {
324: value = ((Map) object).get(name);
325: } else {
326: Method method = classCache.getGetter(name);
327: if (method == null) {
328: throw new NoSuchMethodException(
329: "No GET method for property " + name
330: + " on instance of "
331: + object.getClass().getName());
332: }
333: try {
334: value = method.invoke(object, NO_ARGUMENTS);
335: } catch (Throwable t) {
336: throw ClassInfo.unwrapThrowable(t);
337: }
338: }
339: }
340: return value;
341: } catch (ProbeException e) {
342: throw e;
343: } catch (Throwable t) {
344: if (object == null) {
345: throw new ProbeException("Could not get property '"
346: + name + "' from null reference. Cause: "
347: + t.toString(), t);
348: } else {
349: throw new ProbeException("Could not get property '"
350: + name + "' from "
351: + object.getClass().getName() + ". Cause: "
352: + t.toString(), t);
353: }
354: }
355: }
356:
357: protected void setProperty(Object object, String name, Object value) {
358: ClassInfo classCache = ClassInfo.getInstance(object.getClass());
359: try {
360: if (name.indexOf('[') > -1) {
361: setIndexedProperty(object, name, value);
362: } else {
363: if (object instanceof Map) {
364: ((Map) object).put(name, value);
365: } else {
366: Method method = classCache.getSetter(name);
367: if (method == null) {
368: throw new NoSuchMethodException(
369: "No SET method for property " + name
370: + " on instance of "
371: + object.getClass().getName());
372: }
373: Object[] params = new Object[1];
374: params[0] = value;
375: try {
376: method.invoke(object, params);
377: } catch (Throwable t) {
378: throw ClassInfo.unwrapThrowable(t);
379: }
380: }
381: }
382: } catch (ProbeException e) {
383: throw e;
384: } catch (Throwable t) {
385: if (object == null) {
386: throw new ProbeException("Could not set property '"
387: + name + "' for null reference. Cause: "
388: + t.toString(), t);
389: } else {
390: throw new ProbeException("Could not set property '"
391: + name + "' for " + object.getClass().getName()
392: + ". Cause: " + t.toString(), t);
393: }
394: }
395: }
396:
397: }
|