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