content: /*
   * Copyright The OpenTelemetry Authors
   * SPDX-License-Identifier: Apache-2.0
   */
  
  package io.opentelemetry.instrumentation.awslambdacore.v1_0.internal;
  
  import com.amazonaws.services.lambda.runtime.Context;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.InvocationTargetException;
  import java.lang.reflect.Method;
  import java.lang.reflect.Parameter;
  import java.util.Arrays;
  import java.util.List;
  import java.util.Optional;
  
  /**
   * This class is internal and is hence not for public use. Its APIs are unstable and can change at
   * any time.
   */
  public final class WrappedLambda {
  
    public static final String OTEL_LAMBDA_HANDLER_ENV_KEY =
        "OTEL_INSTRUMENTATION_AWS_LAMBDA_HANDLER";
  
    private final Object targetObject;
    private final Class<?> targetClass;
    private final String targetMethodName;
  
    /**
     * Creates new lambda wrapper out of configuration. Supported env properties: - {@value
     * OTEL_LAMBDA_HANDLER_ENV_KEY} - lambda handler in format: package.ClassName::methodName
     */
    public static WrappedLambda fromConfiguration() {
  
      String lambdaHandler = System.getenv(OTEL_LAMBDA_HANDLER_ENV_KEY);
      if (lambdaHandler == null || lambdaHandler.isEmpty()) {
        throw new IllegalStateException(OTEL_LAMBDA_HANDLER_ENV_KEY + " was not specified.");
      }
      // expect format to be package.ClassName::methodName
      String[] split = lambdaHandler.split("::");
      if (split.length != 2) {
        throw new IllegalStateException(
            lambdaHandler
                + " is not a valid handler name. Expected format: package.ClassName::methodName");
      }
      String handlerClassName = split[0];
      String targetMethodName = split[1];
      Class<?> targetClass;
      try {
        targetClass = Class.forName(handlerClassName);
      } catch (ClassNotFoundException e) {
        // no class found
        throw new IllegalStateException(handlerClassName + " not found in classpath", e);
      }
      return new WrappedLambda(targetClass, targetMethodName);
    }
  
    // Visible for testing
    public WrappedLambda(Class<?> targetClass, String targetMethodName) {
      this.targetClass = targetClass;
      this.targetMethodName = targetMethodName;
      this.targetObject = instantiateTargetClass();
    }
  
    private Object instantiateTargetClass() {
      Object targetObject;
      try {
        Constructor<?> ctor = targetClass.getConstructor();
        targetObject = ctor.newInstance();
      } catch (NoSuchMethodException e) {
        throw new IllegalStateException(
            targetClass.getName() + " does not have an appropriate constructor", e);
      } catch (InstantiationException e) {
        throw new IllegalStateException(targetClass.getName() + " cannot be an abstract class", e);
      } catch (IllegalAccessException e) {
        throw new IllegalStateException(
            targetClass.getName() + "'s constructor is not accessible", e);
      } catch (InvocationTargetException e) {
        throw new IllegalStateException(
            targetClass.getName() + " threw an exception from the constructor", e);
      }
      return targetObject;
    }
  
    private static boolean isLastParameterContext(Parameter[] parameters) {
      if (parameters.length == 0) {
        return false;
      }
      return parameters[parameters.length - 1].getType().equals(Context.class);
    }
  
    public Method getRequestTargetMethod() {
  
      List<Method> methods = Arrays.asList(targetClass.getMethods());
      Optional<Method> firstOptional =
          methods.stream()
              .filter((Method m) -> m.getName().equals(targetMethodName))
              .min(WrappedLambda::methodComparator);
      if (!firstOptional.isPresent()) {
        throw new IllegalStateException("Method " + targetMethodName + " not found");
      }
      return firstOptional.get();
    }
  
    /*
     Per method selection specifications
     http://docs.aws.amazon.com/lambda/latest/dg/java-programming-model-handler-types.html
     - Context can be omitted
     - Select the method with the largest number of parameters.
     - If two or more methods have the same number of parameters, AWS Lambda selects the method that has the Context as the last parameter.
     - Non-Bridge methods are preferred
     - If none or all of these methods have the Context parameter, then the behavior is undefined.
  
     Examples:
     - handleA(String, String, Integer), handleB(String, Context) - handleA is selected (number of parameters)
     - handleA(String, String, Integer), handleB(String, String, Context) - handleB is selected (has Context as the last parameter)
     - generic method handleG(T, U, Context), implementation (T, U - String) handleA(String, String, Context), bridge method handleB(Object, Object, Context) - handleA is selected (non-bridge)
    */
    private static int methodComparator(Method a, Method b) {
      // greater number of params wins
      if (a.getParameterCount() != b.getParameterCount()) {
        return b.getParameterCount() - a.getParameterCount();
      }
      // only one of the methods has last param context ?
      int onlyOneHasCtx = onlyOneHasContextAsLastParam(a, b);
      if (onlyOneHasCtx != 0) {
        return onlyOneHasCtx;
      }
      // one of the methods is a bridge, otherwise - undefined
      return onlyOneIsBridgeMethod(a, b);
    }
  
    private static int onlyOneIsBridgeMethod(Method first, Method second) {
      boolean firstBridge = first.isBridge();
      boolean secondBridge = second.isBridge();
      if (firstBridge && !secondBridge) {
        return 1;
      } else if (!firstBridge && secondBridge) {
        return -1;
      }
      return 0;
    }
  
    private static int onlyOneHasContextAsLastParam(Method first, Method second) {
      boolean firstCtx = isLastParameterContext(first.getParameters());
      boolean secondCtx = isLastParameterContext(second.getParameters());
      // only one of the methods has last param context ?
      if (firstCtx && !secondCtx) {
        return -1;
      } else if (!firstCtx && secondCtx) {
        return 1;
      }
      return 0;
    }
  
    public Object getTargetObject() {
      return targetObject;
    }
  
    public Class<?> getTargetClass() {
      return targetClass;
    }
  }
  