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;
  
  /**
   * An {@link AnnotationVisitor} that generates a corresponding 'annotation' or 'type_annotation'
   * structure, as defined in the Java Virtual Machine Specification (JVMS). AnnotationWriter
   * instances can be chained in a doubly linked list, from which Runtime[In]Visible[Type]Annotations
   * attributes can be generated with the {@link #putAnnotations} method. Similarly, arrays of such
   * lists can be used to generate Runtime[In]VisibleParameterAnnotations attributes.
   *
   * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16">JVMS
   *     4.7.16</a>
   * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.20">JVMS
   *     4.7.20</a>
   * @author Eric Bruneton
   * @author Eugene Kuleshov
   */
  final class AnnotationWriter extends AnnotationVisitor {
  
    /** Where the constants used in this AnnotationWriter must be stored. */
    private final SymbolTable symbolTable;
  
    /**
     * Whether values are named or not. AnnotationWriter instances used for annotation default and
     * annotation arrays use unnamed values (i.e. they generate an 'element_value' structure for each
     * value, instead of an element_name_index followed by an element_value).
     */
    private final boolean useNamedValues;
  
    /**
     * The 'annotation' or 'type_annotation' JVMS structure corresponding to the annotation values
     * visited so far. All the fields of these structures, except the last one - the
     * element_value_pairs array, must be set before this ByteVector is passed to the constructor
     * (num_element_value_pairs can be set to 0, it is reset to the correct value in {@link
     * #visitEnd()}). The element_value_pairs array is filled incrementally in the various visit()
     * methods.
     *
     * <p>Note: as an exception to the above rules, for AnnotationDefault attributes (which contain a
     * single element_value by definition), this ByteVector is initially empty when passed to the
     * constructor, and {@link #numElementValuePairsOffset} is set to -1.
     */
    private final ByteVector annotation;
  
    /**
     * The offset in {@link #annotation} where {@link #numElementValuePairs} must be stored (or -1 for
     * the case of AnnotationDefault attributes).
     */
    private final int numElementValuePairsOffset;
  
    /** The number of element value pairs visited so far. */
    private int numElementValuePairs;
  
    /**
     * The previous AnnotationWriter. This field is used to store the list of annotations of a
     * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations
     * (annotation values of annotation type), or for AnnotationDefault attributes.
     */
    private final AnnotationWriter previousAnnotation;
  
    /**
     * The next AnnotationWriter. This field is used to store the list of annotations of a
     * Runtime[In]Visible[Type]Annotations attribute. It is unused for nested or array annotations
     * (annotation values of annotation type), or for AnnotationDefault attributes.
     */
    private AnnotationWriter nextAnnotation;
  
    // -----------------------------------------------------------------------------------------------
    // Constructors
    // -----------------------------------------------------------------------------------------------
  
    /**
     * Constructs a new {@link AnnotationWriter}.
     *
     * @param symbolTable where the constants used in this AnnotationWriter must be stored.
     * @param useNamedValues whether values are named or not. AnnotationDefault and annotation arrays
     *     use unnamed values.
     * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to
     *     the visited content must be stored. This ByteVector must already contain all the fields of
     *     the structure except the last one (the element_value_pairs array).
     * @param previousAnnotation the previously visited annotation of the
     *     Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or null in
     *     other cases (e.g. nested or array annotations).
     */
    AnnotationWriter(
        final SymbolTable symbolTable,
        final boolean useNamedValues,
        final ByteVector annotation,
        final AnnotationWriter previousAnnotation) {
      super(Opcodes.ASM6);
      this.symbolTable = symbolTable;
      this.useNamedValues = useNamedValues;
      this.annotation = annotation;
      // By hypothesis, num_element_value_pairs is stored in the last unsigned short of 'annotation'.
      this.numElementValuePairsOffset = annotation.length == 0 ? -1 : annotation.length - 2;
      this.previousAnnotation = previousAnnotation;
      if (previousAnnotation != null) {
        previousAnnotation.nextAnnotation = this;
      }
    }
  
    /**
     * Constructs a new {@link AnnotationWriter} using named values.
     *
     * @param symbolTable where the constants used in this AnnotationWriter must be stored.
     * @param annotation where the 'annotation' or 'type_annotation' JVMS structure corresponding to
     *     the visited content must be stored. This ByteVector must already contain all the fields of
     *     the structure except the last one (the element_value_pairs array).
     * @param previousAnnotation the previously visited annotation of the
     *     Runtime[In]Visible[Type]Annotations attribute to which this annotation belongs, or null in
     *     other cases (e.g. nested or array annotations).
     */
    AnnotationWriter(
        final SymbolTable symbolTable,
        final ByteVector annotation,
        final AnnotationWriter previousAnnotation) {
      this(symbolTable, /* useNamedValues = */ true, annotation, previousAnnotation);
    }
  
    // -----------------------------------------------------------------------------------------------
    // Implementation of the AnnotationVisitor abstract class
    // -----------------------------------------------------------------------------------------------
  
    @Override
    public void visit(final String name, final Object value) {
      // Case of an element_value with a const_value_index, class_info_index or array_index field.
      // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
      ++numElementValuePairs;
      if (useNamedValues) {
        annotation.putShort(symbolTable.addConstantUtf8(name));
      }
      if (value instanceof String) {
        annotation.put12('s', symbolTable.addConstantUtf8((String) value));
      } else if (value instanceof Byte) {
        annotation.put12('B', symbolTable.addConstantInteger(((Byte) value).byteValue()).index);
      } else if (value instanceof Boolean) {
        int booleanValue = ((Boolean) value).booleanValue() ? 1 : 0;
        annotation.put12('Z', symbolTable.addConstantInteger(booleanValue).index);
      } else if (value instanceof Character) {
        annotation.put12('C', symbolTable.addConstantInteger(((Character) value).charValue()).index);
      } else if (value instanceof Short) {
        annotation.put12('S', symbolTable.addConstantInteger(((Short) value).shortValue()).index);
      } else if (value instanceof Type) {
        annotation.put12('c', symbolTable.addConstantUtf8(((Type) value).getDescriptor()));
      } else if (value instanceof byte[]) {
        byte[] byteArray = (byte[]) value;
        annotation.put12('[', byteArray.length);
        for (byte byteValue : byteArray) {
          annotation.put12('B', symbolTable.addConstantInteger(byteValue).index);
        }
      } else if (value instanceof boolean[]) {
        boolean[] booleanArray = (boolean[]) value;
        annotation.put12('[', booleanArray.length);
        for (boolean booleanValue : booleanArray) {
          annotation.put12('Z', symbolTable.addConstantInteger(booleanValue ? 1 : 0).index);
        }
      } else if (value instanceof short[]) {
        short[] shortArray = (short[]) value;
        annotation.put12('[', shortArray.length);
        for (short shortValue : shortArray) {
          annotation.put12('S', symbolTable.addConstantInteger(shortValue).index);
        }
      } else if (value instanceof char[]) {
        char[] charArray = (char[]) value;
        annotation.put12('[', charArray.length);
        for (char charValue : charArray) {
          annotation.put12('C', symbolTable.addConstantInteger(charValue).index);
        }
      } else if (value instanceof int[]) {
        int[] intArray = (int[]) value;
        annotation.put12('[', intArray.length);
        for (int intValue : intArray) {
          annotation.put12('I', symbolTable.addConstantInteger(intValue).index);
        }
      } else if (value instanceof long[]) {
        long[] longArray = (long[]) value;
        annotation.put12('[', longArray.length);
        for (long longValue : longArray) {
          annotation.put12('J', symbolTable.addConstantLong(longValue).index);
        }
      } else if (value instanceof float[]) {
        float[] floatArray = (float[]) value;
        annotation.put12('[', floatArray.length);
        for (float floatValue : floatArray) {
          annotation.put12('F', symbolTable.addConstantFloat(floatValue).index);
        }
      } else if (value instanceof double[]) {
        double[] doubleArray = (double[]) value;
        annotation.put12('[', doubleArray.length);
        for (double doubleValue : doubleArray) {
          annotation.put12('D', symbolTable.addConstantDouble(doubleValue).index);
        }
      } else {
        Symbol symbol = symbolTable.addConstant(value);
        annotation.put12(".s.IFJDCS".charAt(symbol.tag), symbol.index);
      }
    }
  
    @Override
    public void visitEnum(final String name, final String descriptor, final String value) {
      // Case of an element_value with an enum_const_value field.
      // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
      ++numElementValuePairs;
      if (useNamedValues) {
        annotation.putShort(symbolTable.addConstantUtf8(name));
      }
      annotation
          .put12('e', symbolTable.addConstantUtf8(descriptor))
          .putShort(symbolTable.addConstantUtf8(value));
    }
  
    @Override
    public AnnotationVisitor visitAnnotation(final String name, final String descriptor) {
      // Case of an element_value with an annotation_value field.
      // See https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1.
      ++numElementValuePairs;
      if (useNamedValues) {
        annotation.putShort(symbolTable.addConstantUtf8(name));
      }
      // Write tag and type_index, and reserve 2 bytes for num_element_value_pairs.
      annotation.put12('@', symbolTable.addConstantUtf8(descriptor)).putShort(0);
      return new AnnotationWriter(symbolTable, annotation, null);
    }
  
    @Override
    public AnnotationVisitor visitArray(final String name) {
      // Case of an element_value with an array_value field.
      // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.16.1
      ++numElementValuePairs;
      if (useNamedValues) {
        annotation.putShort(symbolTable.addConstantUtf8(name));
      }
      // Write tag, and reserve 2 bytes for num_values. Here we take advantage of the fact that the
      // end of an element_value of array type is similar to the end of an 'annotation' structure: an
      // unsigned short num_values followed by num_values element_value, versus an unsigned short
      // num_element_value_pairs, followed by num_element_value_pairs { element_name_index,
      // element_value } tuples. This allows us to use an AnnotationWriter with unnamed values to
      // visit the array elements. Its num_element_value_pairs will correspond to the number of array
      // elements and will be stored in what is in fact num_values.
      annotation.put12('[', 0);
      return new AnnotationWriter(symbolTable, /* useNamedValues = */ false, annotation, null);
    }
  
    @Override
    public void visitEnd() {
      if (numElementValuePairsOffset != -1) {
        byte[] data = annotation.data;
        data[numElementValuePairsOffset] = (byte) (numElementValuePairs >>> 8);
        data[numElementValuePairsOffset + 1] = (byte) numElementValuePairs;
      }
    }
  
    // -----------------------------------------------------------------------------------------------
    // Utility methods
    // -----------------------------------------------------------------------------------------------
  
    /**
     * Returns the size of a Runtime[In]Visible[Type]Annotations attribute containing this annotation
     * and all its <i>predecessors</i> (see {@link #previousAnnotation}. Also adds the attribute name
     * to the constant pool of the class (if not null).
     *
     * @param attributeName one of "Runtime[In]Visible[Type]Annotations", or null.
     * @return the size in bytes of a Runtime[In]Visible[Type]Annotations attribute containing this
     *     annotation and all its predecessors. This includes the size of the attribute_name_index and
     *     attribute_length fields.
     */
    int computeAnnotationsSize(final String attributeName) {
      if (attributeName != null) {
        symbolTable.addConstantUtf8(attributeName);
      }
      // The attribute_name_index, attribute_length and num_annotations fields use 8 bytes.
      int attributeSize = 8;
      AnnotationWriter annotationWriter = this;
      while (annotationWriter != null) {
        attributeSize += annotationWriter.annotation.length;
        annotationWriter = annotationWriter.previousAnnotation;
      }
      return attributeSize;
    }
  
    /**
     * Puts a Runtime[In]Visible[Type]Annotations attribute containing this annotations and all its
     * <i>predecessors</i> (see {@link #previousAnnotation} in the given ByteVector. Annotations are
     * put in the same order they have been visited.
     *
     * @param attributeNameIndex the constant pool index of the attribute name (one of
     *     "Runtime[In]Visible[Type]Annotations").
     * @param output where the attribute must be put.
     */
    void putAnnotations(final int attributeNameIndex, final ByteVector output) {
      int attributeLength = 2; // For num_annotations.
      int numAnnotations = 0;
      AnnotationWriter annotationWriter = this;
      AnnotationWriter firstAnnotation = null;
      while (annotationWriter != null) {
        // In case the user forgot to call visitEnd().
        annotationWriter.visitEnd();
        attributeLength += annotationWriter.annotation.length;
        numAnnotations++;
        firstAnnotation = annotationWriter;
        annotationWriter = annotationWriter.previousAnnotation;
      }
      output.putShort(attributeNameIndex);
      output.putInt(attributeLength);
      output.putShort(numAnnotations);
      annotationWriter = firstAnnotation;
      while (annotationWriter != null) {
        output.putByteArray(annotationWriter.annotation.data, 0, annotationWriter.annotation.length);
        annotationWriter = annotationWriter.nextAnnotation;
      }
    }
  
    /**
     * Returns the size of a Runtime[In]VisibleParameterAnnotations attribute containing all the
     * annotation lists from the given AnnotationWriter sub-array. Also adds the attribute name to the
     * constant pool of the class.
     *
     * @param attributeName one of "Runtime[In]VisibleParameterAnnotations".
     * @param annotationWriters an array of AnnotationWriter lists (designated by their <i>last</i>
     *     element).
     * @param annotableParameterCount the number of elements in annotationWriters to take into account
     *     (elements [0..annotableParameterCount[ are taken into account).
     * @return the size in bytes of a Runtime[In]VisibleParameterAnnotations attribute corresponding
     *     to the given sub-array of AnnotationWriter lists. This includes the size of the
     *     attribute_name_index and attribute_length fields.
     */
    static int computeParameterAnnotationsSize(
        final String attributeName,
        final AnnotationWriter[] annotationWriters,
        final int annotableParameterCount) {
      // Note: attributeName is added to the constant pool by the call to computeAnnotationsSize
      // below. This assumes that there is at least one non-null element in the annotationWriters
      // sub-array (which is ensured by the lazy instantiation of this array in MethodWriter).
      // The attribute_name_index, attribute_length and num_parameters fields use 7 bytes, and each
      // element of the parameter_annotations array uses 2 bytes for its num_annotations field.
      int attributeSize = 7 + 2 * annotableParameterCount;
      for (int i = 0; i < annotableParameterCount; ++i) {
        AnnotationWriter annotationWriter = annotationWriters[i];
        attributeSize +=
            annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(attributeName) - 8;
      }
      return attributeSize;
    }
  
    /**
     * Puts a Runtime[In]VisibleParameterAnnotations attribute containing all the annotation lists
     * from the given AnnotationWriter sub-array in the given ByteVector.
     *
     * @param attributeNameIndex constant pool index of the attribute name (one of
     *     Runtime[In]VisibleParameterAnnotations).
     * @param annotationWriters an array of AnnotationWriter lists (designated by their <i>last</i>
     *     element).
     * @param annotableParameterCount the number of elements in annotationWriters to put (elements
     *     [0..annotableParameterCount[ are put).
     * @param output where the attribute must be put.
     */
    static void putParameterAnnotations(
        final int attributeNameIndex,
        final AnnotationWriter[] annotationWriters,
        final int annotableParameterCount,
        final ByteVector output) {
      // The num_parameters field uses 1 byte, and each element of the parameter_annotations array
      // uses 2 bytes for its num_annotations field.
      int attributeLength = 1 + 2 * annotableParameterCount;
      for (int i = 0; i < annotableParameterCount; ++i) {
        AnnotationWriter annotationWriter = annotationWriters[i];
        attributeLength +=
            annotationWriter == null ? 0 : annotationWriter.computeAnnotationsSize(null) - 8;
      }
      output.putShort(attributeNameIndex);
      output.putInt(attributeLength);
      output.putByte(annotableParameterCount);
      for (int i = 0; i < annotableParameterCount; ++i) {
        AnnotationWriter annotationWriter = annotationWriters[i];
        AnnotationWriter firstAnnotation = null;
        int numAnnotations = 0;
        while (annotationWriter != null) {
          // In case user the forgot to call visitEnd().
          annotationWriter.visitEnd();
          numAnnotations++;
          firstAnnotation = annotationWriter;
          annotationWriter = annotationWriter.previousAnnotation;
        }
        output.putShort(numAnnotations);
        annotationWriter = firstAnnotation;
        while (annotationWriter != null) {
          output.putByteArray(
              annotationWriter.annotation.data, 0, annotationWriter.annotation.length);
          annotationWriter = annotationWriter.nextAnnotation;
        }
      }
    }
  }
  