package com.sobot.gson;

import com.sobot.gson.internal.ReflectionAccessFilterHelper;

import java.lang.reflect.AccessibleObject;

/**
 * Filter for determining whether reflection based serialization and
 * deserialization is allowed for a class.
 *
 * <p>A filter can be useful in multiple scenarios, for example when
 * upgrading to newer Java versions which use the Java Platform Module
 * System (JPMS). A filter then allows to {@linkplain FilterResult#BLOCK_INACCESSIBLE
 * prevent making inaccessible members accessible}, even if the used
 * Java version might still allow illegal access (but logs a warning),
 * or if {@code java} command line arguments are used to open the inaccessible
 * packages to other parts of the application. This interface defines some
 * convenience filters for this task, such as {@link #BLOCK_INACCESSIBLE_JAVA}.
 *
 * <p>A filter can also be useful to prevent mixing model classes of a
 * project with other non-model classes; the filter could
 * {@linkplain FilterResult#BLOCK_ALL block all reflective access} to
 * non-model classes.
 *
 * <p>A reflection access filter is similar to an {@link ExclusionStrategy}
 * with the major difference that a filter will cause an exception to be
 * thrown when access is disallowed while an exclusion strategy just skips
 * fields and classes.
 *
 * @see GsonBuilder#addReflectionAccessFilter(ReflectionAccessFilter)
 */
public interface ReflectionAccessFilter {
  /**
   * Result of a filter check.
   */
  enum FilterResult {
    /**
     * Reflection access for the class is allowed.
     *
     * <p>Note that this does not affect the Java access checks in any way,
     * it only permits Gson to try using reflection for a class. The Java
     * runtime might still deny such access.
     */
    ALLOW,
    /**
     * The filter is indecisive whether reflection access should be allowed.
     * The next registered filter will be consulted to get the result. If
     * there is no next filter, this result acts like {@link #ALLOW}.
     */
    INDECISIVE,
    /**
     * Blocks reflection access if a member of the class is not accessible
     * by default and would have to be made accessible. This is unaffected
     * by any {@code java} command line arguments being used to make packages
     * accessible, or by module declaration directives which <i>open</i> the
     * complete module or certain packages for reflection and will consider
     * such packages inaccessible.
     *
     * <p>Note that this <b>only works for Java 9 and higher</b>, for older
     * Java versions its functionality will be limited and it might behave like
     * {@link #ALLOW}. Access checks are only performed as defined by the Java
     * Language Specification (<a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.6">JLS 11 &sect;6.6</a>),
     * restrictions imposed by a {@link SecurityManager} are not considered.
     *
     * <p>This result type is mainly intended to help enforce the access checks of
     * the Java Platform Module System. It allows detecting illegal access, even if
     * the used Java version would only log a warning, or is configured to open
     * packages for reflection using command line arguments.
     *
     * @see AccessibleObject#canAccess(Object)
     */
    BLOCK_INACCESSIBLE,
    /**
     * Blocks all reflection access for the class. Other means for serializing
     * and deserializing the class, such as a {@link TypeAdapter}, have to
     * be used.
     */
    BLOCK_ALL
  }

  /**
   * Blocks all reflection access to members of standard Java classes which are
   * not accessible by default. However, reflection access is still allowed for
   * classes for which all fields are accessible and which have an accessible
   * no-args constructor (or for which an {@link InstanceCreator} has been registered).
   *
   * <p>If this filter encounters a class other than a standard Java class it
   * returns {@link FilterResult#INDECISIVE}.
   *
   * <p>This filter is mainly intended to help enforcing the access checks of
   * Java Platform Module System. It allows detecting illegal access, even if
   * the used Java version would only log a warning, or is configured to open
   * packages for reflection. However, this filter <b>only works for Java 9 and
   * higher</b>, when using an older Java version its functionality will be
   * limited.
   *
   * <p>Note that this filter might not cover all standard Java classes. Currently
   * only classes in a {@code java.*} or {@code javax.*} package are considered. The
   * set of detected classes might be expanded in the future without prior notice.
   *
   * @see FilterResult#BLOCK_INACCESSIBLE
   */
  ReflectionAccessFilter BLOCK_INACCESSIBLE_JAVA = new ReflectionAccessFilter() {
    @Override public FilterResult check(Class<?> rawClass) {
      return ReflectionAccessFilterHelper.isJavaType(rawClass)
        ? FilterResult.BLOCK_INACCESSIBLE
        : FilterResult.INDECISIVE;
    }
  };

