content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.instrumentation.spring.webflux.v5_3.internal;
  
  import static java.util.logging.Level.FINE;
  
  import java.lang.invoke.MethodHandle;
  import java.lang.invoke.MethodHandles;
  import java.lang.invoke.MethodType;
  import java.util.function.Function;
  import java.util.logging.Logger;
  import javax.annotation.Nullable;
  import org.springframework.http.HttpStatus;
  import org.springframework.web.reactive.function.client.ClientResponse;
  
  final class StatusCodes {
  
    private static final Logger logger = Logger.getLogger(StatusCodes.class.getName());
  
    @Nullable
    private static final Function<ClientResponse, Integer> statusCodeFunction =
        getStatusCodeFunction();
  
    @Nullable
    static Integer get(ClientResponse response) {
      if (statusCodeFunction == null) {
        return null;
      }
      return statusCodeFunction.apply(response);
    }
  
    @Nullable
    private static Function<ClientResponse, Integer> getStatusCodeFunction() {
      Function<ClientResponse, Integer> statusCodeFunction = getStatusCodeFunction60();
      if (statusCodeFunction != null) {
        return statusCodeFunction;
      }
      statusCodeFunction = getStatusCodeFunction51();
      if (statusCodeFunction != null) {
        return statusCodeFunction;
      }
      return getStatusCodeFunction50();
    }
  
    // in webflux 6.0, HttpStatusCode class was introduced, and statusCode() was changed to return
    // HttpStatusCode instead of HttpStatus
    @Nullable
    private static Function<ClientResponse, Integer> getStatusCodeFunction60() {
      MethodHandle statusCode;
      MethodHandle value;
      try {
        Class<?> httpStatusCodeClass = Class.forName("org.springframework.http.HttpStatusCode");
        statusCode =
            MethodHandles.publicLookup()
                .findVirtual(
                    ClientResponse.class, "statusCode", MethodType.methodType(httpStatusCodeClass));
        value =
            MethodHandles.publicLookup()
                .findVirtual(httpStatusCodeClass, "value", MethodType.methodType(int.class));
      } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException e) {
        return null;
      }
  
      return response -> {
        try {
          Object httpStatusCode = statusCode.invoke(response);
          return (int) value.invoke(httpStatusCode);
        } catch (Throwable e) {
          logger.log(FINE, e.getMessage(), e);
          return null;
        }
      };
    }
  
    // in webflux 5.1, rawStatusCode() was introduced to retrieve the exact status code
    // note: rawStatusCode() was deprecated in 6.0
    @Nullable
    private static Function<ClientResponse, Integer> getStatusCodeFunction51() {
      MethodHandle rawStatusCode;
      try {
        rawStatusCode =
            MethodHandles.publicLookup()
                .findVirtual(ClientResponse.class, "rawStatusCode", MethodType.methodType(int.class));
      } catch (IllegalAccessException | NoSuchMethodException e) {
        return null;
      }
  
      return response -> {
        try {
          return (int) rawStatusCode.invoke(response);
        } catch (Throwable e) {
          logger.log(FINE, e.getMessage(), e);
          return null;
        }
      };
    }
  
    // in webflux 5.0, statusCode() returns HttpStatus, which only represents standard status codes
    // (there's no way to capture arbitrary status codes)
    @Nullable
    private static Function<ClientResponse, Integer> getStatusCodeFunction50() {
      MethodHandle statusCode;
      MethodHandle value;
      try {
        statusCode =
            MethodHandles.publicLookup()
                .findVirtual(
                    ClientResponse.class, "statusCode", MethodType.methodType(HttpStatus.class));
        value =
            MethodHandles.publicLookup()
                .findVirtual(HttpStatus.class, "value", MethodType.methodType(int.class));
      } catch (IllegalAccessException | NoSuchMethodException e) {
        return null;
      }
  
      return response -> {
        try {
          Object httpStatusCode = statusCode.invoke(response);
          return (int) value.invoke(httpStatusCode);
        } catch (Throwable e) {
          logger.log(FINE, e.getMessage(), e);
          return null;
        }
      };
    }
  
    private StatusCodes() {}
  }
  