content: package com.alibaba.gaiax.js.support;
  
  import java.lang.reflect.Array;
  import java.lang.reflect.GenericArrayType;
  import java.lang.reflect.GenericDeclaration;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.ParameterizedType;
  import java.lang.reflect.Type;
  import java.lang.reflect.TypeVariable;
  import java.lang.reflect.WildcardType;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Map;
  import java.util.NoSuchElementException;
  import java.util.Properties;
  
  // https://github.com/square/moshi/blob/master/moshi/src/main/java/com/squareup/moshi/Types.java
  // https://github.com/square/moshi/blob/master/moshi/src/main/java/com/squareup/moshi/internal/Util.java
  
  public class Types {
  
      private static final Type[] EMPTY_TYPE_ARRAY = new Type[]{};
  
      /**
       * Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. Use this
       * method if {@code rawType} is not enclosed in another type.
       */
      public static ParameterizedType newParameterizedType(Type rawType, Type... typeArguments) {
          return new ParameterizedTypeImpl(null, rawType, typeArguments);
      }
  
      /**
       * Returns a new parameterized type, applying {@code typeArguments} to {@code rawType}. Use this
       * method if {@code rawType} is enclosed in {@code ownerType}.
       */
      public static ParameterizedType newParameterizedTypeWithOwner(
              Type ownerType, Type rawType, Type... typeArguments) {
          return new ParameterizedTypeImpl(ownerType, rawType, typeArguments);
      }
  
      /**
       * Returns an array type whose elements are all instances of {@code componentType}.
       */
      public static GenericArrayType arrayOf(Type componentType) {
          return new GenericArrayTypeImpl(componentType);
      }
  
      /**
       * Returns a type that represents an unknown type that extends {@code bound}. For example, if
       * {@code bound} is {@code CharSequence.class}, this returns {@code ? extends CharSequence}. If
       * {@code bound} is {@code Object.class}, this returns {@code ?}, which is shorthand for {@code
       * ? extends Object}.
       */
      public static WildcardType subtypeOf(Type bound) {
          return new WildcardTypeImpl(new Type[]{bound}, EMPTY_TYPE_ARRAY);
      }
  
      /**
       * Returns a type that represents an unknown supertype of {@code bound}. For example, if {@code
       * bound} is {@code String.class}, this returns {@code ? super String}.
       */
      public static WildcardType supertypeOf(Type bound) {
          return new WildcardTypeImpl(new Type[]{Object.class}, new Type[]{bound});
      }
  
      public static Class<?> getRawType(Type type) {
          if (type instanceof Class<?>) {
              // type is a normal class.
              return (Class<?>) type;
  
          } else if (type instanceof ParameterizedType) {
              ParameterizedType parameterizedType = (ParameterizedType) type;
  
              // I'm not exactly sure why getRawType() returns Type instead of Class. Neal isn't either but
              // suspects some pathological case related to nested classes exists.
              Type rawType = parameterizedType.getRawType();
              return (Class<?>) rawType;
  
          } else if (type instanceof GenericArrayType) {
              Type componentType = ((GenericArrayType) type).getGenericComponentType();
              return Array.newInstance(getRawType(componentType), 0).getClass();
  
          } else if (type instanceof TypeVariable) {
              // We could use the variable's bounds, but that won't work if there are multiple. having a raw
              // type that's more general than necessary is okay.
              return Object.class;
  
          } else if (type instanceof WildcardType) {
              return getRawType(((WildcardType) type).getUpperBounds()[0]);
  
          } else {
              String className = type == null ? "null" : type.getClass().getName();
              throw new IllegalArgumentException("Expected a Class, ParameterizedType, or "
                      + "GenericArrayType, but <" + type + "> is of type " + className);
          }
      }
  
      /**
       * Returns the element type of this collection type.
       *
       * @throws IllegalArgumentException if this type is not a collection.
       */
      public static Type collectionElementType(Type context, Class<?> contextRawType) {
          Type collectionType = getSupertype(context, contextRawType, Collection.class);
  
          if (collectionType instanceof WildcardType) {
              collectionType = ((WildcardType) collectionType).getUpperBounds()[0];
          }
          if (collectionType instanceof ParameterizedType) {
              return ((ParameterizedType) collectionType).getActualTypeArguments()[0];
          }
          return Object.class;
      }
  
      /**
       * Returns true if {@code a} and {@code b} are equal.
       */
      public static boolean equals(Type a, Type b) {
          if (a == b) {
              return true; // Also handles (a == null && b == null).
  
          } else if (a instanceof Class) {
              if (b instanceof GenericArrayType) {
                  return equals(((Class) a).getComponentType(),
                          ((GenericArrayType) b).getGenericComponentType());
              }
              return a.equals(b); // Class already specifies equals().
  
          } else if (a instanceof ParameterizedType) {
              if (!(b instanceof ParameterizedType)) return false;
              ParameterizedType pa = (ParameterizedType) a;
              ParameterizedType pb = (ParameterizedType) b;
              Type[] aTypeArguments = pa instanceof ParameterizedTypeImpl
                      ? ((ParameterizedTypeImpl) pa).typeArguments
                      : pa.getActualTypeArguments();
              Type[] bTypeArguments = pb instanceof ParameterizedTypeImpl
                      ? ((ParameterizedTypeImpl) pb).typeArguments
                      : pb.getActualTypeArguments();
              return equals(pa.getOwnerType(), pb.getOwnerType())
                      && pa.getRawType().equals(pb.getRawType())
                      && Arrays.equals(aTypeArguments, bTypeArguments);
  
          } else if (a instanceof GenericArrayType) {
              if (b instanceof Class) {
                  return equals(((Class) b).getComponentType(),
                          ((GenericArrayType) a).getGenericComponentType());
              }
              if (!(b instanceof GenericArrayType)) return false;
              GenericArrayType ga = (GenericArrayType) a;
              GenericArrayType gb = (GenericArrayType) b;
              return equals(ga.getGenericComponentType(), gb.getGenericComponentType());
  
          } else if (a instanceof WildcardType) {
              if (!(b instanceof WildcardType)) return false;
              WildcardType wa = (WildcardType) a;
              WildcardType wb = (WildcardType) b;
              return Arrays.equals(wa.getUpperBounds(), wb.getUpperBounds())
                      && Arrays.equals(wa.getLowerBounds(), wb.getLowerBounds());
  
          } else if (a instanceof TypeVariable) {
              if (!(b instanceof TypeVariable)) return false;
              TypeVariable<?> va = (TypeVariable<?>) a;
              TypeVariable<?> vb = (TypeVariable<?>) b;
              return va.getGenericDeclaration() == vb.getGenericDeclaration()
                      && va.getName().equals(vb.getName());
  
          } else {
              // This isn't a supported type.
              return false;
          }
      }
  
      /**
       * Returns a two element array containing this map's key and value types in positions 0 and 1
       * respectively.
       */
      static Type[] mapKeyAndValueTypes(Type context, Class<?> contextRawType) {
          // Work around a problem with the declaration of java.util.Properties. That class should extend
          // Hashtable<String, String>, but it's declared to extend Hashtable<Object, Object>.
          if (context == Properties.class) return new Type[]{String.class, String.class};
  
          Type mapType = getSupertype(context, contextRawType, Map.class);
          if (mapType instanceof ParameterizedType) {
              ParameterizedType mapParameterizedType = (ParameterizedType) mapType;
              return mapParameterizedType.getActualTypeArguments();
          }
          return new Type[]{Object.class, Object.class};
      }
  
      /**
       * Returns the generic form of {@code supertype}. For example, if this is {@code
       * ArrayList<String>}, this returns {@code Iterable<String>} given the input {@code
       * Iterable.class}.
       *
       * @param supertype a superclass of, or interface implemented by, this.
       */
      static Type getSupertype(Type context, Class<?> contextRawType, Class<?> supertype) {
          if (!supertype.isAssignableFrom(contextRawType)) throw new IllegalArgumentException();
          return resolve(context, contextRawType,
                  getGenericSupertype(context, contextRawType, supertype));
      }
  
      static Type getGenericSuperclass(Type type) {
          Class<?> rawType = Types.getRawType(type);
          return resolve(type, rawType, rawType.getGenericSuperclass());
      }
  
      static Type[] getGenericInterfaces(Type type) {
          Class<?> rawType = Types.getRawType(type);
          Type[] interfaces = rawType.getGenericInterfaces();
          Type[] result = new Type[interfaces.length];
          for (int i = 0; i < interfaces.length; i++) {
              result[i] = resolve(type, rawType, interfaces[i]);
          }
          return result;
      }
  
      /**
       * Returns the element type of {@code type} if it is an array type, or null if it is not an
       * array type.
       */
      public static Type arrayComponentType(Type type) {
          if (type instanceof GenericArrayType) {
              return ((GenericArrayType) type).getGenericComponentType();
          } else if (type instanceof Class) {
              return ((Class<?>) type).getComponentType();
          } else {
              return null;
          }
      }
  
      /**
       * Returns true if {@code rawType} is built in. We don't reflect on private fields of platform
       * types because they're unspecified and likely to be different on Java vs. Android.
       */
      public static boolean isPlatformType(Class<?> rawType) {
          String name = rawType.getName();
          return name.startsWith("android.")
                  || name.startsWith("androidx.")
                  || name.startsWith("java.")
                  || name.startsWith("javax.")
                  || name.startsWith("kotlin.")
                  || name.startsWith("scala.");
      }
  
      /**
       * Throws the cause of {@code e}, wrapping it if it is checked.
       */
      public static RuntimeException rethrowCause(InvocationTargetException e) {
          Throwable cause = e.getTargetException();
          if (cause instanceof RuntimeException) throw (RuntimeException) cause;
          if (cause instanceof Error) throw (Error) cause;
          throw new RuntimeException(cause);
      }
  
      /**
       * Returns a type that is functionally equal but not necessarily equal according to {@link
       * Object#equals(Object) Object.equals()}.
       */
      public static Type canonicalize(Type type) {
          if (type instanceof Class) {
              Class<?> c = (Class<?>) type;
              return c.isArray() ? new GenericArrayTypeImpl(canonicalize(c.getComponentType())) : c;
  
          } else if (type instanceof ParameterizedType) {
              if (type instanceof ParameterizedTypeImpl) return type;
              ParameterizedType p = (ParameterizedType) type;
              return new ParameterizedTypeImpl(p.getOwnerType(),
                      p.getRawType(), p.getActualTypeArguments());
  
          } else if (type instanceof GenericArrayType) {
              if (type instanceof GenericArrayTypeImpl) return type;
              GenericArrayType g = (GenericArrayType) type;
              return new GenericArrayTypeImpl(g.getGenericComponentType());
  
          } else if (type instanceof WildcardType) {
              if (type instanceof WildcardTypeImpl) return type;
              WildcardType w = (WildcardType) type;
              return new WildcardTypeImpl(w.getUpperBounds(), w.getLowerBounds());
  
          } else {
              return type; // This type is unsupported!
          }
      }
  
      /**
       * If type is a "? extends X" wildcard, returns X; otherwise returns type unchanged.
       */
      static Type removeSubtypeWildcard(Type type) {
          if (!(type instanceof WildcardType)) return type;
  
          Type[] lowerBounds = ((WildcardType) type).getLowerBounds();
          if (lowerBounds.length != 0) return type;
  
          Type[] upperBounds = ((WildcardType) type).getUpperBounds();
          if (upperBounds.length != 1) throw new IllegalArgumentException();
  
          return upperBounds[0];
      }
  
      public static Type resolve(Type context, Class<?> contextRawType, Type toResolve) {
          // This implementation is made a little more complicated in an attempt to avoid object-creation.
          while (true) {
              if (toResolve instanceof TypeVariable) {
                  TypeVariable<?> typeVariable = (TypeVariable<?>) toResolve;
                  toResolve = resolveTypeVariable(context, contextRawType, typeVariable);
                  if (toResolve == typeVariable) return toResolve;
  
              } else if (toResolve instanceof Class && ((Class<?>) toResolve).isArray()) {
                  Class<?> original = (Class<?>) toResolve;
                  Type componentType = original.getComponentType();
                  Type newComponentType = resolve(context, contextRawType, componentType);
                  return componentType == newComponentType
                          ? original
                          : arrayOf(newComponentType);
  
              } else if (toResolve instanceof GenericArrayType) {
                  GenericArrayType original = (GenericArrayType) toResolve;
                  Type componentType = original.getGenericComponentType();
                  Type newComponentType = resolve(context, contextRawType, componentType);
                  return componentType == newComponentType
                          ? original
                          : arrayOf(newComponentType);
  
              } else if (toResolve instanceof ParameterizedType) {
                  ParameterizedType original = (ParameterizedType) toResolve;
                  Type ownerType = original.getOwnerType();
                  Type newOwnerType = resolve(context, contextRawType, ownerType);
                  boolean changed = newOwnerType != ownerType;
  
                  Type[] args = original.getActualTypeArguments();
                  for (int t = 0, length = args.length; t < length; t++) {
                      Type resolvedTypeArgument = resolve(context, contextRawType, args[t]);
                      if (resolvedTypeArgument != args[t]) {
                          if (!changed) {
                              args = args.clone();
                              changed = true;
                          }
                          args[t] = resolvedTypeArgument;
                      }
                  }
  
                  return changed
                          ? new ParameterizedTypeImpl(newOwnerType, original.getRawType(), args)
                          : original;
  
              } else if (toResolve instanceof WildcardType) {
                  WildcardType original = (WildcardType) toResolve;
                  Type[] originalLowerBound = original.getLowerBounds();
                  Type[] originalUpperBound = original.getUpperBounds();
  
                  if (originalLowerBound.length == 1) {
                      Type lowerBound = resolve(context, contextRawType, originalLowerBound[0]);
                      if (lowerBound != originalLowerBound[0]) {
                          return supertypeOf(lowerBound);
                      }
                  } else if (originalUpperBound.length == 1) {
                      Type upperBound = resolve(context, contextRawType, originalUpperBound[0]);
                      if (upperBound != originalUpperBound[0]) {
                          return subtypeOf(upperBound);
                      }
                  }
                  return original;
  
              } else {
                  return toResolve;
              }
          }
      }
  
      private static Type resolveTypeVariable(Type context, Class<?> contextRawType, TypeVariable<?> unknown) {
          Class<?> declaredByRaw = declaringClassOf(unknown);
  
          // We can't reduce this further.
          if (declaredByRaw == null) return unknown;
  
          Type declaredBy = getGenericSupertype(context, contextRawType, declaredByRaw);
          if (declaredBy instanceof ParameterizedType) {
              int index = indexOf(declaredByRaw.getTypeParameters(), unknown);
              return ((ParameterizedType) declaredBy).getActualTypeArguments()[index];
          }
  
          return unknown;
      }
  
      /**
       * Returns the generic supertype for {@code supertype}. For example, given a class {@code
       * IntegerSet}, the result for when supertype is {@code Set.class} is {@code Set<Integer>} and the
       * result when the supertype is {@code Collection.class} is {@code Collection<Integer>}.
       */
      private static Type getGenericSupertype(Type context, Class<?> rawType, Class<?> toResolve) {
          if (toResolve == rawType) {
              return context;
          }
  
          // we skip searching through interfaces if unknown is an interface
          if (toResolve.isInterface()) {
              Class<?>[] interfaces = rawType.getInterfaces();
              for (int i = 0, length = interfaces.length; i < length; i++) {
                  if (interfaces[i] == toResolve) {
                      return rawType.getGenericInterfaces()[i];
                  } else if (toResolve.isAssignableFrom(interfaces[i])) {
                      return getGenericSupertype(rawType.getGenericInterfaces()[i], interfaces[i], toResolve);
                  }
              }
          }
  
          // check our supertypes
          if (!rawType.isInterface()) {
              while (rawType != Object.class) {
                  Class<?> rawSupertype = rawType.getSuperclass();
                  if (rawSupertype == toResolve) {
                      return rawType.getGenericSuperclass();
                  } else if (toResolve.isAssignableFrom(rawSupertype)) {
                      return getGenericSupertype(rawType.getGenericSuperclass(), rawSupertype, toResolve);
                  }
                  rawType = rawSupertype;
              }
          }
  
          // we can't resolve this further
          return toResolve;
      }
  
      private static int hashCodeOrZero(Object o) {
          return o != null ? o.hashCode() : 0;
      }
  
      private static String typeToString(Type type) {
          return type instanceof Class ? ((Class<?>) type).getName() : type.toString();
      }
  
      private static int indexOf(Object[] array, Object toFind) {
          for (int i = 0; i < array.length; i++) {
              if (toFind.equals(array[i])) return i;
          }
          throw new NoSuchElementException();
      }
  
      /**
       * Returns the declaring class of {@code typeVariable}, or {@code null} if it was not declared by
       * a class.
       */
      private static Class<?> declaringClassOf(TypeVariable<?> typeVariable) {
          GenericDeclaration genericDeclaration = typeVariable.getGenericDeclaration();
          return genericDeclaration instanceof Class ? (Class<?>) genericDeclaration : null;
      }
  
      private static void checkNotPrimitive(Type type) {
          if ((type instanceof Class<?>) && ((Class<?>) type).isPrimitive()) {
              throw new IllegalArgumentException("Unexpected primitive " + type + ". Use the boxed type.");
          }
      }
  
      static final class ParameterizedTypeImpl implements ParameterizedType {
          private final Type ownerType;
          private final Type rawType;
          final Type[] typeArguments;
  
          ParameterizedTypeImpl(Type ownerType, Type rawType, Type... typeArguments) {
              // Require an owner type if the raw type needs it.
              if (rawType instanceof Class<?>) {
                  Class<?> enclosingClass = ((Class<?>) rawType).getEnclosingClass();
                  if (ownerType != null) {
                      if (enclosingClass == null || Types.getRawType(ownerType) != enclosingClass) {
                          throw new IllegalArgumentException(
                                  "unexpected owner type for " + rawType + ": " + ownerType);
                      }
                  } else if (enclosingClass != null) {
                      throw new IllegalArgumentException(
                              "unexpected owner type for " + rawType + ": null");
                  }
              }
  
              this.ownerType = ownerType == null ? null : canonicalize(ownerType);
              this.rawType = canonicalize(rawType);
              this.typeArguments = typeArguments.clone();
              for (int t = 0; t < this.typeArguments.length; t++) {
                  if (this.typeArguments[t] == null) throw new NullPointerException();
                  checkNotPrimitive(this.typeArguments[t]);
                  this.typeArguments[t] = canonicalize(this.typeArguments[t]);
              }
          }
  
          @Override
          public Type[] getActualTypeArguments() {
              return typeArguments.clone();
          }
  
          @Override
          public Type getRawType() {
              return rawType;
          }
  
          @Override
          public Type getOwnerType() {
              return ownerType;
          }
  
          @Override
          public boolean equals(Object other) {
              return other instanceof ParameterizedType
                      && Types.equals(this, (ParameterizedType) other);
          }
  
          @Override
          public int hashCode() {
              return 31 * (31 * hashCodeOrZero(rawType) + hashCodeOrZero(ownerType)) +
                      Arrays.hashCode(typeArguments);
          }
  
          @Override
          public String toString() {
              StringBuilder result = new StringBuilder(30 * (typeArguments.length + 1));
              result.append(typeToString(rawType));
  
              if (typeArguments.length == 0) {
                  return result.toString();
              }
  
              result.append("<").append(typeToString(typeArguments[0]));
              for (int i = 1; i < typeArguments.length; i++) {
                  result.append(", ").append(typeToString(typeArguments[i]));
              }
              return result.append(">").toString();
          }
      }
  
      static final class GenericArrayTypeImpl implements GenericArrayType {
          private final Type componentType;
  
          GenericArrayTypeImpl(Type componentType) {
              this.componentType = canonicalize(componentType);
          }
  
          @Override
          public Type getGenericComponentType() {
              return componentType;
          }
  
          @Override
          public boolean equals(Object o) {
              return o instanceof GenericArrayType
                      && Types.equals(this, (GenericArrayType) o);
          }
  
          @Override
          public int hashCode() {
              return componentType.hashCode();
          }
  
          @Override
          public String toString() {
              return typeToString(componentType) + "[]";
          }
      }
  
      /**
       * The WildcardType interface supports multiple upper bounds and multiple lower bounds. We only
       * support what the Java 6 language needs - at most one bound. If a lower bound is set, the upper
       * bound must be Object.class.
       */
      static final class WildcardTypeImpl implements WildcardType {
          private final Type upperBound;
          private final Type lowerBound;
  
          WildcardTypeImpl(Type[] upperBounds, Type[] lowerBounds) {
              if (lowerBounds.length > 1) throw new IllegalArgumentException();
              if (upperBounds.length != 1) throw new IllegalArgumentException();
  
              if (lowerBounds.length == 1) {
                  if (lowerBounds[0] == null) throw new NullPointerException();
                  checkNotPrimitive(lowerBounds[0]);
                  if (upperBounds[0] != Object.class) throw new IllegalArgumentException();
                  this.lowerBound = canonicalize(lowerBounds[0]);
                  this.upperBound = Object.class;
  
              } else {
                  if (upperBounds[0] == null) throw new NullPointerException();
                  checkNotPrimitive(upperBounds[0]);
                  this.lowerBound = null;
                  this.upperBound = canonicalize(upperBounds[0]);
              }
          }
  
          @Override
          public Type[] getUpperBounds() {
              return new Type[]{upperBound};
          }
  
          @Override
          public Type[] getLowerBounds() {
              return lowerBound != null ? new Type[]{lowerBound} : EMPTY_TYPE_ARRAY;
          }
  
          @Override
          public boolean equals(Object other) {
              return other instanceof WildcardType
                      && Types.equals(this, (WildcardType) other);
          }
  
          @Override
          public int hashCode() {
              // This equals Arrays.hashCode(getLowerBounds()) ^ Arrays.hashCode(getUpperBounds()).
              return (lowerBound != null ? 31 + lowerBound.hashCode() : 1)
                      ^ (31 + upperBound.hashCode());
          }
  
          @Override
          public String toString() {
              if (lowerBound != null) {
                  return "? super " + typeToString(lowerBound);
              } else if (upperBound == Object.class) {
                  return "?";
              } else {
                  return "? extends " + typeToString(upperBound);
              }
          }
      }
  }
  