ClassPropertyFetcher.java
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 methodthrows 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() && 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('$'== -&& 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 (Tvalue;
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 fieldthrows IllegalArgumentException, IllegalAccessException;
383     }
384 
385     static interface MethodCallback {
386         void doWith(Method methodthrows IllegalArgumentException, IllegalAccessException;
387     }
388 }