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