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<?>, ClassDescriptor> descriptorCache;
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.descriptorCache = new ConcurrentHashMap<Class<?>, ClassDescriptor>();
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 BypassConstructorAllocator getInstanceFactory() {
094                return instanceFactory;
095        }
096
097        public Comparator<Property> getLexicalPropertyComparator() {
098                return lexicalPropertyComparator;
099        }
100
101        public Class<?> loadClass(String className) throws ClassNotFoundException {
102                return getClassLoader().loadClass(className);
103        }
104        
105        @SuppressWarnings("unchecked")
106        public <T> T newInstance(Class<T> cls)
107                throws InstantiationException, IllegalAccessException, IllegalArgumentException,
108                InvocationTargetException, SecurityException, NoSuchMethodException {
109                
110                ClassDescriptor desc = descriptorCache.get(cls);
111                if (desc != null)
112                        return (T)desc.newInstance();
113            
114                try {
115                Constructor<T> constructor = cls.getConstructor();
116                return constructor.newInstance();
117            }
118            catch (NoSuchMethodException e) {
119                return (T)instanceFactory.newInstantiator(cls).newInstance();
120            }
121        }
122        
123        @SuppressWarnings("unchecked")
124        public <T> T newInstance(String className)
125                throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException,
126                InvocationTargetException, SecurityException, NoSuchMethodException {
127                
128                return newInstance((Class<T>)loadClass(className));
129        }
130        
131        public Property findSerializableProperty(Class<?> cls, String name) throws SecurityException {
132                List<Property> properties = findSerializableProperties(cls);
133                for (Property property : properties) {
134                        if (name.equals(property.getName()))
135                                return property;
136                }
137                return null;
138        }
139        
140        public ClassDescriptor getDescriptor(Class<?> cls) {
141                if (cls == null || cls == Object.class || !isRegularClass(cls))
142                        return null;
143
144                ClassDescriptor descriptor = descriptorCache.get(cls);
145                if (descriptor == null) {
146                        descriptor = new ClassDescriptor(this, cls);
147                        ClassDescriptor previousDescriptor = descriptorCache.putIfAbsent(cls, descriptor);
148                        if (previousDescriptor != null)
149                                descriptor = previousDescriptor;
150                }
151                return descriptor;
152        }
153        
154        public List<Property> findSerializableProperties(Class<?> cls) throws SecurityException {
155                ClassDescriptor descriptor = getDescriptor(cls);
156                if (descriptor == null)
157                        return Collections.emptyList();
158                return descriptor.getInheritedSerializableProperties();
159        }
160        
161        protected FieldProperty newFieldProperty(Field field) {
162                return new SimpleFieldProperty(field);
163        }
164        
165        protected MethodProperty newMethodProperty(Method getter, Method setter, String name) {
166                return new SimpleMethodProperty(getter, setter, name);
167        }
168
169        protected List<Property> findSerializableDeclaredProperties(Class<?> cls) throws SecurityException {
170                
171                if (!isRegularClass(cls))
172                        throw new IllegalArgumentException("Not a regular class: " + cls);
173                
174                Field[] declaredFields = cls.getDeclaredFields();
175                List<Property> serializableProperties = new ArrayList<Property>(declaredFields.length);
176                for (Field field : declaredFields) {
177                        int modifiers = field.getModifiers();
178                        if ((modifiers & STATIC_TRANSIENT_MASK) == 0 && !field.isAnnotationPresent(Exclude.class)) {
179                                field.setAccessible(true);
180                                serializableProperties.add(newFieldProperty(field));
181                        }
182                }
183                
184                Method[] declaredMethods = cls.getDeclaredMethods();
185                for (Method method : declaredMethods) {
186                        int modifiers = method.getModifiers();
187                        if ((modifiers & STATIC_PRIVATE_PROTECTED_MASK) == 0 &&
188                                method.isAnnotationPresent(Include.class) &&
189                                method.getParameterTypes().length == 0 &&
190                                method.getReturnType() != Void.TYPE) {
191                                
192                                String name = method.getName();
193                                if (name.startsWith("get")) {
194                                        if (name.length() <= 3)
195                                                continue;
196                                        name = name.substring(3, 4).toLowerCase() + name.substring(4);
197                                }
198                                else if (name.startsWith("is") &&
199                                        (method.getReturnType() == Boolean.class || method.getReturnType() == Boolean.TYPE)) {
200                                        if (name.length() <= 2)
201                                                continue;
202                                        name = name.substring(2, 3).toLowerCase() + name.substring(3);
203                                }
204                                else
205                                        continue;
206                                
207                                serializableProperties.add(newMethodProperty(method, null, name));
208                        }
209                }
210                
211                Serialized serialized = cls.getAnnotation(Serialized.class);
212                if (serialized != null && serialized.propertiesOrder().length > 0) {
213                        String[] value = serialized.propertiesOrder();
214                        
215                        if (value.length != serializableProperties.size())
216                                throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (bad length)");
217                        
218                        for (int i = 0; i < value.length; i++) {
219                                String propertyName = value[i];
220                                
221                                boolean found = false;
222                                for (int j = i; j < value.length; j++) {
223                                        Property property = serializableProperties.get(j);
224                                        if (property.getName().equals(propertyName)) {
225                                                found = true;
226                                                if (i != j) {
227                                                        serializableProperties.set(j, serializableProperties.get(i));
228                                                        serializableProperties.set(i, property);
229                                                }
230                                                break;
231                                        }
232                                }
233                                if (!found)
234                                        throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (\"" + propertyName + "\" isn't a property name)");
235                        }
236                }
237                else
238                        Collections.sort(serializableProperties, lexicalPropertyComparator);
239                
240                return serializableProperties;
241        }
242        
243        public boolean isRegularClass(Class<?> cls) {
244                return cls != Class.class && !cls.isAnnotation() && !cls.isArray() &&
245                        !cls.isEnum() && !cls.isInterface() && !cls.isPrimitive();
246        }
247        
248        public Property findProperty(Class<?> cls, String name, Class<?> type) {
249                NameTypePropertyKey key = new NameTypePropertyKey(cls, name, type);
250                
251                Property property = singlePropertyCache.get(key);
252                
253                if (property == null) {
254                        Field field = null;
255                        
256                        for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass()) {
257                                try {
258                                        field = c.getDeclaredField(name);
259                                }
260                                catch (NoSuchFieldException e) {
261                                        continue;
262                                }
263                                
264                                if (field.getType() != type)
265                                        continue;
266                                
267                                field.setAccessible(true);
268                                break;
269                        }
270                        
271                        if (field == null)
272                                property = NULL_PROPERTY;
273                        else
274                                property = newFieldProperty(field);
275                        
276                        Property previous = singlePropertyCache.putIfAbsent(key, property);
277                        if (previous != null)
278                                property = previous;
279                }
280                
281                return (property != NULL_PROPERTY ? property : null);
282        }
283        
284        public Property findProperty(Class<?> cls, Class<? extends Annotation> annotationClass) {
285                AnnotatedPropertyKey key = new AnnotatedPropertyKey(cls, annotationClass);
286                
287                Property property = singlePropertyCache.get(key);
288                
289                if (property == null) {
290                        boolean searchFields = false;
291                        boolean searchMethods = false;
292                        
293                        if (!annotationClass.isAnnotationPresent(Target.class))
294                                searchFields = searchMethods = true;
295                        else {
296                                Target target = annotationClass.getAnnotation(Target.class);
297                                for (ElementType targetType : target.value()) {
298                                        if (targetType == ElementType.FIELD)
299                                                searchFields = true;
300                                        else if (targetType == ElementType.METHOD)
301                                                searchMethods = true;
302                                }
303                        }
304                        
305                        if (searchFields == false && searchMethods == false)
306                                return null;
307                        
308                        final int modifierMask = Modifier.PUBLIC | Modifier.STATIC;
309                        
310                        classLoop:
311                        for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass()) {
312                                if (searchMethods) {
313                                        for (Method method : c.getDeclaredMethods()) {
314                                                if ((method.getModifiers() & modifierMask) != Modifier.PUBLIC ||
315                                                        !method.isAnnotationPresent(annotationClass))
316                                                        continue;
317                                                
318                                                if (method.getReturnType() == Void.TYPE) {
319                                                        if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) {
320                                                                String name = method.getName().substring(3);
321                                                                
322                                                                if (name.length() == 0)
323                                                                        continue;
324                                                                
325                                                                Method getter = null;
326                                                                try {
327                                                                        getter = cls.getMethod("get" + name);
328                                                                }
329                                                                catch (NoSuchMethodException e) {
330                                                                        try {
331                                                                                getter = cls.getMethod("is" + name);
332                                                                        }
333                                                                        catch (Exception f) {
334                                                                        }
335                                                                }
336                                                                
337                                                                if (getter != null && (getter.getModifiers() & Modifier.STATIC) != 0 &&
338                                                                        getter.getReturnType() != method.getParameterTypes()[0])
339                                                                        getter = null;
340                                                                
341                                                                if (getter == null)
342                                                                        continue;
343                                                                
344                                                                name = name.substring(0, 1).toLowerCase() + name.substring(1);
345                                                                property = newMethodProperty(getter, method, name);
346                                                                break classLoop;
347                                                        }
348                                                }
349                                                else if (method.getParameterTypes().length == 0 && (method.getName().startsWith("get") || method.getName().startsWith("is"))) {
350                                                        String name;
351                                                        if (method.getName().startsWith("get"))
352                                                                name = method.getName().substring(3);
353                                                        else
354                                                                name = method.getName().substring(2);
355                                                        
356                                                        if (name.length() == 0)
357                                                                continue;
358                                                        
359                                                        Method setter = null;
360                                                        try {
361                                                                setter = cls.getMethod("set" + name);
362                                                        }
363                                                        catch (NoSuchMethodException e) {
364                                                        }
365                                                        
366                                                        if (setter != null && (setter.getModifiers() & Modifier.STATIC) != 0 &&
367                                                                method.getReturnType() != setter.getParameterTypes()[0])
368                                                                setter = null;
369                                                        
370                                                        name = name.substring(0, 1).toLowerCase() + name.substring(1);
371                                                        property = newMethodProperty(method, setter, name);
372                                                        break classLoop;
373                                                }
374                                        }
375                                }
376                                
377                                if (searchFields) {
378                                        for (Field field : c.getDeclaredFields()) {
379                                                if ((field.getModifiers() & Modifier.STATIC) == 0 && field.isAnnotationPresent(annotationClass)) {
380                                                        property = newFieldProperty(field);
381                                                        break classLoop;
382                                                }
383                                        }
384                                }
385                        }
386                        
387                        if (property == null)
388                                property = NULL_PROPERTY;
389                        
390                        Property previous = singlePropertyCache.putIfAbsent(key, property);
391                        if (previous != null)
392                                property = previous;
393                }
394                
395                return (property != NULL_PROPERTY ? property : null);
396        }
397        
398        protected static abstract class SinglePropertyKey {
399
400                protected final Class<?> cls;
401                
402                public SinglePropertyKey(Class<?> cls) {
403                        this.cls = cls;
404                }
405        }
406        
407        protected static class AnnotatedPropertyKey extends SinglePropertyKey {
408                
409                private final Class<? extends Annotation> annotationClass;
410                
411                public AnnotatedPropertyKey(Class<?> cls, Class<? extends Annotation> annotationClass) {
412                        super(cls);
413                        this.annotationClass = annotationClass;
414                }
415
416                public Class<?> getCls() {
417                        return cls;
418                }
419
420                public Class<? extends Annotation> getAnnotationClass() {
421                        return annotationClass;
422                }
423
424                @Override
425                public int hashCode() {
426                        return (31 * cls.hashCode()) + annotationClass.hashCode();
427                }
428
429                @Override
430                public boolean equals(Object obj) {
431                        if (obj == this)
432                                return true;
433                        if (!(obj instanceof AnnotatedPropertyKey))
434                                return false;
435                        AnnotatedPropertyKey key = (AnnotatedPropertyKey)obj;
436                        return cls.equals(key.cls) && annotationClass.equals(key.annotationClass);
437                }
438        }
439        
440        protected static class NameTypePropertyKey extends SinglePropertyKey {
441                
442                private final String name;
443                private final Class<?> type;
444
445                public NameTypePropertyKey(Class<?> cls, String name, Class<?> type) {
446                        super(cls);
447                        this.name = name;
448                        this.type = type;
449                }
450
451                public Class<?> getCls() {
452                        return cls;
453                }
454
455                public String getName() {
456                        return name;
457                }
458
459                public Class<?> getType() {
460                        return type;
461                }
462
463                @Override
464                public int hashCode() {
465                        return (31 * (31 * cls.hashCode()) + name.hashCode()) + type.hashCode();
466                }
467
468                @Override
469                public boolean equals(Object obj) {
470                        if (obj == this)
471                                return true;
472                        if (!(obj instanceof NameTypePropertyKey))
473                                return false;
474                        NameTypePropertyKey key = (NameTypePropertyKey)obj;
475                        return cls.equals(key.cls) && name.equals(key.name) && type.equals(key.type);
476                }
477        }
478}
479