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;
  
  import com.google.common.collect.ImmutableList;
  import io.airlift.bytecode.debug.LineNumberNode;
  import io.airlift.bytecode.instruction.Constant;
  import io.airlift.bytecode.instruction.InvokeInstruction;
  import io.airlift.bytecode.instruction.JumpInstruction;
  import io.airlift.bytecode.instruction.LabelNode;
  import io.airlift.bytecode.instruction.TypeInstruction;
  import io.airlift.bytecode.instruction.VariableInstruction;
  import org.objectweb.asm.MethodVisitor;
  
  import javax.annotation.concurrent.NotThreadSafe;
  
  import java.lang.invoke.MethodType;
  import java.lang.reflect.Constructor;
  import java.lang.reflect.Field;
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  import java.util.ArrayList;
  import java.util.List;
  
  import static com.google.common.base.Preconditions.checkArgument;
  import static io.airlift.bytecode.Access.STATIC;
  import static io.airlift.bytecode.ParameterizedType.type;
  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.loadFloat;
  import static io.airlift.bytecode.instruction.Constant.loadInt;
  import static io.airlift.bytecode.instruction.Constant.loadLong;
  import static io.airlift.bytecode.instruction.Constant.loadNumber;
  import static io.airlift.bytecode.instruction.FieldInstruction.getFieldInstruction;
  import static io.airlift.bytecode.instruction.FieldInstruction.getStaticInstruction;
  import static io.airlift.bytecode.instruction.FieldInstruction.putFieldInstruction;
  import static io.airlift.bytecode.instruction.FieldInstruction.putStaticInstruction;
  import static io.airlift.bytecode.instruction.TypeInstruction.cast;
  import static io.airlift.bytecode.instruction.TypeInstruction.instanceOf;
  import static io.airlift.bytecode.instruction.VariableInstruction.loadVariable;
  import static io.airlift.bytecode.instruction.VariableInstruction.storeVariable;
  
  @SuppressWarnings("UnusedDeclaration")
  @NotThreadSafe
  public class BytecodeBlock
          implements BytecodeNode
  {
      private final List<BytecodeNode> nodes = new ArrayList<>();
  
      private String description;
      private int currentLineNumber = -1;
  
      public String getDescription()
      {
          return description;
      }
  
      public BytecodeBlock setDescription(String description)
      {
          this.description = description;
          return this;
      }
  
      @Override
      public List<BytecodeNode> getChildNodes()
      {
          return ImmutableList.copyOf(nodes);
      }
  
      public BytecodeBlock append(BytecodeNode node)
      {
          nodes.add(node);
          return this;
      }
  
      public BytecodeBlock comment(String comment)
      {
          nodes.add(new Comment(comment));
          return this;
      }
  
      public BytecodeBlock comment(String comment, Object... args)
      {
          nodes.add(new Comment(String.format(comment, args)));
          return this;
      }
  
      public boolean isEmpty()
      {
          return nodes.isEmpty();
      }
  
      public BytecodeBlock visitLabel(LabelNode label)
      {
          nodes.add(label);
          return this;
      }
  
      public BytecodeBlock gotoLabel(LabelNode label)
      {
          nodes.add(JumpInstruction.jump(label));
          return this;
      }
  
      public BytecodeBlock ifFalseGoto(LabelNode label)
      {
          return ifZeroGoto(label);
      }
  
      public BytecodeBlock ifTrueGoto(LabelNode label)
      {
          return ifNotZeroGoto(label);
      }
  
      public BytecodeBlock ifZeroGoto(LabelNode label)
      {
          nodes.add(JumpInstruction.jumpIfEqualZero(label));
          return this;
      }
  
      public BytecodeBlock ifNotZeroGoto(LabelNode label)
      {
          nodes.add(JumpInstruction.jumpIfNotEqualZero(label));
          return this;
      }
  
      public BytecodeBlock ifNullGoto(LabelNode label)
      {
          nodes.add(JumpInstruction.jumpIfNull(label));
          return this;
      }
  
      public BytecodeBlock ifNotNullGoto(LabelNode label)
      {
          nodes.add(JumpInstruction.jumpIfNotNull(label));
          return this;
      }
  
      public BytecodeBlock intAdd()
      {
          nodes.add(OpCode.IADD);
          return this;
      }
  
      public BytecodeBlock longAdd()
      {
          nodes.add(OpCode.LADD);
          return this;
      }
  
      public BytecodeBlock longCompare()
      {
          nodes.add(OpCode.LCMP);
          return this;
      }
  
      /**
       * Compare two doubles. If either is NaN comparison is -1.
       */
      public BytecodeBlock doubleCompareNanLess()
      {
          nodes.add(OpCode.DCMPL);
          return this;
      }
  
      /**
       * Compare two doubles. If either is NaN comparison is 1.
       */
      public BytecodeBlock doubleCompareNanGreater()
      {
          nodes.add(OpCode.DCMPG);
          return this;
      }
  
      public BytecodeBlock intLeftShift()
      {
          nodes.add(OpCode.ISHL);
          return this;
      }
  
      public BytecodeBlock intRightShift()
      {
          nodes.add(OpCode.ISHR);
          return this;
      }
  
      public BytecodeBlock longLeftShift()
      {
          nodes.add(OpCode.LSHL);
          return this;
      }
  
      public BytecodeBlock longRightShift()
      {
          nodes.add(OpCode.LSHR);
          return this;
      }
  
      public BytecodeBlock unsignedIntRightShift()
      {
          nodes.add(OpCode.IUSHR);
          return this;
      }
  
      public BytecodeBlock unsignedLongRightShift()
      {
          nodes.add(OpCode.LUSHR);
          return this;
      }
  
      public BytecodeBlock intBitAnd()
      {
          nodes.add(OpCode.IAND);
          return this;
      }
  
      public BytecodeBlock intBitOr()
      {
          nodes.add(OpCode.IOR);
          return this;
      }
  
      public BytecodeBlock intBitXor()
      {
          nodes.add(OpCode.IXOR);
          return this;
      }
  
      public BytecodeBlock longBitAnd()
      {
          nodes.add(OpCode.LAND);
          return this;
      }
  
      public BytecodeBlock longBitOr()
      {
          nodes.add(OpCode.LOR);
          return this;
      }
  
      public BytecodeBlock longBitXor()
      {
          nodes.add(OpCode.LXOR);
          return this;
      }
  
      public BytecodeBlock intNegate()
      {
          nodes.add(OpCode.INEG);
          return this;
      }
  
      public BytecodeBlock intToLong()
      {
          nodes.add(OpCode.I2L);
          return this;
      }
  
      public BytecodeBlock longNegate()
      {
          nodes.add(OpCode.LNEG);
          return this;
      }
  
      public BytecodeBlock longToInt()
      {
          nodes.add(OpCode.L2I);
          return this;
      }
  
      public BytecodeBlock isInstanceOf(Class<?> type)
      {
          nodes.add(instanceOf(type));
          return this;
      }
  
      public BytecodeBlock isInstanceOf(ParameterizedType type)
      {
          nodes.add(instanceOf(type));
          return this;
      }
  
      public BytecodeBlock checkCast(Class<?> type)
      {
          nodes.add(cast(type));
          return this;
      }
  
      public BytecodeBlock checkCast(ParameterizedType type)
      {
          nodes.add(cast(type));
          return this;
      }
  
      public BytecodeBlock invokeStatic(Method method)
      {
          nodes.add(InvokeInstruction.invokeStatic(method));
          return this;
      }
  
      public BytecodeBlock invokeStatic(MethodDefinition method)
      {
          nodes.add(InvokeInstruction.invokeStatic(method));
          return this;
      }
  
      public BytecodeBlock invokeStatic(Class<?> type, String name, Class<?> returnType, Class<?>... parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeStatic(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeStatic(Class<?> type, String name, Class<?> returnType, Iterable<Class<?>> parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeStatic(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeStatic(ParameterizedType type, String name, ParameterizedType returnType, ParameterizedType... parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeStatic(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeStatic(ParameterizedType type, String name, ParameterizedType returnType, Iterable<ParameterizedType> parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeStatic(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeVirtual(Method method)
      {
          nodes.add(InvokeInstruction.invokeVirtual(method));
          return this;
      }
  
      public BytecodeBlock invokeVirtual(MethodDefinition method)
      {
          nodes.add(InvokeInstruction.invokeVirtual(method));
          return this;
      }
  
      public BytecodeBlock invokeVirtual(Class<?> type, String name, Class<?> returnType, Class<?>... parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeVirtual(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeVirtual(Class<?> type, String name, Class<?> returnType, Iterable<Class<?>> parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeVirtual(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeVirtual(ParameterizedType type, String name, ParameterizedType returnType, ParameterizedType... parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeVirtual(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeVirtual(ParameterizedType type, String name, ParameterizedType returnType, Iterable<ParameterizedType> parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeVirtual(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeInterface(Method method)
      {
          nodes.add(InvokeInstruction.invokeInterface(method));
          return this;
      }
  
      public BytecodeBlock invokeInterface(MethodDefinition method)
      {
          nodes.add(InvokeInstruction.invokeInterface(method));
          return this;
      }
  
      public BytecodeBlock invokeInterface(Class<?> type, String name, Class<?> returnType, Class<?>... parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeInterface(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeInterface(Class<?> type, String name, Class<?> returnType, Iterable<Class<?>> parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeInterface(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeInterface(ParameterizedType type, String name, ParameterizedType returnType, ParameterizedType... parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeInterface(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeInterface(ParameterizedType type, String name, ParameterizedType returnType, Iterable<ParameterizedType> parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeInterface(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeConstructor(Constructor<?> constructor)
      {
          nodes.add(InvokeInstruction.invokeConstructor(constructor));
          return this;
      }
  
      public BytecodeBlock invokeConstructor(Class<?> type, Class<?>... parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeConstructor(type, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeConstructor(Class<?> type, Iterable<Class<?>> parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeConstructor(type, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeConstructor(ParameterizedType type, ParameterizedType... parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeConstructor(type, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeConstructor(ParameterizedType type, Iterable<ParameterizedType> parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeConstructor(type, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeSpecial(Method method)
      {
          nodes.add(InvokeInstruction.invokeSpecial(method));
          return this;
      }
  
      public BytecodeBlock invokeSpecial(MethodDefinition method)
      {
          nodes.add(InvokeInstruction.invokeSpecial(method));
          return this;
      }
  
      public BytecodeBlock invokeSpecial(Class<?> type, String name, Class<?> returnType, Class<?>... parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeSpecial(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeSpecial(Class<?> type, String name, Class<?> returnType, Iterable<Class<?>> parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeSpecial(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeSpecial(ParameterizedType type, String name, ParameterizedType returnType, ParameterizedType... parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeSpecial(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeSpecial(ParameterizedType type, String name, ParameterizedType returnType, Iterable<ParameterizedType> parameterTypes)
      {
          nodes.add(InvokeInstruction.invokeSpecial(type, name, returnType, parameterTypes));
          return this;
      }
  
      public BytecodeBlock invokeDynamic(String name, MethodType methodType, Method bootstrapMethod, Object... defaultBootstrapArguments)
      {
          nodes.add(InvokeInstruction.invokeDynamic(name, methodType, bootstrapMethod, defaultBootstrapArguments));
          return this;
      }
  
      public BytecodeNode invokeDynamic(String name,
              ParameterizedType returnType,
              Iterable<ParameterizedType> parameterTypes,
              Method bootstrapMethod,
              List<Object> bootstrapArgs)
      {
          nodes.add(InvokeInstruction.invokeDynamic(name, returnType, parameterTypes, bootstrapMethod, bootstrapArgs));
          return this;
      }
  
      public BytecodeBlock ret(Class<?> type)
      {
          if (type == long.class) {
              retLong();
          }
          else if (type == boolean.class) {
              retBoolean();
          }
          else if (type == int.class || type == byte.class || type == char.class || type == short.class) {
              retInt();
          }
          else if (type == float.class) {
              retFloat();
          }
          else if (type == double.class) {
              retDouble();
          }
          else if (type == void.class) {
              ret();
          }
          else if (!type.isPrimitive()) {
              retObject();
          }
          else {
              throw new IllegalArgumentException("Unsupported type: " + type.getName());
          }
  
          return this;
      }
  
      public BytecodeBlock ret()
      {
          nodes.add(OpCode.RETURN);
          return this;
      }
  
      public BytecodeBlock retObject()
      {
          nodes.add(OpCode.ARETURN);
          return this;
      }
  
      public BytecodeBlock retFloat()
      {
          nodes.add(OpCode.FRETURN);
          return this;
      }
  
      public BytecodeBlock retDouble()
      {
          nodes.add(OpCode.DRETURN);
          return this;
      }
  
      public BytecodeBlock retBoolean()
      {
          nodes.add(OpCode.IRETURN);
          return this;
      }
  
      public BytecodeBlock retLong()
      {
          nodes.add(OpCode.LRETURN);
          return this;
      }
  
      public BytecodeBlock retInt()
      {
          nodes.add(OpCode.IRETURN);
          return this;
      }
  
      public BytecodeBlock throwObject()
      {
          nodes.add(OpCode.ATHROW);
          return this;
      }
  
      public BytecodeBlock newObject(Class<?> type)
      {
          nodes.add(TypeInstruction.newObject(type));
          return this;
      }
  
      public BytecodeBlock newObject(ParameterizedType type)
      {
          nodes.add(TypeInstruction.newObject(type));
          return this;
      }
  
      public BytecodeBlock newArray(Class<?> type)
      {
          nodes.add(TypeInstruction.newObjectArray(type));
          return this;
      }
  
      public BytecodeBlock dup()
      {
          nodes.add(OpCode.DUP);
          return this;
      }
  
      public BytecodeBlock dup(Class<?> type)
      {
          if (type == long.class || type == double.class) {
              nodes.add(OpCode.DUP2);
          }
          else if (type != void.class) {
              nodes.add(OpCode.DUP);
          }
          return this;
      }
  
      public BytecodeBlock pop()
      {
          nodes.add(OpCode.POP);
          return this;
      }
  
      public BytecodeBlock pop(Class<?> type)
      {
          if (type == long.class || type == double.class) {
              nodes.add(OpCode.POP2);
          }
          else if (type != void.class) {
              nodes.add(OpCode.POP);
          }
          return this;
      }
  
      public BytecodeBlock pop(ParameterizedType type)
      {
          Class<?> primitiveType = type.getPrimitiveType();
          if (primitiveType == long.class || primitiveType == double.class) {
              nodes.add(OpCode.POP2);
          }
          else if (primitiveType != void.class) {
              nodes.add(OpCode.POP);
          }
          return this;
      }
  
      public BytecodeBlock swap()
      {
          nodes.add(OpCode.SWAP);
          return this;
      }
  
      //
      // Fields (non-static)
      //
  
      public BytecodeBlock getField(Field field)
      {
          return getField(field.getDeclaringClass(), field.getName(), field.getType());
      }
  
      public BytecodeBlock getField(FieldDefinition field)
      {
          getField(field.getDeclaringClass().getType(), field.getName(), field.getType());
          return this;
      }
  
      public BytecodeBlock getField(Class<?> target, String fieldName, Class<?> fieldType)
      {
          getField(type(target), fieldName, type(fieldType));
          return this;
      }
  
      public BytecodeBlock getField(ParameterizedType target, String fieldName, ParameterizedType fieldType)
      {
          nodes.add(getFieldInstruction(target, fieldName, fieldType));
          return this;
      }
  
      public BytecodeBlock putField(Field field)
      {
          return putField(field.getDeclaringClass(), field.getName(), field.getType());
      }
  
      public BytecodeBlock putField(Class<?> target, String fieldName, Class<?> fieldType)
      {
          putField(type(target), fieldName, type(fieldType));
          return this;
      }
  
      public BytecodeBlock putField(FieldDefinition field)
      {
          checkArgument(!field.getAccess().contains(STATIC), "Field is static: %s", field);
          putField(field.getDeclaringClass().getType(), field.getName(), field.getType());
          return this;
      }
  
      public BytecodeBlock putField(ParameterizedType target, String fieldName, ParameterizedType fieldType)
      {
          nodes.add(putFieldInstruction(target, fieldName, fieldType));
          return this;
      }
  
      //
      // Static fields
      //
  
      public BytecodeBlock getStaticField(FieldDefinition field)
      {
          getStaticField(field.getDeclaringClass().getType(), field.getName(), field.getType());
          return this;
      }
  
      public BytecodeBlock getStaticField(Field field)
      {
          checkArgument(Modifier.isStatic(field.getModifiers()), "Field is not static: %s", field);
          getStaticField(type(field.getDeclaringClass()), field.getName(), type(field.getType()));
          return this;
      }
  
      public BytecodeBlock getStaticField(Class<?> target, String fieldName, Class<?> fieldType)
      {
          nodes.add(getStaticInstruction(target, fieldName, fieldType));
          return this;
      }
  
      public BytecodeBlock getStaticField(ParameterizedType target, String fieldName, ParameterizedType fieldType)
      {
          nodes.add(getStaticInstruction(target, fieldName, fieldType));
          return this;
      }
  
      public BytecodeBlock getStaticField(ParameterizedType target, FieldDefinition field)
      {
          nodes.add(getStaticInstruction(target, field.getName(), field.getType()));
          return this;
      }
  
      public BytecodeBlock putStaticField(FieldDefinition field)
      {
          putStaticField(field.getDeclaringClass().getType(), field.getName(), field.getType());
          return this;
      }
  
      public BytecodeBlock putStaticField(ParameterizedType target, FieldDefinition field)
      {
          checkArgument(field.getAccess().contains(STATIC), "Field is not static: %s", field);
          putStaticField(target, field.getName(), field.getType());
          return this;
      }
  
      public BytecodeBlock putStaticField(ParameterizedType target, String fieldName, ParameterizedType fieldType)
      {
          nodes.add(putStaticInstruction(target, fieldName, fieldType));
          return this;
      }
  
      //
      // Load constants
      //
  
      public BytecodeBlock pushNull()
      {
          nodes.add(OpCode.ACONST_NULL);
          return this;
      }
  
      public BytecodeBlock push(Class<?> type)
      {
          nodes.add(loadClass(type));
          return this;
      }
  
      public BytecodeBlock push(ParameterizedType type)
      {
          nodes.add(loadClass(type));
          return this;
      }
  
      public BytecodeBlock push(String value)
      {
          nodes.add(Constant.loadString(value));
          return this;
      }
  
      public BytecodeBlock push(Number value)
      {
          nodes.add(loadNumber(value));
          return this;
      }
  
      public BytecodeBlock push(int value)
      {
          nodes.add(loadInt(value));
          return this;
      }
  
      public BytecodeBlock push(boolean value)
      {
          nodes.add(loadBoolean(value));
          return this;
      }
  
      public BytecodeBlock pushJavaDefault(Class<?> type)
      {
          if (type == void.class) {
              return this;
          }
          if (type == boolean.class || type == byte.class || type == char.class || type == short.class || type == int.class) {
              return push(0);
          }
          if (type == long.class) {
              return push(0L);
          }
          if (type == float.class) {
              return push(0.0f);
          }
          if (type == double.class) {
              return push(0.0d);
          }
          return pushNull();
      }
  
      public BytecodeBlock initializeVariable(Variable variable)
      {
          ParameterizedType type = variable.getType();
          if (type.getType().length() == 1) {
              switch (type.getType().charAt(0)) {
                  case 'B':
                  case 'Z':
                  case 'S':
                  case 'C':
                  case 'I':
                      nodes.add(loadInt(0));
                      break;
                  case 'F':
                      nodes.add(loadFloat(0));
                      break;
                  case 'D':
                      nodes.add(loadDouble(0));
                      break;
                  case 'J':
                      nodes.add(loadLong(0));
                      break;
                  default:
                      checkArgument(false, "Unknown type '%s'", variable.getType());
              }
          }
          else {
              nodes.add(Constant.loadNull());
          }
  
          nodes.add(storeVariable(variable));
  
          return this;
      }
  
      public BytecodeBlock getVariable(Variable variable)
      {
          nodes.add(loadVariable(variable));
          return this;
      }
  
      public BytecodeBlock putVariable(Variable variable)
      {
          nodes.add(storeVariable(variable));
          return this;
      }
  
      public BytecodeBlock putVariable(Variable variable, Class<?> type)
      {
          nodes.add(loadClass(type));
          putVariable(variable);
          return this;
      }
  
      public BytecodeBlock putVariable(Variable variable, ParameterizedType type)
      {
          nodes.add(loadClass(type));
          putVariable(variable);
          return this;
      }
  
      public BytecodeBlock putVariable(Variable variable, String value)
      {
          nodes.add(Constant.loadString(value));
          putVariable(variable);
          return this;
      }
  
      public BytecodeBlock putVariable(Variable variable, Number value)
      {
          nodes.add(loadNumber(value));
          putVariable(variable);
          return this;
      }
  
      public BytecodeBlock putVariable(Variable variable, int value)
      {
          nodes.add(loadInt(value));
          putVariable(variable);
          return this;
      }
  
      public BytecodeBlock putVariable(Variable variable, boolean value)
      {
          nodes.add(loadBoolean(value));
          putVariable(variable);
          return this;
      }
  
      public BytecodeBlock incrementVariable(Variable variable, byte increment)
      {
          String type = variable.getType().getClassName();
          checkArgument(ImmutableList.of("byte", "short", "int").contains(type), "variable must be an byte, short or int, but is %s", type);
          nodes.add(VariableInstruction.incrementVariable(variable, increment));
          return this;
      }
  
      public BytecodeBlock getObjectArrayElement()
      {
          nodes.add(OpCode.AALOAD);
          return this;
      }
  
      public BytecodeBlock putObjectArrayElement()
      {
          nodes.add(OpCode.AASTORE);
          return this;
      }
  
      public BytecodeBlock getIntArrayElement()
      {
          nodes.add(OpCode.IALOAD);
          return this;
      }
  
      public BytecodeBlock putIntArrayElement()
      {
          nodes.add(OpCode.IASTORE);
          return this;
      }
  
      public BytecodeBlock visitLineNumber(int currentLineNumber)
      {
          checkArgument(currentLineNumber >= 0, "currentLineNumber must be positive");
          if (this.currentLineNumber != currentLineNumber) {
              nodes.add(new LineNumberNode(currentLineNumber));
              this.currentLineNumber = currentLineNumber;
          }
          return this;
      }
  
      @Override
      public void accept(MethodVisitor visitor, MethodGenerationContext generationContext)
      {
          for (BytecodeNode node : nodes) {
              node.accept(visitor, generationContext);
          }
      }
  
      @Override
      public <T> T accept(BytecodeNode parent, BytecodeVisitor<T> visitor)
      {
          return visitor.visitBlock(parent, this);
      }
  }
  