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.collect.ImmutableList;
  import com.google.common.primitives.Primitives;
  import io.airlift.bytecode.BytecodeNode;
  import io.airlift.bytecode.BytecodeVisitor;
  import io.airlift.bytecode.MethodGenerationContext;
  import io.airlift.bytecode.ParameterizedType;
  import org.objectweb.asm.ConstantDynamic;
  import org.objectweb.asm.Handle;
  import org.objectweb.asm.MethodVisitor;
  import org.objectweb.asm.Opcodes;
  import org.objectweb.asm.Type;
  
  import java.lang.reflect.Method;
  import java.util.List;
  
  import static com.google.common.base.MoreObjects.toStringHelper;
  import static io.airlift.bytecode.MethodDefinition.methodDescription;
  import static io.airlift.bytecode.OpCode.ACONST_NULL;
  import static io.airlift.bytecode.OpCode.BIPUSH;
  import static io.airlift.bytecode.OpCode.DCONST_0;
  import static io.airlift.bytecode.OpCode.DCONST_1;
  import static io.airlift.bytecode.OpCode.FCONST_0;
  import static io.airlift.bytecode.OpCode.FCONST_1;
  import static io.airlift.bytecode.OpCode.FCONST_2;
  import static io.airlift.bytecode.OpCode.ICONST_0;
  import static io.airlift.bytecode.OpCode.ICONST_1;
  import static io.airlift.bytecode.OpCode.ICONST_2;
  import static io.airlift.bytecode.OpCode.ICONST_3;
  import static io.airlift.bytecode.OpCode.ICONST_4;
  import static io.airlift.bytecode.OpCode.ICONST_5;
  import static io.airlift.bytecode.OpCode.ICONST_M1;
  import static io.airlift.bytecode.OpCode.LCONST_0;
  import static io.airlift.bytecode.OpCode.LCONST_1;
  import static io.airlift.bytecode.OpCode.SIPUSH;
  import static io.airlift.bytecode.ParameterizedType.type;
  import static io.airlift.bytecode.instruction.FieldInstruction.getStaticInstruction;
  import static io.airlift.bytecode.instruction.InvokeInstruction.invokeStatic;
  import static java.util.Objects.requireNonNull;
  
  @SuppressWarnings("UnusedDeclaration")
  public abstract class Constant
          implements InstructionNode
  {
      public static Constant loadNull()
      {
          return new NullConstant();
      }
  
      public static Constant loadBoolean(boolean value)
      {
          return new BooleanConstant(value);
      }
  
      public static Constant loadBoxedBoolean(boolean value)
      {
          return new BoxedBooleanConstant(value);
      }
  
      public static Constant loadInt(int value)
      {
          return new IntConstant(value);
      }
  
      public static Constant loadBoxedInt(int value)
      {
          return new BoxedIntegerConstant(value);
      }
  
      public static Constant loadFloat(float value)
      {
          return new FloatConstant(value);
      }
  
      public static Constant loadBoxedFloat(float value)
      {
          return new BoxedFloatConstant(value);
      }
  
      public static Constant loadLong(long value)
      {
          return new LongConstant(value);
      }
  
      public static Constant loadBoxedLong(long value)
      {
          return new BoxedLongConstant(value);
      }
  
      public static Constant loadDouble(double value)
      {
          return new DoubleConstant(value);
      }
  
      public static Constant loadBoxedDouble(double value)
      {
          return new BoxedDoubleConstant(value);
      }
  
      public static Constant loadNumber(Number value)
      {
          requireNonNull(value, "value is null");
          if (value instanceof Byte) {
              return loadInt((value).intValue());
          }
          if (value instanceof Short) {
              return loadInt((value).intValue());
          }
          if (value instanceof Integer) {
              return loadInt((Integer) value);
          }
          if (value instanceof Long) {
              return loadLong((Long) value);
          }
          if (value instanceof Float) {
              return loadFloat((Float) value);
          }
          if (value instanceof Double) {
              return loadDouble((Double) value);
          }
          throw new IllegalStateException("Unsupported number type " + value.getClass().getSimpleName());
      }
  
      public static Constant loadString(String value)
      {
          requireNonNull(value, "value is null");
          return new StringConstant(value);
      }
  
      public static Constant loadClass(Class<?> value)
      {
          requireNonNull(value, "value is null");
          return new ClassConstant(type(value));
      }
  
      public static Constant loadClass(ParameterizedType value)
      {
          requireNonNull(value, "value is null");
          return new ClassConstant(value);
      }
  
      public static Constant loadDynamic(
              String name,
              ParameterizedType type,
              Method bootstrapMethod,
              Iterable<Object> bootstrapArguments)
      {
          return new DynamicConstant(
                  name,
                  type,
                  bootstrapMethod,
                  ImmutableList.copyOf(bootstrapArguments));
      }
  
      public static Constant loadDynamic(
              String name,
              ParameterizedType type,
              Method bootstrapMethod,
              Object... bootstrapArguments)
      {
          return new DynamicConstant(
                  name,
                  type,
                  bootstrapMethod,
                  ImmutableList.copyOf(bootstrapArguments));
      }
  
      public abstract Object getValue();
  
      @Override
      public List<BytecodeNode> getChildNodes()
      {
          return ImmutableList.of();
      }
  
      @Override
      public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
      {
          return visitor.visitConstant(parent, this);
      }
  
      @Override
      public String toString()
      {
          return toStringHelper(this)
                  .add("value", getValue())
                  .toString();
      }
  
      public static class NullConstant
              extends Constant
      {
          @Override
          public Object getValue()
          {
              return null;
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              visitor.visitInsn(ACONST_NULL.getOpCode());
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitConstant(parent, this);
          }
      }
  
      public static class BooleanConstant
              extends Constant
      {
          private final boolean value;
  
          private BooleanConstant(boolean value)
          {
              this.value = value;
          }
  
          @Override
          public Boolean getValue()
          {
              return value;
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              if (value) {
                  visitor.visitInsn(ICONST_1.getOpCode());
              }
              else {
                  visitor.visitInsn(ICONST_0.getOpCode());
              }
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitBooleanConstant(parent, this);
          }
      }
  
      public static class BoxedBooleanConstant
              extends Constant
      {
          private final boolean value;
  
          private BoxedBooleanConstant(boolean value)
          {
              this.value = value;
          }
  
          @Override
          public Boolean getValue()
          {
              return value;
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              if (value) {
                  getStaticInstruction(Boolean.class, "TRUE", Boolean.class).accept(visitor, generationContext);
              }
              else {
                  getStaticInstruction(Boolean.class, "FALSE", Boolean.class).accept(visitor, generationContext);
              }
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitBoxedBooleanConstant(parent, this);
          }
      }
  
      public static class IntConstant
              extends Constant
      {
          private final int value;
  
          private IntConstant(int value)
          {
              this.value = value;
          }
  
          @Override
          public Integer getValue()
          {
              return value;
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              if (value <= Byte.MAX_VALUE && value >= Byte.MIN_VALUE) {
                  switch (value) {
                      case -1:
                          visitor.visitInsn(ICONST_M1.getOpCode());
                          break;
                      case 0:
                          visitor.visitInsn(ICONST_0.getOpCode());
                          break;
                      case 1:
                          visitor.visitInsn(ICONST_1.getOpCode());
                          break;
                      case 2:
                          visitor.visitInsn(ICONST_2.getOpCode());
                          break;
                      case 3:
                          visitor.visitInsn(ICONST_3.getOpCode());
                          break;
                      case 4:
                          visitor.visitInsn(ICONST_4.getOpCode());
                          break;
                      case 5:
                          visitor.visitInsn(ICONST_5.getOpCode());
                          break;
                      default:
                          visitor.visitIntInsn(BIPUSH.getOpCode(), value);
                          break;
                  }
              }
              else if (value <= Short.MAX_VALUE && value >= Short.MIN_VALUE) {
                  visitor.visitIntInsn(SIPUSH.getOpCode(), value);
              }
              else {
                  visitor.visitLdcInsn(value);
              }
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitIntConstant(parent, this);
          }
      }
  
      public static class BoxedIntegerConstant
              extends Constant
      {
          private final int value;
  
          private BoxedIntegerConstant(int value)
          {
              this.value = value;
          }
  
          @Override
          public Integer getValue()
          {
              return value;
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              loadInt(value).accept(visitor, generationContext);
              invokeStatic(Integer.class, "valueOf", Integer.class, int.class).accept(visitor, generationContext);
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitBoxedIntegerConstant(parent, this);
          }
      }
  
      public static class FloatConstant
              extends Constant
      {
          private final float value;
  
          private FloatConstant(float value)
          {
              this.value = value;
          }
  
          @Override
          public Float getValue()
          {
              return value;
          }
  
          @Override
          @SuppressWarnings("FloatingPointEquality")
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              // We can not use "value == 0.0" because when value is "-0.0" the expression
              // will evaluate to true and we would convert "-0.0" to "0.0" which is
              // not the same value
              if (Float.floatToIntBits(value) == Float.floatToIntBits(0.0f)) {
                  visitor.visitInsn(FCONST_0.getOpCode());
              }
              else if (value == 1.0f) {
                  visitor.visitInsn(FCONST_1.getOpCode());
              }
              else if (value == 2.0f) {
                  visitor.visitInsn(FCONST_2.getOpCode());
              }
              else {
                  visitor.visitLdcInsn(value);
              }
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitFloatConstant(parent, this);
          }
      }
  
      public static class BoxedFloatConstant
              extends Constant
      {
          private final float value;
  
          private BoxedFloatConstant(float value)
          {
              this.value = value;
          }
  
          @Override
          public Float getValue()
          {
              return value;
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              loadFloat(value).accept(visitor, generationContext);
              invokeStatic(Float.class, "valueOf", Float.class, float.class).accept(visitor, generationContext);
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitBoxedFloatConstant(parent, this);
          }
      }
  
      public static class LongConstant
              extends Constant
      {
          private final long value;
  
          private LongConstant(long value)
          {
              this.value = value;
          }
  
          @Override
          public Long getValue()
          {
              return value;
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              if (value == 0) {
                  visitor.visitInsn(LCONST_0.getOpCode());
              }
              else if (value == 1) {
                  visitor.visitInsn(LCONST_1.getOpCode());
              }
              else {
                  visitor.visitLdcInsn(value);
              }
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitLongConstant(parent, this);
          }
      }
  
      public static class BoxedLongConstant
              extends Constant
      {
          private final long value;
  
          private BoxedLongConstant(long value)
          {
              this.value = value;
          }
  
          @Override
          public Long getValue()
          {
              return value;
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              loadLong(value).accept(visitor, generationContext);
              invokeStatic(Long.class, "valueOf", Long.class, long.class).accept(visitor, generationContext);
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitBoxedLongConstant(parent, this);
          }
      }
  
      public static class DoubleConstant
              extends Constant
      {
          private final double value;
  
          private DoubleConstant(double value)
          {
              this.value = value;
          }
  
          @Override
          public Double getValue()
          {
              return value;
          }
  
          @Override
          @SuppressWarnings("FloatingPointEquality")
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              // We can not use "value == 0.0" because when value is "-0.0" the expression
              // will evaluate to true and we would convert "-0.0" to "0.0" which is
              // not the same value
              if (Double.doubleToLongBits(value) == Double.doubleToLongBits(0.0)) {
                  visitor.visitInsn(DCONST_0.getOpCode());
              }
              else if (value == 1.0) {
                  visitor.visitInsn(DCONST_1.getOpCode());
              }
              else {
                  visitor.visitLdcInsn(value);
              }
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitDoubleConstant(parent, this);
          }
      }
  
      public static class BoxedDoubleConstant
              extends Constant
      {
          private final double value;
  
          private BoxedDoubleConstant(double value)
          {
              this.value = value;
          }
  
          @Override
          public Double getValue()
          {
              return value;
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              loadDouble(value).accept(visitor, generationContext);
              invokeStatic(Double.class, "valueOf", Double.class, double.class).accept(visitor, generationContext);
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitBoxedDoubleConstant(parent, this);
          }
      }
  
      public static class StringConstant
              extends Constant
      {
          private final String value;
  
          private StringConstant(String value)
          {
              this.value = value;
          }
  
          @Override
          public String getValue()
          {
              return value;
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              visitor.visitLdcInsn(value);
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitStringConstant(parent, this);
          }
      }
  
      public static class ClassConstant
              extends Constant
      {
          private final ParameterizedType value;
  
          private ClassConstant(ParameterizedType value)
          {
              this.value = value;
          }
  
          @Override
          public ParameterizedType getValue()
          {
              return value;
          }
  
          @Override
          public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
          {
              if (value.isPrimitive()) {
                  Class<?> wrapper = Primitives.wrap(value.getPrimitiveType());
                  getStaticInstruction(wrapper, "TYPE", Class.class).accept(visitor, generationContext);
              }
              else {
                  visitor.visitLdcInsn(Type.getType(value.getType()));
              }
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitClassConstant(parent, this);
          }
      }
  
      public static class DynamicConstant
              extends Constant
      {
          private final String name;
          private final ParameterizedType type;
          private final Method bootstrapMethod;
          private final List<Object> bootstrapArguments;
  
          public DynamicConstant(String name, ParameterizedType type, Method bootstrapMethod, List<Object> bootstrapArguments)
          {
              this.name = name;
              this.type = type;
              this.bootstrapMethod = bootstrapMethod;
              this.bootstrapArguments = bootstrapArguments;
          }
  
          @Override
          public ParameterizedType getValue()
          {
              return type;
          }
  
          public String getName()
          {
              return name;
          }
  
          public Method getBootstrapMethod()
          {
              return bootstrapMethod;
          }
  
          public List<Object> getBootstrapArguments()
          {
              return 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.visitLdcInsn(new ConstantDynamic(
                      name,
                      type.getType(),
                      bootstrapMethodHandle,
                      bootstrapArguments.toArray()));
          }
  
          @Override
          public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
          {
              return visitor.visitDynamicConstant(parent, this);
          }
      }
  }
  