code: // ASM: a very small and fast Java bytecode manipulation framework
  // Copyright (c) 2000-2011 INRIA, France Telecom
  // All rights reserved.
  //
  // Redistribution and use in source and binary forms, with or without
  // modification, are permitted provided that the following conditions
  // are met:
  // 1. Redistributions of source code must retain the above copyright
  //    notice, this list of conditions and the following disclaimer.
  // 2. Redistributions in binary form must reproduce the above copyright
  //    notice, this list of conditions and the following disclaimer in the
  //    documentation and/or other materials provided with the distribution.
  // 3. Neither the name of the copyright holders nor the names of its
  //    contributors may be used to endorse or promote products derived from
  //    this software without specific prior written permission.
  //
  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  // THE POSSIBILITY OF SUCH DAMAGE.
  package clojure.asm.commons;
  
  import clojure.asm.AnnotationVisitor;
  import clojure.asm.Label;
  import clojure.asm.MethodVisitor;
  import clojure.asm.Opcodes;
  import clojure.asm.Type;
  import clojure.asm.TypePath;
  
  /**
   * A {@link MethodVisitor} that renumbers local variables in their order of appearance. This adapter
   * allows one to easily add new local variables to a method. It may be used by inheriting from this
   * class, but the preferred way of using it is via delegation: the next visitor in the chain can
   * indeed add new locals when needed by calling {@link #newLocal} on this adapter (this requires a
   * reference back to this {@link LocalVariablesSorter}).
   *
   * @author Chris Nokleberg
   * @author Eugene Kuleshov
   * @author Eric Bruneton
   */
  public class LocalVariablesSorter extends MethodVisitor {
  
    /** The type of the java.lang.Object class. */
    private static final Type OBJECT_TYPE = Type.getObjectType("java/lang/Object");
  
    /**
     * The mapping from old to new local variable indices. A local variable at index i of size 1 is
     * remapped to 'mapping[2*i]', while a local variable at index i of size 2 is remapped to
     * 'mapping[2*i+1]'.
     */
    private int[] remappedVariableIndices = new int[40];
  
    /**
     * The local variable types after remapping. The format of this array is the same as in {@link
     * MethodVisitor#visitFrame}, except that long and double types use two slots.
     */
    private Object[] remappedLocalTypes = new Object[20];
  
    /** The index of the first local variable, after formal parameters. */
    protected final int firstLocal;
  
    /** The index of the next local variable to be created by {@link #newLocal}. */
    protected int nextLocal;
  
    /**
     * Constructs a new {@link LocalVariablesSorter}. <i>Subclasses must not use this constructor</i>.
     * Instead, they must use the {@link #LocalVariablesSorter(int, int, String, MethodVisitor)}
     * version.
     *
     * @param access access flags of the adapted method.
     * @param descriptor the method's descriptor (see {@link Type}).
     * @param methodVisitor the method visitor to which this adapter delegates calls.
     * @throws IllegalStateException if a subclass calls this constructor.
     */
    public LocalVariablesSorter(
        final int access, final String descriptor, final MethodVisitor methodVisitor) {
      this(Opcodes.ASM6, access, descriptor, methodVisitor);
      if (getClass() != LocalVariablesSorter.class) {
        throw new IllegalStateException();
      }
    }
  
    /**
     * Constructs a new {@link LocalVariablesSorter}.
     *
     * @param api the ASM API version implemented by this visitor. Must be one of {@link
     *     Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7_EXPERIMENTAL}.
     * @param access access flags of the adapted method.
     * @param descriptor the method's descriptor (see {@link Type}).
     * @param methodVisitor the method visitor to which this adapter delegates calls.
     */
    protected LocalVariablesSorter(
        final int api, final int access, final String descriptor, final MethodVisitor methodVisitor) {
      super(api, methodVisitor);
      nextLocal = (Opcodes.ACC_STATIC & access) == 0 ? 1 : 0;
      for (Type argumentType : Type.getArgumentTypes(descriptor)) {
        nextLocal += argumentType.getSize();
      }
      firstLocal = nextLocal;
    }
  
    @Override
    public void visitVarInsn(final int opcode, final int var) {
      Type varType;
      switch (opcode) {
        case Opcodes.LLOAD:
        case Opcodes.LSTORE:
          varType = Type.LONG_TYPE;
          break;
        case Opcodes.DLOAD:
        case Opcodes.DSTORE:
          varType = Type.DOUBLE_TYPE;
          break;
        case Opcodes.FLOAD:
        case Opcodes.FSTORE:
          varType = Type.FLOAT_TYPE;
          break;
        case Opcodes.ILOAD:
        case Opcodes.ISTORE:
          varType = Type.INT_TYPE;
          break;
        case Opcodes.ALOAD:
        case Opcodes.ASTORE:
        case Opcodes.RET:
          varType = OBJECT_TYPE;
          break;
        default:
          throw new IllegalArgumentException("Invalid opcode " + opcode);
      }
      super.visitVarInsn(opcode, remap(var, varType));
    }
  
    @Override
    public void visitIincInsn(final int var, final int increment) {
      super.visitIincInsn(remap(var, Type.INT_TYPE), increment);
    }
  
    @Override
    public void visitMaxs(final int maxStack, final int maxLocals) {
      super.visitMaxs(maxStack, nextLocal);
    }
  
    @Override
    public void visitLocalVariable(
        final String name,
        final String descriptor,
        final String signature,
        final Label start,
        final Label end,
        final int index) {
      int remappedIndex = remap(index, Type.getType(descriptor));
      super.visitLocalVariable(name, descriptor, signature, start, end, remappedIndex);
    }
  
    @Override
    public AnnotationVisitor visitLocalVariableAnnotation(
        final int typeRef,
        final TypePath typePath,
        final Label[] start,
        final Label[] end,
        final int[] index,
        final String descriptor,
        final boolean visible) {
      Type type = Type.getType(descriptor);
      int[] remappedIndex = new int[index.length];
      for (int i = 0; i < remappedIndex.length; ++i) {
        remappedIndex[i] = remap(index[i], type);
      }
      return super.visitLocalVariableAnnotation(
          typeRef, typePath, start, end, remappedIndex, descriptor, visible);
    }
  
    @Override
    public void visitFrame(
        final int type,
        final int nLocal,
        final Object[] local,
        final int nStack,
        final Object[] stack) {
      if (type != Opcodes.F_NEW) { // Uncompressed frame.
        throw new IllegalArgumentException(
            "LocalVariablesSorter only accepts expanded frames (see ClassReader.EXPAND_FRAMES)");
      }
  
      // Create a copy of remappedLocals.
      Object[] oldRemappedLocals = new Object[remappedLocalTypes.length];
      System.arraycopy(remappedLocalTypes, 0, oldRemappedLocals, 0, oldRemappedLocals.length);
  
      updateNewLocals(remappedLocalTypes);
  
      // Copy the types from 'local' to 'remappedLocals'. 'remappedLocals' already contains the
      // variables added with 'newLocal'.
      int oldVar = 0; // Old local variable index.
      for (int i = 0; i < nLocal; ++i) {
        Object localType = local[i];
        if (localType != Opcodes.TOP) {
          Type varType = OBJECT_TYPE;
          if (localType == Opcodes.INTEGER) {
            varType = Type.INT_TYPE;
          } else if (localType == Opcodes.FLOAT) {
            varType = Type.FLOAT_TYPE;
          } else if (localType == Opcodes.LONG) {
            varType = Type.LONG_TYPE;
          } else if (localType == Opcodes.DOUBLE) {
            varType = Type.DOUBLE_TYPE;
          } else if (localType instanceof String) {
            varType = Type.getObjectType((String) localType);
          }
          setFrameLocal(remap(oldVar, varType), localType);
        }
        oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1;
      }
  
      // Remove TOP after long and double types as well as trailing TOPs.
      oldVar = 0;
      int newVar = 0;
      int remappedNLocal = 0;
      while (oldVar < remappedLocalTypes.length) {
        Object localType = remappedLocalTypes[oldVar];
        oldVar += localType == Opcodes.LONG || localType == Opcodes.DOUBLE ? 2 : 1;
        if (localType != null && localType != Opcodes.TOP) {
          remappedLocalTypes[newVar++] = localType;
          remappedNLocal = newVar;
        } else {
          remappedLocalTypes[newVar++] = Opcodes.TOP;
        }
      }
  
      // Visit the remapped frame.
      super.visitFrame(type, remappedNLocal, remappedLocalTypes, nStack, stack);
  
      // Restore the original value of 'remappedLocals'.
      remappedLocalTypes = oldRemappedLocals;
    }
  
    // -----------------------------------------------------------------------------------------------
  
    /**
     * Constructs a new local variable of the given type.
     *
     * @param type the type of the local variable to be created.
     * @return the identifier of the newly created local variable.
     */
    public int newLocal(final Type type) {
      Object localType;
      switch (type.getSort()) {
        case Type.BOOLEAN:
        case Type.CHAR:
        case Type.BYTE:
        case Type.SHORT:
        case Type.INT:
          localType = Opcodes.INTEGER;
          break;
        case Type.FLOAT:
          localType = Opcodes.FLOAT;
          break;
        case Type.LONG:
          localType = Opcodes.LONG;
          break;
        case Type.DOUBLE:
          localType = Opcodes.DOUBLE;
          break;
        case Type.ARRAY:
          localType = type.getDescriptor();
          break;
        case Type.OBJECT:
          localType = type.getInternalName();
          break;
        default:
          throw new AssertionError();
      }
      int local = newLocalMapping(type);
      setLocalType(local, type);
      setFrameLocal(local, localType);
      return local;
    }
  
    /**
     * Notifies subclasses that a new stack map frame is being visited. The array argument contains
     * the stack map frame types corresponding to the local variables added with {@link #newLocal}.
     * This method can update these types in place for the stack map frame being visited. The default
     * implementation of this method does nothing, i.e. a local variable added with {@link #newLocal}
     * will have the same type in all stack map frames. But this behavior is not always the desired
     * one, for instance if a local variable is added in the middle of a try/catch block: the frame
     * for the exception handler should have a TOP type for this new local.
     *
     * @param newLocals the stack map frame types corresponding to the local variables added with
     *     {@link #newLocal} (and null for the others). The format of this array is the same as in
     *     {@link MethodVisitor#visitFrame}, except that long and double types use two slots. The
     *     types for the current stack map frame must be updated in place in this array.
     */
    protected void updateNewLocals(final Object[] newLocals) {
      // The default implementation does nothing.    
    }
  
    /**
     * Notifies subclasses that a local variable has been added or remapped. The default
     * implementation of this method does nothing.
     *
     * @param local a local variable identifier, as returned by {@link #newLocal}.
     * @param type the type of the value being stored in the local variable.
     */
    protected void setLocalType(final int local, final Type type) {
      // The default implementation does nothing.
    }
  
    private void setFrameLocal(final int local, final Object type) {
      int numLocals = remappedLocalTypes.length;
      if (local >= numLocals) {
        Object[] newRemappedLocalTypes = new Object[Math.max(2 * numLocals, local + 1)];
        System.arraycopy(remappedLocalTypes, 0, newRemappedLocalTypes, 0, numLocals);
        remappedLocalTypes = newRemappedLocalTypes;
      }
      remappedLocalTypes[local] = type;
    }
  
    private int remap(final int var, final Type type) {
      if (var + type.getSize() <= firstLocal) {
        return var;
      }
      int key = 2 * var + type.getSize() - 1;
      int size = remappedVariableIndices.length;
      if (key >= size) {
        int[] newRemappedVariableIndices = new int[Math.max(2 * size, key + 1)];
        System.arraycopy(remappedVariableIndices, 0, newRemappedVariableIndices, 0, size);
        remappedVariableIndices = newRemappedVariableIndices;
      }
      int value = remappedVariableIndices[key];
      if (value == 0) {
        value = newLocalMapping(type);
        setLocalType(value, type);
        remappedVariableIndices[key] = value + 1;
      } else {
        value--;
      }
      return value;
    }
  
    protected int newLocalMapping(final Type type) {
      int local = nextLocal;
      nextLocal += type.getSize();
      return local;
    }
  }
  