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