ClassPropertyFetcher.java
001 /*
002  * Copyright 2008-2014 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.*;
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 2.0.0
035  */
036 public class ClassPropertyFetcher {
037     private final Class<?> clazz;
038     final Map<String, PropertyFetcher> staticFetchers = new LinkedHashMap<>();
039     final Map<String, PropertyFetcher> instanceFetchers = new LinkedHashMap<>();
040     private final ReferenceInstanceCallback callback;
041     private PropertyDescriptor[] propertyDescriptors;
042     private String[] propertiesWithFields;
043 
044     private static final Map<Class<?>, ClassPropertyFetcher> CACHED_CLASS_PROPERTY_FETCHERS = new ConcurrentHashMap<>();
045 
046     public static void clearClassPropertyFetcherCache() {
047         CACHED_CLASS_PROPERTY_FETCHERS.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 = CACHED_CLASS_PROPERTY_FETCHERS.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             CACHED_CLASS_PROPERTY_FETCHERS.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 methodthrows 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() && 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<>();
199         for (Class<?> c : allClasses) {
200             final List<String> props = new ArrayList<>();
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))
208                     continue;
209                 final String fieldName = field.getName();
210                 if ("class".equals(fieldName|| "metaClass".equals(fieldName))
211                     continue;
212                 if (fieldName.indexOf('$'== -&& props.contains(fieldName))
213                     properties.add(fieldName);
214             }
215         }
216         propertiesWithFields = properties.toArray(new String[properties.size()]);
217     }
218 
219     private List<Class<?>> resolveAllClasses(Class<?> c) {
220         List<Class<?>> list = new ArrayList<>();
221         Class<?> currentClass = c;
222         while (currentClass != null) {
223             list.add(currentClass);
224             currentClass = currentClass.getSuperclass();
225         }
226         Collections.reverse(list);
227         return list;
228     }
229 
230     public Object getPropertyValue(String name) {
231         return getPropertyValue(name, false);
232     }
233 
234     public Object getPropertyValue(String name, boolean onlyInstanceProperties) {
235         PropertyFetcher fetcher = resolveFetcher(name, onlyInstanceProperties);
236         return getPropertyValueWithFetcher(name, fetcher);
237     }
238 
239     private Object getPropertyValueWithFetcher(String name, PropertyFetcher fetcher) {
240         if (fetcher != null) {
241             try {
242                 return fetcher.get(callback);
243             catch (Exception e) {
244                 // ignore ?
245                 // log.warn("Error fetching property's " + name + " value from class " + clazz.getName(), e);
246             }
247         }
248         return null;
249     }
250 
251     public <T> T getStaticPropertyValue(String name, Class<T> c) {
252         PropertyFetcher fetcher = staticFetchers.get(name);
253         if (fetcher != null) {
254             Object v = getPropertyValueWithFetcher(name, fetcher);
255             return returnOnlyIfInstanceOf(v, c);
256         }
257         return null;
258     }
259 
260     public <T> T getPropertyValue(String name, Class<T> c) {
261         return returnOnlyIfInstanceOf(getPropertyValue(name, false), c);
262     }
263 
264     @SuppressWarnings("unchecked")
265     public <T> T returnOnlyIfInstanceOf(Object value, Class<T> type) {
266         if ((value != null&& (type == Object.class || type.isAssignableFrom(value.getClass()))) {
267             return (Tvalue;
268         }
269 
270         return null;
271     }
272 
273     private PropertyFetcher resolveFetcher(String name, boolean onlyInstanceProperties) {
274         PropertyFetcher fetcher = null;
275         if (!onlyInstanceProperties) {
276             fetcher = staticFetchers.get(name);
277         }
278         if (fetcher == null) {
279             fetcher = instanceFetchers.get(name);
280         }
281         return fetcher;
282     }
283 
284     public Class<?> getPropertyType(String name) {
285         return getPropertyType(name, false);
286     }
287 
288     public Class<?> getPropertyType(String name, boolean onlyInstanceProperties) {
289         PropertyFetcher fetcher = resolveFetcher(name, onlyInstanceProperties);
290         if (fetcher != null) {
291             return fetcher.getPropertyType(name);
292         }
293         return null;
294     }
295 
296     public static interface ReferenceInstanceCallback {
297         public Object getReferenceInstance();
298     }
299 
300     static interface PropertyFetcher {
301         public Object get(ReferenceInstanceCallback callback)
302             throws IllegalArgumentException, IllegalAccessException, InvocationTargetException;
303 
304         public Class<?> getPropertyType(String name);
305     }
306 
307     static class GetterPropertyFetcher implements PropertyFetcher {
308         private final Method readMethod;
309         private final boolean staticMethod;
310 
311         GetterPropertyFetcher(Method readMethod, boolean staticMethod) {
312             this.readMethod = readMethod;
313             this.staticMethod = staticMethod;
314             makeAccessible(readMethod);
315         }
316 
317         public Object get(ReferenceInstanceCallback callback)
318             throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
319             if (staticMethod) {
320                 return readMethod.invoke(null);
321             }
322 
323             if (callback != null) {
324                 return readMethod.invoke(callback.getReferenceInstance());
325             }
326 
327             return null;
328         }
329 
330         public Class<?> getPropertyType(String name) {
331             return readMethod.getReturnType();
332         }
333     }
334 
335     static class FieldReaderFetcher implements PropertyFetcher {
336         private final Field field;
337         private final boolean staticField;
338 
339         public FieldReaderFetcher(Field field, boolean staticField) {
340             this.field = field;
341             this.staticField = staticField;
342             makeAccessible(field);
343         }
344 
345         public Object get(ReferenceInstanceCallback callback)
346             throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
347             if (staticField) {
348                 return field.get(null);
349             }
350 
351             if (callback != null) {
352                 return field.get(callback.getReferenceInstance());
353             }
354 
355             return null;
356         }
357 
358         public Class<?> getPropertyType(String name) {
359             return field.getType();
360         }
361     }
362 
363     private static void makeAccessible(AccessibleObject obj) {
364         if (!obj.isAccessible()) {
365             try {
366                 obj.setAccessible(true);
367             catch (SecurityException e) {
368                 // skip
369             }
370         }
371     }
372 
373     static interface FieldCallback {
374         void doWith(Field fieldthrows IllegalArgumentException, IllegalAccessException;
375     }
376 
377     static interface MethodCallback {
378         void doWith(Method methodthrows IllegalArgumentException, IllegalAccessException;
379     }
380 }