All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.echobox.api.tiktok.util.ReflectionUtils Maven / Gradle / Ivy

/**
 * Copyright (c) 2010-2018 Mark Allen, Norbert Bartels.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
//Source - https://restfb.com/

package com.echobox.api.tiktok.util;

import static java.lang.String.format;
import static java.util.Collections.sort;
import static java.util.Collections.synchronizedMap;
import static java.util.Collections.unmodifiableList;

import com.echobox.api.tiktok.exception.TikTokJsonMappingException;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * A collection of reflection-related utility methods.
 * 
 * @author Mark Allen
 * @author Igor Kabiljo
 * @author Scott Hernandez
 */
public final class ReflectionUtils {
  /**
   * In-memory shared cache of reflection data for {@link #findFieldsWithAnnotation(Class, Class)}.
   */
  private static final Map> FIELDS_WITH_ANNOTATION_CACHE =
      synchronizedMap(new HashMap>());

  /**
   * In-memory shared cache of reflection data for {@link #findMethodsWithAnnotation(Class, Class)}.
   */
  private static final Map> METHODS_WITH_ANNOTATION_CACHE =
      synchronizedMap(new HashMap>());

  /**
   * Prevents instantiation.
   */
  private ReflectionUtils() {
    // prevent instantiation
  }

  /**
   * Is the given {@code object} a primitive type or wrapper for a primitive type?
   * 
   * @param object The object to check for primitive-ness.
   *
   * @return {@code true} if {@code object} is a primitive type or wrapper for a primitive type,
   * {@code false} otherwise.
   */
  public static boolean isPrimitive(Object object) {
    if (object == null) {
      return false;
    }

    Class type = object.getClass();

    return object instanceof String || (object instanceof Integer || Integer.TYPE.equals(type))
        || (object instanceof Boolean || Boolean.TYPE.equals(type))
        || (object instanceof Long || Long.TYPE.equals(type))
        || (object instanceof Double || Double.TYPE.equals(type))
        || (object instanceof Float || Float.TYPE.equals(type))
        || (object instanceof Byte || Byte.TYPE.equals(type))
        || (object instanceof Short || Short.TYPE.equals(type))
        || (object instanceof Character || Character.TYPE.equals(type));
  }

  /**
   * Finds fields on the given {@code type} and all of its superclasses annotated with
   * annotations of type {@code annotationType}.
   * 
   * @param  The annotation type.
   * @param type The target type token.
   * @param annotationType The annotation type token.
   *
   * @return A list of field/annotation pairs.
   */
  public static  List>
      findFieldsWithAnnotation(Class type, Class annotationType) {
    ClassAnnotationCacheKey cacheKey = new ClassAnnotationCacheKey(type, annotationType);

    @SuppressWarnings("unchecked")
    List> cachedResults =
        (List>) FIELDS_WITH_ANNOTATION_CACHE.get(cacheKey);

    if (cachedResults != null) {
      return cachedResults;
    }

    List> fieldsWithAnnotation = new ArrayList<>();

    // Walk all superclasses looking for annotated fields until we hit Object
    while (!Object.class.equals(type) && type != null) {
      for (Field field : type.getDeclaredFields()) {
        T annotation = field.getAnnotation(annotationType);
        if (annotation != null) {
          fieldsWithAnnotation.add(new FieldWithAnnotation<>(field, annotation));
        }

      }

      type = type.getSuperclass();
    }

    fieldsWithAnnotation = unmodifiableList(fieldsWithAnnotation);
    FIELDS_WITH_ANNOTATION_CACHE.put(cacheKey, fieldsWithAnnotation);
    return fieldsWithAnnotation;
  }

