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.expression;
  
  import com.google.common.collect.ImmutableList;
  import io.airlift.bytecode.FieldDefinition;
  import io.airlift.bytecode.MethodDefinition;
  import io.airlift.bytecode.OpCode;
  import io.airlift.bytecode.ParameterizedType;
  
  import java.lang.invoke.MethodType;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import java.util.stream.StreamSupport;
  
  import static com.google.common.base.Preconditions.checkArgument;
  import static com.google.common.collect.ImmutableList.toImmutableList;
  import static com.google.common.collect.Iterables.toArray;
  import static io.airlift.bytecode.ParameterizedType.type;
  import static io.airlift.bytecode.expression.ArithmeticBytecodeExpression.createArithmeticBytecodeExpression;
  import static io.airlift.bytecode.instruction.Constant.loadBoolean;
  import static io.airlift.bytecode.instruction.Constant.loadClass;
  import static io.airlift.bytecode.instruction.Constant.loadDouble;
  import static io.airlift.bytecode.instruction.Constant.loadDynamic;
  import static io.airlift.bytecode.instruction.Constant.loadFloat;
  import static io.airlift.bytecode.instruction.Constant.loadInt;
  import static io.airlift.bytecode.instruction.Constant.loadLong;
  import static io.airlift.bytecode.instruction.Constant.loadNull;
  import static io.airlift.bytecode.instruction.Constant.loadString;
  import static java.util.Arrays.stream;
  import static java.util.Objects.requireNonNull;
  
  public final class BytecodeExpressions
  {
      private BytecodeExpressions() {}
  
      //
      // Constants
      //
  
      public static BytecodeExpression constantTrue()
      {
          return new ConstantBytecodeExpression(boolean.class, loadBoolean(true));
      }
  
      public static BytecodeExpression constantFalse()
      {
          return new ConstantBytecodeExpression(boolean.class, loadBoolean(false));
      }
  
      public static BytecodeExpression constantBoolean(boolean value)
      {
          return new ConstantBytecodeExpression(boolean.class, loadBoolean(value));
      }
  
      public static BytecodeExpression constantClass(Class<?> value)
      {
          return new ConstantBytecodeExpression(Class.class, loadClass(value));
      }
  
      public static BytecodeExpression constantClass(ParameterizedType value)
      {
          return new ConstantBytecodeExpression(Class.class, loadClass(value));
      }
  
      public static BytecodeExpression constantDouble(double value)
      {
          return new ConstantBytecodeExpression(double.class, loadDouble(value));
      }
  
      public static BytecodeExpression constantFloat(float value)
      {
          return new ConstantBytecodeExpression(float.class, loadFloat(value));
      }
  
      public static BytecodeExpression constantInt(int value)
      {
          return new ConstantBytecodeExpression(int.class, loadInt(value));
      }
  
      public static BytecodeExpression constantLong(long value)
      {
          return new ConstantBytecodeExpression(long.class, loadLong(value));
      }
  
      public static BytecodeExpression constantNumber(Number value)
      {
          if (value instanceof Byte) {
              return constantInt((value).intValue()).cast(byte.class);
          }
          if (value instanceof Short) {
              return constantInt((value).intValue()).cast(short.class);
          }
          if (value instanceof Integer) {
              return constantInt((Integer) value);
          }
          if (value instanceof Long) {
              return constantLong((Long) value);
          }
          if (value instanceof Float) {
              return constantFloat((Float) value);
          }
          if (value instanceof Double) {
              return constantDouble((Double) value);
          }
          throw new IllegalArgumentException("Unsupported number type: " + value.getClass().getName());
      }
  
      public static BytecodeExpression constantNull(Class<?> type)
      {
          return new ConstantBytecodeExpression(type, loadNull());
      }
  
      public static BytecodeExpression constantNull(ParameterizedType type)
      {
          return new ConstantBytecodeExpression(type, loadNull());
      }
  
      public static BytecodeExpression constantString(String value)
      {
          return new ConstantBytecodeExpression(String.class, loadString(value));
      }
  
      public static BytecodeExpression constantDynamic(
              String name,
              Class<?> type,
              Method bootstrapMethod,
              Iterable<? extends Object> bootstrapArgs)
      {
          return constantDynamic(name, type(type), bootstrapMethod, ImmutableList.copyOf(bootstrapArgs));
      }
  
      public static BytecodeExpression constantDynamic(
              String name,
              Class<?> type,
              Method bootstrapMethod,
              Object... bootstrapArgs)
      {
          return constantDynamic(name, type(type), bootstrapMethod, ImmutableList.copyOf(bootstrapArgs));
      }
  
      public static BytecodeExpression constantDynamic(
              String name,
              ParameterizedType type,
              Method bootstrapMethod,
              Object... bootstrapArgs)
      {
          return constantDynamic(name, type, bootstrapMethod, ImmutableList.copyOf(bootstrapArgs));
      }
  
      public static BytecodeExpression constantDynamic(
              String name,
              ParameterizedType type,
              Method bootstrapMethod,
              Iterable<? extends Object> bootstrapArgs)
      {
          requireNonNull(name, "name is null");
          requireNonNull(type, "type is null");
          requireNonNull(bootstrapMethod, "bootstrapMethod is null");
          requireNonNull(bootstrapArgs, "bootstrapArgs is null");
          return new ConstantBytecodeExpression(
                  type,
                  loadDynamic(
                          name,
                          type,
                          bootstrapMethod,
                          toArray(bootstrapArgs, Object.class)));
      }
  
      public static BytecodeExpression defaultValue(ParameterizedType type)
      {
          if (type.isPrimitive()) {
              return defaultValue(type.getPrimitiveType());
          }
          return constantNull(type);
      }
  
      public static BytecodeExpression defaultValue(Class<?> type)
      {
          requireNonNull(type, "type is null");
          if (type == boolean.class) {
              return constantInt(0).cast(boolean.class);
          }
          if (type == byte.class) {
              return constantInt(0).cast(byte.class);
          }
          if (type == int.class) {
              return constantInt(0);
          }
          if (type == short.class) {
              return constantInt(0).cast(short.class);
          }
          if (type == long.class) {
              return constantLong(0L);
          }
          if (type == float.class) {
              return constantFloat(0.0f);
          }
          if (type == double.class) {
              return constantDouble(0.0d);
          }
          checkArgument(!type.isPrimitive(), "Unsupported type %s", type);
          return constantNull(type);
      }
  
      //
      // Get static field
      //
  
      public static BytecodeExpression getStatic(Class<?> declaringClass, String name)
      {
          return new GetFieldBytecodeExpression(null, declaringClass, name);
      }
  
      public static BytecodeExpression getStatic(Field staticField)
      {
          return new GetFieldBytecodeExpression(null, staticField);
      }
  
      public static BytecodeExpression getStatic(FieldDefinition staticField)
      {
          return new GetFieldBytecodeExpression(null, staticField);
      }
  
      public static BytecodeExpression getStatic(ParameterizedType declaringClass, String name, ParameterizedType type)
      {
          return new GetFieldBytecodeExpression(null, declaringClass, name, type);
      }
  
      //
      // Set static field
      //
  
      public static BytecodeExpression setStatic(Class<?> declaringClass, String name, BytecodeExpression value)
      {
          return new SetFieldBytecodeExpression(null, declaringClass, name, value);
      }
  
      public static BytecodeExpression setStatic(Field staticField, BytecodeExpression value)
      {
          return new SetFieldBytecodeExpression(null, staticField, value);
      }
  
      public static BytecodeExpression setStatic(FieldDefinition staticField, BytecodeExpression value)
      {
          return new SetFieldBytecodeExpression(null, staticField, value);
      }
  
      public static BytecodeExpression setStatic(ParameterizedType declaringClass, String name, BytecodeExpression value)
      {
          return new SetFieldBytecodeExpression(null, declaringClass, name, value);
      }
  
      //
      // New instance
      //
  
      public static BytecodeExpression newInstance(Constructor<?> constructor, BytecodeExpression... parameters)
      {
          return newInstance(constructor, ImmutableList.copyOf(parameters));
      }
  
      public static BytecodeExpression newInstance(Constructor<?> constructor, Iterable<? extends BytecodeExpression> parameters)
      {
          return newInstance(
                  type(constructor.getDeclaringClass()),
                  stream(constructor.getParameterTypes())
                          .map(ParameterizedType::type)
                          .collect(toImmutableList()),
                  parameters);
      }
  
      public static BytecodeExpression newInstance(Class<?> returnType, BytecodeExpression... parameters)
      {
          return newInstance(type(returnType), ImmutableList.copyOf(requireNonNull(parameters, "parameters is null")));
      }
  
      public static BytecodeExpression newInstance(Class<?> returnType, Iterable<? extends BytecodeExpression> parameters)
      {
          return newInstance(type(returnType), parameters);
      }
  
      public static BytecodeExpression newInstance(ParameterizedType returnType, BytecodeExpression... parameters)
      {
          requireNonNull(parameters, "parameters is null");
  
          return newInstance(returnType, ImmutableList.copyOf(parameters));
      }
  
      public static BytecodeExpression newInstance(ParameterizedType returnType, Iterable<? extends BytecodeExpression> parameters)
      {
          requireNonNull(parameters, "parameters is null");
  
          return newInstance(
                  returnType,
                  StreamSupport.stream(parameters.spliterator(), false).map(BytecodeExpression::getType).collect(toImmutableList()),
                  parameters);
      }
  
      public static BytecodeExpression newInstance(Class<?> returnType, Iterable<? extends Class<?>> parameterTypes, BytecodeExpression... parameters)
      {
          return newInstance(
                  type(returnType),
                  StreamSupport.stream(parameterTypes.spliterator(), false).map(ParameterizedType::type).collect(toImmutableList()),
                  ImmutableList.copyOf(requireNonNull(parameters, "parameters is null")));
      }
  
      public static BytecodeExpression newInstance(ParameterizedType returnType, Iterable<ParameterizedType> parameterTypes, BytecodeExpression... parameters)
      {
          return newInstance(returnType, parameterTypes, ImmutableList.copyOf(requireNonNull(parameters, "parameters is null")));
      }
  
      public static BytecodeExpression newInstance(
              ParameterizedType type,
              Iterable<ParameterizedType> parameterTypes,
              Iterable<? extends BytecodeExpression> parameters)
      {
          return new NewInstanceBytecodeExpression(type, parameterTypes, parameters);
      }
  
      //
      // Array
      //
      public static BytecodeExpression newArray(ParameterizedType type, int length)
      {
          return new NewArrayBytecodeExpression(type, length);
      }
  
      public static BytecodeExpression newArray(ParameterizedType type, BytecodeExpression length)
      {
          return new NewArrayBytecodeExpression(type, length);
      }
  
      public static BytecodeExpression newArray(ParameterizedType type, BytecodeExpression... elements)
      {
          return new NewArrayBytecodeExpression(type, ImmutableList.copyOf(elements));
      }
  
      public static BytecodeExpression newArray(ParameterizedType type, Iterable<? extends BytecodeExpression> elements)
      {
          return new NewArrayBytecodeExpression(type, ImmutableList.copyOf(elements));
      }
  
      public static BytecodeExpression length(BytecodeExpression instance)
      {
          return new ArrayLengthBytecodeExpression(instance);
      }
  
      public static BytecodeExpression get(BytecodeExpression instance, BytecodeExpression index)
      {
          return new GetElementBytecodeExpression(instance, index);
      }
  
      public static BytecodeExpression set(BytecodeExpression instance, BytecodeExpression index, BytecodeExpression value)
      {
          return new SetArrayElementBytecodeExpression(instance, index, value);
      }
  
      //
      // Invoke static method
      //
  
      public static BytecodeExpression invokeStatic(MethodDefinition method, BytecodeExpression... parameters)
      {
          return invokeStatic(
                  method.getDeclaringClass().getType(),
                  method.getName(),
                  method.getReturnType(),
                  method.getParameterTypes(),
                  ImmutableList.copyOf(parameters));
      }
  
      public static BytecodeExpression invokeStatic(Method method, BytecodeExpression... parameters)
      {
          return invokeStatic(method, ImmutableList.copyOf(requireNonNull(parameters, "parameters is null")));
      }
  
      public static BytecodeExpression invokeStatic(Method method, Iterable<? extends BytecodeExpression> parameters)
      {
          return invokeStatic(
                  type(method.getDeclaringClass()),
                  method.getName(),
                  type(method.getReturnType()),
                  stream(method.getParameterTypes())
                          .map(ParameterizedType::type)
                          .collect(toImmutableList()),
                  parameters);
      }
  
      public static BytecodeExpression invokeStatic(Class<?> methodTargetType, String methodName, Class<?> returnType, BytecodeExpression... parameters)
      {
          return invokeStatic(methodTargetType, methodName, returnType, ImmutableList.copyOf(requireNonNull(parameters, "parameters is null")));
      }
  
      public static BytecodeExpression invokeStatic(
              Class<?> methodTargetType,
              String methodName,
              Class<?> returnType,
              Iterable<? extends BytecodeExpression> parameters)
      {
          return invokeStatic(type(methodTargetType), methodName, type(returnType), parameters);
      }
  
      public static BytecodeExpression invokeStatic(
              ParameterizedType methodTargetType,
              String methodName,
              ParameterizedType returnType,
              Iterable<? extends BytecodeExpression> parameters)
      {
          requireNonNull(methodTargetType, "methodTargetType is null");
          requireNonNull(returnType, "returnType is null");
          requireNonNull(parameters, "parameters is null");
  
          return invokeStatic(
                  methodTargetType,
                  methodName,
                  returnType,
                  StreamSupport.stream(parameters.spliterator(), false).map(BytecodeExpression::getType).collect(toImmutableList()),
                  parameters);
      }
  
      public static BytecodeExpression invokeStatic(
              Class<?> methodTargetType,
              String methodName,
              Class<?> returnType,
              Iterable<? extends Class<?>> parameterTypes,
              BytecodeExpression... parameters)
      {
          requireNonNull(methodTargetType, "methodTargetType is null");
          requireNonNull(returnType, "returnType is null");
          requireNonNull(parameterTypes, "parameterTypes is null");
          requireNonNull(parameters, "parameters is null");
  
          return invokeStatic(
                  type(methodTargetType),
                  methodName,
                  type(returnType),
                  StreamSupport.stream(parameterTypes.spliterator(), false).map(ParameterizedType::type).collect(toImmutableList()),
                  ImmutableList.copyOf(parameters));
      }
  
      public static BytecodeExpression invokeStatic(
              ParameterizedType methodTargetType,
              String methodName,
              ParameterizedType returnType,
              Iterable<ParameterizedType> parameterTypes,
              BytecodeExpression... parameters)
      {
          return invokeStatic(methodTargetType, methodName, returnType, parameterTypes, ImmutableList.copyOf(requireNonNull(parameters, "parameters is null")));
      }
  
      public static BytecodeExpression invokeStatic(
              ParameterizedType methodTargetType,
              String methodName,
              ParameterizedType returnType,
              Iterable<ParameterizedType> parameterTypes,
              Iterable<? extends BytecodeExpression> parameters)
      {
          return new InvokeBytecodeExpression(
                  null,
                  methodTargetType,
                  methodName,
                  returnType,
                  parameterTypes,
                  parameters);
      }
  
      //
      // Invoke dynamic
      //
  
      public static BytecodeExpression invokeDynamic(
              Method bootstrapMethod,
              Iterable<? extends Object> bootstrapArgs,
              String methodName,
              Class<?> returnType,
              BytecodeExpression... parameters)
      {
          return invokeDynamic(bootstrapMethod, bootstrapArgs, methodName, returnType, ImmutableList.copyOf(requireNonNull(parameters, "parameters is null")));
      }
  
      public static BytecodeExpression invokeDynamic(
              Method bootstrapMethod,
              Iterable<? extends Object> bootstrapArgs,
              String methodName,
              Class<?> returnType,
              Iterable<? extends BytecodeExpression> parameters)
      {
          requireNonNull(returnType, "returnType is null");
          requireNonNull(parameters, "parameters is null");
  
          return invokeDynamic(
                  bootstrapMethod,
                  bootstrapArgs,
                  methodName,
                  type(returnType),
                  StreamSupport.stream(parameters.spliterator(), false).map(BytecodeExpression::getType).collect(toImmutableList()),
                  parameters);
      }
  
      public static BytecodeExpression invokeDynamic(
              Method bootstrapMethod,
              Iterable<? extends Object> bootstrapArgs,
              String methodName,
              ParameterizedType returnType,
              BytecodeExpression... parameters)
      {
          return invokeDynamic(bootstrapMethod, bootstrapArgs, methodName, returnType, ImmutableList.copyOf(requireNonNull(parameters, "parameters is null")));
      }
  
      public static BytecodeExpression invokeDynamic(
              Method bootstrapMethod,
              Iterable<? extends Object> bootstrapArgs,
              String methodName,
              ParameterizedType returnType,
              Iterable<? extends BytecodeExpression> parameters)
      {
          requireNonNull(returnType, "returnType is null");
          requireNonNull(parameters, "parameters is null");
  
          return invokeDynamic(
                  bootstrapMethod,
                  bootstrapArgs,
                  methodName,
                  returnType,
                  StreamSupport.stream(parameters.spliterator(), false).map(BytecodeExpression::getType).collect(toImmutableList()),
                  parameters);
      }
  
      public static BytecodeExpression invokeDynamic(
              Method bootstrapMethod,
              Iterable<? extends Object> bootstrapArgs,
              String methodName,
              MethodType methodType,
              BytecodeExpression... parameters)
      {
          requireNonNull(methodType, "methodType is null");
          requireNonNull(parameters, "parameters is null");
  
          return invokeDynamic(bootstrapMethod, bootstrapArgs, methodName, methodType, ImmutableList.copyOf(parameters));
      }
  
      public static BytecodeExpression invokeDynamic(
              Method bootstrapMethod,
              Iterable<? extends Object> bootstrapArgs,
              String methodName,
              MethodType methodType,
              Iterable<? extends BytecodeExpression> parameters)
      {
          return invokeDynamic(
                  bootstrapMethod,
                  bootstrapArgs,
                  methodName,
                  type(methodType.returnType()),
                  methodType.parameterList().stream().map(ParameterizedType::type).collect(toImmutableList()),
                  ImmutableList.copyOf(requireNonNull(parameters, "parameters is null")));
      }
  
      public static BytecodeExpression invokeDynamic(
              Method bootstrapMethod,
              Iterable<? extends Object> bootstrapArgs,
              String methodName,
              ParameterizedType returnType,
              Iterable<ParameterizedType> parameterTypes,
              Iterable<? extends BytecodeExpression> parameters)
      {
          return new InvokeDynamicBytecodeExpression(
                  bootstrapMethod,
                  bootstrapArgs,
                  methodName,
                  returnType,
                  parameters,
                  parameterTypes);
      }
  
      //
      // Arithmetic operations
      //
  
      public static BytecodeExpression add(BytecodeExpression left, BytecodeExpression right)
      {
          return createArithmeticBytecodeExpression(OpCode.IADD, left, right);
      }
  
      public static BytecodeExpression subtract(BytecodeExpression left, BytecodeExpression right)
      {
          return createArithmeticBytecodeExpression(OpCode.ISUB, left, right);
      }
  
      public static BytecodeExpression multiply(BytecodeExpression left, BytecodeExpression right)
      {
          return createArithmeticBytecodeExpression(OpCode.IMUL, left, right);
      }
  
      public static BytecodeExpression divide(BytecodeExpression left, BytecodeExpression right)
      {
          return createArithmeticBytecodeExpression(OpCode.IDIV, left, right);
      }
  
      public static BytecodeExpression remainder(BytecodeExpression left, BytecodeExpression right)
      {
          return createArithmeticBytecodeExpression(OpCode.IREM, left, right);
      }
  
      public static BytecodeExpression bitwiseAnd(BytecodeExpression left, BytecodeExpression right)
      {
          return createArithmeticBytecodeExpression(OpCode.IAND, left, right);
      }
  
      public static BytecodeExpression bitwiseOr(BytecodeExpression left, BytecodeExpression right)
      {
          return createArithmeticBytecodeExpression(OpCode.IOR, left, right);
      }
  
      public static BytecodeExpression bitwiseXor(BytecodeExpression left, BytecodeExpression right)
      {
          return createArithmeticBytecodeExpression(OpCode.IXOR, left, right);
      }
  
      public static BytecodeExpression shiftLeft(BytecodeExpression left, BytecodeExpression right)
      {
          return createArithmeticBytecodeExpression(OpCode.ISHL, left, right);
      }
  
      public static BytecodeExpression shiftRight(BytecodeExpression left, BytecodeExpression right)
      {
          return createArithmeticBytecodeExpression(OpCode.ISHR, left, right);
      }
  
      public static BytecodeExpression shiftRightUnsigned(BytecodeExpression left, BytecodeExpression right)
      {
          return createArithmeticBytecodeExpression(OpCode.IUSHR, left, right);
      }
  
      public static BytecodeExpression negate(BytecodeExpression value)
      {
          return new NegateBytecodeExpression(value);
      }
  
      //
      // Comparison operations
      //
  
      public static BytecodeExpression lessThan(BytecodeExpression left, BytecodeExpression right)
      {
          return ComparisonBytecodeExpression.lessThan(left, right);
      }
  
      public static BytecodeExpression greaterThan(BytecodeExpression left, BytecodeExpression right)
      {
          return ComparisonBytecodeExpression.greaterThan(left, right);
      }
  
      public static BytecodeExpression lessThanOrEqual(BytecodeExpression left, BytecodeExpression right)
      {
          return ComparisonBytecodeExpression.lessThanOrEqual(left, right);
      }
  
      public static BytecodeExpression greaterThanOrEqual(BytecodeExpression left, BytecodeExpression right)
      {
          return ComparisonBytecodeExpression.greaterThanOrEqual(left, right);
      }
  
      public static BytecodeExpression equal(BytecodeExpression left, BytecodeExpression right)
      {
          return ComparisonBytecodeExpression.equal(left, right);
      }
  
      public static BytecodeExpression notEqual(BytecodeExpression left, BytecodeExpression right)
      {
          return ComparisonBytecodeExpression.notEqual(left, right);
      }
  
      //
      // Null comparison operations
      //
  
      public static BytecodeExpression isNull(BytecodeExpression value)
      {
          return equal(value, constantNull(value.getType()));
      }
  
      public static BytecodeExpression isNotNull(BytecodeExpression value)
      {
          return notEqual(value, constantNull(value.getType()));
      }
  
      //
      // Logical binary operations
      //
  
      public static BytecodeExpression and(BytecodeExpression left, BytecodeExpression right)
      {
          return new AndBytecodeExpression(left, right);
      }
  
      public static BytecodeExpression or(BytecodeExpression left, BytecodeExpression right)
      {
          return new OrBytecodeExpression(left, right);
      }
  
      public static BytecodeExpression not(BytecodeExpression value)
      {
          return new NotBytecodeExpression(value);
      }
  
      //
      // Complex expressions
      //
  
      public static BytecodeExpression inlineIf(BytecodeExpression condition, BytecodeExpression ifTrue, BytecodeExpression ifFalse)
      {
          return new InlineIfBytecodeExpression(condition, ifTrue, ifFalse);
      }
  
      //
      // Print
      //
      public static BytecodeExpression print(BytecodeExpression variable)
      {
          BytecodeExpression out = getStatic(System.class, "out");
          return out.invoke("println", void.class, variable);
      }
  }
  