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 method) throws 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() > 3 && 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('$') == -1 && 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 (T) value;
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 field) throws IllegalArgumentException, IllegalAccessException;
375 }
376
377 static interface MethodCallback {
378 void doWith(Method method) throws IllegalArgumentException, IllegalAccessException;
379 }
380 }
|