  /**
   * Finds methods on the given {@code type} and all of its superclasses annotated with
   * annotations of type {@code annotationType}.
   * 

* These results are cached to mitigate performance overhead. * * @param The annotation type. * @param type The target type token. * @param annotationType The annotation type token. * * @return A list of methods with the given annotation. */ public static List findMethodsWithAnnotation(Class type, Class annotationType) { ClassAnnotationCacheKey cacheKey = new ClassAnnotationCacheKey(type, annotationType); List cachedResults = METHODS_WITH_ANNOTATION_CACHE.get(cacheKey); if (cachedResults != null) { return cachedResults; } List methodsWithAnnotation = new ArrayList<>(); // Walk all superclasses looking for annotated methods until we hit Object while (!Object.class.equals(type)) { for (Method method : type.getDeclaredMethods()) { T annotation = method.getAnnotation(annotationType); if (annotation != null) { methodsWithAnnotation.add(method); } } type = type.getSuperclass(); } methodsWithAnnotation = unmodifiableList(methodsWithAnnotation); METHODS_WITH_ANNOTATION_CACHE.put(cacheKey, methodsWithAnnotation); return methodsWithAnnotation; } /** * For a given {@code field}, get its first parameterized type argument. *

* For example, a field of type {@code List} would have a first type argument of {@code * Long.class}. *

* If the field has no type arguments, {@code null} is returned. * * @param field The field to check. * @return The field's first parameterized type argument, or {@code null} if none exists. */ public static Class getFirstParameterizedTypeArgument(Field field) { return getParameterizedTypeArgument(field, 0); } /** * For a given {@code field}, get its second parameterized type argument. * * If the field has no type arguments, {@code null} is returned. * * @param field The field to check. * * @return The field's second parameterized type argument, or {@code null} if none exists. */ public static Class getSecondParameterizedTypeArgument(Field field) { return getParameterizedTypeArgument(field, 1); } private static Class getParameterizedTypeArgument(Field field, int i) { Type type = field.getGenericType(); if (!(type instanceof ParameterizedType)) { return null; } ParameterizedType parameterizedType = (ParameterizedType) type; Type firstTypeArgument = parameterizedType.getActualTypeArguments()[i]; return (firstTypeArgument instanceof Class) ? (Class) firstTypeArgument : null; } /** * Gets all accessor methods for the given {@code clazz}. * * @param clazz The class for which accessors are extracted. * * @return All accessor methods for the given {@code clazz}. */ public static List getAccessors(Class clazz) { if (clazz == null) { throw new IllegalArgumentException("The 'clazz' parameter cannot be null."); } List methods = new ArrayList<>(); for (Method method : clazz.getMethods()) { String methodName = method.getName(); if (!"getClass".equals(methodName) && !"hashCode".equals(methodName) && method.getReturnType() != null && !Void.class.equals(method.getReturnType()) && method.getParameterTypes().length == 0 && ((methodName.startsWith("get") && methodName.length() > 3) || (methodName.startsWith("is") && methodName.length() > 2) || (methodName.startsWith("has") && methodName.length() > 3))) { methods.add(method); } } // Order the methods alphabetically by name sort(methods, new Comparator() { /** * @see Comparator#compare(Object, Object) */ @Override public int compare(Method method1, Method method2) { return method1.getName().compareTo(method2.getName()); } }); return unmodifiableList(methods); } /** * Reflection-based implementation of {@link Object#toString()}. * * @param object The object to convert to a string representation. * * @return A string representation of {@code object}. * @throws IllegalStateException If an error occurs while performing reflection operations. */ public static String toString(Object object) { StringBuilder buffer = new StringBuilder(object.getClass().getSimpleName()); buffer.append("["); boolean first = true; for (Method method : getAccessors(object.getClass())) { if (first) { first = false; } else { buffer.append(" "); } try { String methodName = method.getName(); int offset = methodName.startsWith("is") ? 2 : 3; methodName = methodName.substring(offset, offset + 1).toLowerCase() + methodName.substring(offset + 1); buffer.append(methodName); buffer.append("="); // Accessors are guaranteed to take no parameters and return a value buffer.append(method.invoke(object)); } catch (Exception e) { throw new IllegalStateException("Unable to reflectively invoke " + method + " on " + object.getClass(), e); } } buffer.append("]"); return buffer.toString(); } /** * Reflection-based implementation of {@link Object#hashCode()}. * * @param object The object to hash. * * @return A hashcode for {@code object}. * @throws IllegalStateException If an error occurs while performing reflection operations. */ public static int hashCode(Object object) { if (object == null) { return 0; } int hashCode = 17; for (Method method : getAccessors(object.getClass())) { try { Object result = method.invoke(object); if (result != null) { hashCode = hashCode * 31 + result.hashCode(); } } catch (Exception e) { throw new IllegalStateException("Unable to reflectively invoke " + method + " on " + object, e); } } return hashCode; } /** * Reflection-based implementation of {@link Object#equals(Object)}. * * @param object1 One object to compare. * @param object2 Another object to compare. * * @return {@code true} if the objects are equal, {@code false} otherwise. * @throws IllegalStateException If an error occurs while performing reflection operations. */ public static boolean equals(Object object1, Object object2) { if (object1 == null && object2 == null) { return true; } if (!(object1 != null && object2 != null)) { return false; } // Bail if the classes aren't at least one-way assignable to each other if (!(object1.getClass().isInstance(object2) || object2.getClass().isInstance(object1))) { return false; } // Only compare accessors that are present in both classes Set accessorMethodsIntersection = new HashSet<>(getAccessors(object1.getClass())); accessorMethodsIntersection.retainAll(getAccessors(object2.getClass())); for (Method method : accessorMethodsIntersection) { try { Object result1 = method.invoke(object1); Object result2 = method.invoke(object2); if (result1 == null && result2 == null) { continue; } if (!(result1 != null && result2 != null)) { return false; } if (!result1.equals(result2)) { return false; } } catch (Exception e) { throw new IllegalStateException("Unable to reflectively invoke " + method, e); } } return true; } /** * Creates a new instance of the given {@code type}. *

* * @param Java type to map to. * @param type Type token. * * @return A new instance of {@code type}. * @throws TikTokJsonMappingException If an error occurs when creating a new instance ({@code type} * is inaccessible, doesn't have a no-arg constructor, etc.) */ public static T createInstance(Class type) { String errorMessage = "Unable to create an instance of " + type + ". Please make sure that if it's a nested class, is marked 'static'. " + "It should have a no-argument constructor."; try { Constructor defaultConstructor = type.getDeclaredConstructor(); if (defaultConstructor == null) { throw new TikTokJsonMappingException("Unable to find a default constructor for " + type); } // Allows protected, private, and package-private constructors to be invoked defaultConstructor.setAccessible(true); return defaultConstructor.newInstance(); } catch (Exception e) { throw new TikTokJsonMappingException(errorMessage, e); } } /** * A field/annotation pair. * * @author Mark Allen */ public static class FieldWithAnnotation { /** * A field. */ private Field field; /** * An annotation on the field. */ private T annotation; /** * Creates a field/annotation pair. * * @param field A field. * @param annotation An annotation on the field. */ public FieldWithAnnotation(Field field, T annotation) { this.field = field; this.annotation = annotation; } /** * Gets the field. * * @return The field. */ public Field getField() { return field; } /** * Gets the annotation on the field. * * @return The annotation on the field. */ public T getAnnotation() { return annotation; } @Override public String toString() { return format("Field %s.%s (%s): %s", field.getDeclaringClass().getName(), field.getName(), field.getType(), annotation); } } /** * Cache key composed of a class and annotation pair. * Used by {@link ReflectionUtils#FIELDS_WITH_ANNOTATION_CACHE}. * * @author Igor Kabiljo */ private static final class ClassAnnotationCacheKey { /** * Class component of this cache key. */ private final Class clazz; /** * Annotation component of this cache key. */ private final Class annotation; /** * Creates a cache key with the given {@code clazz}/@{code annotation} pair. * * @param clazz Class component of this cache key. * @param annotation Annotation component of this cache key. */ private ClassAnnotationCacheKey(Class clazz, Class annotation) { this.clazz = clazz; this.annotation = annotation; } /** * @see Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (annotation == null ? 0 : annotation.hashCode()); result = prime * result + (clazz == null ? 0 : clazz.hashCode()); return result; } /** * @see Object#equals(Object) */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } ClassAnnotationCacheKey other = (ClassAnnotationCacheKey) obj; if (annotation == null) { if (other.annotation != null) { return false; } } else if (!annotation.equals(other.annotation)) { return false; } if (clazz == null) { if (other.clazz != null) { return false; } } else if (!clazz.equals(other.clazz)) { return false; } return true; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy