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