001/* 002 * Copyright (C) 2012 Facebook, Inc. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may 005 * not use this file except in compliance with the License. You may obtain 006 * a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 012 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 013 * License for the specific language governing permissions and limitations 014 * under the License. 015 */ 016package com.facebook.swift.codec.metadata; 017 018import com.facebook.swift.codec.ThriftField; 019import com.google.common.base.Function; 020import com.google.common.base.Throwables; 021import com.google.common.collect.ImmutableList; 022import com.google.common.collect.ImmutableSet; 023import com.google.common.collect.Lists; 024import com.google.common.reflect.TypeToken; 025import com.thoughtworks.paranamer.AdaptiveParanamer; 026import com.thoughtworks.paranamer.AnnotationParanamer; 027import com.thoughtworks.paranamer.BytecodeReadingParanamer; 028import com.thoughtworks.paranamer.CachingParanamer; 029import com.thoughtworks.paranamer.Paranamer; 030 031import javax.annotation.Nullable; 032 033import java.lang.annotation.Annotation; 034import java.lang.reflect.AccessibleObject; 035import java.lang.reflect.Array; 036import java.lang.reflect.Constructor; 037import java.lang.reflect.Field; 038import java.lang.reflect.Method; 039import java.lang.reflect.Type; 040import java.util.ArrayList; 041import java.util.Arrays; 042import java.util.Collection; 043import java.util.Iterator; 044import java.util.List; 045import java.util.Map; 046import java.util.Set; 047import java.util.concurrent.Future; 048 049import static com.google.common.base.Preconditions.checkNotNull; 050import static java.lang.reflect.Modifier.isStatic; 051 052public final class ReflectionHelper 053{ 054 private ReflectionHelper() 055 { 056 } 057 058 private static final Type MAP_KEY_TYPE; 059 private static final Type MAP_VALUE_TYPE; 060 private static final Type ITERATOR_TYPE; 061 private static final Type ITERATOR_ELEMENT_TYPE; 062 private static final Type FUTURE_RETURN_TYPE; 063 064 static { 065 try { 066 Method mapPutMethod = Map.class.getMethod("put", Object.class, Object.class); 067 MAP_KEY_TYPE = mapPutMethod.getGenericParameterTypes()[0]; 068 MAP_VALUE_TYPE = mapPutMethod.getGenericParameterTypes()[1]; 069 070 ITERATOR_TYPE = Iterable.class.getMethod("iterator").getGenericReturnType(); 071 ITERATOR_ELEMENT_TYPE = Iterator.class.getMethod("next").getGenericReturnType(); 072 073 Method futureGetMethod = Future.class.getMethod("get"); 074 FUTURE_RETURN_TYPE = futureGetMethod.getGenericReturnType(); 075 } 076 catch (Exception e) { 077 throw Throwables.propagate(e); 078 } 079 } 080 081 public static boolean isArray(Type type) 082 { 083 return TypeToken.of(type).getComponentType() != null; 084 } 085 086 public static Class<?> getArrayOfType(Type componentType) 087 { 088 // this creates an extra object but is the simplest way to get an array class 089 Class<?> rawComponentType = TypeToken.of(componentType).getRawType(); 090 return Array.newInstance(rawComponentType, 0).getClass(); 091 } 092 093 public static Type getMapKeyType(Type type) 094 { 095 return TypeToken.of(type).resolveType(MAP_KEY_TYPE).getType(); 096 } 097 098 public static Type getMapValueType(Type type) 099 { 100 return TypeToken.of(type).resolveType(MAP_VALUE_TYPE).getType(); 101 } 102 103 public static Type getIterableType(Type type) 104 { 105 return TypeToken.of(type).resolveType(ITERATOR_TYPE).resolveType(ITERATOR_ELEMENT_TYPE).getType(); 106 } 107 108 public static Type getFutureReturnType(Type type) 109 { 110 return TypeToken.of(type).resolveType(FUTURE_RETURN_TYPE).getType(); 111 } 112 113 public static <T extends Annotation> Set<T> getEffectiveClassAnnotations(Class<?> type, Class<T> annotation) 114 { 115 // if the class is directly annotated, it is considered the only annotation 116 if (type.isAnnotationPresent(annotation)) { 117 return ImmutableSet.of(type.getAnnotation(annotation)); 118 } 119 120 // otherwise find all annotations from all super classes and interfaces 121 ImmutableSet.Builder<T> builder = ImmutableSet.builder(); 122 addEffectiveClassAnnotations(type, annotation, builder); 123 return builder.build(); 124 } 125 126 private static <T extends Annotation> void addEffectiveClassAnnotations(Class<?> type, Class<T> annotation, ImmutableSet.Builder<T> builder) 127 { 128 if (type.isAnnotationPresent(annotation)) { 129 builder.add(type.getAnnotation(annotation)); 130 return; 131 } 132 if (type.getSuperclass() != null) { 133 addEffectiveClassAnnotations(type.getSuperclass(), annotation, builder); 134 } 135 for (Class<?> anInterface : type.getInterfaces()) { 136 addEffectiveClassAnnotations(anInterface, annotation, builder); 137 } 138 } 139 140 public static Iterable<Method> getAllDeclaredMethods(Class<?> type) 141 { 142 ImmutableList.Builder<Method> methods = ImmutableList.builder(); 143 144 for (Class<?> clazz = type; clazz != null && !clazz.equals(Object.class); clazz = clazz.getSuperclass()) { 145 methods.addAll(ImmutableList.copyOf(clazz.getDeclaredMethods())); 146 } 147 return methods.build(); 148 } 149 150 public static Iterable<Field> getAllDeclaredFields(Class<?> type) 151 { 152 ImmutableList.Builder<Field> fields = ImmutableList.builder(); 153 for (Class<?> clazz = type; clazz != null && !clazz.equals(Object.class); clazz = clazz.getSuperclass()) { 154 fields.addAll(ImmutableList.copyOf(clazz.getDeclaredFields())); 155 } 156 return fields.build(); 157 } 158 159 /** 160 * Find methods that are tagged with a given annotation somewhere in the hierarchy 161 */ 162 public static Collection<Method> findAnnotatedMethods(Class<?> type, Class<? extends Annotation> annotation) 163 { 164 List<Method> result = new ArrayList<>(); 165 166 // gather all publicly available methods 167 // this returns everything, even if it's declared in a parent 168 for (Method method : type.getMethods()) { 169 // skip methods that are used internally by the vm for implementing covariance, etc 170 if (method.isSynthetic() || method.isBridge() || isStatic(method.getModifiers())) { 171 continue; 172 } 173 174 // look for annotations recursively in super-classes or interfaces 175 Method managedMethod = findAnnotatedMethod( 176 type, 177 annotation, 178 method.getName(), 179 method.getParameterTypes()); 180 if (managedMethod != null) { 181 result.add(managedMethod); 182 } 183 } 184 185 return result; 186 } 187 188 @SuppressWarnings("PMD.EmptyCatchBlock") 189 public static Method findAnnotatedMethod(Class<?> configClass, Class<? extends Annotation> annotation, String methodName, Class<?>... paramTypes) 190 { 191 try { 192 Method method = configClass.getDeclaredMethod(methodName, paramTypes); 193 if (method != null && method.isAnnotationPresent(annotation)) { 194 return method; 195 } 196 } 197 catch (NoSuchMethodException e) { 198 // ignore 199 } 200 201 if (configClass.getSuperclass() != null) { 202 Method managedMethod = findAnnotatedMethod( 203 configClass.getSuperclass(), 204 annotation, 205 methodName, 206 paramTypes); 207 if (managedMethod != null) { 208 return managedMethod; 209 } 210 } 211 212 for (Class<?> iface : configClass.getInterfaces()) { 213 Method managedMethod = findAnnotatedMethod(iface, annotation, methodName, paramTypes); 214 if (managedMethod != null) { 215 return managedMethod; 216 } 217 } 218 219 return null; 220 } 221 222 public static Collection<Field> findAnnotatedFields(Class<?> type, Class<? extends Annotation> annotation) 223 { 224 List<Field> result = new ArrayList<>(); 225 226 // gather all publicly available fields 227 // this returns everything, even if it's declared in a parent 228 for (Field field : type.getFields()) { 229 if (field.isSynthetic() || isStatic(field.getModifiers())) { 230 continue; 231 } 232 233 if (field.isAnnotationPresent(annotation)) { 234 result.add(field); 235 } 236 } 237 238 return result; 239 } 240 241 private static final Paranamer PARANAMER = new CachingParanamer( 242 new AdaptiveParanamer( 243 new ThriftFieldParanamer(), 244 new BytecodeReadingParanamer(), 245 new GeneralParanamer())); 246 247 public static String[] extractParameterNames(AccessibleObject methodOrConstructor) 248 { 249 String[] names = PARANAMER.lookupParameterNames(methodOrConstructor); 250 return names; 251 } 252 253 private static class ThriftFieldParanamer extends AnnotationParanamer 254 { 255 @Override 256 protected String getNamedValue(Annotation annotation) 257 { 258 if (annotation instanceof ThriftField) { 259 String name = ((ThriftField) annotation).name(); 260 if (!name.isEmpty()) { 261 return name; 262 } 263 } 264 return super.getNamedValue(annotation); 265 } 266 267 @Override 268 protected boolean isNamed(Annotation annotation) 269 { 270 return annotation instanceof ThriftField || super.isNamed(annotation); 271 } 272 } 273 274 private static class GeneralParanamer implements Paranamer 275 { 276 @Override 277 public String[] lookupParameterNames(AccessibleObject methodOrConstructor) 278 { 279 String[] names; 280 if (methodOrConstructor instanceof Method) { 281 Method method = (Method) methodOrConstructor; 282 names = new String[method.getParameterTypes().length]; 283 } 284 else if (methodOrConstructor instanceof Constructor<?>) { 285 Constructor<?> constructor = (Constructor<?>) methodOrConstructor; 286 names = new String[constructor.getParameterTypes().length]; 287 } 288 else { 289 throw new IllegalArgumentException("methodOrConstructor is not an instance of Method or Constructor but is " + methodOrConstructor.getClass().getName()); 290 } 291 for (int i = 0; i < names.length; i++) { 292 names[i] = "arg" + i; 293 } 294 return names; 295 } 296 297 @Override 298 public String[] lookupParameterNames(AccessibleObject methodOrConstructor, boolean throwExceptionIfMissing) 299 { 300 return lookupParameterNames(methodOrConstructor); 301 } 302 } 303 304 public static String extractFieldName(Method method) 305 { 306 checkNotNull(method, "method is null"); 307 return extractFieldName(method.getName()); 308 } 309 310 public static String extractFieldName(String methodName) 311 { 312 checkNotNull(methodName, "methodName is null"); 313 if ((methodName.startsWith("get") || methodName.startsWith("set")) && methodName.length() > 3) { 314 String name = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4); 315 return name; 316 } 317 else if (methodName.startsWith("is") && methodName.length() > 2) { 318 String name = Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3); 319 return name; 320 } 321 else { 322 return methodName; 323 } 324 } 325 326 public static Type resolveFieldType(Type structType, Type genericType) 327 { 328 return TypeToken.of(structType).resolveType(genericType).getType(); 329 } 330 331 public static Type[] resolveFieldTypes(final Type structType, Type[] genericTypes) 332 { 333 return Lists.transform(Arrays.asList(genericTypes), new Function<Type, Type>() 334 { 335 @Nullable 336 @Override 337 public Type apply(@Nullable Type input) 338 { 339 return resolveFieldType(structType, input); 340 } 341 }).toArray(new Type[0]); 342 } 343}