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, Map trace, Map parentMap) {
062 this.bean = bean;
063 this.rootPackages = rootPackages;
064 this.trace = trace;
065 this.parentMap = parentMap;
066 this.identity = new Integer(System.identityHashCode(bean));
067 }
068
069 protected boolean inTheRightPackage(Class clazz) {
070 String name = clazz.getName();
071 for (int i=0; i<rootPackages.length; ++i) {
072 if (clazz.isArray()) {
073 if (name.startsWith("[L"+rootPackages[i]+".")) {
074 return true;
075 }
076 } else {
077 if (name.startsWith(rootPackages[i]+".")) {
078 return true;
079 }
080 }
081 }
082 return false;
083 }
084
085 public boolean accept(Visitor visitor) {
086 if (trace.containsKey(identity)) {
087 return false;
088 }
089 trace.put(identity, bean);
090 if (visitor.visit(bean)) {
091 Class beanClass=bean.getClass();
092 final Object[] args = new Object[] {};
093 Method[] methods = beanClass.getMethods();
094 for (int i=0; i<methods.length; i++) {
095 // getXXX() methods. Object.getClass() is not included.
096 Method method = methods[i];
097 if (!(void.class.equals(method.getReturnType()))
098 && (inTheRightPackage(method.getReturnType()) || Collection.class.isAssignableFrom(method.getReturnType()))
099 && inTheRightPackage(method.getDeclaringClass())
100 && Modifier.isPublic(method.getModifiers())
101 && !Modifier.isAbstract(method.getModifiers())
102 && !(method.getDeclaringClass().equals(Object.class))
103 && !Modifier.isStatic(method.getModifiers())
104 && method.getName().startsWith("get")
105 && method.getParameterTypes().length==0) {
106 try {
107 Object value = method.invoke(bean, args);
108 if (value instanceof Collection) {
109 Iterator it = ((Collection) value).iterator();
110 while (it.hasNext()) {
111 wrap(it.next()).accept(visitor);
112 }
113 } else if (value.getClass().isArray()) {
114 Iterator it = (Arrays.asList((Object[]) value)).iterator();
115 while (it.hasNext()) {
116 wrap(it.next()).accept(visitor);
117 }
118 } else {
119 wrap(value).accept(visitor);
120 }
121 } catch (Exception e) {
122 handleAccessError(method, e);
123 }
124 }
125 }
126
127 Field[] fields = beanClass.getFields();
128 for (int i=0; i<fields.length; i++) {
129 Field field = fields[i];
130 if (!Modifier.isStatic(field.getModifiers())
131 && Modifier.isPublic(field.getModifiers())
132 && inTheRightPackage(field.getDeclaringClass())
133 && (inTheRightPackage(field.getType()) || Collection.class.isAssignableFrom(field.getType()))) {
134 try {
135 Object value = field.get(bean);
136 if (value instanceof Collection) {
137 Iterator it = ((Collection) value).iterator();
138 while (it.hasNext()) {
139 wrap(it.next()).accept(visitor);
140 }
141 } else if (value.getClass().isArray()) {
142 Iterator it = (Arrays.asList((Object[]) value)).iterator();
143 while (it.hasNext()) {
144 wrap(it.next()).accept(visitor);
145 }
146 } else {
147 wrap(value).accept(visitor);
148 }
149 } catch (Exception e) {
150 handleAccessError(fields[i], e);
151 }
152 }
153 }
154
155 if (visitor instanceof PoliteVisitor) {
156 ((PoliteVisitor) visitor).leave(bean);
157 }
158 }
159 return false;
160 }
161
162 /**
163 * Prints stack trace to System.err. Override if necessary
164 * @param field
165 * @param e
166 */
167 protected void handleAccessError(Field field, Exception e) {
168 System.err.println("Error accessing field "+field);
169 e.printStackTrace();
170 }
171
172 /**
173 * Prints stack trace to System.err. Override if necessary
174 * @param method
175 * @param e
176 */
177 protected void handleAccessError(Method method, Exception e) {
178 System.err.println("Error accessing method "+method);
179 e.printStackTrace();
180 }
181
182 /**
183 * Wraps child into Visitable and updates path.
184 * If child is already instance of Visitable it is returned as is and path is not
185 * updated.
186 * @param child
187 * @return
188 */
189 protected Visitable wrap(Object child) {
190 if (child instanceof Visitable) {
191 return (Visitable) child;
192 }
193
194 BeanVisitable ret = new BeanVisitable(child, rootPackages, trace, parentMap);
195 parentMap.put(ret.identity, identity);
196 return ret;
197 }
198
199 /**
200 * @return Path from given object to the root of the model, the given object included.
201 */
202 public Object[] getPath(Object obj) {
203 LinkedList path = new LinkedList();
204 fillPath(path, obj);
205 return path.toArray();
206 }
207
208 private void fillPath(LinkedList path, Object obj) {
209 path.addFirst(obj);
210 Object parentKey = parentMap.get(new Integer(System.identityHashCode(obj)));
211 if (parentKey!=null) {
212 Object parent = trace.get(parentKey);
213 if (parent!=null) {
214 fillPath(path, parent);
215 }
216 }
217
218 }
219
220 /**
221 * @return System hash code of underlying bean
222 */
223 public Integer getIdentity() {
224 return identity;
225 }
226
227 }
228