content: /*
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package io.airlift.bytecode.instruction;
  
  import com.google.common.base.CharMatcher;
  import com.google.common.collect.ImmutableList;
  import io.airlift.bytecode.BytecodeNode;
  import io.airlift.bytecode.BytecodeVisitor;
  import io.airlift.bytecode.MethodDefinition;
  import io.airlift.bytecode.MethodGenerationContext;
  import io.airlift.bytecode.OpCode;
  import io.airlift.bytecode.ParameterizedType;
  import org.objectweb.asm.Handle;
  import org.objectweb.asm.MethodVisitor;
  import org.objectweb.asm.Opcodes;
  
  import java.lang.invoke.MethodType;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.Method;
  import java.util.Arrays;
  import java.util.List;
  import java.util.stream.StreamSupport;
  
  import static com.google.common.base.Preconditions.checkArgument;
  import static com.google.common.collect.ImmutableList.toImmutableList;
  import static io.airlift.bytecode.MethodDefinition.methodDescription;
  import static io.airlift.bytecode.OpCode.INVOKEDYNAMIC;
  import static io.airlift.bytecode.OpCode.INVOKEINTERFACE;
  import static io.airlift.bytecode.OpCode.INVOKESPECIAL;
  import static io.airlift.bytecode.OpCode.INVOKESTATIC;
  import static io.airlift.bytecode.OpCode.INVOKEVIRTUAL;
  import static io.airlift.bytecode.ParameterizedType.type;
  import static java.util.Objects.requireNonNull;
  
  @SuppressWarnings("UnusedDeclaration")
  public class InvokeInstruction
          implements InstructionNode
  {
      //
      // Invoke Static
      //
  
      public static InstructionNode invokeStatic(Method method)
      {
          return invoke(INVOKESTATIC, method);
      }
  
      public static InstructionNode invokeStatic(MethodDefinition method)
      {
          return invoke(INVOKESTATIC, method);
      }
  
      public static InstructionNode invokeStatic(Class<?> target, String name, Class<?> returnType, Class<?>... parameterTypes)
      {
          return invoke(INVOKESTATIC, target, name, returnType, ImmutableList.copyOf(parameterTypes));
      }
  
      public static InstructionNode invokeStatic(Class<?> target, String name, Class<?> returnType, Iterable<Class<?>> parameterTypes)
      {
          return invoke(INVOKESTATIC, target, name, returnType, parameterTypes);
      }
  
      public static InstructionNode invokeStatic(ParameterizedType target, String name, ParameterizedType returnType, ParameterizedType... parameterTypes)
      {
          return invoke(INVOKESTATIC, target, name, returnType, ImmutableList.copyOf(parameterTypes));
      }
  
      public static InstructionNode invokeStatic(ParameterizedType target, String name, ParameterizedType returnType, Iterable<ParameterizedType> parameterTypes)
      {
          return invoke(INVOKESTATIC, target, name, returnType, parameterTypes);
      }
  
      //
      // Invoke Virtual
      //
  
      public static InstructionNode invokeVirtual(Method method)
      {
          return invoke(INVOKEVIRTUAL, method);
      }
  
      public static InstructionNode invokeVirtual(MethodDefinition method)
      {
          return invoke(INVOKEVIRTUAL, method);
      }
  
      public static InstructionNode invokeVirtual(Class<?> target, String name, Class<?> returnType, Class<?>... parameterTypes)
      {
          return invoke(INVOKEVIRTUAL, target, name, returnType, ImmutableList.copyOf(parameterTypes));
      }
  
      public static InstructionNode invokeVirtual(Class<?> target, String name, Class<?> returnType, Iterable<Class<?>> parameterTypes)
      {
          return invoke(INVOKEVIRTUAL, target, name, returnType, parameterTypes);
      }
  
      public static InstructionNode invokeVirtual(ParameterizedType target, String name, ParameterizedType returnType, ParameterizedType... parameterTypes)
      {
          return invoke(INVOKEVIRTUAL, target, name, returnType, ImmutableList.copyOf(parameterTypes));
      }
  
      public static InstructionNode invokeVirtual(ParameterizedType target, String name, ParameterizedType returnType, Iterable<ParameterizedType> parameterTypes)
      {
          return invoke(INVOKEVIRTUAL, target, name, returnType, parameterTypes);
      }
  
      //
      // Invoke Interface
      //
  
      public static InstructionNode invokeInterface(Method method)
      {
          return invoke(INVOKEINTERFACE, method);
      }
  
      public static InstructionNode invokeInterface(MethodDefinition method)
      {
          return invoke(INVOKEINTERFACE, method);
      }
  
      public static InstructionNode invokeInterface(Class<?> target, String name, Class<?> returnType, Class<?>... parameterTypes)
      {
          return invoke(INVOKEINTERFACE, target, name, returnType, ImmutableList.copyOf(parameterTypes));
      }
  
      public static InstructionNode invokeInterface(Class<?> target, String name, Class<?> returnType, Iterable<Class<?>> parameterTypes)
      {
          return invoke(INVOKEINTERFACE, target, name, returnType, parameterTypes);
      }
  
      public static InstructionNode invokeInterface(ParameterizedType target, String name, ParameterizedType returnType, ParameterizedType... parameterTypes)
      {
          return invoke(INVOKEINTERFACE, target, name, returnType, ImmutableList.copyOf(parameterTypes));
      }
  
      public static InstructionNode invokeInterface(ParameterizedType target, String name, ParameterizedType returnType, Iterable<ParameterizedType> parameterTypes)
      {
          return invoke(INVOKEINTERFACE, target, name, returnType, parameterTypes);
      }
  
      //
      // Invoke Constructor
      //
  
      public static InstructionNode invokeConstructor(Constructor<?> constructor)
      {
          return invokeConstructor(constructor.getDeclaringClass(), constructor.getParameterTypes());
      }
  
      public static InstructionNode invokeConstructor(Class<?> target, Class<?>... parameterTypes)
      {
          return invokeConstructor(type(target), Arrays.stream(parameterTypes).map(ParameterizedType::type).collect(toImmutableList()));
      }
  
      public static InstructionNode invokeConstructor(Class<?> target, Iterable<Class<?>> parameterTypes)
      {
          return invokeConstructor(type(target), StreamSupport.stream(parameterTypes.spliterator(), false).map(ParameterizedType::type).collect(toImmutableList()));
      }
  
      public static InstructionNode invokeConstructor(ParameterizedType target, ParameterizedType... parameterTypes)
      {
          return invokeConstructor(target, ImmutableList.copyOf(parameterTypes));
      }
  
      public static InstructionNode invokeConstructor(ParameterizedType target, Iterable<ParameterizedType> parameterTypes)
      {
          return invokeSpecial(target, "<init>", type(void.class), parameterTypes);
      }
  
      //
      // Invoke Special
      //
  
      public static InstructionNode invokeSpecial(Method method)
      {
          return invoke(INVOKESPECIAL, method);
      }
  
      public static InstructionNode invokeSpecial(MethodDefinition method)
      {
          return invoke(INVOKESPECIAL, method);
      }
  
      public static InstructionNode invokeSpecial(Class<?> target, String name, Class<?> returnType, Class<?>... parameterTypes)
      {
          return invoke(INVOKESPECIAL, target, name, returnType, ImmutableList.copyOf(parameterTypes));
      }
  
      public static InstructionNode invokeSpecial(Class<?> target, String name, Class<?> returnType, Iterable<Class<?>> parameterTypes)
      {
          return invoke(INVOKESPECIAL, target, name, returnType, parameterTypes);
      }
  
      public static InstructionNode invokeSpecial(ParameterizedType target, String name, ParameterizedType returnType, ParameterizedType... parameterTypes)
      {
          return invoke(INVOKESPECIAL, target, name, returnType, ImmutableList.copyOf(parameterTypes));
      }
  
      public static InstructionNode invokeSpecial(ParameterizedType target, String name, ParameterizedType returnType, Iterable<ParameterizedType> parameterTypes)
      {
          return invoke(INVOKESPECIAL, target, name, returnType, parameterTypes);
      }
  
      //
      // Generic
      //
  
      private static InstructionNode invoke(OpCode invocationType, Method method)
      {
          return new InvokeInstruction(invocationType,
                  type(method.getDeclaringClass()),
                  method.getName(),
                  type(method.getReturnType()),
                  Arrays.stream(method.getParameterTypes()).map(ParameterizedType::type).collect(toImmutableList()));
      }
  
      private static InstructionNode invoke(OpCode invocationType, MethodDefinition method)
      {
          return new InvokeInstruction(invocationType,
                  method.getDeclaringClass().getType(),
                  method.getName(),
                  method.getReturnType(),
                  method.getParameterTypes());
      }
  
      private static InstructionNode invoke(OpCode invocationType, ParameterizedType target, String name, ParameterizedType returnType, Iterable<ParameterizedType> parameterTypes)
      {
          return new InvokeInstruction(invocationType,
                  target,
                  name,
                  returnType,
                  parameterTypes);
      }
  
      private static InstructionNode invoke(OpCode invocationType, Class<?> target, String name, Class<?> returnType, Iterable<Class<?>> parameterTypes)
      {
          return new InvokeInstruction(invocationType,
                  type(target),
                  name,
                  type(returnType),
                  StreamSupport.stream(parameterTypes.spliterator(), false).map(ParameterizedType::type).collect(toImmutableList()));
      }
  
      //
      // Invoke Dynamic
      //
  
      public static InstructionNode invokeDynamic(String name,
              ParameterizedType returnType,
              Iterable<ParameterizedType> parameterTypes,
              Method bootstrapMethod,
              Iterable<Object> bootstrapArguments)
      {
          return new InvokeDynamicInstruction(name,
                  returnType,
                  parameterTypes,
                  bootstrapMethod,
                  ImmutableList.copyOf(bootstrapArguments));
      }
  
      public static InstructionNode invokeDynamic(String name,
              ParameterizedType returnType,
              Iterable<ParameterizedType> parameterTypes,
              Method bootstrapMethod,
              Object... bootstrapArguments)
      {
          return new InvokeDynamicInstruction(name,
                  returnType,
                  parameterTypes,
                  bootstrapMethod,
                  ImmutableList.copyOf(bootstrapArguments));
      }
  
      public static InstructionNode invokeDynamic(String name,
              MethodType methodType,
              Method bootstrapMethod,
              Iterable<Object> bootstrapArguments)
      {
          return new InvokeDynamicInstruction(name,
                  type(methodType.returnType()),
                  methodType.parameterList().stream().map(ParameterizedType::type).collect(toImmutableList()),
                  bootstrapMethod,
                  ImmutableList.copyOf(bootstrapArguments));
      }
  
      public static InstructionNode invokeDynamic(String name,
              MethodType methodType,
              Method bootstrapMethod,
              Object... bootstrapArguments)
      {
          return new InvokeDynamicInstruction(name,
                  type(methodType.returnType()),
                  methodType.parameterList().stream().map(ParameterizedType::type).collect(toImmutableList()),
                  bootstrapMethod,
                  ImmutableList.copyOf(bootstrapArguments));
      }
  
      private final OpCode opCode;
      private final ParameterizedType target;
      private final String name;
      private final ParameterizedType returnType;
      private final List<ParameterizedType> parameterTypes;
  
      public InvokeInstruction(OpCode opCode,
              ParameterizedType target,
              String name,
              ParameterizedType returnType,
              Iterable<ParameterizedType> parameterTypes)
      {
          checkUnqualifiedName(name);
          this.opCode = opCode;
          this.target = target;
          this.name = name;
          this.returnType = returnType;
          this.parameterTypes = ImmutableList.copyOf(parameterTypes);
      }
  
      public OpCode getOpCode()
      {
          return opCode;
      }
  
      public ParameterizedType getTarget()
      {
          return target;
      }
  
      public String getName()
      {
          return name;
      }
  
      public ParameterizedType getReturnType()
      {
          return returnType;
      }
  
      public List<ParameterizedType> getParameterTypes()
      {
          return parameterTypes;
      }
  
      public String getMethodDescription()
      {
          return methodDescription(returnType, parameterTypes);
      }
  
      @Override
      public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
      {
          visitor.visitMethodInsn(opCode.getOpCode(), target.getClassName(), name, getMethodDescription(), target.isInterface());
      }
  
      @Override
      public List<BytecodeNode> getChildNodes()
      {
          return ImmutableList.of();
      }
  
      @Override
      public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
      {
          return visitor.visitInvoke(parent, this);
      }
  
      public static class InvokeDynamicInstruction
              extends InvokeInstruction
      {
          private final Method bootstrapMethod;
          private final List<Object> bootstrapArguments;
  
          public InvokeDynamicInstruction(String name,
                  ParameterizedType returnType,
                  Iterable<ParameterizedType> parameterTypes,
                  Method bootstrapMethod,
                  List<Object> bootstrapArguments)
          {
              super(INVOKEDYNAMIC, null, name, returnType, parameterTypes);
              this.bootstrapMethod = bootstrapMethod;
              this.bootstrapArguments = ImmutableList.copyOf(bootstrapArguments);
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              Handle bootstrapMethodHandle = new Handle(Opcodes.H_INVOKESTATIC,
                      type(bootstrapMethod.getDeclaringClass()).getClassName(),
                      bootstrapMethod.getName(),
                      methodDescription(
                              bootstrapMethod.getReturnType(),
                              bootstrapMethod.getParameterTypes()),
                      false);
  
              visitor.visitInvokeDynamicInsn(getName(),
                      getMethodDescription(),
                      bootstrapMethodHandle,
                      bootstrapArguments.toArray(new Object[0]));
          }
  
          public Method getBootstrapMethod()
          {
              return bootstrapMethod;
          }
  
          public List<Object> getBootstrapArguments()
          {
              return bootstrapArguments;
          }
  
          @Override
          public List<BytecodeNode> getChildNodes()
          {
              return ImmutableList.of();
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitInvokeDynamic(parent, this);
          }
      }
  
      private static void checkUnqualifiedName(String name)
      {
          // JVM Specification 4.2.2 Unqualified Names
          requireNonNull(name, "name is null");
          checkArgument(!name.isEmpty(), "name is empty");
          if (name.equals("<init>") || name.equals("<clinit>")) {
              return;
          }
          CharMatcher invalid = CharMatcher.anyOf(".;[/<>");
          checkArgument(invalid.matchesNoneOf(name), "invalid name: %s", name);
      }
  }
  