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