001    /**
002     *   GRANITE DATA SERVICES
003     *   Copyright (C) 2006-2013 GRANITE DATA SERVICES S.A.S.
004     *
005     *   This file is part of the Granite Data Services Platform.
006     *
007     *   Granite Data Services is free software; you can redistribute it and/or
008     *   modify it under the terms of the GNU Lesser General Public
009     *   License as published by the Free Software Foundation; either
010     *   version 2.1 of the License, or (at your option) any later version.
011     *
012     *   Granite Data Services is distributed in the hope that it will be useful,
013     *   but WITHOUT ANY WARRANTY; without even the implied warranty of
014     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
015     *   General Public License for more details.
016     *
017     *   You should have received a copy of the GNU Lesser General Public
018     *   License along with this library; if not, write to the Free Software
019     *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
020     *   USA, or see <http://www.gnu.org/licenses/>.
021     */
022    package org.granite.util;
023    
024    import java.lang.annotation.Annotation;
025    import java.lang.reflect.AnnotatedElement;
026    import java.lang.reflect.Array;
027    import java.lang.reflect.Constructor;
028    import java.lang.reflect.Field;
029    import java.lang.reflect.GenericArrayType;
030    import java.lang.reflect.InvocationTargetException;
031    import java.lang.reflect.Member;
032    import java.lang.reflect.Method;
033    import java.lang.reflect.Modifier;
034    import java.lang.reflect.ParameterizedType;
035    import java.lang.reflect.Type;
036    import java.lang.reflect.TypeVariable;
037    import java.lang.reflect.WildcardType;
038    import java.net.MalformedURLException;
039    import java.net.URL;
040    import java.util.ArrayList;
041    import java.util.Collection;
042    import java.util.Collections;
043    import java.util.List;
044    import java.util.Map;
045    import java.util.Set;
046    
047    /**
048     * @author Franck WOLFF
049     */
050    public 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    }