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