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}