001: package biz.hammurapi.util;
002:
003: import java.lang.reflect.Field;
004: import java.lang.reflect.Method;
005: import java.lang.reflect.Modifier;
006: import java.util.Arrays;
007: import java.util.Collection;
008: import java.util.HashMap;
009: import java.util.Iterator;
010: import java.util.LinkedList;
011: import java.util.Map;
012:
013: import biz.hammurapi.util.PoliteVisitor;
014: import biz.hammurapi.util.Visitable;
015: import biz.hammurapi.util.Visitor;
016:
017: /**
018: * Wraps bean into visitable. Children are inferred from: <UL> <LI>getXXX methods which <UL><LI>Are declared in classes which belong to one of root packages or its sub-packages
019: * </LI><LI>Have return type either collection or class (including arrays) belonging to one of root packages or its subpackages.</LI></UL>
020: * <LI>Public fields of type belonging to one of root packages or subpackages (including arrays) or of collection type.</LI></UL>
021: * @author Pavel Vlasov
022: */
023: public class BeanVisitable implements Visitable {
024:
025: private Object bean;
026: private String[] rootPackages;
027: private Map trace;
028: private Map parentMap;
029: private Integer identity;
030:
031: public BeanVisitable(Object bean) {
032: this (bean, packageName(bean.getClass()));
033: }
034:
035: private static String packageName(Class clazz) {
036: int idx = clazz.getName().lastIndexOf(".");
037: return idx == -1 ? "" : clazz.getName().substring(0, idx);
038: }
039:
040: /**
041: * @param bean Bean to visit
042: * @param rootPackage Package for child classes to visit.
043: */
044: public BeanVisitable(Object bean, String rootPackage) {
045: this (bean, new String[] { rootPackage });
046: }
047:
048: /**
049: * @param bean Bean to visit
050: * @param rootPackages Packages for child classes to visit.
051: */
052: public BeanVisitable(Object bean, String[] rootPackages) {
053: this (bean, rootPackages, new HashMap(), new HashMap());
054: }
055:
056: /**
057: * This constructor is used by BeanVisitable itself to wrap children into visitable.
058: * @param bean Bean to visit
059: * @param rootPackages Package for child classes to visit.
060: */
061: protected BeanVisitable(Object bean, String[] rootPackages,
062: Map trace, Map parentMap) {
063: this .bean = bean;
064: this .rootPackages = rootPackages;
065: this .trace = trace;
066: this .parentMap = parentMap;
067: this .identity = new Integer(System.identityHashCode(bean));
068: }
069:
070: protected boolean inTheRightPackage(Class clazz) {
071: String name = clazz.getName();
072: for (int i = 0; i < rootPackages.length; ++i) {
073: if (clazz.isArray()) {
074: if (name.startsWith("[L" + rootPackages[i] + ".")) {
075: return true;
076: }
077: } else {
078: if (name.startsWith(rootPackages[i] + ".")) {
079: return true;
080: }
081: }
082: }
083: return false;
084: }
085:
086: public boolean accept(Visitor visitor) {
087: if (trace.containsKey(identity)) {
088: return false;
089: }
090: trace.put(identity, bean);
091: if (visitor.visit(bean)) {
092: Class beanClass = bean.getClass();
093: final Object[] args = new Object[] {};
094: Method[] methods = beanClass.getMethods();
095: for (int i = 0; i < methods.length; i++) {
096: // getXXX() methods. Object.getClass() is not included.
097: Method method = methods[i];
098: if (!(void.class.equals(method.getReturnType()))
099: && (inTheRightPackage(method.getReturnType()) || Collection.class
100: .isAssignableFrom(method
101: .getReturnType()))
102: && inTheRightPackage(method.getDeclaringClass())
103: && Modifier.isPublic(method.getModifiers())
104: && !Modifier.isAbstract(method.getModifiers())
105: && !(method.getDeclaringClass()
106: .equals(Object.class))
107: && !Modifier.isStatic(method.getModifiers())
108: && method.getName().startsWith("get")
109: && method.getParameterTypes().length == 0) {
110: try {
111: Object value = method.invoke(bean, args);
112: if (value instanceof Collection) {
113: Iterator it = ((Collection) value)
114: .iterator();
115: while (it.hasNext()) {
116: wrap(it.next()).accept(visitor);
117: }
118: } else if (value.getClass().isArray()) {
119: Iterator it = (Arrays
120: .asList((Object[]) value))
121: .iterator();
122: while (it.hasNext()) {
123: wrap(it.next()).accept(visitor);
124: }
125: } else {
126: wrap(value).accept(visitor);
127: }
128: } catch (Exception e) {
129: handleAccessError(method, e);
130: }
131: }
132: }
133:
134: Field[] fields = beanClass.getFields();
135: for (int i = 0; i < fields.length; i++) {
136: Field field = fields[i];
137: if (!Modifier.isStatic(field.getModifiers())
138: && Modifier.isPublic(field.getModifiers())
139: && inTheRightPackage(field.getDeclaringClass())
140: && (inTheRightPackage(field.getType()) || Collection.class
141: .isAssignableFrom(field.getType()))) {
142: try {
143: Object value = field.get(bean);
144: if (value instanceof Collection) {
145: Iterator it = ((Collection) value)
146: .iterator();
147: while (it.hasNext()) {
148: wrap(it.next()).accept(visitor);
149: }
150: } else if (value.getClass().isArray()) {
151: Iterator it = (Arrays
152: .asList((Object[]) value))
153: .iterator();
154: while (it.hasNext()) {
155: wrap(it.next()).accept(visitor);
156: }
157: } else {
158: wrap(value).accept(visitor);
159: }
160: } catch (Exception e) {
161: handleAccessError(fields[i], e);
162: }
163: }
164: }
165:
166: if (visitor instanceof PoliteVisitor) {
167: ((PoliteVisitor) visitor).leave(bean);
168: }
169: }
170: return false;
171: }
172:
173: /**
174: * Prints stack trace to System.err. Override if necessary
175: * @param field
176: * @param e
177: */
178: protected void handleAccessError(Field field, Exception e) {
179: System.err.println("Error accessing field " + field);
180: e.printStackTrace();
181: }
182:
183: /**
184: * Prints stack trace to System.err. Override if necessary
185: * @param method
186: * @param e
187: */
188: protected void handleAccessError(Method method, Exception e) {
189: System.err.println("Error accessing method " + method);
190: e.printStackTrace();
191: }
192:
193: /**
194: * Wraps child into Visitable and updates path.
195: * If child is already instance of Visitable it is returned as is and path is not
196: * updated.
197: * @param child
198: * @return
199: */
200: protected Visitable wrap(Object child) {
201: if (child instanceof Visitable) {
202: return (Visitable) child;
203: }
204:
205: BeanVisitable ret = new BeanVisitable(child, rootPackages,
206: trace, parentMap);
207: parentMap.put(ret.identity, identity);
208: return ret;
209: }
210:
211: /**
212: * @return Path from given object to the root of the model, the given object included.
213: */
214: public Object[] getPath(Object obj) {
215: LinkedList path = new LinkedList();
216: fillPath(path, obj);
217: return path.toArray();
218: }
219:
220: private void fillPath(LinkedList path, Object obj) {
221: path.addFirst(obj);
222: Object parentKey = parentMap.get(new Integer(System
223: .identityHashCode(obj)));
224: if (parentKey != null) {
225: Object parent = trace.get(parentKey);
226: if (parent != null) {
227: fillPath(path, parent);
228: }
229: }
230:
231: }
232:
233: /**
234: * @return System hash code of underlying bean
235: */
236: public Integer getIdentity() {
237: return identity;
238: }
239:
240: }
|