`ClassPropertyFetcher.java`
`001 /* 002 * Copyright 2004-2012 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.codehaus.griffon.runtime.core; 017 018 import griffon.util.GriffonClassUtils; 019 import griffon.util.GriffonNameUtils; 020 021 import java.beans.PropertyDescriptor; 022 import java.lang.reflect.*; 023 import java.util.*; 024 import java.util.concurrent.ConcurrentHashMap; 025 026 /** 027 * Accesses class "properties": static fields, static getters, instance fields 028 * or instance getters 029 * <p/> 030 * Method and Field instances are cached for fast access 031 * 032 * @author Lari Hotari, Sagire Software Oy (Grails) 033 * @author Andres Almiray 034 * @since 0.9.1 035 */ 036 public class ClassPropertyFetcher { 037 private final Class<?> clazz; 038 final Map<String, PropertyFetcher> staticFetchers = new LinkedHashMap<String, PropertyFetcher>(); 039 final Map<String, PropertyFetcher> instanceFetchers = new LinkedHashMap<String, PropertyFetcher>(); 040 private final ReferenceInstanceCallback callback; 041 private PropertyDescriptor[] propertyDescriptors; 042 private String[] propertiesWithFields; 043 044 private static Map<Class<?>, ClassPropertyFetcher> cachedClassPropertyFetchers = new ConcurrentHashMap<Class<?>, ClassPropertyFetcher>(); 045 046 public static void clearClassPropertyFetcherCache() { 047 cachedClassPropertyFetchers.clear(); 048 } 049 050 public static ClassPropertyFetcher forClass(Class<?> c) { 051 return forClass(c, null); 052 } 053 054 public static ClassPropertyFetcher forClass(final Class<?> c, ReferenceInstanceCallback callback) { 055 ClassPropertyFetcher cpf = cachedClassPropertyFetchers.get(c); 056 if (cpf == null) { 057 if (callback == null) { 058 callback = new ReferenceInstanceCallback() { 059 private Object o; 060 061 public Object getReferenceInstance() { 062 if (o == null) { 063 o = GriffonClassUtils.instantiateClass(c); 064 } 065 return o; 066 } 067 }; 068 } 069 cpf = new ClassPropertyFetcher(c, callback); 070 cachedClassPropertyFetchers.put(c, cpf); 071 } 072 return cpf; 073 } 074 075 ClassPropertyFetcher(Class<?> clazz, ReferenceInstanceCallback callback) { 076 this.clazz = clazz; 077 this.callback = callback; 078 init(); 079 } 080 081 public Object getReference() { 082 if (callback != null) { 083 return callback.getReferenceInstance(); 084 } 085 return null; 086 } 087 088 public PropertyDescriptor[] getPropertyDescriptors() { 089 return propertyDescriptors; 090 } 091 092 public boolean isReadableProperty(String name) { 093 return staticFetchers.containsKey(name) 094 || instanceFetchers.containsKey(name); 095 } 096 097 public String[] getPropertiesWithFields() { 098 return propertiesWithFields; 099 } 100 101 private void init() { 102 FieldCallback fieldCallback = new FieldCallback() { 103 public void doWith(Field field) { 104 if (field.isSynthetic()) 105 return; 106 final int modifiers = field.getModifiers(); 107 if (!Modifier.isPublic(modifiers)) 108 return; 109 110 final String name = field.getName(); 111 if (name.indexOf('$') == -1) { 112 boolean staticField = Modifier.isStatic(modifiers); 113 if (staticField) { 114 staticFetchers.put(name, new FieldReaderFetcher(field, 115 staticField)); 116 } else { 117 instanceFetchers.put(name, new FieldReaderFetcher( 118 field, staticField)); 119 } 120 } 121 } 122 }; 123 124 MethodCallback methodCallback = new MethodCallback() { 125 public void doWith(Method method) throws IllegalArgumentException, 126 IllegalAccessException { 127 if (method.isSynthetic()) 128 return; 129 if (!Modifier.isPublic(method.getModifiers())) 130 return; 131 if (Modifier.isStatic(method.getModifiers()) 132 && method.getReturnType() != Void.class) { 133 if (method.getParameterTypes().length == 0) { 134 String name = method.getName(); 135 if (name.indexOf('$') == -1) { 136 if (name.length() > 3 && name.startsWith("get") 137 && Character.isUpperCase(name.charAt(3))) { 138 name = name.substring(3); 139 } else if (name.length() > 2 140 && name.startsWith("is") 141 && Character.isUpperCase(name.charAt(2)) 142 && (method.getReturnType() == Boolean.class || method 143 .getReturnType() == boolean.class)) { 144 name = name.substring(2); 145 } 146 PropertyFetcher fetcher = new GetterPropertyFetcher( 147 method, true); 148 staticFetchers.put(name, fetcher); 149 staticFetchers.put(GriffonNameUtils.uncapitalize(name), fetcher); 150 } 151 } 152 } 153 } 154 }; 155 156 List<Class<?>> allClasses = resolveAllClasses(clazz); 157 for (Class<?> c : allClasses) { 158 Field[] fields = c.getDeclaredFields(); 159 for (Field field : fields) { 160 try { 161 fieldCallback.doWith(field); 162 } catch (IllegalAccessException ex) { 163 throw new IllegalStateException( 164 "Shouldn't be illegal to access field '" 165 + field.getName() + "': " + ex); 166 } 167 } 168 Method[] methods = c.getDeclaredMethods(); 169 for (Method method : methods) { 170 try { 171 methodCallback.doWith(method); 172 } catch (IllegalAccessException ex) { 173 throw new IllegalStateException( 174 "Shouldn't be illegal to access method '" 175 + method.getName() + "': " + ex); 176 } 177 } 178 } 179 180 propertyDescriptors = GriffonClassUtils.getPropertyDescriptors(clazz); 181 for (PropertyDescriptor desc : propertyDescriptors) { 182 Method readMethod = desc.getReadMethod(); 183 if (readMethod != null) { 184 boolean staticReadMethod = Modifier.isStatic(readMethod 185 .getModifiers()); 186 if (staticReadMethod) { 187 staticFetchers.put(desc.getName(), 188 new GetterPropertyFetcher(readMethod, 189 staticReadMethod)); 190 } else { 191 instanceFetchers.put(desc.getName(), 192 new GetterPropertyFetcher(readMethod, 193 staticReadMethod)); 194 } 195 } 196 } 197 198 final List<String> properties = new ArrayList<String>(); 199 for (Class<?> c : allClasses) { 200 final List<String> props = new ArrayList<String>(); 201 for (PropertyDescriptor p : GriffonClassUtils.getPropertyDescriptors(clazz)) { 202 props.add(p.getName()); 203 } 204 for (Field field : c.getDeclaredFields()) { 205 if (field.isSynthetic()) continue; 206 final int modifiers = field.getModifiers(); 207 if (!Modifier.isPrivate(modifiers) || Modifier.isStatic(modifiers)) continue; 208 final String fieldName = field.getName(); 209 if ("class".equals(fieldName) || "metaClass".equals(fieldName)) continue; 210 if (fieldName.indexOf('$') == -1 && props.contains(fieldName)) properties.add(fieldName); 211 } 212 } 213 propertiesWithFields = properties.toArray(new String[properties.size()]); 214 } 215 216 private List<Class<?>> resolveAllClasses(Class<?> c) { 217 List<Class<?>> list = new ArrayList<Class<?>>(); 218 Class<?> currentClass = c; 219 while (currentClass != null) { 220 list.add(currentClass); 221 currentClass = currentClass.getSuperclass(); 222 } 223 Collections.reverse(list); 224 return list; 225 } 226 227 public Object getPropertyValue(String name) { 228 return getPropertyValue(name, false); 229 } 230 231 public Object getPropertyValue(String name, boolean onlyInstanceProperties) { 232 PropertyFetcher fetcher = resolveFetcher(name, onlyInstanceProperties); 233 return getPropertyValueWithFetcher(name, fetcher); 234 } 235 236 private Object getPropertyValueWithFetcher(String name, PropertyFetcher fetcher) { 237 if (fetcher != null) { 238 try { 239 return fetcher.get(callback); 240 } catch (Exception e) { 241 // ignore ? 242 // log.warn("Error fetching property's " + name + " value from class " + clazz.getName(), e); 243 } 244 } 245 return null; 246 } 247 248 public <T> T getStaticPropertyValue(String name, Class<T> c) { 249 PropertyFetcher fetcher = staticFetchers.get(name); 250 if (fetcher != null) { 251 Object v = getPropertyValueWithFetcher(name, fetcher); 252 return returnOnlyIfInstanceOf(v, c); 253 } 254 return null; 255 } 256 257 public <T> T getPropertyValue(String name, Class<T> c) { 258 return returnOnlyIfInstanceOf(getPropertyValue(name, false), c); 259 } 260 261 @SuppressWarnings("unchecked") 262 private <T> T returnOnlyIfInstanceOf(Object value, Class<T> type) { 263 if ((value != null) && (type == Object.class || GriffonClassUtils.isGroovyAssignableFrom(type, value.getClass()))) { 264 return (T) value; 265 } 266 267 return null; 268 } 269 270 private PropertyFetcher resolveFetcher(String name, boolean onlyInstanceProperties) { 271 PropertyFetcher fetcher = null; 272 if (!onlyInstanceProperties) { 273 fetcher = staticFetchers.get(name); 274 } 275 if (fetcher == null) { 276 fetcher = instanceFetchers.get(name); 277 } 278 return fetcher; 279 } 280 281 public Class<?> getPropertyType(String name) { 282 return getPropertyType(name, false); 283 } 284 285 public Class<?> getPropertyType(String name, boolean onlyInstanceProperties) { 286 PropertyFetcher fetcher = resolveFetcher(name, onlyInstanceProperties); 287 if (fetcher != null) { 288 return fetcher.getPropertyType(name); 289 } 290 return null; 291 } 292 293 public static interface ReferenceInstanceCallback { 294 public Object getReferenceInstance(); 295 } 296 297 static interface PropertyFetcher { 298 public Object get(ReferenceInstanceCallback callback) 299 throws IllegalArgumentException, IllegalAccessException, InvocationTargetException; 300 301 public Class<?> getPropertyType(String name); 302 } 303 304 static class GetterPropertyFetcher implements PropertyFetcher { 305 private final Method readMethod; 306 private final boolean staticMethod; 307 308 GetterPropertyFetcher(Method readMethod, boolean staticMethod) { 309 this.readMethod = readMethod; 310 this.staticMethod = staticMethod; 311 makeAccessible(readMethod); 312 } 313 314 public Object get(ReferenceInstanceCallback callback) 315 throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 316 if (staticMethod) { 317 return readMethod.invoke(null); 318 } 319 320 if (callback != null) { 321 return readMethod.invoke(callback.getReferenceInstance()); 322 } 323 324 return null; 325 } 326 327 public Class<?> getPropertyType(String name) { 328 return readMethod.getReturnType(); 329 } 330 } 331 332 static class FieldReaderFetcher implements PropertyFetcher { 333 private final Field field; 334 private final boolean staticField; 335 336 public FieldReaderFetcher(Field field, boolean staticField) { 337 this.field = field; 338 this.staticField = staticField; 339 makeAccessible(field); 340 } 341 342 public Object get(ReferenceInstanceCallback callback) 343 throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 344 if (staticField) { 345 return field.get(null); 346 } 347 348 if (callback != null) { 349 return field.get(callback.getReferenceInstance()); 350 } 351 352 return null; 353 } 354 355 public Class<?> getPropertyType(String name) { 356 return field.getType(); 357 } 358 } 359 360 private static void makeAccessible(AccessibleObject obj) { 361 if (!obj.isAccessible()) { 362 try { 363 obj.setAccessible(true); 364 } catch (SecurityException e) { 365 // skip 366 } 367 } 368 } 369 370 static interface FieldCallback { 371 void doWith(Field field) throws IllegalArgumentException, IllegalAccessException; 372 } 373 374 static interface MethodCallback { 375 void doWith(Method method) throws IllegalArgumentException, IllegalAccessException; 376 } 377 }`