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