content: package com.taobao.arthas.core.util.reflect;
  
  import com.taobao.arthas.core.util.StringUtils;
  
  import java.lang.reflect.AccessibleObject;
  import java.lang.reflect.Field;
  import java.lang.reflect.Member;
  import java.lang.reflect.Modifier;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashSet;
  import java.util.LinkedHashSet;
  import java.util.List;
  
  /**
   * @author ralf0131 2016-12-28 14:39.
   */
  public class FieldUtils {
  
      private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE;
  
      /**
       * Reads the named {@code public} {@link Field}. Only the class of the specified object will be considered.
       *
       * @param target
       *            the object to reflect, must not be {@code null}
       * @param fieldName
       *            the field name to obtain
       * @return the value of the field
       * @throws IllegalArgumentException
       *             if {@code target} is {@code null}, or the field name is blank or empty or could not be found
       * @throws IllegalAccessException
       *             if the named field is not {@code public}
       */
      public static Object readDeclaredField(final Object target, final String fieldName) throws IllegalAccessException {
          return readDeclaredField(target, fieldName, false);
      }
  
      /**
       * Gets a {@link Field} value by name. Only the class of the specified object will be considered.
       *
       * @param target
       *            the object to reflect, must not be {@code null}
       * @param fieldName
       *            the field name to obtain
       * @param forceAccess
       *            whether to break scope restrictions using the
       *            {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
       *            match public fields.
       * @return the Field object
       * @throws IllegalArgumentException
       *             if {@code target} is {@code null}, or the field name is blank or empty or could not be found
       * @throws IllegalAccessException
       *             if the field is not made accessible
       */
      public static Object readDeclaredField(final Object target, final String fieldName, final boolean forceAccess) throws IllegalAccessException {
          isTrue(target != null, "target object must not be null");
          final Class<?> cls = target.getClass();
          final Field field = getDeclaredField(cls, fieldName, forceAccess);
          isTrue(field != null, "Cannot locate declared field %s.%s", cls, fieldName);
          // already forced access above, don't repeat it here:
          return readField(field, target, false);
      }
  
      /**
       * Gets an accessible {@link Field} by name, breaking scope if requested. Only the specified class will be
       * considered.
       *
       * @param cls
       *            the {@link Class} to reflect, must not be {@code null}
       * @param fieldName
       *            the field name to obtain
       * @param forceAccess
       *            whether to break scope restrictions using the
       *            {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
       *            match {@code public} fields.
       * @return the Field object
       * @throws IllegalArgumentException
       *             if the class is {@code null}, or the field name is blank or empty
       */
      public static Field getDeclaredField(final Class<?> cls, final String fieldName, final boolean forceAccess) {
          isTrue(cls != null, "The class must not be null");
          isTrue(!StringUtils.isBlank(fieldName), "The field name must not be blank/empty");
          try {
              // only consider the specified class by using getDeclaredField()
              final Field field = cls.getDeclaredField(fieldName);
              if (!isAccessible(field)) {
                  if (forceAccess) {
                      field.setAccessible(true);
                  } else {
                      return null;
                  }
              }
              return field;
          } catch (final NoSuchFieldException e) { // NOPMD
              // ignore
          }
          return null;
      }
  
      /**
       * Reads a {@link Field}.
       *
       * @param field
       *            the field to use
       * @param target
       *            the object to call on, may be {@code null} for {@code static} fields
       * @param forceAccess
       *            whether to break scope restrictions using the
       *            {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method.
       * @return the field value
       * @throws IllegalArgumentException
       *             if the field is {@code null}
       * @throws IllegalAccessException
       *             if the field is not made accessible
       */
      public static Object readField(final Field field, final Object target, final boolean forceAccess) throws IllegalAccessException {
          isTrue(field != null, "The field must not be null");
          if (forceAccess && !field.isAccessible()) {
              field.setAccessible(true);
          } else {
              setAccessibleWorkaround(field);
          }
          return field.get(target);
      }
  
      /**
       * Reads an accessible {@code static} {@link Field}.
       *
       * @param field
       *            to read
       * @return the field value
       * @throws IllegalArgumentException
       *             if the field is {@code null}, or not {@code static}
       * @throws IllegalAccessException
       *             if the field is not accessible
       */
      public static Object readStaticField(final Field field) throws IllegalAccessException {
          return readStaticField(field, false);
      }
  
      /**
       * Reads a static {@link Field}.
       *
       * @param field
       *            to read
       * @param forceAccess
       *            whether to break scope restrictions using the
       *            {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method.
       * @return the field value
       * @throws IllegalArgumentException
       *             if the field is {@code null} or not {@code static}
       * @throws IllegalAccessException
       *             if the field is not made accessible
       */
      public static Object readStaticField(final Field field, final boolean forceAccess) throws IllegalAccessException {
          isTrue(field != null, "The field must not be null");
          isTrue(Modifier.isStatic(field.getModifiers()), "The field '%s' is not static", field.getName());
          return readField(field, (Object) null, forceAccess);
      }
  
      /**
       * Writes a {@code public static} {@link Field}.
       *
       * @param field
       *            to write
       * @param value
       *            to set
       * @throws IllegalArgumentException
       *             if the field is {@code null} or not {@code static}, or {@code value} is not assignable
       * @throws IllegalAccessException
       *             if the field is not {@code public} or is {@code final}
       */
      public static void writeStaticField(final Field field, final Object value) throws IllegalAccessException {
          writeStaticField(field, value, false);
      }
  
      /**
       * Writes a static {@link Field}.
       *
       * @param field
       *            to write
       * @param value
       *            to set
       * @param forceAccess
       *            whether to break scope restrictions using the
       *            {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
       *            match {@code public} fields.
       * @throws IllegalArgumentException
       *             if the field is {@code null} or not {@code static}, or {@code value} is not assignable
       * @throws IllegalAccessException
       *             if the field is not made accessible or is {@code final}
       */
      public static void writeStaticField(final Field field, final Object value, final boolean forceAccess) throws IllegalAccessException {
          isTrue(field != null, "The field must not be null");
          isTrue(Modifier.isStatic(field.getModifiers()), "The field %s.%s is not static", field.getDeclaringClass().getName(),
                  field.getName());
          writeField(field, (Object) null, value, forceAccess);
      }
  
      /**
       * Writes a {@link Field}.
       *
       * @param field
       *            to write
       * @param target
       *            the object to call on, may be {@code null} for {@code static} fields
       * @param value
       *            to set
       * @param forceAccess
       *            whether to break scope restrictions using the
       *            {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
       *            match {@code public} fields.
       * @throws IllegalArgumentException
       *             if the field is {@code null} or {@code value} is not assignable
       * @throws IllegalAccessException
       *             if the field is not made accessible or is {@code final}
       */
      public static void writeField(final Field field, final Object target, final Object value, final boolean forceAccess)
              throws IllegalAccessException {
          isTrue(field != null, "The field must not be null");
          if (forceAccess && !field.isAccessible()) {
              field.setAccessible(true);
          } else {
              setAccessibleWorkaround(field);
          }
          field.set(target, value);
      }
  
      /**
       * Gets all fields of the given class and its parents (if any).
       *
       * @param cls
       *            the {@link Class} to query
       * @return an array of Fields (possibly empty).
       * @throws IllegalArgumentException
       *             if the class is {@code null}
       * @since 3.2
       */
      public static Field[] getAllFields(final Class<?> cls) {
          final List<Field> allFieldsList = getAllFieldsList(cls);
          return allFieldsList.toArray(new Field[0]);
      }
  
      /**
       * Gets all fields of the given class and its parents (if any).
       *
       * @param cls
       *            the {@link Class} to query
       * @return an array of Fields (possibly empty).
       * @throws IllegalArgumentException
       *             if the class is {@code null}
       * @since 3.2
       */
      public static List<Field> getAllFieldsList(final Class<?> cls) {
          isTrue(cls != null, "The class must not be null");
          final List<Field> allFields = new ArrayList<Field>();
          Class<?> currentClass = cls;
          while (currentClass != null) {
              final Field[] declaredFields = currentClass.getDeclaredFields();
              allFields.addAll(Arrays.asList(declaredFields));
              currentClass = currentClass.getSuperclass();
          }
          return allFields;
      }
  
      /**
       * Gets an accessible {@link Field} by name respecting scope. Superclasses/interfaces will be considered.
       *
       * @param cls
       *            the {@link Class} to reflect, must not be {@code null}
       * @param fieldName
       *            the field name to obtain
       * @return the Field object
       * @throws IllegalArgumentException
       *             if the class is {@code null}, or the field name is blank or empty
       */
      public static Field getField(final Class<?> cls, final String fieldName) {
          final Field field = getField(cls, fieldName, false);
          setAccessibleWorkaround(field);
          return field;
      }
  
      /**
       * Gets an accessible {@link Field} by name, breaking scope if requested. Superclasses/interfaces will be
       * considered.
       *
       * @param cls
       *            the {@link Class} to reflect, must not be {@code null}
       * @param fieldName
       *            the field name to obtain
       * @param forceAccess
       *            whether to break scope restrictions using the
       *            {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} method. {@code false} will only
       *            match {@code public} fields.
       * @return the Field object
       * @throws IllegalArgumentException
       *             if the class is {@code null}, or the field name is blank or empty or is matched at multiple places
       *             in the inheritance hierarchy
       */
      public static Field getField(final Class<?> cls, final String fieldName, final boolean forceAccess) {
          isTrue(cls != null, "The class must not be null");
          isTrue(!StringUtils.isBlank(fieldName), "The field name must not be blank/empty");
          // FIXME is this workaround still needed? lang requires Java 6
          // Sun Java 1.3 has a bugged implementation of getField hence we write the
          // code ourselves
  
          // getField() will return the Field object with the declaring class
          // set correctly to the class that declares the field. Thus requesting the
          // field on a subclass will return the field from the superclass.
          //
          // priority order for lookup:
          // searchclass private/protected/package/public
          // superclass protected/package/public
          // private/different package blocks access to further superclasses
          // implementedinterface public
  
          // check up the superclass hierarchy
          for (Class<?> acls = cls; acls != null; acls = acls.getSuperclass()) {
              try {
                  final Field field = acls.getDeclaredField(fieldName);
                  // getDeclaredField checks for non-public scopes as well
                  // and it returns accurate results
                  if (!Modifier.isPublic(field.getModifiers())) {
                      if (forceAccess) {
                          field.setAccessible(true);
                      } else {
                          continue;
                      }
                  }
                  return field;
              } catch (final NoSuchFieldException ex) { // NOPMD
                  // ignore
              }
          }
          // check the public interface case. This must be manually searched for
          // incase there is a public supersuperclass field hidden by a private/package
          // superclass field.
          Field match = null;
          for (final Class<?> class1 : getAllInterfaces(cls)) {
              try {
                  final Field test = class1.getField(fieldName);
                  isTrue(match == null, "Reference to field %s is ambiguous relative to %s"
                          + "; a matching field exists on two or more implemented interfaces.", fieldName, cls);
                  match = test;
              } catch (final NoSuchFieldException ex) { // NOPMD
                  // ignore
              }
          }
          return match;
      }
  
      /**
       * <p>Gets a {@code List} of all interfaces implemented by the given
       * class and its superclasses.</p>
       *
       * <p>The order is determined by looking through each interface in turn as
       * declared in the source file and following its hierarchy up. Then each
       * superclass is considered in the same way. Later duplicates are ignored,
       * so the order is maintained.</p>
       *
       * @param cls  the class to look up, may be {@code null}
       * @return the {@code List} of interfaces in order,
       *  {@code null} if null input
       */
      public static List<Class<?>> getAllInterfaces(final Class<?> cls) {
          if (cls == null) {
              return null;
          }
  
          final LinkedHashSet<Class<?>> interfacesFound = new LinkedHashSet<Class<?>>();
          getAllInterfaces(cls, interfacesFound);
  
          return new ArrayList<Class<?>>(interfacesFound);
      }
  
      /**
       * Get the interfaces for the specified class.
       *
       * @param cls  the class to look up, may be {@code null}
       * @param interfacesFound the {@code Set} of interfaces for the class
       */
      private static void getAllInterfaces(Class<?> cls, final HashSet<Class<?>> interfacesFound) {
          while (cls != null) {
              final Class<?>[] interfaces = cls.getInterfaces();
  
              for (final Class<?> i : interfaces) {
                  if (interfacesFound.add(i)) {
                      getAllInterfaces(i, interfacesFound);
                  }
              }
  
              cls = cls.getSuperclass();
          }
      }
  
  
      /**
       * XXX Default access superclass workaround.
       *
       * When a {@code public} class has a default access superclass with {@code public} members,
       * these members are accessible. Calling them from compiled code works fine.
       * Unfortunately, on some JVMs, using reflection to invoke these members
       * seems to (wrongly) prevent access even when the modifier is {@code public}.
       * Calling {@code setAccessible(true)} solves the problem but will only work from
       * sufficiently privileged code. Better workarounds would be gratefully
       * accepted.
       * @param o the AccessibleObject to set as accessible
       * @return a boolean indicating whether the accessibility of the object was set to true.
       */
      static boolean setAccessibleWorkaround(final AccessibleObject o) {
          if (o == null || o.isAccessible()) {
              return false;
          }
          final Member m = (Member) o;
          if (!o.isAccessible() && Modifier.isPublic(m.getModifiers()) && isPackageAccess(m.getDeclaringClass().getModifiers())) {
              try {
                  o.setAccessible(true);
                  return true;
              } catch (final SecurityException e) { // NOPMD
                  // ignore in favor of subsequent IllegalAccessException
              }
          }
          return false;
      }
  
      /**
       * Returns whether a given set of modifiers implies package access.
       * @param modifiers to test
       * @return {@code true} unless {@code package}/{@code protected}/{@code private} modifier detected
       */
      static boolean isPackageAccess(final int modifiers) {
          return (modifiers & ACCESS_TEST) == 0;
      }
  
      /**
       * Returns whether a {@link Member} is accessible.
       * @param m Member to check
       * @return {@code true} if <code>m</code> is accessible
       */
      static boolean isAccessible(final Member m) {
          return m != null && Modifier.isPublic(m.getModifiers()) && !m.isSynthetic();
      }
  
      static void isTrue(final boolean expression, final String message, final Object... values) {
          if (!expression) {
              throw new IllegalArgumentException(String.format(message, values));
          }
      }
  }
  