001: /**
002: * Copyright 2006 Webmedia Group Ltd.
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: **/package org.araneaframework.backend.util;
016:
017: import java.lang.reflect.InvocationTargetException;
018: import java.lang.reflect.Method;
019: import java.lang.reflect.Modifier;
020: import java.util.ArrayList;
021: import java.util.Iterator;
022: import java.util.List;
023: import org.apache.commons.lang.StringUtils;
024: import org.apache.commons.lang.Validate;
025: import org.apache.commons.lang.exception.NestableRuntimeException;
026: import org.araneaframework.core.Assert;
027:
028: /**
029: * This class provides methods to manipulate Bean fields.
030: *
031: * Simple (e.g. 'name') as well as nested (e.g. 'location.city') Bean fields
032: * are both supported.
033: *
034: * To propagate an empty Bean by nested fields,
035: * {@link #fillFieldValue(Object, String, Object)} method is recommended
036: * instead of {@link #setFieldValue(Object, String, Object)} to create
037: * missing Beans automatically.
038: *
039: * @author <a href="mailto:rein@araneaframework.org">Rein Raudjärv</a>
040: *
041: * @see BeanMapper
042: */
043: public class BeanUtil {
044:
045: /**
046: * The delimiter that separates the components of a nested reference.
047: */
048: public static final char NESTED_DELIM = '.';
049:
050: /**
051: * Returns <code>List<String></code>- the <code>List</code> of Bean
052: * field names.
053: * <p>
054: * Only simple fields (not nested) are returned.
055: * </p>
056: *
057: * @param beanClass
058: * the class implementing the Bean pattern.
059: * @return <code>List<String></code>- the <code>List</code> of Bean
060: * field names.
061: */
062: public static List getFields(Class beanClass) {
063: Assert.notNull(beanClass, "No bean class specified.");
064:
065: List result = new ArrayList();
066:
067: Method[] methods = beanClass.getMethods();
068: for (int i = 0; i < methods.length; i++) {
069: Method method = methods[i];
070: // Checking that method may be a valid getter method
071: if (Modifier.isPublic(method.getModifiers())
072: && (method.getParameterTypes().length == 0)
073: && !(method.getReturnType()
074: .isAssignableFrom(Void.class))) {
075: if (method.getName().startsWith("get")
076: && !"getClass".equals(method.getName())) {
077: //Adding the field...
078: result.add(method.getName().substring(3, 4)
079: .toLowerCase()
080: + method.getName().substring(4));
081: } else if (method.getName().startsWith("is")
082: && (Boolean.class
083: .equals(method.getReturnType()) || boolean.class
084: .equals(method.getReturnType()))) {
085: //Adding the field...
086: result.add(method.getName().substring(2, 3)
087: .toLowerCase()
088: + method.getName().substring(3));
089: }
090: }
091: }
092:
093: return result;
094: }
095:
096: /**
097: * Returns the value of Bean field identified with name <code>field</code>
098: * for object <code>bean</code>.
099: * <p>
100: * Returns null if no bean specified or such method found.
101: * </p>
102: * <p>
103: * Only simple fields are supported.
104: * Use {@link #getFieldValue(Object, String)} for nested fields.
105: * </p>
106: *
107: * @param bean
108: * Object, which value to return.
109: * @param field
110: * The name of VO field.
111: * @return The value of the field.
112: */
113: private static Object getSimpleFieldValue(Object bean, String field) {
114: Validate.notNull(field, "No field name specified");
115: if (bean == null) {
116: return null;
117: }
118:
119: Object result = null;
120: try {
121: Method getter = getSimpleReadMethod(bean.getClass(), field);
122: if (getter != null) {
123: result = getter.invoke(bean, (Object[]) null);
124: }
125: } catch (InvocationTargetException e) {
126: throw new NestableRuntimeException(
127: "There was a problem getting field '" + field
128: + "' value", e);
129: } catch (IllegalAccessException e) {
130: throw new NestableRuntimeException(
131: "There was a problem getting field '" + field
132: + "' value", e);
133: }
134: return result;
135: }
136:
137: /**
138: * Returns the value of Bean field identified with name <code>field</code>
139: * for object <code>bean</code>.
140: * <p>
141: * Returns null if no bean specified or such method found.
142: * </p>
143: *
144: * @param bean
145: * Object, which value to return.
146: * @param field
147: * The name of VO field.
148: * @return The value of the field.
149: */
150: public static Object getFieldValue(Object bean, String field) {
151: Validate.notNull(field, "No field name specified");
152: if (bean == null) {
153: return null;
154: }
155:
156: String[] fields = StringUtils.split(field, NESTED_DELIM);
157: Object subValue = bean;
158: for (int i = 0; i < fields.length && subValue != null; i++) {
159: subValue = getSimpleFieldValue(subValue, fields[i]);
160: }
161: return subValue;
162: }
163:
164: /**
165: * Sets the value of Bean field identified by name <code>field</code> for
166: * object <code>bean</code>.
167: * <p>
168: * Nothing happens if no bean specified, one of its sub-field is null or
169: * no such method found.
170: * </p>
171: * <p>
172: * Only simple fields are supported.
173: * Use {@link #setFieldValue(Object, String, Object)} for nested fields.
174: * </p>
175: *
176: * @param bean
177: * bean Object, which value to set.
178: * @param field
179: * The name of Bean field.
180: * @param value
181: * The new value of the field.
182: *
183: * @see #fillFieldValue(Object, String, Object)
184: */
185: private static void setSimpleFieldValue(Object bean, String field,
186: Object value) {
187: Validate.notNull(field, "No field name specified");
188: if (bean == null) {
189: return;
190: }
191:
192: try {
193: Method setter = getSimpleWriteMethod(bean.getClass(), field);
194: if (setter != null) {
195: setter.invoke(bean, new Object[] { value });
196: }
197: } catch (InvocationTargetException e) {
198: throw new NestableRuntimeException(
199: "There was a problem setting field '" + field
200: + "' to value " + value, e);
201: } catch (IllegalAccessException e) {
202: throw new NestableRuntimeException(
203: "There was a problem setting field '" + field
204: + "' to value " + value, e);
205: }
206: }
207:
208: /**
209: * Sets the value of Bean field identified by name <code>field</code> for
210: * object <code>bean</code>.
211: * <p>
212: * Nothing happens if no bean specified, one of its sub-field is null or
213: * no such method found.
214: * </p>
215: * <p>
216: * If one of the sub-fields (not the last one) is null, they are not
217: * automatically propagated. In order for this, use
218: * {@link #fillFieldValue(Object, String, Object)} method.
219: * </p>
220: *
221: * @param bean
222: * bean Object, which value to set.
223: * @param field
224: * The name of Bean field.
225: * @param value
226: * The new value of the field.
227: *
228: * @see #fillFieldValue(Object, String, Object)
229: */
230: public static void setFieldValue(Object bean, String field,
231: Object value) {
232: Validate.notNull(field, "No field name specified");
233: if (bean == null) {
234: return;
235: }
236:
237: String[] fields = StringUtils.split(field, NESTED_DELIM);
238: Object subBean = bean;
239: for (int i = 0; i < fields.length - 1 && subBean != null; i++) {
240: subBean = getSimpleFieldValue(subBean, fields[i]);
241: }
242: if (subBean != null) {
243: setSimpleFieldValue(subBean, fields[fields.length - 1],
244: value);
245: }
246: }
247:
248: /**
249: * Sets the value of Bean field identified by name <code>field</code> for
250: * object <code>bean</code>.
251: * <p>
252: * Nothing happens if no bean specified or such method found.
253: * </p>
254: * This method is identical to
255: * {@link #setFieldValue(Object, String, Object)} except that mssing beans
256: * in sub-fields (not the last one) of bean Object are created
257: * automatically.
258: *
259: * @param bean
260: * bean Object, which value to set.
261: * @param field
262: * The name of Bean field.
263: * @param value
264: * The new value of the field.
265: *
266: * @see #setFieldValue(Object, String, Object)
267: */
268: public static void fillFieldValue(Object bean, String field,
269: Object value) {
270: Validate.notNull(field, "No field name specified");
271: if (bean == null) {
272: return;
273: }
274:
275: String[] fields = StringUtils.split(field, NESTED_DELIM);
276: Object subBean = bean;
277: for (int i = 0; i < fields.length - 1 && subBean != null; i++) {
278: Object tmp = getSimpleFieldValue(subBean, fields[i]);
279: if (tmp == null) {
280: tmp = newInstance(getSimpleFieldType(
281: subBean.getClass(), fields[i]));
282: setSimpleFieldValue(subBean, fields[i], tmp);
283: }
284: subBean = tmp;
285: }
286: setSimpleFieldValue(subBean, fields[fields.length - 1], value);
287: }
288:
289: /**
290: * Returns type of Bean field identified by name <code>field</code>.
291: * <p>
292: * Null is returned if no such method found.
293: * </p>
294: * <p>
295: * Only simple fields are supported.
296: * Use {@link #getFieldType(Class, String)} for nested fields.
297: * </p>
298: *
299: * @param beanClass
300: * the class implementing the Bean pattern.
301: * @param field
302: * The name of Bean field.
303: * @return The type of the field.
304: */
305: private static Class getSimpleFieldType(Class beanClass,
306: String field) {
307: Validate.notNull(beanClass, "No bean class specified");
308: Validate.notNull(field, "No field name specified");
309:
310: Class result = null;
311: Method getter = getSimpleReadMethod(beanClass, field);
312: if (getter != null) {
313: result = getter.getReturnType();
314: }
315: return result;
316: }
317:
318: /**
319: * Returns type of Bean field identified by name <code>field</code>.
320: * <p>
321: * Null is returned if no such method found.
322: * </p>
323: *
324: * @param beanClass
325: * the class implementing the Bean pattern.
326: * @param field
327: * The name of Bean field.
328: * @return The type of the field.
329: */
330: public static Class getFieldType(Class beanClass, String field) {
331: Validate.notNull(beanClass, "No bean class specified");
332: Validate.notNull(field, "No field name specified");
333:
334: String[] fields = StringUtils.split(field, NESTED_DELIM);
335: Class subBeanType = beanClass;
336: for (int i = 0; i < fields.length && subBeanType != null; i++) {
337: subBeanType = getSimpleFieldType(subBeanType, fields[i]);
338: }
339: return subBeanType;
340: }
341:
342: /**
343: * Checks that the field identified by <code>field</code> is a valid
344: * Bean field (can be read-only).
345: * <p>
346: * To enable reading the field, the spcfified <code>beanClass</code> must
347: * have getter (field's name starts with <code>get</code> or
348: * <code>is</code>) for this field.
349: * </p>
350: *
351: * @param beanClass
352: * the class implementing the Bean pattern.
353: * @param field
354: * Bean field name.
355: * @return if this field is in Bean.
356: */
357: public static boolean isReadable(Class beanClass, String field) {
358: return (getReadMethod(beanClass, field) != null);
359: }
360:
361: /**
362: * Checks that the field identified by <code>field</code> is a writable
363: * Bean field.
364: * <p>
365: * To enable writing the field, the spcfified <code>beanClass</code> must
366: * have setter (field's name starts with <code>set</code>) for this field.
367: * </p>
368: *
369: * @param beanClass
370: * the class implementing the Bean pattern.
371: * @param field
372: * Bean field name.
373: * @return if this field is in Bean.
374: */
375: public static boolean isWritable(Class beanClass, String field) {
376: return (getWriteMethod(beanClass, field) != null);
377: }
378:
379: /**
380: * Returns write method (setter) for the field.
381: * <p>
382: * Null is returned if no such method found.
383: * </p>
384: * <p>
385: * Only simple fields are supported.
386: * Use {@link #getWriteMethod(Class, String)} for nested fields.
387: * </p>
388: *
389: * @param beanClass
390: * the class implementing the Bean pattern.
391: * @param field
392: * Bean field name.
393: * @return write method (setter) for the field.
394: */
395: private static Method getSimpleWriteMethod(Class beanClass,
396: String field) {
397: Validate.notNull(beanClass, "No bean class specified");
398: Validate.notNull(field, "No field name specified");
399:
400: String setterName = "set" + field.substring(0, 1).toUpperCase()
401: + field.substring(1);
402: try {
403: return beanClass
404: .getMethod(setterName,
405: new Class[] { getSimpleFieldType(beanClass,
406: field) });
407: } catch (NoSuchMethodException e) {
408: // There is no 'set' method for this field
409: }
410:
411: return null;
412: }
413:
414: /**
415: * Returns write method (setter) for the field.
416: * <p>
417: * Null is returned if no such method found.
418: * </p>
419: *
420: * @param beanClass
421: * the class implementing the Bean pattern.
422: * @param field
423: * Bean field name.
424: * @return write method (setter) for the field.
425: */
426: public static Method getWriteMethod(Class beanClass, String field) {
427: Validate.notNull(beanClass, "No bean class specified");
428: Validate.notNull(field, "No field name specified");
429:
430: String[] fields = StringUtils.split(field, NESTED_DELIM);
431: Class subBeanType = beanClass;
432: for (int i = 0; i < fields.length - 1 && subBeanType != null; i++) {
433: subBeanType = getSimpleFieldType(subBeanType, fields[i]);
434: }
435: if (subBeanType != null) {
436: return getSimpleWriteMethod(subBeanType,
437: fields[fields.length - 1]);
438: }
439: return null;
440: }
441:
442: /**
443: * Returns read method (getter) for the field.
444: * <p>
445: * Null is returned if no such method found.
446: * </p>
447: * <p>
448: * Only simple fields are supported.
449: * Use {@link #getWriteMethod(Class, String)} for nested fields.
450: * </p>
451: *
452: * @param beanClass
453: * the class implementing the Bean pattern.
454: * @param field
455: * Bean field name.
456: * @return read method (getter) for the field.
457: */
458: private static Method getSimpleReadMethod(Class beanClass,
459: String field) {
460: Validate.notNull(beanClass, "No bean class specified");
461: Validate.notNull(field, "No field name specified");
462:
463: String getterName = "get" + field.substring(0, 1).toUpperCase()
464: + field.substring(1);
465: try {
466: return beanClass.getMethod(getterName, (Class[]) null);
467: } catch (NoSuchMethodException e) {
468: // There is no 'get' method for this field
469: }
470:
471: getterName = "is" + field.substring(0, 1).toUpperCase()
472: + field.substring(1);
473: try {
474: return beanClass.getMethod(getterName, (Class[]) null);
475: } catch (NoSuchMethodException e) {
476: // There is no 'is' method for this field
477: }
478:
479: return null;
480: }
481:
482: /**
483: * Returns read method (getter) for the field.
484: * <p>
485: * Null is returned if no such method found.
486: * </p>
487: *
488: * @param beanClass
489: * the class implementing the Bean pattern.
490: * @param field
491: * Bean field name.
492: * @return read method (getter) for the field.
493: */
494: public static Method getReadMethod(Class beanClass, String field) {
495: Validate.notNull(beanClass, "No bean class specified");
496: Validate.notNull(field, "No field name specified");
497:
498: String[] fields = StringUtils.split(field, NESTED_DELIM);
499: Class subBeanType = beanClass;
500: for (int i = 0; i < fields.length - 1 && subBeanType != null; i++) {
501: subBeanType = getSimpleFieldType(subBeanType, fields[i]);
502: }
503: if (subBeanType != null) {
504: return getSimpleReadMethod(subBeanType,
505: fields[fields.length - 1]);
506: }
507: return null;
508: }
509:
510: /**
511: * Creates new instance of the specified <code>beanClass</code>.
512: * <p>
513: * In order to be Bean type, it must have a constructor without arguments.
514: * <p>
515: * <p>
516: * If creating the new instance fails, a RuntimeException is thrown.
517: * </p>
518: *
519: * @param beanClass
520: * the class implementing the Bean pattern.
521: * @return new instance of the Bean type.
522: */
523: public static Object newInstance(Class beanClass) {
524: Validate.notNull(beanClass, "No bean class specified");
525:
526: Object result;
527: try {
528: result = beanClass.newInstance();
529: } catch (InstantiationException e) {
530: throw new NestableRuntimeException(
531: "Could not create an instance of class '"
532: + beanClass + "'", e);
533: } catch (IllegalAccessException e) {
534: throw new NestableRuntimeException(
535: "Could not create an instance of class '"
536: + beanClass + "'", e);
537: }
538: return result;
539: }
540:
541: /**
542: * Sets all the fields with same names to same values.
543: * <p>
544: * NB! the values are references (there'is no deep copy made)!
545: * </p>
546: * <p>
547: * <code>from</code> Bean fields that are not supported by <code>to</code>
548: * are ignored.
549: * </p>
550: *
551: * @param from
552: * <code>Bean</code> from which to convert.
553: * @param to
554: * <code>Bean</code> to which to convert.
555: * @return <code>to</code> with <codefrom</code> values
556: *
557: * @see #copy(Object, Class)
558: */
559: public static Object copy(Object from, Object to) {
560: Assert.isTrue(from != null && to != null,
561: "BeanUtil.copy() cannot accept NULL arguments.");
562:
563: List fromVoFields = getFields(from.getClass());
564: for (Iterator i = fromVoFields.iterator(); i.hasNext();) {
565: String field = (String) i.next();
566: Class toFieldType = getSimpleFieldType(to.getClass(), field);
567: Class fromFieldType = getSimpleFieldType(from.getClass(),
568: field);
569: if (isWritable(to.getClass(), field)
570: && toFieldType.isAssignableFrom(fromFieldType)) {
571: setSimpleFieldValue(to, field, getSimpleFieldValue(
572: from, field));
573: }
574: }
575: return to;
576: }
577:
578: /**
579: * Creates a new instance of Class <code>toType</code> and sets its field values to be
580: * the same as given <code>from</code> Object. Only fields with same names that exist in
581: * both <code>from</code> object and <code>toType</code> class are affected.
582: *
583: * @param from
584: * <code>Bean</code> from which to read field values.
585: * @param toType
586: * <code>Class</code> which object instance to create.
587: * @return new instance of <code>toType</code> with <code>from</code> values
588: *
589: * @see #copy(Object, Object)
590: * @see #clone()
591: */
592: public static Object copy(Object from, Class toType) {
593: Validate.isTrue(from != null && from != null,
594: "You cannot convert a Bean to null or vice versa");
595: return copy(from, newInstance(toType));
596: }
597:
598: /**
599: * Clones <code>bean</code> by copying its fields values (references) to a
600: * new instance of the same type.
601: *
602: * @param bean
603: * bean Object, which value to set.
604: * @return new instance of <code>bean</code> type with same fields values
605: * (references)
606: *
607: * @see #copy(Object, Object)
608: * @see #copy(Object, Class)
609: */
610: public static Object clone(Object bean) {
611: Validate.notNull(bean, "No bean specified");
612: return copy(bean, bean.getClass());
613: }
614:
615: /**
616: * Returns whether the given object type is a Bean type.
617: *
618: * @param clazz
619: * the class.
620: * @return whether the given object type is a Bean type.
621: */
622: public static boolean isBean(Class clazz) {
623: return (getFields(clazz).size() != 0);
624: }
625: }
|