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.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.Collection;
042import java.util.Collections;
043import java.util.List;
044import java.util.Map;
045import java.util.Set;
046
047/**
048 * @author Franck WOLFF
049 */
050public abstract class TypeUtil {
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 TypeUtil.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>)TypeUtil.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<?> componentClassOfType(Type arrayOrCollectionType) {
137                Class<?> arrayOrCollectionClass = classOfType(arrayOrCollectionType);
138                if (!arrayOrCollectionClass.isArray() && !Collection.class.isAssignableFrom(arrayOrCollectionClass))
139                        throw new IllegalArgumentException("Not an array or Collection: " + arrayOrCollectionType);
140                
141                if (arrayOrCollectionType instanceof Class) {
142                        Class<?> cls = (Class<?>)arrayOrCollectionType;
143                        
144                        if (cls.isArray())
145                                return cls.getComponentType();
146                        
147                        Type genericSuperclass = cls.getGenericSuperclass();
148                        if (genericSuperclass != null && Collection.class.isAssignableFrom(classOfType(genericSuperclass)))
149                                return componentClassOfType(genericSuperclass);
150                        
151                        Type[] genericInterfaces = cls.getGenericInterfaces();
152                        for (Type genericInterface : genericInterfaces) {
153                                if (Collection.class.isAssignableFrom(classOfType(genericInterface)))
154                                        return componentClassOfType(genericInterface);
155                        }
156                        
157                        return Object.class;
158                }
159                if (arrayOrCollectionType instanceof ParameterizedType) {
160                        ParameterizedType pType = (ParameterizedType)arrayOrCollectionType;
161                        Type[] args = pType.getActualTypeArguments();
162                        
163                        if (args.length > 0)
164                                return classOfType(args[0]);
165                        
166                        return Object.class; 
167                }
168                if (arrayOrCollectionType instanceof GenericArrayType)
169                        return classOfType(((GenericArrayType)arrayOrCollectionType).getGenericComponentType());
170                
171                return Object.class;
172        }
173
174    public static Class<?> classOfType(Type type) {
175        if (type instanceof Class<?>)
176            return (Class<?>)type;
177        if (type instanceof ParameterizedType)
178            return (Class<?>)((ParameterizedType)type).getRawType();
179        if (type instanceof WildcardType) {
180            // Forget lower bounds and only deal with first upper bound...
181            Type[] ubs = ((WildcardType)type).getUpperBounds();
182            if (ubs.length > 0)
183                return classOfType(ubs[0]);
184        }
185        if (type instanceof GenericArrayType) {
186            Class<?> ct = classOfType(((GenericArrayType)type).getGenericComponentType());
187            return (ct != null ? Array.newInstance(ct, 0).getClass() : Object[].class);
188        }
189        if (type instanceof TypeVariable<?>) {
190            // Only deal with first (upper) bound...
191            Type[] ubs = ((TypeVariable<?>)type).getBounds();
192            if (ubs.length > 0)
193                return classOfType(ubs[0]);
194        }
195        // Should never append...
196        return Object.class;
197    }
198    
199    public static Type getBoundType(TypeVariable<?> typeVariable) {
200        Type[] ubs = typeVariable.getBounds();
201        if (ubs.length > 0)
202                return ubs[0];
203        
204        // should never happen...
205        if (typeVariable.getGenericDeclaration() instanceof Type)
206                return (Type)typeVariable.getGenericDeclaration();
207        return typeVariable;
208    }
209    
210    public static ParameterizedType[] getDeclaringTypes(Class<?> type) {
211                List<ParameterizedType> supertypes = new ArrayList<ParameterizedType>();
212                
213                Type stype = type.getGenericSuperclass();
214                Class<?> sclass = type.getSuperclass();
215                while (sclass != null && sclass != Object.class) {
216                        if (stype instanceof ParameterizedType)
217                                supertypes.add((ParameterizedType)stype);
218                        stype = sclass.getGenericSuperclass();
219                        sclass = sclass.getSuperclass();
220                }
221                
222                collectGenericInterfaces(type.getGenericInterfaces(), supertypes);
223                
224                return supertypes.isEmpty() ? null : supertypes.toArray(new ParameterizedType[supertypes.size()]);
225    }
226    
227    private static void collectGenericInterfaces(Type[] types, List<ParameterizedType> supertypes) {
228        if (types == null)
229                return;
230                for (Type t : types) {
231                        if (t instanceof ParameterizedType)
232                                supertypes.add((ParameterizedType)t);
233                        else
234                                collectGenericInterfaces(((Class<?>)t).getGenericInterfaces(), supertypes);
235                }
236    }
237   
238    public static Type resolveTypeVariable(Type genericType, Class<?> declaringClass, ParameterizedType[] declaringTypes) {
239        if (genericType instanceof TypeVariable && declaringTypes != null) {
240                int index = -1;
241                TypeVariable<?> typeVariable = (TypeVariable<?>)genericType;
242                ParameterizedType declaringType = null;
243                for (int j = 0; j < declaringClass.getTypeParameters().length; j++) {
244                        Type typeParameter = declaringClass.getTypeParameters()[j];
245                        if (typeParameter == typeVariable)
246                                index = j;
247                        else if (typeVariable.getBounds() != null) {
248                                for (Type t : typeVariable.getBounds()) {
249                                        if (typeParameter == t) {
250                                                index = j;
251                                                break;
252                                        }
253                                }
254                        }
255                        if (index >= 0) {
256                                        for (ParameterizedType t : declaringTypes) {
257                                                if (declaringClass.isAssignableFrom(classOfType(t))) {
258                                                        declaringType = t;
259                                                        break;
260                                                }
261                                        }
262                                        break;
263                        }
264                }
265                if (declaringType != null && index >= 0 && index < declaringType.getActualTypeArguments().length)
266                        return declaringType.getActualTypeArguments()[index];
267        }
268        return genericType;
269    }
270    public static String getPackageName(Class<?> clazz) {
271        return clazz.getPackage() != null ? clazz.getPackage().getName() : "";
272    }
273    
274    public static PropertyDescriptor[] getProperties(Class<?> clazz) {
275        try {
276                PropertyDescriptor[] properties = Introspector.getPropertyDescriptors(clazz);
277                Field[] fields = clazz.getDeclaredFields();
278                for (Field field : fields) {
279                        if (Boolean.class.equals(field.getType())) {
280                                boolean found = false;
281                                for (PropertyDescriptor property : properties) {
282                                        if (property.getName().equals(field.getName())) {
283                                                found = true;
284                                                if (property.getReadMethod() == null) {
285                                                        try {
286                                                                Method readMethod = clazz.getDeclaredMethod(getIsMethodName(field.getName()));
287                                                                if (Modifier.isPublic(readMethod.getModifiers()) && !Modifier.isStatic(readMethod.getModifiers()))
288                                                                        property.setReadMethod(readMethod);
289                                                        }
290                                                        catch (NoSuchMethodException e) {
291                                                        }
292                                                }
293                                                break;
294                                        }
295                                }
296                                if (!found) {
297                                                try {
298                                                        Method readMethod = clazz.getDeclaredMethod(getIsMethodName(field.getName()));
299                                                        if (Modifier.isPublic(readMethod.getModifiers()) && !Modifier.isStatic(readMethod.getModifiers())) {
300                                                                PropertyDescriptor[] propertiesTmp = new PropertyDescriptor[properties.length + 1];
301                                                                System.arraycopy(properties, 0, propertiesTmp, 0, properties.length);
302                                                                propertiesTmp[properties.length] = new PropertyDescriptor(field.getName(), readMethod, null);
303                                                                properties = propertiesTmp;
304                                                        }
305                                                }
306                                                catch (NoSuchMethodException e) {
307                                                }
308                                }
309                        }
310                }
311            return properties;
312        } catch (Exception e) {
313            throw new RuntimeException("Could not introspect properties of class: " + clazz, e);
314        }
315    }
316    
317    private static String getIsMethodName(String name) {
318        return "is" + name.substring(0, 1).toUpperCase() + name.substring(1);
319    }
320   
321    public static ClassLoader getClassLoader(Class<?> clazz) {
322        return (clazz.getClassLoader() != null ? clazz.getClassLoader() : ClassLoader.getSystemClassLoader());
323    }
324
325    public static URL findResource(Class<?> clazz) {
326        while (clazz.isArray())
327            clazz = clazz.getComponentType();
328        if (clazz.isPrimitive())
329            return null;
330        URL url = getClassLoader(clazz).getResource(toResourceName(clazz));
331        String path = url.toString();
332        if (path.indexOf(' ') != -1) {
333                try {
334                                url = new URL(path.replace(" ", "%20"));
335                        } catch (MalformedURLException e) {
336                                // should never happen...
337                        }
338        }
339        return url;
340    }
341
342    public static String toResourceName(Class<?> clazz) {
343        return clazz.getName().replace('.', '/').concat(".class");
344    }
345    
346    public static String getMethodSignature(Method method) {
347        StringBuilder sb = new StringBuilder();
348        sb.append(method.getName()).append('(');
349        Class<?>[] params = method.getParameterTypes();
350        for (int i = 0; i < params.length; i++) {
351                if (i > 0)
352                        sb.append(',');
353                sb.append(getTypeSignature(params[i]));
354        }
355        sb.append(')');
356        return sb.toString();
357    }
358        
359    public static String getTypeSignature(Class<?> type) {
360                if (type.isArray()) {
361                    try {
362                                int dimensions = 1;
363                                Class<?> clazz = type.getComponentType();
364                                while (clazz.isArray()) {
365                                        dimensions++;
366                                        clazz = clazz.getComponentType();
367                                }
368                                
369                                StringBuffer sb = new StringBuffer(clazz.getName());
370                                while (dimensions-- > 0)
371                                    sb.append("[]");
372                                return sb.toString();
373                    } catch (Throwable e) {
374                        // fallback...
375                    }
376                }
377                return type.getName();
378        }
379    
380    public static Method getMethod(Class<?> clazz, String signature) throws NoSuchMethodException {
381        signature = StringUtil.removeSpaces(signature);
382                
383        if (!signature.endsWith(")"))
384                        signature += "()";
385                
386                for (Method method : clazz.getMethods()) {
387                        if (signature.equals(getMethodSignature(method)))
388                                return method;
389                }
390                
391                throw new NoSuchMethodException("Could not find method: " + signature + " in class: " + clazz);
392    }
393    
394    public static boolean isAnnotationPresent(AnnotatedElement elmt, Class<? extends Annotation> annotationClass) {
395        return getAnnotation(elmt, annotationClass) != null;
396    }
397    
398    public static <T extends Annotation> DeclaredAnnotation<T> getAnnotation(AnnotatedElement elmt, Class<T> annotationClass) {
399        T annotation = elmt.getAnnotation(annotationClass);
400        
401        if (annotation != null) {
402                Class<?> declaringClass = (elmt instanceof Member ? ((Member)elmt).getDeclaringClass() : (Class<?>)elmt);
403                return new DeclaredAnnotation<T>(annotation, elmt, declaringClass);
404        }
405        
406        if (elmt instanceof Field)
407                return null;
408        
409        if (elmt instanceof Method) {
410                Method m = (Method)elmt;
411                return getMethodAnnotation(m.getDeclaringClass(), m.getName(), m.getParameterTypes(), annotationClass);
412        }
413        
414        if (elmt instanceof Constructor) {
415                Constructor<?> c = (Constructor<?>)elmt;
416                return getConstructorAnnotation(c.getDeclaringClass(), annotationClass);
417        }
418        
419        if (elmt instanceof Class) {
420                Class<?> c = (Class<?>)elmt;
421                return getClassAnnotation(c.getDeclaringClass(), annotationClass);
422        }
423        
424        throw new RuntimeException("Unsupported annotated element: " + elmt);
425    }
426    
427    public static <T extends Annotation> DeclaredAnnotation<T> getMethodAnnotation(Class<?> clazz, String name, Class<?>[] parameterTypes, Class<T> annotationClass) {
428        DeclaredAnnotation<T> declaredAnnotation = null;
429        
430        try {
431                Method method = clazz.getDeclaredMethod(name, parameterTypes);
432                T annotation = clazz.getDeclaredMethod(name, parameterTypes).getAnnotation(annotationClass);
433                if (annotation != null)
434                        declaredAnnotation = new DeclaredAnnotation<T>(annotation, method, clazz);
435        }
436        catch (NoSuchMethodException e) {
437                // fallback...
438        }
439        
440        if (declaredAnnotation == null && clazz.getSuperclass() != null)
441                declaredAnnotation = getMethodAnnotation(clazz.getSuperclass(), name, parameterTypes, annotationClass);
442        
443        if (declaredAnnotation == null) {
444                for (Class<?> interfaze : clazz.getInterfaces()) {
445                        declaredAnnotation = getMethodAnnotation(interfaze, name, parameterTypes, annotationClass);
446                        if (declaredAnnotation != null)
447                                break;
448                }
449        }
450                
451        return declaredAnnotation;
452    }
453    
454    public static <T extends Annotation> DeclaredAnnotation<T> getConstructorAnnotation(Class<?> clazz, Class<T> annotationClass) {
455        DeclaredAnnotation<T> declaredAnnotation = null;
456        
457        for (Constructor<?> constructor : clazz.getDeclaredConstructors()) {
458                T annotation = constructor.getAnnotation(annotationClass);
459                if (annotation != null) {
460                        declaredAnnotation = new DeclaredAnnotation<T>(annotation, constructor, clazz);
461                        break;
462                }
463        }
464        
465        if (declaredAnnotation == null && clazz.getSuperclass() != null)
466                declaredAnnotation = getConstructorAnnotation(clazz.getSuperclass(), annotationClass);
467                
468        return declaredAnnotation;
469    }
470    
471    public static <T extends Annotation> DeclaredAnnotation<T> getClassAnnotation(Class<?> clazz, Class<T> annotationClass) {
472        DeclaredAnnotation<T> declaredAnnotation = null;
473        
474        T annotation = clazz.getAnnotation(annotationClass);
475        if (annotation != null)
476                declaredAnnotation = new DeclaredAnnotation<T>(annotation, clazz, clazz);
477        else {
478                if (clazz.getSuperclass() != null)
479                        declaredAnnotation = getClassAnnotation(clazz.getSuperclass(), annotationClass);
480                
481                if (declaredAnnotation == null) {
482                        for (Class<?> interfaze : clazz.getInterfaces()) {
483                                declaredAnnotation = getClassAnnotation(interfaze, annotationClass);
484                                if (declaredAnnotation != null)
485                                        break;
486                        }
487                }
488        }
489                
490        return declaredAnnotation;
491    }
492    
493    public static class DeclaredAnnotation<T extends Annotation> {
494
495        public final T annotation;
496        public final AnnotatedElement annotatedElement;
497        public final Class<?> declaringClass;
498                
499        public DeclaredAnnotation(T annotation, AnnotatedElement annotatedElement, Class<?> declaringClass) {
500                        this.annotation = annotation;
501                        this.annotatedElement = annotatedElement;
502                        this.declaringClass = declaringClass;
503                }
504
505                @Override
506                public String toString() {
507                        return getClass().getName() + "{annotation=" + annotation + ", annotatedElement=" + annotatedElement + ", declaringClass=" + declaringClass + "}";
508                }
509    }
510}