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