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