001 /**
002 * GRANITE DATA SERVICES
003 * Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004 *
005 * This file is part of the Granite Data Services Platform.
006 *
007 * Granite Data Services is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * Granite Data Services is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015 * General Public License for more details.
016 *
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020 * USA, or see <http://www.gnu.org/licenses/>.
021 */
022 package org.granite.messaging.reflect;
023
024 import java.lang.annotation.Annotation;
025 import java.lang.annotation.ElementType;
026 import java.lang.annotation.Target;
027 import java.lang.reflect.Constructor;
028 import java.lang.reflect.Field;
029 import java.lang.reflect.InvocationTargetException;
030 import java.lang.reflect.Method;
031 import java.lang.reflect.Modifier;
032 import java.util.ArrayList;
033 import java.util.Collections;
034 import java.util.Comparator;
035 import java.util.List;
036 import java.util.concurrent.ConcurrentHashMap;
037 import java.util.concurrent.ConcurrentMap;
038
039 import org.granite.messaging.annotations.Exclude;
040 import org.granite.messaging.annotations.Include;
041 import org.granite.messaging.annotations.Serialized;
042
043 /**
044 * Reflection provider
045 *
046 * @author Franck WOLFF
047 */
048 public class Reflection {
049
050 protected static final int STATIC_TRANSIENT_MASK = Modifier.STATIC | Modifier.TRANSIENT;
051 protected static final int STATIC_PRIVATE_PROTECTED_MASK = Modifier.STATIC | Modifier.PRIVATE | Modifier.PROTECTED;
052 protected static final Property NULL_PROPERTY = new NullProperty();
053
054 protected final ClassLoader classLoader;
055 protected final BypassConstructorAllocator instanceFactory;
056 protected final Comparator<Property> lexicalPropertyComparator;
057
058 protected final ConcurrentMap<Class<?>, List<Property>> serializablePropertiesCache;
059 protected final ConcurrentMap<SinglePropertyKey, Property> singlePropertyCache;
060
061 public Reflection(ClassLoader classLoader) {
062 this(classLoader, null);
063 }
064
065 public Reflection(ClassLoader classLoader, BypassConstructorAllocator instanceFactory) {
066 this.classLoader = classLoader;
067
068 if (instanceFactory != null)
069 this.instanceFactory = instanceFactory;
070 else {
071 try {
072 this.instanceFactory = new SunBypassConstructorAllocator();
073 }
074 catch (Exception e) {
075 throw new RuntimeException("Could not instantiate BypassConstructorAllocator", e);
076 }
077 }
078
079 this.lexicalPropertyComparator = new Comparator<Property>() {
080 public int compare(Property p1, Property p2) {
081 return p1.getName().compareTo(p2.getName());
082 }
083 };
084
085 this.serializablePropertiesCache = new ConcurrentHashMap<Class<?>, List<Property>>();
086 this.singlePropertyCache = new ConcurrentHashMap<SinglePropertyKey, Property>();
087 }
088
089 public ClassLoader getClassLoader() {
090 return (classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader());
091 }
092
093 public Class<?> loadClass(String className) throws ClassNotFoundException {
094 return getClassLoader().loadClass(className);
095 }
096
097 public <T> T newInstance(Class<T> cls)
098 throws InstantiationException, IllegalAccessException, IllegalArgumentException,
099 InvocationTargetException, SecurityException, NoSuchMethodException {
100
101 try {
102 Constructor<T> constructor = cls.getConstructor();
103 return constructor.newInstance();
104 }
105 catch (NoSuchMethodException e) {
106 return instanceFactory.newInstance(cls);
107 }
108 }
109
110 @SuppressWarnings("unchecked")
111 public <T> T newInstance(String className)
112 throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException,
113 InvocationTargetException, SecurityException, NoSuchMethodException {
114
115 return newInstance((Class<T>)loadClass(className));
116 }
117
118 public Property findSerializableProperty(Class<?> cls, String name) throws SecurityException {
119 List<Property> properties = findSerializableProperties(cls);
120 for (Property property : properties) {
121 if (name.equals(property.getName()))
122 return property;
123 }
124 return null;
125 }
126
127 public List<Property> findSerializableProperties(Class<?> cls) throws SecurityException {
128 List<Property> serializableProperties = serializablePropertiesCache.get(cls);
129
130 if (serializableProperties == null) {
131 List<Class<?>> hierarchy = new ArrayList<Class<?>>();
132 for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass())
133 hierarchy.add(c);
134
135 serializableProperties = new ArrayList<Property>();
136 for (int i = hierarchy.size() - 1; i >= 0; i--) {
137 Class<?> c = hierarchy.get(i);
138 serializableProperties.addAll(findSerializableDeclaredProperties(c));
139 }
140 serializableProperties = Collections.unmodifiableList(serializableProperties);
141 List<Property> previous = serializablePropertiesCache.putIfAbsent(cls, serializableProperties);
142 if (previous != null)
143 serializableProperties = previous;
144 }
145
146 return serializableProperties;
147 }
148
149 protected FieldProperty newFieldProperty(Field field) {
150 return new SimpleFieldProperty(field);
151 }
152
153 protected MethodProperty newMethodProperty(Method getter, Method setter, String name) {
154 return new SimpleMethodProperty(getter, setter, name);
155 }
156
157 protected List<Property> findSerializableDeclaredProperties(Class<?> cls) throws SecurityException {
158
159 if (!isRegularClass(cls))
160 throw new IllegalArgumentException("Not a regular class: " + cls);
161
162 Field[] declaredFields = cls.getDeclaredFields();
163 List<Property> serializableProperties = new ArrayList<Property>(declaredFields.length);
164 for (Field field : declaredFields) {
165 int modifiers = field.getModifiers();
166 if ((modifiers & STATIC_TRANSIENT_MASK) == 0 && !field.isAnnotationPresent(Exclude.class)) {
167 field.setAccessible(true);
168 serializableProperties.add(newFieldProperty(field));
169 }
170 }
171
172 Method[] declaredMethods = cls.getDeclaredMethods();
173 for (Method method : declaredMethods) {
174 int modifiers = method.getModifiers();
175 if ((modifiers & STATIC_PRIVATE_PROTECTED_MASK) == 0 &&
176 method.isAnnotationPresent(Include.class) &&
177 method.getParameterTypes().length == 0 &&
178 method.getReturnType() != Void.TYPE) {
179
180 String name = method.getName();
181 if (name.startsWith("get")) {
182 if (name.length() <= 3)
183 continue;
184 name = name.substring(3, 4).toLowerCase() + name.substring(4);
185 }
186 else if (name.startsWith("is") &&
187 (method.getReturnType() == Boolean.class || method.getReturnType() == Boolean.TYPE)) {
188 if (name.length() <= 2)
189 continue;
190 name = name.substring(2, 3).toLowerCase() + name.substring(3);
191 }
192 else
193 continue;
194
195 serializableProperties.add(newMethodProperty(method, null, name));
196 }
197 }
198
199 Serialized serialized = cls.getAnnotation(Serialized.class);
200 if (serialized != null && serialized.propertiesOrder().length > 0) {
201 String[] value = serialized.propertiesOrder();
202
203 if (value.length != serializableProperties.size())
204 throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (bad length)");
205
206 for (int i = 0; i < value.length; i++) {
207 String propertyName = value[i];
208
209 boolean found = false;
210 for (int j = i; j < value.length; j++) {
211 Property property = serializableProperties.get(j);
212 if (property.getName().equals(propertyName)) {
213 found = true;
214 if (i != j) {
215 serializableProperties.set(j, serializableProperties.get(i));
216 serializableProperties.set(i, property);
217 }
218 break;
219 }
220 }
221 if (!found)
222 throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (\"" + propertyName + "\" isn't a property name)");
223 }
224 }
225 else
226 Collections.sort(serializableProperties, lexicalPropertyComparator);
227
228 return serializableProperties;
229 }
230
231 public boolean isRegularClass(Class<?> cls) {
232 return cls != Class.class && !cls.isAnnotation() && !cls.isArray() &&
233 !cls.isEnum() && !cls.isInterface() && !cls.isPrimitive();
234 }
235
236 public Property findProperty(Class<?> cls, String name, Class<?> type) {
237 NameTypePropertyKey key = new NameTypePropertyKey(cls, name, type);
238
239 Property property = singlePropertyCache.get(key);
240
241 if (property == null) {
242 Field field = null;
243
244 for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass()) {
245 try {
246 field = c.getDeclaredField(name);
247 }
248 catch (Exception e) {
249 continue;
250 }
251
252 if (field.getType() != type)
253 continue;
254
255 field.setAccessible(true);
256 break;
257 }
258
259 if (field == null)
260 property = NULL_PROPERTY;
261 else
262 property = newFieldProperty(field);
263
264 Property previous = singlePropertyCache.putIfAbsent(key, property);
265 if (previous != null)
266 property = previous;
267 }
268
269 return (property != NULL_PROPERTY ? property : null);
270 }
271
272 public Property findProperty(Class<?> cls, Class<? extends Annotation> annotationClass) {
273 AnnotatedPropertyKey key = new AnnotatedPropertyKey(cls, annotationClass);
274
275 Property property = singlePropertyCache.get(key);
276
277 if (property == null) {
278 boolean searchFields = false;
279 boolean searchMethods = false;
280
281 if (!annotationClass.isAnnotationPresent(Target.class))
282 searchFields = searchMethods = true;
283 else {
284 Target target = annotationClass.getAnnotation(Target.class);
285 for (ElementType targetType : target.value()) {
286 if (targetType == ElementType.FIELD)
287 searchFields = true;
288 else if (targetType == ElementType.METHOD)
289 searchMethods = true;
290 }
291 }
292
293 if (searchFields == false && searchMethods == false)
294 return null;
295
296 final int modifierMask = Modifier.PUBLIC | Modifier.STATIC;
297
298 classLoop:
299 for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass()) {
300 if (searchMethods) {
301 for (Method method : c.getDeclaredMethods()) {
302 if ((method.getModifiers() & modifierMask) != Modifier.PUBLIC ||
303 !method.isAnnotationPresent(annotationClass))
304 continue;
305
306 if (method.getReturnType() == Void.TYPE) {
307 if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) {
308 String name = method.getName().substring(3);
309
310 if (name.length() == 0)
311 continue;
312
313 Method getter = null;
314 try {
315 getter = cls.getMethod("get" + name);
316 }
317 catch (Exception e) {
318 try {
319 getter = cls.getMethod("is" + name);
320 }
321 catch (Exception f) {
322 }
323 }
324
325 if (getter != null && (getter.getModifiers() & Modifier.STATIC) != 0 &&
326 getter.getReturnType() != method.getParameterTypes()[0])
327 getter = null;
328
329 if (getter == null)
330 continue;
331
332 name = name.substring(0, 1).toLowerCase() + name.substring(1);
333 property = newMethodProperty(getter, method, name);
334 break classLoop;
335 }
336 }
337 else if (method.getParameterTypes().length == 0 && (method.getName().startsWith("get") || method.getName().startsWith("is"))) {
338 String name;
339 if (method.getName().startsWith("get"))
340 name = method.getName().substring(3);
341 else
342 name = method.getName().substring(2);
343
344 if (name.length() == 0)
345 continue;
346
347 Method setter = null;
348 try {
349 setter = cls.getMethod("set" + name);
350 }
351 catch (Exception e) {
352 }
353
354 if (setter != null && (setter.getModifiers() & Modifier.STATIC) != 0 &&
355 method.getReturnType() != setter.getParameterTypes()[0])
356 setter = null;
357
358 name = name.substring(0, 1).toLowerCase() + name.substring(1);
359 property = newMethodProperty(method, setter, name);
360 break classLoop;
361 }
362 }
363 }
364
365 if (searchFields) {
366 for (Field field : c.getDeclaredFields()) {
367 if ((field.getModifiers() & Modifier.STATIC) == 0 && field.isAnnotationPresent(annotationClass)) {
368 property = newFieldProperty(field);
369 break classLoop;
370 }
371 }
372 }
373 }
374
375 if (property == null)
376 property = NULL_PROPERTY;
377
378 Property previous = singlePropertyCache.putIfAbsent(key, property);
379 if (previous != null)
380 property = previous;
381 }
382
383 return (property != NULL_PROPERTY ? property : null);
384 }
385
386 protected static interface SinglePropertyKey {
387 }
388
389 protected static class AnnotatedPropertyKey implements SinglePropertyKey {
390
391 private final Class<?> cls;
392 private final Class<? extends Annotation> annotationClass;
393
394 public AnnotatedPropertyKey(Class<?> cls, Class<? extends Annotation> annotationClass) {
395 this.cls = cls;
396 this.annotationClass = annotationClass;
397 }
398
399 public Class<?> getCls() {
400 return cls;
401 }
402
403 public Class<? extends Annotation> getAnnotationClass() {
404 return annotationClass;
405 }
406
407 @Override
408 public int hashCode() {
409 return cls.hashCode() + annotationClass.hashCode();
410 }
411
412 @Override
413 public boolean equals(Object obj) {
414 if (obj == this)
415 return true;
416 if (!(obj instanceof AnnotatedPropertyKey))
417 return false;
418 AnnotatedPropertyKey key = (AnnotatedPropertyKey)obj;
419 return cls.equals(key.cls) && annotationClass.equals(key.annotationClass);
420 }
421 }
422
423 protected static class NameTypePropertyKey implements SinglePropertyKey {
424
425 private final Class<?> cls;
426 private final String name;
427 private final Class<?> type;
428
429 public NameTypePropertyKey(Class<?> cls, String name, Class<?> type) {
430 this.cls = cls;
431 this.name = name;
432 this.type = type;
433 }
434
435 public Class<?> getCls() {
436 return cls;
437 }
438
439 public String getName() {
440 return name;
441 }
442
443 public Class<?> getType() {
444 return type;
445 }
446
447 @Override
448 public int hashCode() {
449 return cls.hashCode() + name.hashCode() + type.hashCode();
450 }
451
452 @Override
453 public boolean equals(Object obj) {
454 if (obj == this)
455 return true;
456 if (!(obj instanceof NameTypePropertyKey))
457 return false;
458 NameTypePropertyKey key = (NameTypePropertyKey)obj;
459 return cls.equals(key.cls) && name.equals(key.name) && type.equals(key.type);
460 }
461 }
462 }
463