content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.javaagent.instrumentation.jaxrs.v1_0;
  
  import io.opentelemetry.javaagent.bootstrap.jaxrs.ClassHierarchyIterable;
  import java.lang.annotation.Annotation;
  import java.lang.reflect.Method;
  import java.util.Map;
  import java.util.concurrent.ConcurrentHashMap;
  import javax.ws.rs.HttpMethod;
  import javax.ws.rs.Path;
  
  public class HandlerData {
  
    private static final ClassValue<Map<Method, String>> serverSpanNames =
        new ClassValue<Map<Method, String>>() {
          @Override
          protected Map<Method, String> computeValue(Class<?> type) {
            return new ConcurrentHashMap<>();
          }
        };
  
    private final Class<?> target;
    private final Method method;
  
    public HandlerData(Class<?> target, Method method) {
      this.target = target;
      this.method = method;
    }
  
    public Class<?> codeClass() {
      return target;
    }
  
    public String methodName() {
      return method.getName();
    }
  
    /**
     * Returns the span name given a JaxRS annotated method. Results are cached so this method can be
     * called multiple times without significantly impacting performance.
     *
     * @return The result can be an empty string but will never be {@code null}.
     */
    String getServerSpanName() {
      Map<Method, String> classMap = serverSpanNames.get(target);
      String spanName = classMap.get(method);
      if (spanName == null) {
        String httpMethod = null;
        Path methodPath = null;
        Path classPath = findClassPath(target);
        for (Class<?> currentClass : new ClassHierarchyIterable(target)) {
          Method currentMethod;
          if (currentClass.equals(target)) {
            currentMethod = method;
          } else {
            currentMethod = findMatchingMethod(method, currentClass.getDeclaredMethods());
          }
  
          if (currentMethod != null) {
            if (httpMethod == null) {
              httpMethod = locateHttpMethod(currentMethod);
            }
            if (methodPath == null) {
              methodPath = findMethodPath(currentMethod);
            }
  
            if (httpMethod != null && methodPath != null) {
              break;
            }
          }
        }
        spanName = buildSpanName(classPath, methodPath);
        classMap.put(method, spanName);
      }
  
      return spanName;
    }
  
    private static String locateHttpMethod(Method method) {
      String httpMethod = null;
      for (Annotation ann : method.getDeclaredAnnotations()) {
        if (ann.annotationType().getAnnotation(HttpMethod.class) != null) {
          httpMethod = ann.annotationType().getSimpleName();
        }
      }
      return httpMethod;
    }
  
    private static Path findMethodPath(Method method) {
      return method.getAnnotation(Path.class);
    }
  
    private static Path findClassPath(Class<?> target) {
      for (Class<?> currentClass : new ClassHierarchyIterable(target)) {
        Path annotation = currentClass.getAnnotation(Path.class);
        if (annotation != null) {
          // Annotation overridden, no need to continue.
          return annotation;
        }
      }
  
      return null;
    }
  
    private static Method findMatchingMethod(Method baseMethod, Method[] methods) {
      nextMethod:
      for (Method method : methods) {
        if (!baseMethod.getReturnType().equals(method.getReturnType())) {
          continue;
        }
  
        if (!baseMethod.getName().equals(method.getName())) {
          continue;
        }
  
        Class<?>[] baseParameterTypes = baseMethod.getParameterTypes();
        Class<?>[] parameterTypes = method.getParameterTypes();
        if (baseParameterTypes.length != parameterTypes.length) {
          continue;
        }
        for (int i = 0; i < baseParameterTypes.length; i++) {
          if (!baseParameterTypes[i].equals(parameterTypes[i])) {
            continue nextMethod;
          }
        }
        return method;
      }
      return null;
    }
  
    private static String buildSpanName(Path classPath, Path methodPath) {
      StringBuilder spanNameBuilder = new StringBuilder();
      boolean skipSlash = false;
      if (classPath != null) {
        String classPathValue = classPath.value();
        if (!classPathValue.startsWith("/")) {
          spanNameBuilder.append("/");
        }
        spanNameBuilder.append(classPathValue);
        skipSlash = classPathValue.endsWith("/") || classPathValue.isEmpty();
      }
  
      if (methodPath != null) {
        String path = methodPath.value();
        if (skipSlash) {
          if (path.startsWith("/")) {
            path = path.length() == 1 ? "" : path.substring(1);
          }
        } else if (!path.startsWith("/")) {
          spanNameBuilder.append("/");
        }
        spanNameBuilder.append(path);
      }
  
      return spanNameBuilder.toString().trim();
    }
  }
  