001 /*
002 * Copyright 2008-2016 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 }
|