content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.instrumentation.api.annotation.support;
  
  import static org.assertj.core.api.Assertions.assertThat;
  import static org.junit.jupiter.api.Assertions.fail;
  
  import java.io.Serializable;
  import java.lang.reflect.Type;
  import java.lang.reflect.TypeVariable;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.List;
  import java.util.Optional;
  import java.util.RandomAccess;
  import org.hamcrest.Description;
  import org.hamcrest.Matcher;
  import org.hamcrest.TypeSafeMatcher;
  import org.junit.jupiter.api.Test;
  
  class ParameterizedClassTest {
  
    Type stringArrayListType = getGeneric(TestFields.class, "stringArrayListField");
  
    static Type getGeneric(Class<?> clazz, String fieldName) {
      try {
        return clazz.getField(fieldName).getGenericType();
      } catch (NoSuchFieldException e) {
        fail("No such field");
        throw new RuntimeException("No such field", e);
      }
    }
  
    @Test
    void reflectsClass() {
  
      ParameterizedClass underTest = ParameterizedClass.of(ArrayList.class);
      assertThat(underTest.getRawClass()).isEqualTo(ArrayList.class);
      assertThat(underTest.getActualTypeArguments())
          .hasSize(1)
          .allMatch(x -> x instanceof TypeVariable);
    }
  
    @Test
    void reflectsParameterizedType() {
      ParameterizedClass underTest = ParameterizedClass.of(stringArrayListType);
      assertThat(underTest.getRawClass()).isEqualTo(ArrayList.class);
      assertThat(underTest.getActualTypeArguments()).containsExactly(String.class);
    }
  
    @Test
    void getsParameterizedSuperclassWithMappedtypeArguments() {
      ParameterizedClass underTest =
          ParameterizedClass.of(stringArrayListType).getParameterizedSuperclass();
      assertThat(underTest.getActualTypeArguments()).containsExactly(String.class);
    }
  
    @Test
    void getsParameterizedInterfacesWithMappedTypeArguments() {
      ParameterizedClass[] underTest =
          ParameterizedClass.of(stringArrayListType).getParameterizedInterfaces();
      assertThat(underTest)
          .satisfiesExactlyInAnyOrder(
              x -> matchesParameterizedClass(List.class, new Type[] {String.class}).matches(x),
              x -> matchesParameterizedClass(Cloneable.class, new Type[0]).matches(x),
              x -> matchesParameterizedClass(RandomAccess.class, new Type[0]).matches(x),
              x -> matchesParameterizedClass(Serializable.class, new Type[0]).matches(x));
    }
  
    @Test
    void findsParameterizedInterfaceWithMappedTypeArguments() {
      Optional<ParameterizedClass> underTest =
          ParameterizedClass.of(stringArrayListType).findParameterizedSuperclass(List.class);
      assertThat(underTest).isPresent();
      assertThat(underTest.get().getRawClass()).isEqualTo(List.class);
      assertThat(underTest.get().getActualTypeArguments())
          .hasSize(1)
          .allMatch(x -> x == String.class);
    }
  
    @Test
    void findsParameterizedInterfaceOfSuperclassWithMappedTypeArguments() {
      Optional<ParameterizedClass> underTest =
          ParameterizedClass.of(stringArrayListType).findParameterizedSuperclass(Collection.class);
      assertThat(underTest.get().getRawClass()).isEqualTo(Collection.class);
      assertThat(underTest.get().getActualTypeArguments())
          .hasSize(1)
          .allMatch(x -> x == String.class);
    }
  
    @Test
    void doesNotFindParameterizedSuperclassThatTypeDoesNotExtend() {
      Optional<ParameterizedClass> underTest =
          ParameterizedClass.of(stringArrayListType)
              .findParameterizedSuperclass(AbstractTestClass.class);
      assertThat(underTest).isNotPresent();
    }
  
    @Test
    void doesNotFindParameterizedInterfaceThatTypeDoesNotImplement() {
      Optional<ParameterizedClass> underTest =
          ParameterizedClass.of(stringArrayListType).findParameterizedSuperclass(TestInterface.class);
      assertThat(underTest).isNotPresent();
    }
  
    static class TestFields {
      private TestFields() {}
  
      public static List<String> stringListField;
      public static ArrayList<String> stringArrayListField;
    }
  
    interface TestInterface {}
  
    abstract static class AbstractTestClass {}
  
    static Matcher<ParameterizedClass> matchesParameterizedClass(
        Class<?> rawClass, Type[] typeArguments) {
      return new TypeSafeMatcher<ParameterizedClass>() {
        @Override
        public boolean matchesSafely(ParameterizedClass item) {
          return (item != null)
              && (item.getRawClass() == rawClass)
              && Arrays.equals(item.getActualTypeArguments(), typeArguments);
        }
  
        @Override
        public void describeTo(Description description) {
          description
              .appendText("a ParameterizedClass with raw type ")
              .appendValue(rawClass)
              .appendText(" and type parameters ")
              .appendValue(typeArguments);
        }
      };
    }
  }
  