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
023package org.granite.util;
024
025import java.lang.annotation.Annotation;
026import java.lang.reflect.AnnotatedElement;
027import java.lang.reflect.Array;
028import java.lang.reflect.Constructor;
029import java.lang.reflect.Field;
030import java.lang.reflect.GenericArrayType;
031import java.lang.reflect.InvocationTargetException;
032import java.lang.reflect.Member;
033import java.lang.reflect.Method;
034import java.lang.reflect.Modifier;
035import java.lang.reflect.ParameterizedType;
036import java.lang.reflect.Type;
037import java.lang.reflect.TypeVariable;
038import java.lang.reflect.WildcardType;
039import java.net.MalformedURLException;
040import java.net.URL;
041import java.util.ArrayList;
042import java.util.Collections;
043import java.util.HashMap;
044import java.util.List;
045import java.util.Map;
046import java.util.Map.Entry;
047import java.util.Set;
048
049/**
050 * @author Franck WOLFF
051 */
052public abstract class ClassUtil {
053
054    public static Object newInstance(String type)
055        throws ClassNotFoundException, InstantiationException, IllegalAccessException {
056        return forName(type).newInstance();
057    }
058
059    public static <T> T newInstance(String type, Class<T> cast)
060        throws ClassNotFoundException, InstantiationException, IllegalAccessException {
061        return forName(type, cast).newInstance();
062    }
063
064    public static Object newInstance(String type, Class<?>[] argsClass, Object[] argsValues)
065        throws ClassNotFoundException, InstantiationException, IllegalAccessException {
066        return newInstance(forName(type), argsClass, argsValues);
067    }
068
069    @SuppressWarnings("unchecked")
070    public static <T> T newInstance(Class<?> type, Class<T> cast)
071        throws InstantiationException, IllegalAccessException {
072        return (T)type.newInstance();
073    }
074
075    public static <T> T newInstance(Class<T> type, Class<?>[] argsClass, Object[] argsValues)
076        throws InstantiationException, IllegalAccessException {
077        T instance = null;
078        try {
079            Constructor<T> constructorDef = type.getConstructor(argsClass);
080            instance = constructorDef.newInstance(argsValues);
081        } catch (SecurityException e) {
082            throw new InstantiationException(e.getMessage());
083        } catch (NoSuchMethodException e) {
084            throw new InstantiationException(e.getMessage());
085        } catch (IllegalArgumentException e) {
086            throw new InstantiationException(e.getMessage());
087        } catch (InvocationTargetException e) {
088            throw new InstantiationException(e.getMessage());
089        }
090        return instance;
091    }
092
093    public static Class<?> forName(String type) throws ClassNotFoundException {
094        try {
095                return ClassUtil.class.getClassLoader().loadClass(type);
096        }
097        catch (ClassNotFoundException e) {
098                return Thread.currentThread().getContextClassLoader().loadClass(type);
099        }
100    }
101
102    @SuppressWarnings("unchecked")
103    public static <T> Class<T> forName(String type, Class<T> cast) throws ClassNotFoundException {
104        try {
105                return (Class<T>)ClassUtil.class.getClassLoader().loadClass(type);
106        }
107        catch (ClassNotFoundException e) {
108                return (Class<T>)Thread.currentThread().getContextClassLoader().loadClass(type);
109        }
110    }
111
112    public static Constructor<?> getConstructor(String type, Class<?>[] paramTypes)
113        throws ClassNotFoundException, NoSuchMethodException {
114        return getConstructor(forName(type), paramTypes);
115    }
116
117    public static <T> Constructor<T> getConstructor(Class<T> type, Class<?>[] paramTypes)
118        throws NoSuchMethodException {
119        return type.getConstructor(paramTypes);
120    }
121
122    public static <T> List<T> emptyList(Class<T> type) {
123        return Collections.emptyList();
124    }
125
126    public static <T> Set<T> emptySet(Class<T> type) {
127        return Collections.emptySet();
128    }
129
130    public static <T, U> Map<T, U> emptyMap(Class<T> keyType, Class<U> valueType) {
131        return Collections.emptyMap();
132    }
133
134    public static boolean isPrimitive(Type type) {
135        return type instanceof Class<?> && ((Class<?>)type).isPrimitive();
136    }
137
138    public static Class<?> classOfType(Type type) {
139        if (type instanceof Class<?>)
140            return (Class<?>)type;
141        if (type instanceof ParameterizedType)
142            return (Class<?>)((ParameterizedType)type).getRawType();
143        if (type instanceof WildcardType) {
144            // Forget lower bounds and only deal with first upper bound...
145            Type[] ubs = ((WildcardType)type).getUpperBounds();
146            if (ubs.length > 0)
147                return classOfType(ubs[0]);
148        }
149        if (type instanceof GenericArrayType) {
150            Class<?> ct = classOfType(((GenericArrayType)type).getGenericComponentType());
151            return (ct != null ? Array.newInstance(ct, 0).getClass() : Object[].class);
152        }
153        if (type instanceof TypeVariable<?>) {
154            // Only deal with first (upper) bound...
155            Type[] ubs = ((TypeVariable<?>)type).getBounds();
156            if (ubs.length > 0)
157                return classOfType(ubs[0]);
158        }
159        // Should never happen...
160        return Object.class;
161    }
162    
163    public static Type getBoundType(TypeVariable<?> typeVariable) {
164        Type[] ubs = typeVariable.getBounds();
165        if (ubs.length > 0)
166                return ubs[0];
167        
168        // should never happen...
169        if (typeVariable.getGenericDeclaration() instanceof Type)
170                return (Type)typeVariable.getGenericDeclaration();
171        return typeVariable;
172    }
173
174    public static String getPackageName(Class<?> clazz) {
175        return clazz.getPackage() != null ? clazz.getPackage().getName() : "";
176    }
177    
178    public static PropertyDescriptor[] getProperties(Class<?> clazz) {
179        try {
180                PropertyDescriptor[] properties = new BeanInfo(clazz).getPropertyDescriptors();
181                Field[] fields = clazz.getDeclaredFields();
182                for (Field field : fields) {
183                        if (Boolean.class.equals(field.getType())) {
184                                boolean found = false;
185                                for (PropertyDescriptor property : properties) {
186                                        if (property.getName().equals(field.getName())) {
187                                                found = true;
188                                                if (property.getReadMethod() == null) {
189                                                        try {
190                                                                Method readMethod = clazz.getDeclaredMethod(getIsMethodName(field.getName()));
191                                                                if (Modifier.isPublic(readMethod.getModifiers()) && !Modifier.isStatic(readMethod.getModifiers()))
192                                                                        property.setReadMethod(readMethod);
193                                                        }
194                                                        catch (NoSuchMethodException e) {
195                                                        }
196                                                }
197                                                break;
198                                        }
199                                }
200                                if (!found) {
201                                                try {
202                                                        Method readMethod = clazz.getDeclaredMethod(getIsMethodName(field.getName()));
203                                                        if (Modifier.isPublic(readMethod.getModifiers()) && !Modifier.isStatic(readMethod.getModifiers())) {
204                                                                PropertyDescriptor[] propertiesTmp = new PropertyDescriptor[properties.length + 1];
205                                                                System.arraycopy(properties, 0, propertiesTmp, 0, properties.length);
206                                                                propertiesTmp[properties.length] = new PropertyDescriptor(field.getName(), readMethod, null);
207                                                                properties = propertiesTmp;
208                                                        }
209                                                }
210                                                catch (NoSuchMethodException e) {
211                                                }
212                                }
213                        }
214                }
215            return properties;
216        } catch (Exception e) {
217            throw new RuntimeException("Could not introspect properties of class: " + clazz, e);
218        }
219    }
220    
221    private static String getIsMethodName(String name) {
222        return "is" + name.substring(0, 1).toUpperCase() + name.substring(1);
223    }
224   
225    public static ClassLoader getClassLoader(Class<?> clazz) {
226        return (clazz.getClassLoader() != null ? clazz.getClassLoader() : ClassLoader.getSystemClassLoader());
227    }
228
229    public static URL findResource(Class<?> clazz) {
230        while (clazz.isArray())
231            clazz = clazz.getComponentType();
232        if (clazz.isPrimitive())
233            return null;
234        URL url = getClassLoader(clazz).getResource(toResourceName(clazz));
235        String path = url.toString();
236        if (path.indexOf(' ') != -1) {
237                try {
238                                url = new URL(path.replace(" ", "%20"));
239                        } catch (MalformedURLException e) {
240                                // should never happen...
241                        }
242        }
243        return url;
244    }
245
246    public static String toResourceName(Class<?> clazz) {
247        return clazz.getName().replace('.', '/').concat(".class");
248    }
249    
250    public static String getMethodSignature(Method method) {
251        StringBuilder sb = new StringBuilder();
252        sb.append(method.getName()).append('(');
253        Class<?>[] params = method.getParameterTypes();
254        for (int i = 0; i < params.length; i++) {
255                if (i > 0)
256                        sb.append(',');
257                sb.append(getTypeSignature(params[i]));
258        }
259        sb.append(')');
260        return sb.toString();
261    }
262        
263    public static String getTypeSignature(Class<?> type) {
264                if (type.isArray()) {
265                    try {
266                                int dimensions = 1;
267                                Class<?> clazz = type.getComponentType();
268                                while (clazz.isArray()) {
269                                        dimensions++;
270                                        clazz = clazz.getComponentType();
271                                }
272                                
273                                StringBuffer sb = new StringBuffer(clazz.getName());
274                                while (dimensions-- > 0)
275                                    sb.append("[]");
276                                return sb.toString();
277                    } catch (Throwable e) {
278                        // fallback...
279                    }
280                }
281                return type.getName();
282        }
283    
284    public static Method getMethod(Class<?> clazz, String signature) throws NoSuchMethodException {
285        signature = removeSpaces(signature);
286                
287        if (!signature.endsWith(")"))
288                        signature += "()";
289                
290                for (Method method : clazz.getMethods()) {
291                        if (signature.equals(getMethodSignature(method)))
292                                return method;
293                }
294                
295                throw new NoSuchMethodException("Could not find method: " + signature + " in class: " + clazz);
296    }
297        
298        public static String removeSpaces(String s) {
299                if (s == null)
300                        return null;
301        String[] tokens = s.split("\\s", -1);
302        if (tokens.length == 0)
303                return "";
304        if (tokens.length == 1)
305                return tokens[0];
306        StringBuilder sb = new StringBuilder();
307        for (String token : tokens)
308                sb.append(token);
309        return sb.toString();
310    }
311        
312    public static String decapitalize(String name) {
313
314        if (name == null)
315            return null;
316        // The rule for decapitalize is that:
317        // If the first letter of the string is Upper Case, make it lower case
318        // UNLESS the second letter of the string is also Upper Case, in which case no
319        // changes are made.
320        if (name.length() == 0 || (name.length() > 1 && Character.isUpperCase(name.charAt(1)))) {
321            return name;
322        }
323        
324        char[] chars = name.toCharArray();
325        chars[0] = Character.toLowerCase(chars[0]);
326        return new String(chars);
327    }
328    
329    public static boolean isAnnotationPresent(AnnotatedElement elmt, Class<? extends Annotation> annotationClass) {
330        return getAnnotation(elmt, annotationClass) != null;
331    }
332    
333    public static <T extends Annotation> DeclaredAnnotation<T> getAnnotation(AnnotatedElement elmt, Class<T> annotationClass) {
334        T annotation = elmt.getAnnotation(annotationClass);
335        
336        if (annotation != null) {
337                Class<?> declaringClass = (elmt instanceof Member ? ((Member)elmt).getDeclaringClass() : (Class<?>)elmt);
338                return new DeclaredAnnotation<T>(annotation, elmt, declaringClass);
339        }
340        
341        if (elmt instanceof Field)
342                return null;
343        
344        if (elmt instanceof Method) {
345                Method m = (Method)elmt;
346                return getMethodAnnotation(m.getDeclaringClass(), m.getName(), m.getParameterTypes(), annotationClass);
347        }
348        
349        if (elmt instanceof Constructor) {
350                Constructor<?> c = (Constructor<?>)elmt;
351                return getConstructorAnnotation(c.getDeclaringClass(), annotationClass);
352        }
353        
354        if (elmt instanceof Class) {
355                Class<?> c = (Class<?>)elmt;
356                return getClassAnnotation(c.getDeclaringClass(), annotationClass);
357        }
358        
359        throw new RuntimeException("Unsupported annotated element: " + elmt);
360    }
361    
362    public static <T extends Annotation> DeclaredAnnotation<T> getMethodAnnotation(Class<?> clazz, String name, Class<?>[] parameterTypes, Class<T> annotationClass) {
363        DeclaredAnnotation<T> declaredAnnotation = null;
364        
365        try {
366                Method method = clazz.getDeclaredMethod(name, parameterTypes);
367                T annotation = clazz.getDeclaredMethod(name, parameterTypes).getAnnotation(annotationClass);
368                if (annotation != null)
369                        declaredAnnotation = new DeclaredAnnotation<T>(annotation, method, clazz);
370        }
371        catch (NoSuchMethodException e) {
372                // fallback...
373        }
374        
375        if (declaredAnnotation == null && clazz.getSuperclass() != null)
376                declaredAnnotation = getMethodAnnotation(clazz.getSuperclass(), name, parameterTypes, annotationClass);
377        
378        if (declaredAnnotation == null) {
379                for (Class<?> interfaze : clazz.getInterfaces()) {
380                        declaredAnnotation = getMethodAnnotation(interfaze, name, parameterTypes, annotationClass);
381                        if (declaredAnnotation != null)
382                                break;
383                }
384        }
385                
386        return declaredAnnotation;
387    }
388    
389    public static <T extends Annotation> DeclaredAnnotation<T> getConstructorAnnotation(Class<?> clazz, Class<T> annotationClass) {
390        DeclaredAnnotation<T> declaredAnnotation = null;
391        
392        for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
393                T annotation = constructor.getAnnotation(annotationClass);
394                if (annotation != null) {
395                        declaredAnnotation = new DeclaredAnnotation<T>(annotation, constructor, clazz);
396                        break;
397                }
398        }
399        
400        if (declaredAnnotation == null && clazz.getSuperclass() != null)
401                declaredAnnotation = getConstructorAnnotation(clazz.getSuperclass(), annotationClass);
402                
403        return declaredAnnotation;
404    }
405    
406    public static <T extends Annotation> DeclaredAnnotation<T> getClassAnnotation(Class<?> clazz, Class<T> annotationClass) {
407        DeclaredAnnotation<T> declaredAnnotation = null;
408        
409        T annotation = clazz.getAnnotation(annotationClass);
410        if (annotation != null)
411                declaredAnnotation = new DeclaredAnnotation<T>(annotation, clazz, clazz);
412        else {
413                if (clazz.getSuperclass() != null)
414                        declaredAnnotation = getClassAnnotation(clazz.getSuperclass(), annotationClass);
415                
416                if (declaredAnnotation == null) {
417                        for (Class<?> interfaze : clazz.getInterfaces()) {
418                                declaredAnnotation = getClassAnnotation(interfaze, annotationClass);
419                                if (declaredAnnotation != null)
420                                        break;
421                        }
422                }
423        }
424                
425        return declaredAnnotation;
426    }
427    
428    public static class DeclaredAnnotation<T extends Annotation> {
429
430        public final T annotation;
431        public final AnnotatedElement annotatedElement;
432        public final Class<?> declaringClass;
433                
434        public DeclaredAnnotation(T annotation, AnnotatedElement annotatedElement, Class<?> declaringClass) {
435                        this.annotation = annotation;
436                        this.annotatedElement = annotatedElement;
437                        this.declaringClass = declaringClass;
438                }
439
440                @Override
441                public String toString() {
442                        return getClass().getName() + "{annotation=" + annotation + ", annotatedElement=" + annotatedElement + ", declaringClass=" + declaringClass + "}";
443                }
444    }
445    
446    
447    private static class BeanInfo {
448
449        private Class<?> beanClass;
450        private PropertyDescriptor[] properties = null;
451
452        
453        public BeanInfo(Class<?> beanClass) {
454            this.beanClass = beanClass;
455
456            if (properties == null)
457                properties = introspectProperties();
458        }
459
460        public PropertyDescriptor[] getPropertyDescriptors() {
461            return properties;
462        }
463
464        /**
465         * Introspects the supplied class and returns a list of the Properties of
466         * the class
467         * 
468         * @return The list of Properties as an array of PropertyDescriptors
469         */
470        private PropertyDescriptor[] introspectProperties() {
471
472                Method[] methods = beanClass.getMethods();
473                List<Method> methodList = new ArrayList<Method>();
474                
475                for (Method method : methods) {
476                        if (!Modifier.isPublic(method.getModifiers()) || Modifier.isStatic(method.getModifiers()))
477                                continue;
478                        methodList.add(method);
479                }
480
481            Map<String, Map<String, Object>> propertyMap = new HashMap<String, Map<String, Object>>(methodList.size());
482
483            // Search for methods that either get or set a Property
484            for (Method method : methodList) {
485                introspectGet(method, propertyMap);
486                introspectSet(method, propertyMap);
487            }
488
489            // fix possible getter & setter collisions
490            fixGetSet(propertyMap);
491            
492            // Put the properties found into the PropertyDescriptor array
493            List<PropertyDescriptor> propertyList = new ArrayList<PropertyDescriptor>();
494
495            for (Map.Entry<String, Map<String, Object>> entry : propertyMap.entrySet()) {
496                String propertyName = entry.getKey();
497                Map<String, Object> table = entry.getValue();
498                if (table == null)
499                    continue;
500                
501                Method getter = (Method)table.get("getter");
502                Method setter = (Method)table.get("setter");
503
504                PropertyDescriptor propertyDesc = new PropertyDescriptor(propertyName, getter, setter);
505                propertyList.add(propertyDesc);
506            }
507
508            PropertyDescriptor[] properties = new PropertyDescriptor[propertyList.size()];
509            propertyList.toArray(properties);
510            return properties;
511        }
512
513        @SuppressWarnings("unchecked")
514        private static void introspectGet(Method method, Map<String, Map<String, Object>> propertyMap) {
515            String methodName = method.getName();
516            
517            if (!(method.getName().startsWith("get") || method.getName().startsWith("is")))
518                return;
519            
520            if (method.getParameterTypes().length > 0 || method.getReturnType() == void.class)
521                return;
522            
523            if (method.getName().startsWith("is") && method.getReturnType() != boolean.class)
524                return;
525
526            String propertyName = method.getName().startsWith("get") ? methodName.substring(3) : methodName.substring(2);
527            propertyName = decapitalize(propertyName);
528
529            Map<String, Object> table = propertyMap.get(propertyName);
530            if (table == null) {
531                table = new HashMap<String, Object>();
532                propertyMap.put(propertyName, table);
533            }
534
535            List<Method> getters = (List<Method>)table.get("getters");
536            if (getters == null) {
537                getters = new ArrayList<Method>();
538                table.put("getters", getters);
539            }
540            getters.add(method);
541        }
542
543        @SuppressWarnings("unchecked")
544        private static void introspectSet(Method method, Map<String, Map<String, Object>> propertyMap) {
545            String methodName = method.getName();
546            
547            if (!method.getName().startsWith("set"))
548                return;
549            
550            if (method.getParameterTypes().length != 1 || method.getReturnType() != void.class)
551                return;
552
553            String propertyName = decapitalize(methodName.substring(3));
554
555            Map<String, Object> table = propertyMap.get(propertyName);
556            if (table == null) {
557                table = new HashMap<String, Object>();
558                propertyMap.put(propertyName, table);
559            }
560
561            List<Method> setters = (List<Method>)table.get("setters");
562            if (setters == null) {
563                setters = new ArrayList<Method>();
564                table.put("setters", setters);
565            }
566
567            // add new setter
568            setters.add(method);
569        }
570
571        /**
572         * Checks and fixs all cases when several incompatible checkers / getters
573         * were specified for single property.
574         * 
575         * @param propertyMap
576         */
577        private void fixGetSet(Map<String, Map<String, Object>> propertyMap) {
578            if (propertyMap == null)
579                return;
580
581            for (Entry<String, Map<String, Object>> entry : propertyMap.entrySet()) {
582                Map<String, Object> table = entry.getValue();
583                @SuppressWarnings("unchecked")
584                                List<Method> getters = (List<Method>)table.get("getters");
585                @SuppressWarnings("unchecked")
586                                List<Method> setters = (List<Method>)table.get("setters");
587                if (getters == null)
588                    getters = new ArrayList<Method>();
589                if (setters == null)
590                    setters = new ArrayList<Method>();
591
592                Method definedGetter = getters.isEmpty() ? null : getters.get(0);
593                Method definedSetter = null;
594
595                if (definedGetter != null) {
596                    Class<?> propertyType = definedGetter.getReturnType();
597        
598                    for (Method setter : setters) {
599                        if (setter.getParameterTypes().length == 1 && propertyType.equals(setter.getParameterTypes()[0])) {
600                            definedSetter = setter;
601                            break;
602                        }
603                    }
604                    if (definedSetter != null && !setters.isEmpty())
605                        definedSetter = setters.get(0);
606                } 
607                else if (!setters.isEmpty()) {
608                        definedSetter = setters.get(0);
609                }
610
611                table.put("getter", definedGetter);
612                table.put("setter", definedSetter);
613            }
614        }
615    }
616
617}