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 */
022package org.granite.messaging.reflect;
023
024import java.lang.annotation.Annotation;
025import java.lang.annotation.ElementType;
026import java.lang.annotation.Target;
027import java.lang.reflect.Constructor;
028import java.lang.reflect.Field;
029import java.lang.reflect.InvocationTargetException;
030import java.lang.reflect.Method;
031import java.lang.reflect.Modifier;
032import java.util.ArrayList;
033import java.util.Collections;
034import java.util.Comparator;
035import java.util.List;
036import java.util.concurrent.ConcurrentHashMap;
037import java.util.concurrent.ConcurrentMap;
038
039import org.granite.messaging.annotations.Exclude;
040import org.granite.messaging.annotations.Include;
041import org.granite.messaging.annotations.Serialized;
042
043/**
044 * Reflection provider
045 *
046 * @author Franck WOLFF
047 */
048public 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