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<?>, List<Property>> serializablePropertiesCache; 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.serializablePropertiesCache = new ConcurrentHashMap<Class<?>, List<Property>>(); 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 Class<?> loadClass(String className) throws ClassNotFoundException { 094 return getClassLoader().loadClass(className); 095 } 096 097 public <T> T newInstance(Class<T> cls) 098 throws InstantiationException, IllegalAccessException, IllegalArgumentException, 099 InvocationTargetException, SecurityException, NoSuchMethodException { 100 101 try { 102 Constructor<T> constructor = cls.getConstructor(); 103 return constructor.newInstance(); 104 } 105 catch (NoSuchMethodException e) { 106 return instanceFactory.newInstance(cls); 107 } 108 } 109 110 @SuppressWarnings("unchecked") 111 public <T> T newInstance(String className) 112 throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, 113 InvocationTargetException, SecurityException, NoSuchMethodException { 114 115 return newInstance((Class<T>)loadClass(className)); 116 } 117 118 public Property findSerializableProperty(Class<?> cls, String name) throws SecurityException { 119 List<Property> properties = findSerializableProperties(cls); 120 for (Property property : properties) { 121 if (name.equals(property.getName())) 122 return property; 123 } 124 return null; 125 } 126 127 public List<Property> findSerializableProperties(Class<?> cls) throws SecurityException { 128 List<Property> serializableProperties = serializablePropertiesCache.get(cls); 129 130 if (serializableProperties == null) { 131 List<Class<?>> hierarchy = new ArrayList<Class<?>>(); 132 for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass()) 133 hierarchy.add(c); 134 135 serializableProperties = new ArrayList<Property>(); 136 for (int i = hierarchy.size() - 1; i >= 0; i--) { 137 Class<?> c = hierarchy.get(i); 138 serializableProperties.addAll(findSerializableDeclaredProperties(c)); 139 } 140 serializableProperties = Collections.unmodifiableList(serializableProperties); 141 List<Property> previous = serializablePropertiesCache.putIfAbsent(cls, serializableProperties); 142 if (previous != null) 143 serializableProperties = previous; 144 } 145 146 return serializableProperties; 147 } 148 149 protected FieldProperty newFieldProperty(Field field) { 150 return new SimpleFieldProperty(field); 151 } 152 153 protected MethodProperty newMethodProperty(Method getter, Method setter, String name) { 154 return new SimpleMethodProperty(getter, setter, name); 155 } 156 157 protected List<Property> findSerializableDeclaredProperties(Class<?> cls) throws SecurityException { 158 159 if (!isRegularClass(cls)) 160 throw new IllegalArgumentException("Not a regular class: " + cls); 161 162 Field[] declaredFields = cls.getDeclaredFields(); 163 List<Property> serializableProperties = new ArrayList<Property>(declaredFields.length); 164 for (Field field : declaredFields) { 165 int modifiers = field.getModifiers(); 166 if ((modifiers & STATIC_TRANSIENT_MASK) == 0 && !field.isAnnotationPresent(Exclude.class)) { 167 field.setAccessible(true); 168 serializableProperties.add(newFieldProperty(field)); 169 } 170 } 171 172 Method[] declaredMethods = cls.getDeclaredMethods(); 173 for (Method method : declaredMethods) { 174 int modifiers = method.getModifiers(); 175 if ((modifiers & STATIC_PRIVATE_PROTECTED_MASK) == 0 && 176 method.isAnnotationPresent(Include.class) && 177 method.getParameterTypes().length == 0 && 178 method.getReturnType() != Void.TYPE) { 179 180 String name = method.getName(); 181 if (name.startsWith("get")) { 182 if (name.length() <= 3) 183 continue; 184 name = name.substring(3, 4).toLowerCase() + name.substring(4); 185 } 186 else if (name.startsWith("is") && 187 (method.getReturnType() == Boolean.class || method.getReturnType() == Boolean.TYPE)) { 188 if (name.length() <= 2) 189 continue; 190 name = name.substring(2, 3).toLowerCase() + name.substring(3); 191 } 192 else 193 continue; 194 195 serializableProperties.add(newMethodProperty(method, null, name)); 196 } 197 } 198 199 Serialized serialized = cls.getAnnotation(Serialized.class); 200 if (serialized != null && serialized.propertiesOrder().length > 0) { 201 String[] value = serialized.propertiesOrder(); 202 203 if (value.length != serializableProperties.size()) 204 throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (bad length)"); 205 206 for (int i = 0; i < value.length; i++) { 207 String propertyName = value[i]; 208 209 boolean found = false; 210 for (int j = i; j < value.length; j++) { 211 Property property = serializableProperties.get(j); 212 if (property.getName().equals(propertyName)) { 213 found = true; 214 if (i != j) { 215 serializableProperties.set(j, serializableProperties.get(i)); 216 serializableProperties.set(i, property); 217 } 218 break; 219 } 220 } 221 if (!found) 222 throw new ReflectionException("Illegal @Serialized(propertiesOrder) value: " + serialized + " on: " + cls.getName() + " (\"" + propertyName + "\" isn't a property name)"); 223 } 224 } 225 else 226 Collections.sort(serializableProperties, lexicalPropertyComparator); 227 228 return serializableProperties; 229 } 230 231 public boolean isRegularClass(Class<?> cls) { 232 return cls != Class.class && !cls.isAnnotation() && !cls.isArray() && 233 !cls.isEnum() && !cls.isInterface() && !cls.isPrimitive(); 234 } 235 236 public Property findProperty(Class<?> cls, String name, Class<?> type) { 237 NameTypePropertyKey key = new NameTypePropertyKey(cls, name, type); 238 239 Property property = singlePropertyCache.get(key); 240 241 if (property == null) { 242 Field field = null; 243 244 for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass()) { 245 try { 246 field = c.getDeclaredField(name); 247 } 248 catch (Exception e) { 249 continue; 250 } 251 252 if (field.getType() != type) 253 continue; 254 255 field.setAccessible(true); 256 break; 257 } 258 259 if (field == null) 260 property = NULL_PROPERTY; 261 else 262 property = newFieldProperty(field); 263 264 Property previous = singlePropertyCache.putIfAbsent(key, property); 265 if (previous != null) 266 property = previous; 267 } 268 269 return (property != NULL_PROPERTY ? property : null); 270 } 271 272 public Property findProperty(Class<?> cls, Class<? extends Annotation> annotationClass) { 273 AnnotatedPropertyKey key = new AnnotatedPropertyKey(cls, annotationClass); 274 275 Property property = singlePropertyCache.get(key); 276 277 if (property == null) { 278 boolean searchFields = false; 279 boolean searchMethods = false; 280 281 if (!annotationClass.isAnnotationPresent(Target.class)) 282 searchFields = searchMethods = true; 283 else { 284 Target target = annotationClass.getAnnotation(Target.class); 285 for (ElementType targetType : target.value()) { 286 if (targetType == ElementType.FIELD) 287 searchFields = true; 288 else if (targetType == ElementType.METHOD) 289 searchMethods = true; 290 } 291 } 292 293 if (searchFields == false && searchMethods == false) 294 return null; 295 296 final int modifierMask = Modifier.PUBLIC | Modifier.STATIC; 297 298 classLoop: 299 for (Class<?> c = cls; c != null && c != Object.class; c = c.getSuperclass()) { 300 if (searchMethods) { 301 for (Method method : c.getDeclaredMethods()) { 302 if ((method.getModifiers() & modifierMask) != Modifier.PUBLIC || 303 !method.isAnnotationPresent(annotationClass)) 304 continue; 305 306 if (method.getReturnType() == Void.TYPE) { 307 if (method.getName().startsWith("set") && method.getParameterTypes().length == 1) { 308 String name = method.getName().substring(3); 309 310 if (name.length() == 0) 311 continue; 312 313 Method getter = null; 314 try { 315 getter = cls.getMethod("get" + name); 316 } 317 catch (Exception e) { 318 try { 319 getter = cls.getMethod("is" + name); 320 } 321 catch (Exception f) { 322 } 323 } 324 325 if (getter != null && (getter.getModifiers() & Modifier.STATIC) != 0 && 326 getter.getReturnType() != method.getParameterTypes()[0]) 327 getter = null; 328 329 if (getter == null) 330 continue; 331 332 name = name.substring(0, 1).toLowerCase() + name.substring(1); 333 property = newMethodProperty(getter, method, name); 334 break classLoop; 335 } 336 } 337 else if (method.getParameterTypes().length == 0 && (method.getName().startsWith("get") || method.getName().startsWith("is"))) { 338 String name; 339 if (method.getName().startsWith("get")) 340 name = method.getName().substring(3); 341 else 342 name = method.getName().substring(2); 343 344 if (name.length() == 0) 345 continue; 346 347 Method setter = null; 348 try { 349 setter = cls.getMethod("set" + name); 350 } 351 catch (Exception e) { 352 } 353 354 if (setter != null && (setter.getModifiers() & Modifier.STATIC) != 0 && 355 method.getReturnType() != setter.getParameterTypes()[0]) 356 setter = null; 357 358 name = name.substring(0, 1).toLowerCase() + name.substring(1); 359 property = newMethodProperty(method, setter, name); 360 break classLoop; 361 } 362 } 363 } 364 365 if (searchFields) { 366 for (Field field : c.getDeclaredFields()) { 367 if ((field.getModifiers() & Modifier.STATIC) == 0 && field.isAnnotationPresent(annotationClass)) { 368 property = newFieldProperty(field); 369 break classLoop; 370 } 371 } 372 } 373 } 374 375 if (property == null) 376 property = NULL_PROPERTY; 377 378 Property previous = singlePropertyCache.putIfAbsent(key, property); 379 if (previous != null) 380 property = previous; 381 } 382 383 return (property != NULL_PROPERTY ? property : null); 384 } 385 386 protected static interface SinglePropertyKey { 387 } 388 389 protected static class AnnotatedPropertyKey implements SinglePropertyKey { 390 391 private final Class<?> cls; 392 private final Class<? extends Annotation> annotationClass; 393 394 public AnnotatedPropertyKey(Class<?> cls, Class<? extends Annotation> annotationClass) { 395 this.cls = cls; 396 this.annotationClass = annotationClass; 397 } 398 399 public Class<?> getCls() { 400 return cls; 401 } 402 403 public Class<? extends Annotation> getAnnotationClass() { 404 return annotationClass; 405 } 406 407 @Override 408 public int hashCode() { 409 return cls.hashCode() + annotationClass.hashCode(); 410 } 411 412 @Override 413 public boolean equals(Object obj) { 414 if (obj == this) 415 return true; 416 if (!(obj instanceof AnnotatedPropertyKey)) 417 return false; 418 AnnotatedPropertyKey key = (AnnotatedPropertyKey)obj; 419 return cls.equals(key.cls) && annotationClass.equals(key.annotationClass); 420 } 421 } 422 423 protected static class NameTypePropertyKey implements SinglePropertyKey { 424 425 private final Class<?> cls; 426 private final String name; 427 private final Class<?> type; 428 429 public NameTypePropertyKey(Class<?> cls, String name, Class<?> type) { 430 this.cls = cls; 431 this.name = name; 432 this.type = type; 433 } 434 435 public Class<?> getCls() { 436 return cls; 437 } 438 439 public String getName() { 440 return name; 441 } 442 443 public Class<?> getType() { 444 return type; 445 } 446 447 @Override 448 public int hashCode() { 449 return cls.hashCode() + name.hashCode() + type.hashCode(); 450 } 451 452 @Override 453 public boolean equals(Object obj) { 454 if (obj == this) 455 return true; 456 if (!(obj instanceof NameTypePropertyKey)) 457 return false; 458 NameTypePropertyKey key = (NameTypePropertyKey)obj; 459 return cls.equals(key.cls) && name.equals(key.name) && type.equals(key.type); 460 } 461 } 462} 463