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