001: // Copyright (c) 2003-2007, Jodd Team (jodd.sf.net). All Rights Reserved.
002:
003: package jodd.bean;
004:
005: import jodd.introspector.ClassDescriptor;
006: import jodd.introspector.DefaultIntrospector;
007: import jodd.bean.loader.BeanLoader;
008: import jodd.util.ReflectUtil;
009:
010: import java.lang.reflect.Method;
011: import java.lang.reflect.Array;
012: import java.lang.reflect.Field;
013: import java.util.Map;
014: import java.util.List;
015: import java.util.HashMap;
016:
017: /**
018: * Yet another utility for reading and writing bean properties. However, this one is the fastest availiable.
019: * Althought it provides various methods, the whole thing can be easily extended to match most needs.
020: * <p>
021: * BeanUtil supports:
022: * <ul>
023: * <li>Nested properties: separated by a dot ('.')</li>
024: * <li>Indexed properties: arrays or Lists</li>
025: * <li>Simple properties: accessors or Map</li>
026: * </ul>
027: *
028: * <p>
029: * Variants includes combinations of forced, declared and silent writing.
030: * <ul>
031: * <li><i>Forced</i> setting property tries to create destination property so it can be set correctly.</li>
032: * <li><i>Silent</i> doesn't throw an exception if destination doesn't exist or if conversion fails.</li>
033: * <li><i>Declared</i> includes only declared (public) properties.</li>
034: * </ul>
035: * <p>
036: * This utility considers both bean property methods (set and get accessors), and bean fields.
037: * This is done because of several reasons: often there is no need for both set/get accessors, since
038: * bean logic requires just one functionality (e.g. just reading). In such case, other bean manipulation
039: * libraries still requires to have both accessors in order to set or get value.
040: * Another reason is that most common usage is to work with public accessort, and in that case
041: * private fields are ignored.
042: *
043: * @see BeanUtilUtil
044: */
045: public class BeanUtil extends BeanUtilUtil {
046:
047: // ---------------------------------------------------------------- internal resolver
048:
049: /**
050: * Resolves nested property name to the very last indexed property.
051: * If forced, <code>null</code> or non-existing properties will be created.
052: */
053: protected static void resolveNestedProperties(BeanProperty bp) {
054: String name = bp.name;
055: int dotNdx;
056: while ((dotNdx = name.indexOf('.')) != -1) {
057: bp.last = false;
058: bp.name = name.substring(0, dotNdx);
059: bp.setBean(getIndexProperty(bp, true));
060: name = name.substring(dotNdx + 1);
061: }
062: bp.last = true;
063: bp.name = name;
064: }
065:
066: protected static boolean resolveExistingNestedProperties(
067: BeanProperty bp) {
068: String name = bp.name;
069: int dotNdx;
070: while ((dotNdx = name.indexOf('.')) != -1) {
071: bp.last = false;
072: String temp = bp.name = name.substring(0, dotNdx);
073: if (hasIndexProperty(bp, true) == false) {
074: return false;
075: }
076: bp.name = temp;
077: bp.setBean(getIndexProperty(bp, true));
078: name = name.substring(dotNdx + 1);
079: }
080: bp.last = true;
081: bp.name = name;
082: return true;
083: }
084:
085: // ---------------------------------------------------------------- simple property
086:
087: /**
088: * Returns <code>true</code> if simple property exist.
089: */
090: public static boolean hasSimpleProperty(Object bean,
091: String property, boolean suppressSecurity) {
092: return hasSimpleProperty(
093: new BeanProperty(bean, property, false),
094: suppressSecurity);
095: }
096:
097: protected static boolean hasSimpleProperty(BeanProperty bp,
098: boolean suppressSecurity) {
099: if (bp.bean == null) {
100: return false;
101: }
102:
103: // try: getProperty() or isProperty()
104: Method method = bp.cd.getBeanGetter(bp.name, suppressSecurity);
105: if (method != null) {
106: return true;
107: }
108:
109: // try: =property
110: Field field = bp.cd.getField(bp.name, suppressSecurity);
111: if (field != null) {
112: return true;
113: }
114:
115: // try: (Map) get("property")
116: if (bp.cd.isMap()) {
117: Map map = (Map) bp.bean;
118: if (map.containsKey(bp.name) == true) {
119: return true;
120: }
121: }
122:
123: return false;
124: }
125:
126: /**
127: * Reads simple property.
128: */
129: public static Object getSimpleProperty(Object bean,
130: String property, boolean suppressSecurity) {
131: return getSimpleProperty(
132: new BeanProperty(bean, property, false),
133: suppressSecurity);
134: }
135:
136: /**
137: * Reads simple property forced: when property value doesn't exist, it will be created.
138: */
139: public static Object getSimplePropertyForced(Object bean,
140: String property, boolean suppressSecurity) {
141: return getSimpleProperty(
142: new BeanProperty(bean, property, true),
143: suppressSecurity);
144: }
145:
146: protected static Object getSimpleProperty(BeanProperty bp,
147: boolean suppressSecurity) {
148:
149: // try: getProperty() or isProperty()
150: bp.field = null;
151: bp.method = bp.cd.getBeanGetter(bp.name, suppressSecurity);
152: if (bp.method != null) {
153: Object result = invokeGetter(bp.bean, bp.method);
154: if ((result == null) && (bp.forced == true)) {
155: result = createBeanProperty(bp);
156: }
157: return result;
158: }
159:
160: // try: =property
161: bp.field = bp.cd.getField(bp.name, suppressSecurity);
162: if (bp.field != null) {
163: Object result = getField(bp.bean, bp.field);
164: if ((result == null) && (bp.forced == true)) {
165: result = createBeanProperty(bp);
166: }
167: return result;
168: }
169:
170: // try: (Map) get("property")
171: if (bp.cd.isMap()) {
172: Map map = (Map) bp.bean;
173: if (map.containsKey(bp.name) == false) {
174: if (bp.forced == false) {
175: throw new BeanException("Map key '" + bp.name
176: + "' not found.", bp);
177: }
178: Map value = new HashMap();
179: //noinspection unchecked
180: map.put(bp.name, value);
181: return value;
182: }
183: return map.get(bp.name);
184: }
185:
186: // failed
187: throw new BeanException("Simple property '" + bp.name
188: + "' not found.", bp);
189: }
190:
191: public static void setSimpleProperty(Object bean, String property,
192: Object value, boolean suppressSecurity) {
193: setSimpleProperty(new BeanProperty(bean, property, false),
194: value, suppressSecurity);
195: }
196:
197: /**
198: * Sets a value of simple property.
199: */
200: @SuppressWarnings({"unchecked"})
201: protected static void setSimpleProperty(BeanProperty bp,
202: Object value, boolean suppressSecurity) {
203:
204: // try: setProperty(value)
205: Method method = bp.cd.getBeanSetter(bp.name, suppressSecurity);
206: if (method != null) {
207: invokeSetter(bp.bean, method, value);
208: return;
209: }
210:
211: // try: property=
212: Field field = bp.cd.getField(bp.name, suppressSecurity);
213: if (field != null) {
214: setField(bp.bean, field, value);
215: return;
216: }
217:
218: // try: put("property", value)
219: if (bp.cd.isMap() == true) {
220: ((Map) bp.bean).put(bp.name, value);
221: return;
222: }
223: throw new BeanException("Simple property '" + bp.name
224: + "' not found.", bp);
225: }
226:
227: // ---------------------------------------------------------------- indexed property
228:
229: public static boolean hasIndexProperty(Object bean,
230: String property, boolean suppressSecurity) {
231: return hasIndexProperty(
232: new BeanProperty(bean, property, false),
233: suppressSecurity);
234: }
235:
236: protected static boolean hasIndexProperty(BeanProperty bp,
237: boolean suppressSecurity) {
238:
239: if (bp.bean == null) {
240: return false;
241: }
242: String indexString = extractIndex(bp);
243:
244: if (indexString == null) {
245: return hasSimpleProperty(bp, suppressSecurity);
246: }
247:
248: Object resultBean = getSimpleProperty(bp, suppressSecurity);
249:
250: if (resultBean == null) {
251: return false;
252: }
253:
254: // try: property[index]
255: if (resultBean.getClass().isArray() == true) {
256: int index = parseInt(indexString, bp);
257: return (index >= 0)
258: && (index < Array.getLength(resultBean));
259: }
260:
261: // try: list.get(index)
262: if (resultBean instanceof List) {
263: int index = parseInt(indexString, bp);
264: return (index >= 0) && (index < ((List) resultBean).size());
265: }
266: if (resultBean instanceof Map) {
267: return ((Map) resultBean).containsKey(indexString);
268: }
269:
270: // failed
271: return false;
272: }
273:
274: public static Object getIndexProperty(Object bean, String property,
275: boolean suppressSecurity, boolean forced) {
276: return getIndexProperty(
277: new BeanProperty(bean, property, forced),
278: suppressSecurity);
279: }
280:
281: /**
282: * Get non-nested property value: either simple or indexed property.
283: * If forced, missing bean will be created if possible.
284: */
285: protected static Object getIndexProperty(BeanProperty bp,
286: boolean suppressSecurity) {
287: String indexString = extractIndex(bp);
288:
289: Object resultBean = getSimpleProperty(bp, suppressSecurity);
290:
291: if (indexString == null) {
292: return resultBean; // no index, just simple bean
293: }
294: if (resultBean == null) {
295: throw new BeanException("Index property '" + bp.name
296: + "' is null.", bp);
297: }
298:
299: // try: property[index]
300: if (resultBean.getClass().isArray() == true) {
301: int index = parseInt(indexString, bp);
302: if (bp.forced == true) {
303: return arrayForcedGet(bp, resultBean, index);
304: } else {
305: return Array.get(resultBean, index);
306: }
307: }
308:
309: // try: list.get(index)
310: if (resultBean instanceof List) {
311: int index = parseInt(indexString, bp);
312: List list = (List) resultBean;
313: if (bp.forced == false) {
314: return list.get(index);
315: }
316: if (bp.last == false) {
317: ensureListSize(list, index);
318: }
319: Object value = list.get(index);
320: if (value == null) {
321: Class listType = extracticGenericType(bp, 0);
322: if (listType == null) {
323: listType = Map.class;
324: }
325: try {
326: value = ReflectUtil.newInstance(listType);
327: } catch (Exception ex) {
328: throw new BeanException(
329: "Unable to instatiate list element '"
330: + bp.name + '[' + index + "]'.",
331: bp, ex);
332: }
333: //noinspection unchecked
334: list.set(index, value);
335: }
336: return value;
337: }
338:
339: // try: map.get('index')
340: if (resultBean instanceof Map) {
341: Map map = (Map) resultBean;
342: if (bp.forced == false) {
343: return map.get(indexString);
344: }
345: Object value = map.get(indexString);
346: if (bp.last == false) {
347: if (value == null) {
348: Class mapType = extracticGenericType(bp, 1);
349: if (mapType == null) {
350: mapType = Map.class;
351: }
352: try {
353: value = ReflectUtil.newInstance(mapType);
354: } catch (Exception ex) {
355: throw new BeanException(
356: "Unable to instatiate map element '"
357: + bp.name + '[' + indexString
358: + "]'.", bp, ex);
359: }
360:
361: //noinspection unchecked
362: map.put(indexString, value);
363: }
364: }
365: return value;
366: }
367:
368: // failed
369: throw new BeanException("Index property '" + bp.name
370: + "' is neither an array, a list or a map.", bp);
371: }
372:
373: public static void setIndexProperty(Object bean, String property,
374: Object value, boolean suppressSecurity, boolean forced) {
375: setIndexProperty(new BeanProperty(bean, property, forced),
376: value, suppressSecurity);
377: }
378:
379: /**
380: * Sets indexed or regular properties (no nested!).
381: */
382: @SuppressWarnings({"unchecked"})
383: protected static void setIndexProperty(BeanProperty bp,
384: Object value, boolean suppressSecurity) {
385: String indexString = extractIndex(bp);
386:
387: if (indexString == null) {
388: setSimpleProperty(bp, value, suppressSecurity);
389: return;
390: }
391:
392: // try: getInner()
393: Object nextBean = getSimpleProperty(bp, suppressSecurity);
394:
395: // inner bean found
396: if (nextBean.getClass().isArray() == true) {
397: int index = parseInt(indexString, bp);
398: if (bp.forced == true) {
399: arrayForcedSet(bp, nextBean, index, value);
400: } else {
401: Array.set(nextBean, index, value);
402: }
403: return;
404: }
405:
406: if (nextBean instanceof List) {
407: int index = parseInt(indexString, bp);
408: Class listType = extracticGenericType(bp, 0);
409: if (listType != null) {
410: value = ReflectUtil.castType(value, listType);
411: }
412: List list = (List) nextBean;
413: if (bp.forced == true) {
414: ensureListSize(list, index);
415: }
416: list.set(index, value);
417: return;
418: }
419: if (nextBean instanceof Map) {
420: Map map = ((Map) nextBean);
421: Class mapType = extracticGenericType(bp, 1);
422: if (mapType != null) {
423: value = ReflectUtil.castType(value, mapType);
424: }
425: map.put(indexString, value);
426: return;
427: }
428: throw new BeanException("Index property '" + bp.name
429: + "' is neither an array, a list or a map.", bp);
430: }
431:
432: // ---------------------------------------------------------------- SET
433:
434: /**
435: * Sets Java Bean property.
436: */
437: public static void setProperty(Object bean, String name,
438: Object value) {
439: BeanProperty beanProperty = new BeanProperty(bean, name, false);
440: resolveNestedProperties(beanProperty);
441: setIndexProperty(beanProperty, value, false);
442: }
443:
444: /**
445: * Sets Java Bean property silently, without throwing an exception on non-existing properties.
446: */
447: public static boolean setPropertySilent(Object bean, String name,
448: Object value) {
449: BeanProperty beanProperty = new BeanProperty(bean, name, false);
450: try {
451: resolveNestedProperties(beanProperty);
452: setIndexProperty(beanProperty, value, false);
453: return true;
454: } catch (Exception ex) {
455: return false;
456: }
457: }
458:
459: /**
460: * Sets Java Bean property forced.
461: */
462: public static void setPropertyForced(Object bean, String name,
463: Object value) {
464: BeanProperty beanProperty = new BeanProperty(bean, name, true);
465: resolveNestedProperties(beanProperty);
466: setIndexProperty(beanProperty, value, false);
467: }
468:
469: /**
470: * Sets Java Bean property forced, without throwing an exception on non-existing properties.
471: */
472: public static boolean setPropertyForcedSilent(Object bean,
473: String name, Object value) {
474: BeanProperty beanProperty = new BeanProperty(bean, name, true);
475: try {
476: resolveNestedProperties(beanProperty);
477: setIndexProperty(beanProperty, value, false);
478: return true;
479: } catch (Exception ex) {
480: return false;
481: }
482: }
483:
484: /**
485: * Sets declared Java Bean property.
486: */
487: public static void setDeclaredProperty(Object bean, String name,
488: Object value) {
489: BeanProperty beanProperty = new BeanProperty(bean, name, false);
490: resolveNestedProperties(beanProperty);
491: setIndexProperty(beanProperty, value, true);
492: }
493:
494: public static boolean setDeclaredPropertySilent(Object bean,
495: String name, Object value) {
496: BeanProperty beanProperty = new BeanProperty(bean, name, false);
497: try {
498: resolveNestedProperties(beanProperty);
499: setIndexProperty(beanProperty, value, true);
500: return true;
501: } catch (Exception ex) {
502: return false;
503: }
504: }
505:
506: /**
507: * Sets declared Java Bean property forced.
508: */
509: public static void setDeclaredPropertyForced(Object bean,
510: String name, Object value) {
511: BeanProperty beanProperty = new BeanProperty(bean, name, true);
512: resolveNestedProperties(beanProperty);
513: setIndexProperty(beanProperty, value, true);
514: }
515:
516: public static boolean setDeclaredPropertyForcedSilent(Object bean,
517: String name, Object value) {
518: BeanProperty beanProperty = new BeanProperty(bean, name, true);
519: try {
520: resolveNestedProperties(beanProperty);
521: setIndexProperty(beanProperty, value, true);
522: return true;
523: } catch (Exception ex) {
524: return false;
525: }
526: }
527:
528: // ---------------------------------------------------------------- GET
529:
530: /**
531: * Returns value of bean's property.
532: */
533: public static Object getProperty(Object bean, String name) {
534: BeanProperty beanProperty = new BeanProperty(bean, name, false);
535: resolveNestedProperties(beanProperty);
536: return getIndexProperty(beanProperty, false);
537: }
538:
539: /**
540: * Returns value of declared bean's property.
541: */
542: public static Object getDeclaredProperty(Object bean, String name) {
543: BeanProperty beanProperty = new BeanProperty(bean, name, false);
544: resolveNestedProperties(beanProperty);
545: return getIndexProperty(beanProperty, true);
546: }
547:
548: // ---------------------------------------------------------------- HAS
549:
550: public static boolean hasProperty(Object bean, String name) {
551: BeanProperty beanProperty = new BeanProperty(bean, name, false);
552: if (resolveExistingNestedProperties(beanProperty) == false) {
553: return false;
554: }
555: return hasIndexProperty(beanProperty, false);
556: }
557:
558: public static boolean hasDeclaredProperty(Object bean, String name) {
559: BeanProperty beanProperty = new BeanProperty(bean, name, false);
560: if (resolveExistingNestedProperties(beanProperty) == false) {
561: return false;
562: }
563: return hasIndexProperty(beanProperty, true);
564: }
565:
566: // ---------------------------------------------------------------- copy
567:
568: /**
569: * Copies properties of one bean to another. It iterates all getXXX() methods,
570: * reads values and populates destination bean through setXXX().
571: * No exception is thrown on error.
572: *
573: * @param source source bean, one to read properties from
574: * @param destination destination bean, to write properties to
575: * @param supressSecurity
576: */
577: public static void copy(Object source, Object destination,
578: boolean supressSecurity) {
579: ClassDescriptor cdSrc = DefaultIntrospector.lookup(source
580: .getClass());
581: ClassDescriptor cdDest = DefaultIntrospector.lookup(destination
582: .getClass());
583:
584: String[] mdata = cdSrc.getAllBeanGetterNames(supressSecurity);
585: for (String name : mdata) {
586: if (cdDest.getBeanSetter(name, supressSecurity) != null) {
587: Object value = getProperty(source, name);
588: setPropertySilent(destination, name, value);
589: }
590: }
591: }
592:
593: /**
594: * Copies only public properties.
595: * @param source
596: * @param destination
597: * @see #copy(Object, Object, boolean)
598: */
599: public static void copy(Object source, Object destination) {
600: copy(source, destination, false);
601: }
602:
603: /**
604: * Copies all fields values from source to destination. It iterrates all source fields
605: * and copies data to destination. No exception is thrown on error.
606: */
607: public static void copyFields(Object source, Object destination,
608: boolean supressSecurity) {
609: ClassDescriptor cdSrc = DefaultIntrospector.lookup(source
610: .getClass());
611: ClassDescriptor cdDest = DefaultIntrospector.lookup(destination
612: .getClass());
613:
614: Field[] fields = cdSrc.getAllFields(supressSecurity);
615: for (Field field : fields) {
616: Field destField = cdDest.getField(field.getName(),
617: supressSecurity);
618: if (destField != null) {
619: try {
620: Object value = field.get(source);
621: destField.set(destination, value);
622: } catch (IllegalAccessException iaex) {
623: // ignore
624: }
625: }
626: }
627: }
628:
629: public static void copyFields(Object source, Object destination) {
630: copyFields(source, destination, false);
631: }
632:
633: // ---------------------------------------------------------------- load
634:
635: /**
636: * Populates bean from given object by using a loader for given objects type.
637: */
638: public static void load(Object bean, Object source) {
639: BeanLoader loader = BeanLoaderManager.lookup(source);
640: if (loader == null) {
641: throw new BeanException(
642: "No registered bean loader for source: "
643: + source.getClass().getName());
644: }
645: loader.load(bean, source);
646: }
647:
648: public static void load(Object bean, Object source, Class type) {
649: BeanLoader loader = BeanLoaderManager.lookup(type);
650: if (loader == null) {
651: throw new BeanException(
652: "No registered bean loader for type: "
653: + type.getName());
654: }
655: loader.load(bean, source);
656: }
657:
658: }
|