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.messaging.reflect; 023 024import java.lang.annotation.Annotation; 025import java.lang.annotation.ElementType; 026import java.lang.annotation.Target; 027import java.lang.reflect.Constructor; 028import java.lang.reflect.Field; 029import java.lang.reflect.InvocationTargetException; 030import java.lang.reflect.Method; 031import java.lang.reflect.Modifier; 032import java.util.ArrayList; 033import java.util.Collections; 034import java.util.Comparator; 035import java.util.List; 036import java.util.concurrent.ConcurrentHashMap; 037import java.util.concurrent.ConcurrentMap; 038 039import org.granite.messaging.annotations.Exclude; 040import org.granite.messaging.annotations.Include; 041import org.granite.messaging.annotations.Serialized; 042 043/** 044 * Reflection provider 045 * 046 * @author Franck WOLFF 047 */ 048public class Reflection { 049 050 protected static final int STATIC_TRANSIENT_MASK = Modifier.STATIC | Modifier.TRANSIENT; 051 protected static final int STATIC_PRIVATE_PROTECTED_MASK = Modifier.STATIC | Modifier.PRIVATE | Modifier.PROTECTED; 052 protected static final Property NULL_PROPERTY = new NullProperty(); 053 054 protected final ClassLoader classLoader; 055 protected final BypassConstructorAllocator instanceFactory; 056 protected final Comparator<Property> lexicalPropertyComparator; 057 058 protected final ConcurrentMap<Class<?>, ClassDescriptor> descriptorCache; 059 protected final ConcurrentMap<SinglePropertyKey, Property> singlePropertyCache; 060 061 public Reflection(ClassLoader classLoader) { 062 this(classLoader, null); 063 } 064 065 public Reflection(ClassLoader classLoader, BypassConstructorAllocator instanceFactory) { 066 this.classLoader = classLoader; 067 068 if (instanceFactory != null) 069 this.instanceFactory = instanceFactory; 070 else { 071 try { 072 this.instanceFactory = new SunBypassConstructorAllocator(); 073 } 074 catch (Exception e) { 075 throw new RuntimeException("Could not instantiate BypassConstructorAllocator", e); 076 } 077 } 078 079 this.lexicalPropertyComparator = new Comparator<Property>() { 080 public int compare(Property p1, Property p2) { 081 return p1.getName().compareTo(p2.getName()); 082 } 083 }; 084 085 this.descriptorCache = new ConcurrentHashMap<Class<?>, ClassDescriptor>(); 086 this.singlePropertyCache = new ConcurrentHashMap<SinglePropertyKey, Property>(); 087 } 088 089 public ClassLoader getClassLoader() { 090 return (classLoader != null ? classLoader : Thread.currentThread().getContextClassLoader()); 091 } 092 093 public BypassConstructorAllocator getInstanceFactory() { 094 return instanceFactory; 095 } 096 097 public Comparator<Property> getLexicalPropertyComparator() { 098 return lexicalPropertyComparator; 099 } 100 101 public Class<?> loadClass(String className) throws ClassNotFoundException { 102 return getClassLoader().loadClass(className); 103 } 104 105 @SuppressWarnings("unchecked") 106 public <T> T newInstance(Class<T> cls) 107 throws InstantiationException, IllegalAccessException, IllegalArgumentException, 108 InvocationTargetException, SecurityException, NoSuchMethodException { 109 110 ClassDescriptor desc = descriptorCache.get(cls); 111 if (desc != null) 112 return (T)desc.newInstance(); 113 114 try { 115 Constructor<T> constructor = cls.getConstructor(); 116 return constructor.newInstance(); 117 } 118 catch (NoSuchMethodException e) { 119 return (T)instanceFactory.newInstantiator(cls).newInstance(); 120 } 121 } 122 123 @SuppressWarnings("unchecked") 124 public <T> T newInstance(String className) 125 throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, 126 InvocationTargetException, SecurityException, NoSuchMethodException { 127 128 return newInstance((Class<T>)loadClass(className)); 129 } 130 131 public Property findSerializableProperty(Class<?> cls, String name) throws SecurityException { 132 List<Property> properties = findSerializableProperties(cls); 133 for (Property property : properties) { 134 if (name.equals(property.getName())) 135 return property; 136 } 137 return null; 138 } 139 140 public ClassDescriptor getDescriptor(Class<?> cls) { 141 if (cls == null || cls == Object.class || !isRegularClass(cls)) 142 return null; 143 144 ClassDescriptor descriptor = descriptorCache.get(cls); 145 if (descriptor == null) { 146 descriptor = new ClassDescriptor(this, cls); 147 ClassDescriptor previousDescriptor = descriptorCache.putIfAbsent(cls, descriptor); 148 if (previousDescriptor != null) 149 descriptor = previousDescriptor; 150 } 151 return descriptor; 152 } 153 154 public List<Property> findSerializableProperties(Class<?> cls) throws SecurityException { 155 ClassDescriptor descriptor = getDescriptor(cls); 156 if (descriptor == null) 157 return Collections.emptyList(); 158 return descriptor.getInheritedSerializableProperties(); 159 } 160 161 protected FieldProperty newFieldProperty(Field field) { 162 return new SimpleFieldProperty(field); 163 } 164 165 protected MethodProperty newMethodProperty(Method getter, Method setter, String name) { 166 return new SimpleMethodProperty(getter, setter, name); 167 } 168 169 protected List<Property> findSerializableDeclaredProperties(Class<?> cls) throws SecurityException { 170 171 if (!isRegularClass(cls)) 172 throw new IllegalArgumentException("Not a regular class: " + cls); 173 174 Field[] declaredFields = cls.getDeclaredFields(); 175 List<Property> serializableProperties = new ArrayList<Property>(declaredFields.length); 176 for (Field field : declaredFields) { 177 int modifiers = field.getModifiers(); 178 if ((modifiers & STATIC_TRANSIENT_MASK) == 0 && !field.isAnnotationPresent(Exclude.class)) { 179 field.setAccessible(true); 180 serializableProperties.add(newFieldProperty(field)); 181 } 182 } 183 184 Method[] declaredMethods = cls.getDeclaredMethods(); 185 for (Method method : declaredMethods) { 186 int modifiers = method.getModifiers(); 187 if ((modifiers & STATIC_PRIVATE_PROTECTED_MASK) == 0 && 188 method.isAnnotationPresent(Include.class) && 189 method.getParameterTypes().length == 0 && 190 method.getReturnType() != Void.TYPE) { 191 192 String name = method.getName(); 193 if (name.startsWith("get")) { 194 if (name.length() <= 3) 195 continue; 196 name = name.substring(3, 4).toLowerCase() + name.substring(4); 197 } 198 else if (name.startsWith("is") && 199 (method.getReturnType() == Boolean.class || method.getReturnType() == Boolean.TYPE)) { 200 if (name.length() <= 2) 201 continue; 202 name = name.substring(2, 3).toLowerCase() + name.substring(3); 203 } 204 else 205 continue; 206 207 serializableProperties.add(newMethodProperty(method, null, name)); 208 } 209 } 210 211 Serialized serialized = cls.getAnnotation(Serialized.class); 212 if (serialized != null && serialized.propertiesOrder().length > 0) { 213 String[] value = serialized.propertiesOrder(); 214 215 if (value.length != serializableProperties.size()) 216 throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (bad length)"); 217 218 for (int i = 0; i < value.length; i++) { 219 String propertyName = value[i]; 220 221 boolean found = false; 222 for (int j = i; j < value.length; j++) { 223 Property property = serializableProperties.get(j); 224 if (property.getName().equals(propertyName)) { 225 found = true; 226 if (i != j) { 227 serializableProperties.set(j, serializableProperties.get(i)); 228 serializableProperties.set(i, property); 229 } 230 break; 231 } 232 } 233 if (!found) 234 throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (\"" + propertyName + "\" isn't a property name)"); 235 } 236 } 237 else 238 Collections.sort(serializableProperties, lexicalPropertyComparator); 239 240 return serializableProperties; 241 } 242 243 public boolean isRegularClass(Class<?> cls) { 244 return cls != Class.class && !cls.isAnnotation() && !cls.isArray() && 245 !cls.isEnum() && !cls.isInterface() && !cls.isPrimitive(); 246 } 247 248 public Property findProperty(Class<?> cls, String name, Class<?> type) { 249 NameTypePropertyKey key = new NameTypePropertyKey(cls, name, type); 250 251 Property property = singlePropertyCache.get(key); 252 253 if (property == null) { 254 Field field = null; 255 256 for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass()) { 257 try { 258 field = c.getDeclaredField(name); 259 } 260 catch (NoSuchFieldException e) { 261 continue; 262 } 263 264 if (field.getType() != type) 265 continue; 266 267 field.setAccessible(true); 268 break; 269 } 270 271 if (field == null) 272 property = NULL_PROPERTY; 273 else 274 property = newFieldProperty(field); 275 276 Property previous = singlePropertyCache.putIfAbsent(key, property); 277 if (previous != null) 278 property = previous; 279 } 280 281 return (property != NULL_PROPERTY ? property : null); 282 } 283 284 public Property findProperty(Class<?> cls, Class<? extends Annotation> annotationClass) { 285 AnnotatedPropertyKey key = new AnnotatedPropertyKey(cls, annotationClass); 286 287 Property property = singlePropertyCache.get(key); 288 289 if (property == null) { 290 boolean searchFields = false; 291 boolean searchMethods = false; 292 293 if (!annotationClass.isAnnotationPresent(Target.class)) 294 searchFields = searchMethods = true; 295 else { 296 Target target = annotationClass.getAnnotation(Target.class); 297 for (ElementType targetType : target.value()) { 298 if (targetType == ElementType.FIELD) 299 searchFields = true; 300 else if (targetType == ElementType.METHOD) 301 searchMethods = true; 302 } 303 } 304 305 if (searchFields == false && searchMethods == false) 306 return null; 307 308 final int modifierMask = Modifier.PUBLIC | Modifier.STATIC; 309 310 classLoop: 311 for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass()) { 312 if (searchMethods) { 313 for (Method method : c.getDeclaredMethods()) { 314 if ((method.getModifiers() & modifierMask) != Modifier.PUBLIC || 315 !method.isAnnotationPresent(annotationClass)) 316 continue; 317 318 if (method.getReturnType() == Void.TYPE) { 319 if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) { 320 String name = method.getName().substring(3); 321 322 if (name.length() == 0) 323 continue; 324 325 Method getter = null; 326 try { 327 getter = cls.getMethod("get" + name); 328 } 329 catch (NoSuchMethodException e) { 330 try { 331 getter = cls.getMethod("is" + name); 332 } 333 catch (Exception f) { 334 } 335 } 336 337 if (getter != null && (getter.getModifiers() & Modifier.STATIC) != 0 && 338 getter.getReturnType() != method.getParameterTypes()[0]) 339 getter = null; 340 341 if (getter == null) 342 continue; 343 344 name = name.substring(0, 1).toLowerCase() + name.substring(1); 345 property = newMethodProperty(getter, method, name); 346 break classLoop; 347 } 348 } 349 else if (method.getParameterTypes().length == 0 && (method.getName().startsWith("get") || method.getName().startsWith("is"))) { 350 String name; 351 if (method.getName().startsWith("get")) 352 name = method.getName().substring(3); 353 else 354 name = method.getName().substring(2); 355 356 if (name.length() == 0) 357 continue; 358 359 Method setter = null; 360 try { 361 setter = cls.getMethod("set" + name); 362 } 363 catch (NoSuchMethodException e) { 364 } 365 366 if (setter != null && (setter.getModifiers() & Modifier.STATIC) != 0 && 367 method.getReturnType() != setter.getParameterTypes()[0]) 368 setter = null; 369 370 name = name.substring(0, 1).toLowerCase() + name.substring(1); 371 property = newMethodProperty(method, setter, name); 372 break classLoop; 373 } 374 } 375 } 376 377 if (searchFields) { 378 for (Field field : c.getDeclaredFields()) { 379 if ((field.getModifiers() & Modifier.STATIC) == 0 && field.isAnnotationPresent(annotationClass)) { 380 property = newFieldProperty(field); 381 break classLoop; 382 } 383 } 384 } 385 } 386 387 if (property == null) 388 property = NULL_PROPERTY; 389 390 Property previous = singlePropertyCache.putIfAbsent(key, property); 391 if (previous != null) 392 property = previous; 393 } 394 395 return (property != NULL_PROPERTY ? property : null); 396 } 397 398 protected static abstract class SinglePropertyKey { 399 400 protected final Class<?> cls; 401 402 public SinglePropertyKey(Class<?> cls) { 403 this.cls = cls; 404 } 405 } 406 407 protected static class AnnotatedPropertyKey extends SinglePropertyKey { 408 409 private final Class<? extends Annotation> annotationClass; 410 411 public AnnotatedPropertyKey(Class<?> cls, Class<? extends Annotation> annotationClass) { 412 super(cls); 413 this.annotationClass = annotationClass; 414 } 415 416 public Class<?> getCls() { 417 return cls; 418 } 419 420 public Class<? extends Annotation> getAnnotationClass() { 421 return annotationClass; 422 } 423 424 @Override 425 public int hashCode() { 426 return (31 * cls.hashCode()) + annotationClass.hashCode(); 427 } 428 429 @Override 430 public boolean equals(Object obj) { 431 if (obj == this) 432 return true; 433 if (!(obj instanceof AnnotatedPropertyKey)) 434 return false; 435 AnnotatedPropertyKey key = (AnnotatedPropertyKey)obj; 436 return cls.equals(key.cls) && annotationClass.equals(key.annotationClass); 437 } 438 } 439 440 protected static class NameTypePropertyKey extends SinglePropertyKey { 441 442 private final String name; 443 private final Class<?> type; 444 445 public NameTypePropertyKey(Class<?> cls, String name, Class<?> type) { 446 super(cls); 447 this.name = name; 448 this.type = type; 449 } 450 451 public Class<?> getCls() { 452 return cls; 453 } 454 455 public String getName() { 456 return name; 457 } 458 459 public Class<?> getType() { 460 return type; 461 } 462 463 @Override 464 public int hashCode() { 465 return (31 * (31 * cls.hashCode()) + name.hashCode()) + type.hashCode(); 466 } 467 468 @Override 469 public boolean equals(Object obj) { 470 if (obj == this) 471 return true; 472 if (!(obj instanceof NameTypePropertyKey)) 473 return false; 474 NameTypePropertyKey key = (NameTypePropertyKey)obj; 475 return cls.equals(key.cls) && name.equals(key.name) && type.equals(key.type); 476 } 477 } 478} 479