Source code: com/aendvari/griffin/bean/transform/BeanTransformer.java
1 /*
2 * BeanTransformer.java
3 *
4 * Copyright (c) 2001, 2002 Aendvari, Ltd. All Rights Reserved.
5 *
6 * See the file LICENSE for terms of use.
7 *
8 */
9
10 package com.aendvari.griffin.bean.transform;
11
12 import java.beans.*;
13 import java.util.*;
14
15 import java.lang.reflect.*;
16 import java.lang.NoSuchMethodException;
17 import java.lang.IllegalAccessException;
18
19 import com.aendvari.common.util.*;
20
21 import com.aendvari.common.model.*;
22
23
24 /**
25 * <p>
26 * Translates the contents of a <code>Java Bean</code> into the given {@link ModelNode}.
27 * See the {@link com.aendvari.common.model} package for more about the {@link ModelNode}.
28 * </p>
29 *
30 * @author Scott Milne
31 *
32 */
33
34 public class BeanTransformer
35 {
36 /**
37 * Converts the given bean into it's XML representation as required.
38 *
39 * @param beanObject The bean to convert.
40 * @param modelNode The {@link ModelNode} to output nodes to.
41 * @param replaceChildren Specifies whether to replace all children nodes.
42 *
43 * @throws Throws an <code>Exception</code> if a conversion error occurs.
44 *
45 */
46
47 public static void beanToModel( Object beanObject, ModelNode modelNode, boolean replaceChildren )
48 throws Exception
49 {
50 try
51 {
52 ModelTree modelTree = modelNode.getOwnerModelTree();
53
54 // remove all children of output node
55 if (replaceChildren)
56 {
57 Iterator childIterator = modelNode.getChildNodes().iterator();
58 while (childIterator.hasNext())
59 {
60 ModelNode node = (ModelNode)childIterator.next();
61 modelNode.removeChild(node);
62 }
63 }
64
65 // transform node, place created nodes as part of supplied node
66 beanToModel(beanObject, modelTree, modelNode);
67 }
68 /*
69 * NoSuchMethodException
70 * IllegalAccessException
71 * IntrospectionException
72 *
73 */
74 catch (Exception exception)
75 {
76 throw exception;
77 }
78 }
79
80 /**
81 * Converts the given bean into it's XML representation as required. A new node
82 * is created under the supplied model node to contain bean.
83 *
84 * @param beanClass The bean to convert.
85 * @param propertyName The {@link ModelTree} instance to create new nodes from.
86 * @param modelTree The {@link ModelTree} instance to create new nodes from.
87 * @param modelNode The {@link ModelNode} instance to add new nodes to.
88 *
89 * @return A {@link ModelNode} instance of the given bean.
90 * @throws Throws an <code>Exception</code> if a conversion error occurs.
91 *
92 */
93
94 static private ModelNode beanToModel( Object beanObject, String propertyName, ModelTree modelTree, ModelNode modelNode )
95 throws Exception
96 {
97 // create the node for the "class"
98 ModelNode propertyNode = modelTree.createNode( propertyName );
99 modelNode.appendChild(propertyNode);
100
101 // transform node, place created nodes as part of supplied node
102 beanToModel(beanObject, modelTree, propertyNode);
103
104 return propertyNode;
105 }
106
107 /**
108 * Converts the given bean into it's XML representation as required.
109 *
110 * @param beanObject The bean to convert.
111 * @param modelTree The {@link ModelTree} instance to create new nodes from.
112 * @param propertyNode The {@link ModelNode} to output nodes to.
113 *
114 * @throws Throws an <code>Exception</code> if a conversion error occurs.
115 *
116 */
117
118 static private void beanToModel( Object beanObject, ModelTree modelTree, ModelNode propertyNode )
119 throws Exception
120 {
121 // create the <class> child
122 ModelNode classNode = modelTree.createNode( "class", beanObject.getClass().getName() );
123 propertyNode.appendChild(classNode);
124
125 // create an inspected bean instance of this bean
126 InspectedBean bean = new InspectedBean(beanObject);
127
128 // get the list of "get" methods for this bean
129 HashMap getMethods = bean.getGetMethods();
130
131 // iterate through the methods and get their values
132 Iterator keysIterator = getMethods.keySet().iterator();
133
134 while (keysIterator.hasNext())
135 {
136 String propertyNameKey = (String)keysIterator.next();
137
138 Object value = bean.executeGetMethod(propertyNameKey, beanObject);
139
140 // make sure the value was initialized
141 if( value != null )
142 {
143 // examine the property (recursive)
144 examineObject(modelTree, propertyNode, propertyNameKey, value );
145 }
146 }
147 }
148
149 /**
150 * Examines the given object to determine its type, and then transform that object
151 * into it's XML representation as required.
152 *
153 * @param modelTree The {@link ModelTree} instance to create new nodes from.
154 * @param modelNode The {@link ModelNode} instance to add new nodes to.
155 * @param propertyName The name of the property were examining.
156 * @param value The object to examine.
157 *
158 * @throws Throws an <code>Exception</code> if a conversion error occurs.
159 *
160 */
161
162 static private void examineObject( ModelTree modelTree, ModelNode classNode, String propertyName, Object value )
163 throws Exception
164 {
165 try
166 {
167 Class valueClass = value.getClass();
168
169 // if the object is a array[]
170 if (value.getClass().isArray())
171 {
172 int nLength = java.lang.reflect.Array.getLength(value);
173
174 for( int i=0; i<nLength; i++ )
175 {
176 Object indexedObject = java.lang.reflect.Array.get(value,i);
177 examineObject(modelTree, classNode, propertyName, indexedObject );
178 }
179 }
180 // if the object is a Map type
181 else if( value instanceof Map )
182 {
183 Map map = (Map)value;
184
185 // go through each value array checking for the value
186 Iterator keysIterator = map.keySet().iterator();
187 while (keysIterator.hasNext())
188 {
189 // create the new node
190 ModelNode propertyNode = modelTree.createNode( propertyName );
191 classNode.appendChild( propertyNode );
192
193 // create the "key" node and examine the propery for the key
194 Object keyObject = (Object)keysIterator.next();
195 ModelNode keyNode = modelTree.createNode( "key" );
196 propertyNode.appendChild( keyNode );
197 examineObject(modelTree, keyNode, "key", keyObject );
198
199 // create the "value" node and examine the propery for the value
200 Object valueObject = map.get(keyObject);
201 ModelNode valueNode = modelTree.createNode( "value" );
202 propertyNode.appendChild( valueNode );
203 examineObject(modelTree, valueNode, "value", valueObject );
204 }
205 }
206 // if the object is a Set type
207 else if( value instanceof Collection )
208 {
209 // go through each value array checking for the value
210 Iterator valuesIterator = ((Collection)value).iterator();
211 while (valuesIterator.hasNext())
212 {
213 // get the object for this item
214 Object setValue = (Object)valuesIterator.next();
215
216 // create the node for this item
217 ModelNode propertyNode = modelTree.createNode( propertyName );
218 classNode.appendChild( propertyNode );
219
220 // examine the property for this item
221 examineObject(modelTree, propertyNode, propertyName, setValue );
222 }
223 }
224 // if the property is another Bean, convert it as well
225 else if( !valueClass.isPrimitive() && !isJavaUtilClass(value) && !isJavaLangClass(value) )
226 {
227 // get the parent node
228 ModelNode parentNode = classNode.getParentNode();
229
230 // remove the property node in the parent
231 if (classNode.getNodeName().equals(propertyName))
232 {
233 // extract this bean into the model
234 ModelNode tmpBeanNode = beanToModel( value, propertyName, modelTree, classNode );
235
236 // replace the previous property node with the bean node
237 parentNode.replaceChild( tmpBeanNode, classNode );
238 }
239 else
240 {
241 // extract this bean into the model
242 beanToModel( value, propertyName, modelTree, classNode );
243 }
244
245 }
246 // the property name is the same as the node name, and the node does not have a <class> child,
247 // this means we are running within a collection, set or map
248 else if( propertyName.equals(classNode.getNodeName()) && !hasClassChild(classNode) )
249 {
250 // get the parent node
251 ModelNode parentNode = classNode.getParentNode();
252
253 // create a new text node, and replace the current one with
254 // the new one and it's text value.
255 String sValue = value.toString();
256 ModelNode propertyNode = modelTree.createNode( propertyName, sValue );
257 parentNode.replaceChild( propertyNode, classNode );
258 }
259 // otherwise, its either a primitive, or simple property object
260 else
261 {
262 // create the new node
263 String sValue = value.toString();
264 ModelNode propertyNode = modelTree.createNode( propertyName, sValue );
265 classNode.appendChild( propertyNode );
266 }
267 }
268 catch( Exception exception )
269 {
270 throw exception;
271 }
272 }
273
274
275 /**
276 * Determines if the given node has a <code><class></code> child within it.
277 *
278 * @param parentNode The node to search the children of.
279 *
280 */
281
282 static private boolean hasClassChild( ModelNode parentNode )
283 {
284 ModelNode node = parentNode.getFirstChild();
285
286 while (node != null)
287 {
288 String nodeName = node.getNodeName();
289
290 if (nodeName.equals("class"))
291 {
292 return true;
293 }
294
295 node = node.getNextSibling();
296 }
297
298 return false;
299 }
300
301
302 /**
303 * Extract a bean from the XML node.
304 *
305 * @param beanNode The XML node of which the <class> for the bean exists.
306 *
307 * @throws Throws an <code>Exception</code> if a conversion error occurs.
308 * @return Returns a bean instance as an <code>Object</code>.
309 *
310 */
311
312 public static Object createBeanFromModel( ModelNode beanNode )
313 throws Exception
314 {
315 // get the name of the class
316 String beanClassName = beanNode.getFirstChild().getNodeValue();
317
318 // create an inspected bean instance of this bean
319 InspectedBean bean = new InspectedBean(beanClassName);
320
321 // examine the first level of properties (recursive)
322 examineNodeValues(bean, beanNode, false);
323
324 // obtain an instance of the bean
325 Object modelBean = bean.getBeanInstance();
326
327 return modelBean;
328 }
329
330 /**
331 * Extract a bean from the XMl node and places into given bean.
332 * Only the properties in the XML are "set" into the bean, the remainders
333 * are left in their original state.
334 *
335 * @param beanObj The bean to transfer data into.
336 * @param beanNode The XML node of which the <class> for the bean exists.
337 *
338 * @throws Throws an <code>Exception</code> if a conversion error occurs.
339 * @return Returns a bean instance as an <code>Object</code>.
340 *
341 */
342
343 public static Object updateBeanFromModel( Object beanObj, ModelNode beanNode )
344 throws Exception
345 {
346 // create an inspected bean instance of this bean
347 InspectedBean bean = new InspectedBean(beanObj);
348
349 // examine the first level of properties (recursive)
350 examineNodeValues(bean, beanNode, true);
351
352 // obtain an instance of the bean
353 Object modelBean = bean.getBeanInstance();
354
355 return modelBean;
356 }
357
358 /**
359 * Examines the node (and it's children) for the bean properties to
360 * match with the inspected bean.
361 *
362 * Once a property is matched, it is added to the "set" method parameters of the bean.
363 *
364 * @param inspectedBean A {@link InspectedBean} object of the bean we are extracting.
365 * @param modelNode The node to examine for property values.
366 * @param updateExamine If this examine is for an update, set this to "true".
367 *
368 * @throws Throws an <code>Exception</code> if a conversion error occurs.
369 *
370 */
371
372 static private void examineNodeValues( InspectedBean inspectedBean, ModelNode objectNode, boolean updateExamine )
373 throws Exception
374 {
375 ModelNode node = objectNode.getFirstChild();
376
377 while (node != null)
378 {
379 String nodeName = node.getNodeName();
380
381 // by default use the node name as the property name
382 Class methodParamClass = inspectedBean.getSetMethodParamClass(nodeName);
383
384 // get the first child node of this node
385 ModelNode firstChildNode = node.getFirstChild();
386 String firstChildNodeName = "";
387 if (firstChildNode != null)
388 {
389 firstChildNodeName = firstChildNode.getNodeName();
390 }
391
392 // if we're updating, and the first child node name
393 // is not "class", check the bean to see if the related node name
394 // is a class or not
395 if (updateExamine && !firstChildNodeName.equals("class"))
396 {
397 InspectedBean.BeanGetMethod beanGetMethod = inspectedBean.getBeanGetMethod(nodeName);
398 if (beanGetMethod != null)
399 {
400 Method getMethod = beanGetMethod.getMethod();
401 Class returnType = getMethod.getReturnType();
402
403 // make sure the return type is only a bean
404 if( !returnType.isPrimitive() &&
405 !isJavaUtilClass(returnType) &&
406 !isJavaLangClass(returnType) &&
407 !returnType.isArray()
408 )
409 {
410 // if it starts with class, then reset the first child node name
411 if( getMethod.getReturnType().toString().startsWith("class") )
412 {
413 firstChildNodeName = "class";
414 }
415 }
416 }
417 }
418
419 // if the first child node is a class, examine it
420 if( firstChildNodeName.equals("class") )
421 {
422 // get the type of the class from
423 String beanClassName = firstChildNode.getNodeValue();
424
425 // set the method param class to the value that was derived from the node
426 methodParamClass = inspectedBean.getSetMethodParamClass(nodeName);
427
428 // this method param is not a map, so add it's value to the bean's method list of param's
429 if(
430 methodParamClass != null &&
431 !Map.class.isAssignableFrom(methodParamClass) &&
432 !Collection.class.isAssignableFrom(methodParamClass)
433 )
434 {
435 Object newBean = null;
436 InspectedBean bean = null;
437
438 // if were doing an update, use the bean property instead
439 if (updateExamine)
440 {
441 // try getting the property bean from the master bean
442 newBean = inspectedBean.executeGetMethod(nodeName, inspectedBean.getBeanObject() );
443
444 // if null, then create an instance to place values into
445 if (newBean == null)
446 {
447 newBean = methodParamClass.newInstance();
448 }
449
450 // we need to create a new inspector bean for this class
451 bean = new InspectedBean(newBean);
452 }
453 // a "create" is being done, so just pass in the class name
454 // and an instance of that class will be automatically be created.
455 else
456 {
457 // we need to create a new inspector bean for this class
458 bean = new InspectedBean(beanClassName);
459 }
460
461 // using the new inspected bean, examine the child nodes
462 examineNodeValues(bean, node, updateExamine);
463
464 newBean = bean.getBeanInstance();
465
466 // save the method param for the property
467 inspectedBean.addSetMethodParamValue( nodeName, newBean );
468 }
469 // this class is within a collection
470 else if( methodParamClass != null && Collection.class.isAssignableFrom(methodParamClass) )
471 {
472 // we need to create a new inspector bean for this class
473 InspectedBean bean = new InspectedBean(beanClassName);
474
475 // using the new inspected bean, examine the child nodes
476 examineNodeValues(bean, node, updateExamine);
477
478 // create a new instance of the bean using the extracted data
479 // and add it to the method parameters as required.
480 Object newBean = bean.getBeanInstance();
481
482 // save the method param for the property
483 inspectedBean.addSetMethodParamValue( nodeName, newBean );
484 }
485 // this class is within a map object
486 else
487 {
488 // we need to create a new inspector bean for this class
489 InspectedBean bean = new InspectedBean(beanClassName);
490
491 // using the new inspected bean, examine the child nodes
492 examineNodeValues(bean, node, updateExamine);
493
494 // create a new instance of the bean using the extracted data
495 // and add it to the method parameters as required.
496 Object newBean = bean.getBeanInstance();
497
498 // since this class is within a map object, find out
499 // which child node this class is within (key/value)
500 // by asking the parent node of this class what it is.
501 ModelNode parentNode = node.getParentNode();
502
503 if( parentNode != null )
504 {
505 // get the name of the parent node
506 String parentNodeName = parentNode.getNodeName();
507
508 // if the parent node is a "key" set the key for this map
509 if( nodeName.equals("key") )
510 {
511 // save the method param for the property
512 inspectedBean.addSetMethodParamMapKey( parentNodeName, newBean );
513 }
514 // if the parent node is a "value" set the value for this map
515 else if( nodeName.equals("value") )
516 {
517 // save the method param for the property
518 inspectedBean.addSetMethodParamMapValue( parentNodeName, newBean );
519 }
520 }
521 }
522 }
523 // this node is a Map
524 else if( methodParamClass != null && Map.class.isAssignableFrom(methodParamClass) )
525 {
526 // create the map list for this method (not the param list)
527 inspectedBean.createSetMethodParamMap( nodeName );
528
529 // examine this node
530 examineNodeValues(inspectedBean, node, updateExamine);
531
532 // add all the discovered map entries to the map method params
533 inspectedBean.addSetMethodParamMap( nodeName );
534 }
535 // at this point we are dealing with a single property
536 // this can be either a bean property, or the key/value pairs
537 // of a map
538 else
539 {
540 methodParamClass = null;
541
542 // if a valid parent node was found, try getting the methods param class
543 ModelNode parentNode = node.getParentNode();
544 if( parentNode != null )
545 {
546 methodParamClass = inspectedBean.getSetMethodParamClass(parentNode.getNodeName());
547 }
548
549 // if a valid param class was found, test to see if it's a Map
550 if( methodParamClass != null && Map.class.isAssignableFrom(methodParamClass) )
551 {
552 // if this node is a key, add it to the map list
553 if( nodeName.equals("key") )
554 {
555 inspectedBean.addSetMethodParamMapKey( parentNode.getNodeName(), node.getNodeValue() );
556 }
557 // if this node is a value, add it to the map list
558 else if( nodeName.equals("value") )
559 {
560 inspectedBean.addSetMethodParamMapValue( parentNode.getNodeName(), node.getNodeValue() );
561 }
562 }
563 // this is a simple property, add it to the methods param list
564 else
565 {
566 // save the method param for the property
567 inspectedBean.addSetMethodParamValue( nodeName, node.getNodeValue() );
568 }
569 }
570
571 // get next child node
572 node = node.getNextSibling();
573 }
574 }
575
576 /**
577 * Determines if the given object is a class from the <code>java.util package</code>.
578 *
579 * @param Object The object to test.
580 *
581 */
582
583 public static boolean isJavaUtilClass( Object value )
584 {
585 Class valueClass = value.getClass();
586 return isJavaUtilClass(valueClass);
587 }
588
589 /**
590 * Determines if the given class is from the <code>java.util package</code>.
591 *
592 * @param Class The class to test.
593 *
594 */
595
596 public static boolean isJavaUtilClass( Class valueClass )
597 {
598 if( Collection.class.isAssignableFrom(valueClass) ) return true;
599 if( Comparator.class.isAssignableFrom(valueClass) ) return true;
600 if( Enumeration.class.isAssignableFrom(valueClass) ) return true;
601 if( EventListener.class.isAssignableFrom(valueClass) ) return true;
602 if( Iterator.class.isAssignableFrom(valueClass) ) return true;
603 if( List.class.isAssignableFrom(valueClass) ) return true;
604 if( ListIterator.class.isAssignableFrom(valueClass) ) return true;
605 if( Map.class.isAssignableFrom(valueClass) ) return true;
606 if( Set.class.isAssignableFrom(valueClass) ) return true;
607 if( SortedMap.class.isAssignableFrom(valueClass) ) return true;
608 if( SortedSet.class.isAssignableFrom(valueClass) ) return true;
609
610 return false;
611 }
612
613 /**
614 * Determines if the given object is a class from the <code>java.lang package</code>
615 *
616 * @param Object The object to test.
617 *
618 */
619
620 public static boolean isJavaLangClass( Object value )
621 {
622 Class valueClass = value.getClass();
623 return isJavaLangClass(valueClass);
624 }
625
626 /**
627 * Determines if the given class is from the <code>java.lang package</code>.
628 *
629 * @param Class The class to test.
630 *
631 */
632
633 public static boolean isJavaLangClass( Class valueClass )
634 {
635 if( Boolean.class.isAssignableFrom(valueClass) ) return true;
636 if( Byte.class.isAssignableFrom(valueClass) ) return true;
637 if( Character.class.isAssignableFrom(valueClass) ) return true;
638 if( Double.class.isAssignableFrom(valueClass) ) return true;
639 if( Float.class.isAssignableFrom(valueClass) ) return true;
640 if( Integer.class.isAssignableFrom(valueClass) ) return true;
641 if( Long.class.isAssignableFrom(valueClass) ) return true;
642 if( Short.class.isAssignableFrom(valueClass) ) return true;
643 if( String.class.isAssignableFrom(valueClass) ) return true;
644
645 return false;
646 }
647 }
648