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