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