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 method) throws 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() > 3 && 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('$') == -1 && 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 (T) value;
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 field) throws IllegalArgumentException, IllegalAccessException;
385 }
386
387 static interface MethodCallback {
388 void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
389 }
390 }
|