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