  /**
   * Blocks all reflection access to members of standard Java classes.
   *
   * <p>If this filter encounters a class other than a standard Java class it
   * returns {@link FilterResult#INDECISIVE}.
   *
   * <p>This filter is mainly intended to prevent depending on implementation
   * details of the Java platform and to help applications prepare for upgrading
   * to the Java Platform Module System.
   *
   * <p>Note that this filter might not cover all standard Java classes. Currently
   * only classes in a {@code java.*} or {@code javax.*} package are considered. The
   * set of detected classes might be expanded in the future without prior notice.
   *
   * @see #BLOCK_INACCESSIBLE_JAVA
   * @see FilterResult#BLOCK_ALL
   */
  ReflectionAccessFilter BLOCK_ALL_JAVA = new ReflectionAccessFilter() {
    @Override public FilterResult check(Class<?> rawClass) {
      return ReflectionAccessFilterHelper.isJavaType(rawClass)
        ? FilterResult.BLOCK_ALL
        : FilterResult.INDECISIVE;
    }
  };

  /**
   * Blocks all reflection access to members of standard Android classes.
   *
   * <p>If this filter encounters a class other than a standard Android class it
   * returns {@link FilterResult#INDECISIVE}.
   *
   * <p>This filter is mainly intended to prevent depending on implementation
   * details of the Android platform.
   *
   * <p>Note that this filter might not cover all standard Android classes. Currently
   * only classes in an {@code android.*} or {@code androidx.*} package, and standard
   * Java classes in a {@code java.*} or {@code javax.*} package are considered. The
   * set of detected classes might be expanded in the future without prior notice.
   *
   * @see FilterResult#BLOCK_ALL
   */
  ReflectionAccessFilter BLOCK_ALL_ANDROID = new ReflectionAccessFilter() {
    @Override public FilterResult check(Class<?> rawClass) {
      return ReflectionAccessFilterHelper.isAndroidType(rawClass)
        ? FilterResult.BLOCK_ALL
        : FilterResult.INDECISIVE;
    }
  };

  /**
   * Blocks all reflection access to members of classes belonging to programming
   * language platforms, such as Java, Android, Kotlin or Scala.
   *
   * <p>If this filter encounters a class other than a standard platform class it
   * returns {@link FilterResult#INDECISIVE}.
   *
   * <p>This filter is mainly intended to prevent depending on implementation
   * details of the platform classes.
   *
   * <p>Note that this filter might not cover all platform classes. Currently it
   * combines the filters {@link #BLOCK_ALL_JAVA} and {@link #BLOCK_ALL_ANDROID},
   * and checks for other language-specific platform classes like {@code kotlin.*}.
   * The set of detected classes might be expanded in the future without prior notice.
   *
   * @see FilterResult#BLOCK_ALL
   */
  ReflectionAccessFilter BLOCK_ALL_PLATFORM = new ReflectionAccessFilter() {
    @Override public FilterResult check(Class<?> rawClass) {
      return ReflectionAccessFilterHelper.isAnyPlatformType(rawClass)
        ? FilterResult.BLOCK_ALL
        : FilterResult.INDECISIVE;
    }
  };

  /**
   * Checks if reflection access should be allowed for a class.
   *
   * @param rawClass
   *    Class to check
   * @return
   *    Result indicating whether reflection access is allowed
   */
  FilterResult check(Class<?> rawClass);
}
