// 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 jodd.asm7;

/**
 * The constant pool entries, the BootstrapMethods attribute entries and the (ASM specific) type
 * table entries of a class.
 *
 * @see JVMS
 *     4.4
 * @see JVMS
 *     4.7.23
 * @author Eric Bruneton
 */
final class SymbolTable {

  /**
   * The ClassWriter to which this SymbolTable belongs. This is only used to get access to {@link
   * ClassWriter#getCommonSuperClass} and to serialize custom attributes with {@link
   * Attribute#write}.
   */
  final ClassWriter classWriter;

  /**
   * The ClassReader from which this SymbolTable was constructed, or {@literal null} if it was
   * constructed from scratch.
   */
  private final ClassReader sourceClassReader;

  /** The major version number of the class to which this symbol table belongs. */
  private int majorVersion;

  /** The internal name of the class to which this symbol table belongs. */
  private String className;

  /**
   * The total number of {@link Entry} instances in {@link #entries}. This includes entries that are
   * accessible (recursively) via {@link Entry#next}.
   */
  private int entryCount;

  /**
   * A hash set of all the entries in this SymbolTable (this includes the constant pool entries, the
   * bootstrap method entries and the type table entries). Each {@link Entry} instance is stored at
   * the array index given by its hash code modulo the array size. If several entries must be stored
   * at the same array index, they are linked together via their {@link Entry#next} field. The
   * factory methods of this class make sure that this table does not contain duplicated entries.
   */
  private Entry[] entries;

  /**
   * The number of constant pool items in {@link #constantPool}, plus 1. The first constant pool
   * item has index 1, and long and double items count for two items.
   */
  private int constantPoolCount;

  /**
   * The content of the ClassFile's constant_pool JVMS structure corresponding to this SymbolTable.
   * The ClassFile's constant_pool_count field is not included.
   */
  private ByteVector constantPool;

  /**
   * The number of bootstrap methods in {@link #bootstrapMethods}. Corresponds to the
   * BootstrapMethods_attribute's num_bootstrap_methods field value.
   */
  private int bootstrapMethodCount;

  /**
   * The content of the BootstrapMethods attribute 'bootstrap_methods' array corresponding to this
   * SymbolTable. Note that the first 6 bytes of the BootstrapMethods_attribute, and its
   * num_bootstrap_methods field, are not included.
   */
  private ByteVector bootstrapMethods;

  /**
   * The actual number of elements in {@link #typeTable}. These elements are stored from index 0 to
   * typeCount (excluded). The other array entries are empty.
   */
  private int typeCount;

  /**
   * An ASM specific type table used to temporarily store internal names that will not necessarily
   * be stored in the constant pool. This type table is used by the control flow and data flow
   * analysis algorithm used to compute stack map frames from scratch. This array stores {@link
   * Symbol#TYPE_TAG} and {@link Symbol#UNINITIALIZED_TYPE_TAG}) Symbol. The type symbol at index
   * {@code i} has its {@link Symbol#index} equal to {@code i} (and vice versa).
   */
  private Entry[] typeTable;

  /**
   * Constructs a new, empty SymbolTable for the given ClassWriter.
   *
   * @param classWriter a ClassWriter.
   */
  SymbolTable(final ClassWriter classWriter) {
    this.classWriter = classWriter;
    this.sourceClassReader = null;
    this.entries = new Entry[256];
    this.constantPoolCount = 1;
    this.constantPool = new ByteVector();
  }

  /**
   * Constructs a new SymbolTable for the given ClassWriter, initialized with the constant pool and
   * bootstrap methods of the given ClassReader.
   *
   * @param classWriter a ClassWriter.
   * @param classReader the ClassReader whose constant pool and bootstrap methods must be copied to
   *     initialize the SymbolTable.
   */
  SymbolTable(final ClassWriter classWriter, final ClassReader classReader) {
    this.classWriter = classWriter;
    this.sourceClassReader = classReader;

    // Copy the constant pool binary content.
    byte[] inputBytes = classReader.b;
    int constantPoolOffset = classReader.getItem(1) - 1;
    int constantPoolLength = classReader.header - constantPoolOffset;
    constantPoolCount = classReader.getItemCount();
    constantPool = new ByteVector(constantPoolLength);
    constantPool.putByteArray(inputBytes, constantPoolOffset, constantPoolLength);

    // Add the constant pool items in the symbol table entries. Reserve enough space in 'entries' to
    // avoid too many hash set collisions (entries is not dynamically resized by the addConstant*
    // method calls below), and to account for bootstrap method entries.
    entries = new Entry[constantPoolCount * 2];
    char[] charBuffer = new char[classReader.getMaxStringLength()];
    boolean hasBootstrapMethods = false;
    int itemIndex = 1;
    while (itemIndex < constantPoolCount) {
      int itemOffset = classReader.getItem(itemIndex);
      int itemTag = inputBytes[itemOffset - 1];
      int nameAndTypeItemOffset;
      switch (itemTag) {
        case Symbol.CONSTANT_FIELDREF_TAG:
        case Symbol.CONSTANT_METHODREF_TAG:
        case Symbol.CONSTANT_INTERFACE_METHODREF_TAG:
          nameAndTypeItemOffset =
              classReader.getItem(classReader.readUnsignedShort(itemOffset + 2));
          addConstantMemberReference(
              itemIndex,
              itemTag,
              classReader.readClass(itemOffset, charBuffer),
              classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
              classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer));
          break;
        case Symbol.CONSTANT_INTEGER_TAG:
        case Symbol.CONSTANT_FLOAT_TAG:
          addConstantIntegerOrFloat(itemIndex, itemTag, classReader.readInt(itemOffset));
          break;
        case Symbol.CONSTANT_NAME_AND_TYPE_TAG:
          addConstantNameAndType(
              itemIndex,
              classReader.readUTF8(itemOffset, charBuffer),
              classReader.readUTF8(itemOffset + 2, charBuffer));
          break;
        case Symbol.CONSTANT_LONG_TAG:
        case Symbol.CONSTANT_DOUBLE_TAG:
          addConstantLongOrDouble(itemIndex, itemTag, classReader.readLong(itemOffset));
          break;
        case Symbol.CONSTANT_UTF8_TAG:
          addConstantUtf8(itemIndex, classReader.readUtf(itemIndex, charBuffer));
          break;
        case Symbol.CONSTANT_METHOD_HANDLE_TAG:
          int memberRefItemOffset =
              classReader.getItem(classReader.readUnsignedShort(itemOffset + 1));
          nameAndTypeItemOffset =
              classReader.getItem(classReader.readUnsignedShort(memberRefItemOffset + 2));
          addConstantMethodHandle(
              itemIndex,
              classReader.readByte(itemOffset),
              classReader.readClass(memberRefItemOffset, charBuffer),
              classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
              classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer));
          break;
        case Symbol.CONSTANT_DYNAMIC_TAG:
        case Symbol.CONSTANT_INVOKE_DYNAMIC_TAG:
          hasBootstrapMethods = true;
          nameAndTypeItemOffset =
              classReader.getItem(classReader.readUnsignedShort(itemOffset + 2));
          addConstantDynamicOrInvokeDynamicReference(
              itemTag,
              itemIndex,
              classReader.readUTF8(nameAndTypeItemOffset, charBuffer),
              classReader.readUTF8(nameAndTypeItemOffset + 2, charBuffer),
              classReader.readUnsignedShort(itemOffset));
          break;
        case Symbol.CONSTANT_STRING_TAG:
        case Symbol.CONSTANT_CLASS_TAG:
        case Symbol.CONSTANT_METHOD_TYPE_TAG:
        case Symbol.CONSTANT_MODULE_TAG:
        case Symbol.CONSTANT_PACKAGE_TAG:
          addConstantUtf8Reference(
              itemIndex, itemTag, classReader.readUTF8(itemOffset, charBuffer));
          break;
        default:
          throw new IllegalArgumentException();
      }
      itemIndex +=
          (itemTag == Symbol.CONSTANT_LONG_TAG || itemTag == Symbol.CONSTANT_DOUBLE_TAG) ? 2 : 1;
    }

    // Copy the BootstrapMethods, if any.
    if (hasBootstrapMethods) {
      copyBootstrapMethods(classReader, charBuffer);
    }
  }

  /**
   * Read the BootstrapMethods 'bootstrap_methods' array binary content and add them as entries of
   * the SymbolTable.
   *
   * @param classReader the ClassReader whose bootstrap methods must be copied to initialize the
   *     SymbolTable.
   * @param charBuffer a buffer used to read strings in the constant pool.
   */
  private void copyBootstrapMethods(final ClassReader classReader, final char[] charBuffer) {
    // Find attributOffset of the 'bootstrap_methods' array.
    byte[] inputBytes = classReader.b;
    int currentAttributeOffset = classReader.getFirstAttributeOffset();
    for (int i = classReader.readUnsignedShort(currentAttributeOffset - 2); i > 0; --i) {
      String attributeName = classReader.readUTF8(currentAttributeOffset, charBuffer);
      if (Constants.BOOTSTRAP_METHODS.equals(attributeName)) {
        bootstrapMethodCount = classReader.readUnsignedShort(currentAttributeOffset + 6);
        break;
      }
      currentAttributeOffset += 6 + classReader.readInt(currentAttributeOffset + 2);
    }
    if (bootstrapMethodCount > 0) {
      // Compute the offset and the length of the BootstrapMethods 'bootstrap_methods' array.
      int bootstrapMethodsOffset = currentAttributeOffset + 8;
      int bootstrapMethodsLength = classReader.readInt(currentAttributeOffset + 2) - 2;
      bootstrapMethods = new ByteVector(bootstrapMethodsLength);
      bootstrapMethods.putByteArray(inputBytes, bootstrapMethodsOffset, bootstrapMethodsLength);

      // Add each bootstrap method in the symbol table entries.
      int currentOffset = bootstrapMethodsOffset;
      for (int i = 0; i < bootstrapMethodCount; i++) {
        int offset = currentOffset - bootstrapMethodsOffset;
        int bootstrapMethodRef = classReader.readUnsignedShort(currentOffset);
        currentOffset += 2;
        int numBootstrapArguments = classReader.readUnsignedShort(currentOffset);
        currentOffset += 2;
        int hashCode = classReader.readConst(bootstrapMethodRef, charBuffer).hashCode();
        while (numBootstrapArguments-- > 0) {
          int bootstrapArgument = classReader.readUnsignedShort(currentOffset);
          currentOffset += 2;
          hashCode ^= classReader.readConst(bootstrapArgument, charBuffer).hashCode();
        }
        add(new Entry(i, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode & 0x7FFFFFFF));
      }
    }
  }

  /**
   * Returns the ClassReader from which this SymbolTable was constructed.
   *
   * @return the ClassReader from which this SymbolTable was constructed, or {@literal null} if it
   *     was constructed from scratch.
   */
  ClassReader getSource() {
    return sourceClassReader;
  }

  /**
   * Returns the major version of the class to which this symbol table belongs.
   *
   * @return the major version of the class to which this symbol table belongs.
   */
  int getMajorVersion() {
    return majorVersion;
  }

  /**
   * Returns the internal name of the class to which this symbol table belongs.
   *
   * @return the internal name of the class to which this symbol table belongs.
   */
  String getClassName() {
    return className;
  }

  /**
   * Sets the major version and the name of the class to which this symbol table belongs. Also adds
   * the class name to the constant pool.
   *
   * @param majorVersion a major ClassFile version number.
   * @param className an internal class name.
   * @return the constant pool index of a new or already existing Symbol with the given class name.
   */
  int setMajorVersionAndClassName(final int majorVersion, final String className) {
    this.majorVersion = majorVersion;
    this.className = className;
    return addConstantClass(className).index;
  }

  /**
   * Returns the number of items in this symbol table's constant_pool array (plus 1).
   *
   * @return the number of items in this symbol table's constant_pool array (plus 1).
   */
  int getConstantPoolCount() {
    return constantPoolCount;
  }

  /**
   * Returns the length in bytes of this symbol table's constant_pool array.
   *
   * @return the length in bytes of this symbol table's constant_pool array.
   */
  int getConstantPoolLength() {
    return constantPool.length;
  }

  /**
   * Puts this symbol table's constant_pool array in the given ByteVector, preceded by the
   * constant_pool_count value.
   *
   * @param output where the JVMS ClassFile's constant_pool array must be put.
   */
  void putConstantPool(final ByteVector output) {
    output.putShort(constantPoolCount).putByteArray(constantPool.data, 0, constantPool.length);
  }

  /**
   * Returns the size in bytes of this symbol table's BootstrapMethods attribute. Also adds the
   * attribute name in the constant pool.
   *
   * @return the size in bytes of this symbol table's BootstrapMethods attribute.
   */
  int computeBootstrapMethodsSize() {
    if (bootstrapMethods != null) {
      addConstantUtf8(Constants.BOOTSTRAP_METHODS);
      return 8 + bootstrapMethods.length;
    } else {
      return 0;
    }
  }

  /**
   * Puts this symbol table's BootstrapMethods attribute in the given ByteVector. This includes the
   * 6 attribute header bytes and the num_bootstrap_methods value.
   *
   * @param output where the JVMS BootstrapMethods attribute must be put.
   */
  void putBootstrapMethods(final ByteVector output) {
    if (bootstrapMethods != null) {
      output
          .putShort(addConstantUtf8(Constants.BOOTSTRAP_METHODS))
          .putInt(bootstrapMethods.length + 2)
          .putShort(bootstrapMethodCount)
          .putByteArray(bootstrapMethods.data, 0, bootstrapMethods.length);
    }
  }

  // -----------------------------------------------------------------------------------------------
  // Generic symbol table entries management.
  // -----------------------------------------------------------------------------------------------

  /**
   * Returns the list of entries which can potentially have the given hash code.
   *
   * @param hashCode a {@link Entry#hashCode} value.
   * @return the list of entries which can potentially have the given hash code. The list is stored
   *     via the {@link Entry#next} field.
   */
  private Entry get(final int hashCode) {
    return entries[hashCode % entries.length];
  }

  /**
   * Puts the given entry in the {@link #entries} hash set. This method does not check
   * whether {@link #entries} already contains a similar entry or not. {@link #entries} is resized
   * if necessary to avoid hash collisions (multiple entries needing to be stored at the same {@link
   * #entries} array index) as much as possible, with reasonable memory usage.
   *
   * @param entry an Entry (which must not already be contained in {@link #entries}).
   * @return the given entry
   */
  private Entry put(final Entry entry) {
    if (entryCount > (entries.length * 3) / 4) {
      int currentCapacity = entries.length;
      int newCapacity = currentCapacity * 2 + 1;
      Entry[] newEntries = new Entry[newCapacity];
      for (int i = currentCapacity - 1; i >= 0; --i) {
        Entry currentEntry = entries[i];
        while (currentEntry != null) {
          int newCurrentEntryIndex = currentEntry.hashCode % newCapacity;
          Entry nextEntry = currentEntry.next;
          currentEntry.next = newEntries[newCurrentEntryIndex];
          newEntries[newCurrentEntryIndex] = currentEntry;
          currentEntry = nextEntry;
        }
      }
      entries = newEntries;
    }
    entryCount++;
    int index = entry.hashCode % entries.length;
    entry.next = entries[index];
    return entries[index] = entry;
  }

  /**
   * Adds the given entry in the {@link #entries} hash set. This method does not check
   * whether {@link #entries} already contains a similar entry or not, and does not resize
   * {@link #entries} if necessary.
   *
   * @param entry an Entry (which must not already be contained in {@link #entries}).
   */
  private void add(final Entry entry) {
    entryCount++;
    int index = entry.hashCode % entries.length;
    entry.next = entries[index];
    entries[index] = entry;
  }

  // -----------------------------------------------------------------------------------------------
  // Constant pool entries management.
  // -----------------------------------------------------------------------------------------------

  /**
   * Adds a number or string constant to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param value the value of the constant to be added to the constant pool. This parameter must be
   *     an {@link Integer}, {@link Byte}, {@link Character}, {@link Short}, {@link Boolean}, {@link
   *     Float}, {@link Long}, {@link Double}, {@link String}, {@link Type} or {@link Handle}.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstant(final Object value) {
    if (value instanceof Integer) {
      return addConstantInteger(((Integer) value).intValue());
    } else if (value instanceof Byte) {
      return addConstantInteger(((Byte) value).intValue());
    } else if (value instanceof Character) {
      return addConstantInteger(((Character) value).charValue());
    } else if (value instanceof Short) {
      return addConstantInteger(((Short) value).intValue());
    } else if (value instanceof Boolean) {
      return addConstantInteger(((Boolean) value).booleanValue() ? 1 : 0);
    } else if (value instanceof Float) {
      return addConstantFloat(((Float) value).floatValue());
    } else if (value instanceof Long) {
      return addConstantLong(((Long) value).longValue());
    } else if (value instanceof Double) {
      return addConstantDouble(((Double) value).doubleValue());
    } else if (value instanceof String) {
      return addConstantString((String) value);
    } else if (value instanceof Type) {
      Type type = (Type) value;
      int typeSort = type.getSort();
      if (typeSort == Type.OBJECT) {
        return addConstantClass(type.getInternalName());
      } else if (typeSort == Type.METHOD) {
        return addConstantMethodType(type.getDescriptor());
      } else { // type is a primitive or array type.
        return addConstantClass(type.getDescriptor());
      }
    } else if (value instanceof Handle) {
      Handle handle = (Handle) value;
      return addConstantMethodHandle(
          handle.getTag(),
          handle.getOwner(),
          handle.getName(),
          handle.getDesc(),
          handle.isInterface());
    } else if (value instanceof ConstantDynamic) {
      ConstantDynamic constantDynamic = (ConstantDynamic) value;
      return addConstantDynamic(
          constantDynamic.getName(),
          constantDynamic.getDescriptor(),
          constantDynamic.getBootstrapMethod(),
          constantDynamic.getBootstrapMethodArgumentsUnsafe());
    } else {
      throw new IllegalArgumentException("value " + value);
    }
  }

  /**
   * Adds a CONSTANT_Class_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param value the internal name of a class.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantClass(final String value) {
    return addConstantUtf8Reference(Symbol.CONSTANT_CLASS_TAG, value);
  }

  /**
   * Adds a CONSTANT_Fieldref_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param owner the internal name of a class.
   * @param name a field name.
   * @param descriptor a field descriptor.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantFieldref(final String owner, final String name, final String descriptor) {
    return addConstantMemberReference(Symbol.CONSTANT_FIELDREF_TAG, owner, name, descriptor);
  }

  /**
   * Adds a CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to the constant pool of this
   * symbol table. Does nothing if the constant pool already contains a similar item.
   *
   * @param owner the internal name of a class.
   * @param name a method name.
   * @param descriptor a method descriptor.
   * @param isInterface whether owner is an interface or not.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantMethodref(
      final String owner, final String name, final String descriptor, final boolean isInterface) {
    int tag = isInterface ? Symbol.CONSTANT_INTERFACE_METHODREF_TAG : Symbol.CONSTANT_METHODREF_TAG;
    return addConstantMemberReference(tag, owner, name, descriptor);
  }

  /**
   * Adds a CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info to
   * the constant pool of this symbol table. Does nothing if the constant pool already contains a
   * similar item.
   *
   * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG}
   *     or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}.
   * @param owner the internal name of a class.
   * @param name a field or method name.
   * @param descriptor a field or method descriptor.
   * @return a new or already existing Symbol with the given value.
   */
  private Entry addConstantMemberReference(
      final int tag, final String owner, final String name, final String descriptor) {
    int hashCode = hash(tag, owner, name, descriptor);
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == tag
          && entry.hashCode == hashCode
          && entry.owner.equals(owner)
          && entry.name.equals(name)
          && entry.value.equals(descriptor)) {
        return entry;
      }
      entry = entry.next;
    }
    constantPool.put122(
        tag, addConstantClass(owner).index, addConstantNameAndType(name, descriptor));
    return put(new Entry(constantPoolCount++, tag, owner, name, descriptor, 0, hashCode));
  }

  /**
   * Adds a new CONSTANT_Fieldref_info, CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info
   * to the constant pool of this symbol table.
   *
   * @param index the constant pool index of the new Symbol.
   * @param tag one of {@link Symbol#CONSTANT_FIELDREF_TAG}, {@link Symbol#CONSTANT_METHODREF_TAG}
   *     or {@link Symbol#CONSTANT_INTERFACE_METHODREF_TAG}.
   * @param owner the internal name of a class.
   * @param name a field or method name.
   * @param descriptor a field or method descriptor.
   */
  private void addConstantMemberReference(
      final int index,
      final int tag,
      final String owner,
      final String name,
      final String descriptor) {
    add(new Entry(index, tag, owner, name, descriptor, 0, hash(tag, owner, name, descriptor)));
  }

  /**
   * Adds a CONSTANT_String_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param value a string.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantString(final String value) {
    return addConstantUtf8Reference(Symbol.CONSTANT_STRING_TAG, value);
  }

  /**
   * Adds a CONSTANT_Integer_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param value an int.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantInteger(final int value) {
    return addConstantIntegerOrFloat(Symbol.CONSTANT_INTEGER_TAG, value);
  }

  /**
   * Adds a CONSTANT_Float_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param value a float.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantFloat(final float value) {
    return addConstantIntegerOrFloat(Symbol.CONSTANT_FLOAT_TAG, Float.floatToRawIntBits(value));
  }

  /**
   * Adds a CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol table.
   * Does nothing if the constant pool already contains a similar item.
   *
   * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}.
   * @param value an int or float.
   * @return a constant pool constant with the given tag and primitive values.
   */
  private Symbol addConstantIntegerOrFloat(final int tag, final int value) {
    int hashCode = hash(tag, value);
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) {
        return entry;
      }
      entry = entry.next;
    }
    constantPool.putByte(tag).putInt(value);
    return put(new Entry(constantPoolCount++, tag, value, hashCode));
  }

  /**
   * Adds a new CONSTANT_Integer_info or CONSTANT_Float_info to the constant pool of this symbol
   * table.
   *
   * @param index the constant pool index of the new Symbol.
   * @param tag one of {@link Symbol#CONSTANT_INTEGER_TAG} or {@link Symbol#CONSTANT_FLOAT_TAG}.
   * @param value an int or float.
   */
  private void addConstantIntegerOrFloat(final int index, final int tag, final int value) {
    add(new Entry(index, tag, value, hash(tag, value)));
  }

  /**
   * Adds a CONSTANT_Long_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param value a long.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantLong(final long value) {
    return addConstantLongOrDouble(Symbol.CONSTANT_LONG_TAG, value);
  }

  /**
   * Adds a CONSTANT_Double_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param value a double.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantDouble(final double value) {
    return addConstantLongOrDouble(Symbol.CONSTANT_DOUBLE_TAG, Double.doubleToRawLongBits(value));
  }

  /**
   * Adds a CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol table.
   * Does nothing if the constant pool already contains a similar item.
   *
   * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}.
   * @param value a long or double.
   * @return a constant pool constant with the given tag and primitive values.
   */
  private Symbol addConstantLongOrDouble(final int tag, final long value) {
    int hashCode = hash(tag, value);
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == tag && entry.hashCode == hashCode && entry.data == value) {
        return entry;
      }
      entry = entry.next;
    }
    int index = constantPoolCount;
    constantPool.putByte(tag).putLong(value);
    constantPoolCount += 2;
    return put(new Entry(index, tag, value, hashCode));
  }

  /**
   * Adds a new CONSTANT_Long_info or CONSTANT_Double_info to the constant pool of this symbol
   * table.
   *
   * @param index the constant pool index of the new Symbol.
   * @param tag one of {@link Symbol#CONSTANT_LONG_TAG} or {@link Symbol#CONSTANT_DOUBLE_TAG}.
   * @param value a long or double.
   */
  private void addConstantLongOrDouble(final int index, final int tag, final long value) {
    add(new Entry(index, tag, value, hash(tag, value)));
  }

  /**
   * Adds a CONSTANT_NameAndType_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param name a field or method name.
   * @param descriptor a field or method descriptor.
   * @return a new or already existing Symbol with the given value.
   */
  int addConstantNameAndType(final String name, final String descriptor) {
    final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG;
    int hashCode = hash(tag, name, descriptor);
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == tag
          && entry.hashCode == hashCode
          && entry.name.equals(name)
          && entry.value.equals(descriptor)) {
        return entry.index;
      }
      entry = entry.next;
    }
    constantPool.put122(tag, addConstantUtf8(name), addConstantUtf8(descriptor));
    return put(new Entry(constantPoolCount++, tag, name, descriptor, hashCode)).index;
  }

  /**
   * Adds a new CONSTANT_NameAndType_info to the constant pool of this symbol table.
   *
   * @param index the constant pool index of the new Symbol.
   * @param name a field or method name.
   * @param descriptor a field or method descriptor.
   */
  private void addConstantNameAndType(final int index, final String name, final String descriptor) {
    final int tag = Symbol.CONSTANT_NAME_AND_TYPE_TAG;
    add(new Entry(index, tag, name, descriptor, hash(tag, name, descriptor)));
  }

  /**
   * Adds a CONSTANT_Utf8_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param value a string.
   * @return a new or already existing Symbol with the given value.
   */
  int addConstantUtf8(final String value) {
    int hashCode = hash(Symbol.CONSTANT_UTF8_TAG, value);
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == Symbol.CONSTANT_UTF8_TAG
          && entry.hashCode == hashCode
          && entry.value.equals(value)) {
        return entry.index;
      }
      entry = entry.next;
    }
    constantPool.putByte(Symbol.CONSTANT_UTF8_TAG).putUTF8(value);
    return put(new Entry(constantPoolCount++, Symbol.CONSTANT_UTF8_TAG, value, hashCode)).index;
  }

  /**
   * Adds a new CONSTANT_String_info to the constant pool of this symbol table.
   *
   * @param index the constant pool index of the new Symbol.
   * @param value a string.
   */
  private void addConstantUtf8(final int index, final String value) {
    add(new Entry(index, Symbol.CONSTANT_UTF8_TAG, value, hash(Symbol.CONSTANT_UTF8_TAG, value)));
  }

  /**
   * Adds a CONSTANT_MethodHandle_info to the constant pool of this symbol table. Does nothing if
   * the constant pool already contains a similar item.
   *
   * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link
   *     Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link
   *     Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link
   *     Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
   * @param owner the internal name of a class of interface.
   * @param name a field or method name.
   * @param descriptor a field or method descriptor.
   * @param isInterface whether owner is an interface or not.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantMethodHandle(
      final int referenceKind,
      final String owner,
      final String name,
      final String descriptor,
      final boolean isInterface) {
    final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG;
    // Note that we don't need to include isInterface in the hash computation, because it is
    // redundant with owner (we can't have the same owner with different isInterface values).
    int hashCode = hash(tag, owner, name, descriptor, referenceKind);
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == tag
          && entry.hashCode == hashCode
          && entry.data == referenceKind
          && entry.owner.equals(owner)
          && entry.name.equals(name)
          && entry.value.equals(descriptor)) {
        return entry;
      }
      entry = entry.next;
    }
    if (referenceKind <= Opcodes.H_PUTSTATIC) {
      constantPool.put112(tag, referenceKind, addConstantFieldref(owner, name, descriptor).index);
    } else {
      constantPool.put112(
          tag, referenceKind, addConstantMethodref(owner, name, descriptor, isInterface).index);
    }
    return put(
        new Entry(constantPoolCount++, tag, owner, name, descriptor, referenceKind, hashCode));
  }

  /**
   * Adds a new CONSTANT_MethodHandle_info to the constant pool of this symbol table.
   *
   * @param index the constant pool index of the new Symbol.
   * @param referenceKind one of {@link Opcodes#H_GETFIELD}, {@link Opcodes#H_GETSTATIC}, {@link
   *     Opcodes#H_PUTFIELD}, {@link Opcodes#H_PUTSTATIC}, {@link Opcodes#H_INVOKEVIRTUAL}, {@link
   *     Opcodes#H_INVOKESTATIC}, {@link Opcodes#H_INVOKESPECIAL}, {@link
   *     Opcodes#H_NEWINVOKESPECIAL} or {@link Opcodes#H_INVOKEINTERFACE}.
   * @param owner the internal name of a class of interface.
   * @param name a field or method name.
   * @param descriptor a field or method descriptor.
   */
  private void addConstantMethodHandle(
      final int index,
      final int referenceKind,
      final String owner,
      final String name,
      final String descriptor) {
    final int tag = Symbol.CONSTANT_METHOD_HANDLE_TAG;
    int hashCode = hash(tag, owner, name, descriptor, referenceKind);
    add(new Entry(index, tag, owner, name, descriptor, referenceKind, hashCode));
  }

  /**
   * Adds a CONSTANT_MethodType_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param methodDescriptor a method descriptor.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantMethodType(final String methodDescriptor) {
    return addConstantUtf8Reference(Symbol.CONSTANT_METHOD_TYPE_TAG, methodDescriptor);
  }

  /**
   * Adds a CONSTANT_Dynamic_info to the constant pool of this symbol table. Also adds the related
   * bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the constant
   * pool already contains a similar item.
   *
   * @param name a method name.
   * @param descriptor a field descriptor.
   * @param bootstrapMethodHandle a bootstrap method handle.
   * @param bootstrapMethodArguments the bootstrap method arguments.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantDynamic(
      final String name,
      final String descriptor,
      final Handle bootstrapMethodHandle,
      final Object... bootstrapMethodArguments) {
    Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments);
    return addConstantDynamicOrInvokeDynamicReference(
        Symbol.CONSTANT_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index);
  }

  /**
   * Adds a CONSTANT_InvokeDynamic_info to the constant pool of this symbol table. Also adds the
   * related bootstrap method to the BootstrapMethods of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param name a method name.
   * @param descriptor a method descriptor.
   * @param bootstrapMethodHandle a bootstrap method handle.
   * @param bootstrapMethodArguments the bootstrap method arguments.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantInvokeDynamic(
      final String name,
      final String descriptor,
      final Handle bootstrapMethodHandle,
      final Object... bootstrapMethodArguments) {
    Symbol bootstrapMethod = addBootstrapMethod(bootstrapMethodHandle, bootstrapMethodArguments);
    return addConstantDynamicOrInvokeDynamicReference(
        Symbol.CONSTANT_INVOKE_DYNAMIC_TAG, name, descriptor, bootstrapMethod.index);
  }

  /**
   * Adds a CONSTANT_Dynamic or a CONSTANT_InvokeDynamic_info to the constant pool of this symbol
   * table. Does nothing if the constant pool already contains a similar item.
   *
   * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link
   *     Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}.
   * @param name a method name.
   * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG) or a method descriptor for
   *     CONSTANT_INVOKE_DYNAMIC_TAG.
   * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute.
   * @return a new or already existing Symbol with the given value.
   */
  private Symbol addConstantDynamicOrInvokeDynamicReference(
      final int tag, final String name, final String descriptor, final int bootstrapMethodIndex) {
    int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex);
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == tag
          && entry.hashCode == hashCode
          && entry.data == bootstrapMethodIndex
          && entry.name.equals(name)
          && entry.value.equals(descriptor)) {
        return entry;
      }
      entry = entry.next;
    }
    constantPool.put122(tag, bootstrapMethodIndex, addConstantNameAndType(name, descriptor));
    return put(
        new Entry(
            constantPoolCount++, tag, null, name, descriptor, bootstrapMethodIndex, hashCode));
  }

  /**
   * Adds a new CONSTANT_Dynamic_info or CONSTANT_InvokeDynamic_info to the constant pool of this
   * symbol table.
   *
   * @param tag one of {@link Symbol#CONSTANT_DYNAMIC_TAG} or {@link
   *     Symbol#CONSTANT_INVOKE_DYNAMIC_TAG}.
   * @param index the constant pool index of the new Symbol.
   * @param name a method name.
   * @param descriptor a field descriptor for CONSTANT_DYNAMIC_TAG or a method descriptor for
   *     CONSTANT_INVOKE_DYNAMIC_TAG.
   * @param bootstrapMethodIndex the index of a bootstrap method in the BootstrapMethods attribute.
   */
  private void addConstantDynamicOrInvokeDynamicReference(
      final int tag,
      final int index,
      final String name,
      final String descriptor,
      final int bootstrapMethodIndex) {
    int hashCode = hash(tag, name, descriptor, bootstrapMethodIndex);
    add(new Entry(index, tag, null, name, descriptor, bootstrapMethodIndex, hashCode));
  }

  /**
   * Adds a CONSTANT_Module_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param moduleName a fully qualified name (using dots) of a module.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantModule(final String moduleName) {
    return addConstantUtf8Reference(Symbol.CONSTANT_MODULE_TAG, moduleName);
  }

  /**
   * Adds a CONSTANT_Package_info to the constant pool of this symbol table. Does nothing if the
   * constant pool already contains a similar item.
   *
   * @param packageName the internal name of a package.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addConstantPackage(final String packageName) {
    return addConstantUtf8Reference(Symbol.CONSTANT_PACKAGE_TAG, packageName);
  }

  /**
   * Adds a CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info,
   * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table. Does
   * nothing if the constant pool already contains a similar item.
   *
   * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link
   *     Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link
   *     Symbol#CONSTANT_PACKAGE_TAG}.
   * @param value an internal class name, an arbitrary string, a method descriptor, a module or a
   *     package name, depending on tag.
   * @return a new or already existing Symbol with the given value.
   */
  private Symbol addConstantUtf8Reference(final int tag, final String value) {
    int hashCode = hash(tag, value);
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == tag && entry.hashCode == hashCode && entry.value.equals(value)) {
        return entry;
      }
      entry = entry.next;
    }
    constantPool.put12(tag, addConstantUtf8(value));
    return put(new Entry(constantPoolCount++, tag, value, hashCode));
  }

  /**
   * Adds a new CONSTANT_Class_info, CONSTANT_String_info, CONSTANT_MethodType_info,
   * CONSTANT_Module_info or CONSTANT_Package_info to the constant pool of this symbol table.
   *
   * @param index the constant pool index of the new Symbol.
   * @param tag one of {@link Symbol#CONSTANT_CLASS_TAG}, {@link Symbol#CONSTANT_STRING_TAG}, {@link
   *     Symbol#CONSTANT_METHOD_TYPE_TAG}, {@link Symbol#CONSTANT_MODULE_TAG} or {@link
   *     Symbol#CONSTANT_PACKAGE_TAG}.
   * @param value an internal class name, an arbitrary string, a method descriptor, a module or a
   *     package name, depending on tag.
   */
  private void addConstantUtf8Reference(final int index, final int tag, final String value) {
    add(new Entry(index, tag, value, hash(tag, value)));
  }

  // -----------------------------------------------------------------------------------------------
  // Bootstrap method entries management.
  // -----------------------------------------------------------------------------------------------

  /**
   * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if
   * the BootstrapMethods already contains a similar bootstrap method.
   *
   * @param bootstrapMethodHandle a bootstrap method handle.
   * @param bootstrapMethodArguments the bootstrap method arguments.
   * @return a new or already existing Symbol with the given value.
   */
  Symbol addBootstrapMethod(
      final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) {
    ByteVector bootstrapMethodsAttribute = bootstrapMethods;
    if (bootstrapMethodsAttribute == null) {
      bootstrapMethodsAttribute = bootstrapMethods = new ByteVector();
    }

    // The bootstrap method arguments can be Constant_Dynamic values, which reference other
    // bootstrap methods. We must therefore add the bootstrap method arguments to the constant pool
    // and BootstrapMethods attribute first, so that the BootstrapMethods attribute is not modified
    // while adding the given bootstrap method to it, in the rest of this method.
    for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
      addConstant(bootstrapMethodArgument);
    }

    // Write the bootstrap method in the BootstrapMethods table. This is necessary to be able to
    // compare it with existing ones, and will be reverted below if there is already a similar
    // bootstrap method.
    int bootstrapMethodOffset = bootstrapMethodsAttribute.length;
    bootstrapMethodsAttribute.putShort(
        addConstantMethodHandle(
                bootstrapMethodHandle.getTag(),
                bootstrapMethodHandle.getOwner(),
                bootstrapMethodHandle.getName(),
                bootstrapMethodHandle.getDesc(),
                bootstrapMethodHandle.isInterface())
            .index);
    int numBootstrapArguments = bootstrapMethodArguments.length;
    bootstrapMethodsAttribute.putShort(numBootstrapArguments);
    for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
      bootstrapMethodsAttribute.putShort(addConstant(bootstrapMethodArgument).index);
    }

    // Compute the length and the hash code of the bootstrap method.
    int bootstrapMethodlength = bootstrapMethodsAttribute.length - bootstrapMethodOffset;
    int hashCode = bootstrapMethodHandle.hashCode();
    for (Object bootstrapMethodArgument : bootstrapMethodArguments) {
      hashCode ^= bootstrapMethodArgument.hashCode();
    }
    hashCode &= 0x7FFFFFFF;

    // Add the bootstrap method to the symbol table or revert the above changes.
    return addBootstrapMethod(bootstrapMethodOffset, bootstrapMethodlength, hashCode);
  }

  /**
   * Adds a bootstrap method to the BootstrapMethods attribute of this symbol table. Does nothing if
   * the BootstrapMethods already contains a similar bootstrap method (more precisely, reverts the
   * content of {@link #bootstrapMethods} to remove the last, duplicate bootstrap method).
   *
   * @param offset the offset of the last bootstrap method in {@link #bootstrapMethods}, in bytes.
   * @param length the length of this bootstrap method in {@link #bootstrapMethods}, in bytes.
   * @param hashCode the hash code of this bootstrap method.
   * @return a new or already existing Symbol with the given value.
   */
  private Symbol addBootstrapMethod(final int offset, final int length, final int hashCode) {
    final byte[] bootstrapMethodsData = bootstrapMethods.data;
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == Symbol.BOOTSTRAP_METHOD_TAG && entry.hashCode == hashCode) {
        int otherOffset = (int) entry.data;
        boolean isSameBootstrapMethod = true;
        for (int i = 0; i < length; ++i) {
          if (bootstrapMethodsData[offset + i] != bootstrapMethodsData[otherOffset + i]) {
            isSameBootstrapMethod = false;
            break;
          }
        }
        if (isSameBootstrapMethod) {
          bootstrapMethods.length = offset; // Revert to old position.
          return entry;
        }
      }
      entry = entry.next;
    }
    return put(new Entry(bootstrapMethodCount++, Symbol.BOOTSTRAP_METHOD_TAG, offset, hashCode));
  }

  // -----------------------------------------------------------------------------------------------
  // Type table entries management.
  // -----------------------------------------------------------------------------------------------

  /**
   * Returns the type table element whose index is given.
   *
   * @param typeIndex a type table index.
   * @return the type table element whose index is given.
   */
  Symbol getType(final int typeIndex) {
    return typeTable[typeIndex];
  }

  /**
   * Adds a type in the type table of this symbol table. Does nothing if the type table already
   * contains a similar type.
   *
   * @param value an internal class name.
   * @return the index of a new or already existing type Symbol with the given value.
   */
  int addType(final String value) {
    int hashCode = hash(Symbol.TYPE_TAG, value);
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == Symbol.TYPE_TAG && entry.hashCode == hashCode && entry.value.equals(value)) {
        return entry.index;
      }
      entry = entry.next;
    }
    return addTypeInternal(new Entry(typeCount, Symbol.TYPE_TAG, value, hashCode));
  }

  /**
   * Adds an {@link Frame#ITEM_UNINITIALIZED} type in the type table of this symbol table. Does
   * nothing if the type table already contains a similar type.
   *
   * @param value an internal class name.
   * @param bytecodeOffset the bytecode offset of the NEW instruction that created this {@link
   *     Frame#ITEM_UNINITIALIZED} type value.
   * @return the index of a new or already existing type Symbol with the given value.
   */
  int addUninitializedType(final String value, final int bytecodeOffset) {
    int hashCode = hash(Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset);
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == Symbol.UNINITIALIZED_TYPE_TAG
          && entry.hashCode == hashCode
          && entry.data == bytecodeOffset
          && entry.value.equals(value)) {
        return entry.index;
      }
      entry = entry.next;
    }
    return addTypeInternal(
        new Entry(typeCount, Symbol.UNINITIALIZED_TYPE_TAG, value, bytecodeOffset, hashCode));
  }

  /**
   * Adds a merged type in the type table of this symbol table. Does nothing if the type table
   * already contains a similar type.
   *
   * @param typeTableIndex1 a {@link Symbol#TYPE_TAG} type, specified by its index in the type
   *     table.
   * @param typeTableIndex2 another {@link Symbol#TYPE_TAG} type, specified by its index in the type
   *     table.
   * @return the index of a new or already existing {@link Symbol#TYPE_TAG} type Symbol,
   *     corresponding to the common super class of the given types.
   */
  int addMergedType(final int typeTableIndex1, final int typeTableIndex2) {
    // TODO sort the arguments? The merge result should be independent of their order.
    long data = typeTableIndex1 | (((long) typeTableIndex2) << 32);
    int hashCode = hash(Symbol.MERGED_TYPE_TAG, typeTableIndex1 + typeTableIndex2);
    Entry entry = get(hashCode);
    while (entry != null) {
      if (entry.tag == Symbol.MERGED_TYPE_TAG && entry.hashCode == hashCode && entry.data == data) {
        return entry.info;
      }
      entry = entry.next;
    }
    String type1 = typeTable[typeTableIndex1].value;
    String type2 = typeTable[typeTableIndex2].value;
    int commonSuperTypeIndex = addType(classWriter.getCommonSuperClass(type1, type2));
    put(new Entry(typeCount, Symbol.MERGED_TYPE_TAG, data, hashCode)).info = commonSuperTypeIndex;
    return commonSuperTypeIndex;
  }

  /**
   * Adds the given type Symbol to {@link #typeTable}.
   *
   * @param entry a {@link Symbol#TYPE_TAG} or {@link Symbol#UNINITIALIZED_TYPE_TAG} type symbol.
   *     The index of this Symbol must be equal to the current value of {@link #typeCount}.
   * @return the index in {@link #typeTable} where the given type was added, which is also equal to
   *     entry's index by hypothesis.
   */
  private int addTypeInternal(final Entry entry) {
    if (typeTable == null) {
      typeTable = new Entry[16];
    }
    if (typeCount == typeTable.length) {
      Entry[] newTypeTable = new Entry[2 * typeTable.length];
      System.arraycopy(typeTable, 0, newTypeTable, 0, typeTable.length);
      typeTable = newTypeTable;
    }
    typeTable[typeCount++] = entry;
    return put(entry).index;
  }

  // -----------------------------------------------------------------------------------------------
  // Static helper methods to compute hash codes.
  // -----------------------------------------------------------------------------------------------

  private static int hash(final int tag, final int value) {
    return 0x7FFFFFFF & (tag + value);
  }

  private static int hash(final int tag, final long value) {
    return 0x7FFFFFFF & (tag + (int) value + (int) (value >>> 32));
  }

  private static int hash(final int tag, final String value) {
    return 0x7FFFFFFF & (tag + value.hashCode());
  }

  private static int hash(final int tag, final String value1, final int value2) {
    return 0x7FFFFFFF & (tag + value1.hashCode() + value2);
  }

  private static int hash(final int tag, final String value1, final String value2) {
    return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode());
  }

  private static int hash(
      final int tag, final String value1, final String value2, final int value3) {
    return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * (value3 + 1));
  }

  private static int hash(
      final int tag, final String value1, final String value2, final String value3) {
    return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode());
  }

  private static int hash(
      final int tag,
      final String value1,
      final String value2,
      final String value3,
      final int value4) {
    return 0x7FFFFFFF & (tag + value1.hashCode() * value2.hashCode() * value3.hashCode() * value4);
  }

  /**
   * An entry of a SymbolTable. This concrete and private subclass of {@link Symbol} adds two fields
   * which are only used inside SymbolTable, to implement hash sets of symbols (in order to avoid
   * duplicate symbols). See {@link #entries}.
   *
   * @author Eric Bruneton
   */
  private static class Entry extends Symbol {

    /** The hash code of this entry. */
    final int hashCode;

    /**
     * Another entry (and so on recursively) having the same hash code (modulo the size of {@link
     * #entries}) as this one.
     */
    Entry next;

    Entry(
        final int index,
        final int tag,
        final String owner,
        final String name,
        final String value,
        final long data,
        final int hashCode) {
      super(index, tag, owner, name, value, data);
      this.hashCode = hashCode;
    }

    Entry(final int index, final int tag, final String value, final int hashCode) {
      super(index, tag, /* owner = */ null, /* name = */ null, value, /* data = */ 0);
      this.hashCode = hashCode;
    }

    Entry(final int index, final int tag, final String value, final long data, final int hashCode) {
      super(index, tag, /* owner = */ null, /* name = */ null, value, data);
      this.hashCode = hashCode;
    }

    Entry(
        final int index, final int tag, final String name, final String value, final int hashCode) {
      super(index, tag, /* owner = */ null, name, value, /* data = */ 0);
      this.hashCode = hashCode;
    }

    Entry(final int index, final int tag, final long data, final int hashCode) {
      super(index, tag, /* owner = */ null, /* name = */ null, /* value = */ null, data);
      this.hashCode = hashCode;
    }
  }
}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// 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.
//
// 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 HOLDER 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 jodd.typeconverter.impl;

import jodd.typeconverter.TypeConverter;
import jodd.typeconverter.TypeConverterManager;
import jodd.util.StringUtil;

import java.util.ArrayList;
import java.util.Collection;

/**
 * Converts given object to float[].
 */
public class FloatArrayConverter implements TypeConverter<float[]> {

	protected final TypeConverterManager typeConverterManager;

	public FloatArrayConverter(final TypeConverterManager typeConverterManager) {
		this.typeConverterManager = typeConverterManager;
	}

	@Override
	public float[] convert(final Object value) {
		if (value == null) {
			return null;
		}

		final Class valueClass = value.getClass();

		if (!valueClass.isArray()) {
			// source is not an array
			return convertValueToArray(value);
		}

		// source is an array
		return convertArrayToArray(value);
	}

	/**
	 * Converts type using type converter manager.
	 */
	protected float convertType(final Object value) {
		return typeConverterManager.convertType(value, float.class).floatValue();
	}

	/**
	 * Creates an array with single element.
	 */
	protected float[] convertToSingleElementArray(final Object value) {
		return new float[] {convertType(value)};
	}

	/**
	 * Converts non-array value to array. Detects various
	 * collection types and iterates them to make conversion
	 * and to create target array.
 	 */
	protected float[] convertValueToArray(final Object value) {
		if (value instanceof Collection) {
			final Collection collection = (Collection) value;
			final float[] target = new float[collection.size()];

			int i = 0;
			for (final Object element : collection) {
				target[i] = convertType(element);
				i++;
			}

			return target;
		}

		if (value instanceof Iterable) {
			final Iterable iterable = (Iterable) value;

			final ArrayList<Float> floatArrayList = new ArrayList<>();

			for (final Object element : iterable) {
				final float convertedValue = convertType(element);
				floatArrayList.add(Float.valueOf(convertedValue));
			}

			final float[] array = new float[floatArrayList.size()];

			for (int i = 0; i < floatArrayList.size(); i++) {
				final Float f = floatArrayList.get(i);
				array[i] = f.floatValue();
			}

			return array;
		}

		if (value instanceof CharSequence) {
			final String[] strings = StringUtil.splitc(value.toString(), ArrayConverter.NUMBER_DELIMITERS);
			return convertArrayToArray(strings);
		}

		// everything else:
		return convertToSingleElementArray(value);
	}

	/**
	 * Converts array value to array.
	 */
	protected float[] convertArrayToArray(final Object value) {
		final Class valueComponentType = value.getClass().getComponentType();

		final float[] result;

		if (valueComponentType.isPrimitive()) {
			result = convertPrimitiveArrayToArray(value, valueComponentType);
		} else {
			// convert object array to target array
			final Object[] array = (Object[]) value;
			result = new float[array.length];

			for (int i = 0; i < array.length; i++) {
				result[i] = convertType(array[i]);
			}
		}

		return result;
	}

	/**
	 * Converts primitive array to target array.
	 */
	protected float[] convertPrimitiveArrayToArray(final Object value, final Class primitiveComponentType) {
		float[] result = null;

		if (primitiveComponentType == float.class) {
			return (float[]) value;
		}

		if (primitiveComponentType == int.class) {
			final int[] array = (int[]) value;
			result = new float[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i];
			}
		}
		else if (primitiveComponentType == long.class) {
			final long[] array = (long[]) value;
			result = new float[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i];
			}
		}
		else if (primitiveComponentType == double.class) {
			final double[] array = (double[]) value;
			result = new float[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = (float) array[i];
			}
		}
		else if (primitiveComponentType == short.class) {
			final short[] array = (short[]) value;
			result = new float[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i];
			}
		}
		else if (primitiveComponentType == byte.class) {
			final byte[] array = (byte[]) value;
			result = new float[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i];
			}
		}
		else if (primitiveComponentType == char.class) {
			final char[] array = (char[]) value;
			result = new float[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i];
			}
		}
		else if (primitiveComponentType == boolean.class) {
			final boolean[] array = (boolean[]) value;
			result = new float[array.length];
			for (int i = 0; i < array.length; i++) {
				result[i] = array[i] ? 1 : 0;
			}
		}
		return result;
	}

}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// 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.
//
// 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 HOLDER 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 jodd.http;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeSet;

/**
 * General purpose HTTP multi-map. It's optimized Linked-HashMap, designed for
 * small number of items and String non-null keys. It stores keys
 * in case-sensitive way, but, by default, you can read them in case-insensitive
 * way.
 */
public class HttpMultiMap<V> implements Iterable<Map.Entry<String, V>>  {

	private static final int BUCKET_SIZE = 31;
	private final boolean caseSensitive;

	@SuppressWarnings("unchecked")
	private final MapEntry<V>[] entries = new MapEntry[BUCKET_SIZE + 1];
	private final MapEntry<V> head = new MapEntry<>(-1, null, null);

	/**
	 * Creates new case-insensitive multimap.
	 */
	public static <T> HttpMultiMap<T> newCaseInsensitiveMap() {
		return new HttpMultiMap<>(false);
	}
	/**
	 * Creates new case-insensitive map.
	 */
	public static <T> HttpMultiMap<T> newCaseSensitiveMap() {
		return new HttpMultiMap<>(true);
	}

	protected HttpMultiMap(final boolean caseSensitive) {
		head.before = head.after = head;
		this.caseSensitive = caseSensitive;
	}

	/**
	 * Calculates hash value of the input string.
	 */
	private int hash(final String name) {
		int h = 0;
		for (int i = name.length() - 1; i >= 0; i--) {
			char c = name.charAt(i);
			if (!caseSensitive) {
				if (c >= 'A' && c <= 'Z') {
					c += 32;
				}
			}
			h = 31 * h + c;
		}

		if (h > 0) {
			return h;
		}
		if (h == Integer.MIN_VALUE) {
			return Integer.MAX_VALUE;
		}
		return -h;
	}

	/**
	 * Calculates bucket index from the hash.
	 */
	private static int index(final int hash) {
		return hash & BUCKET_SIZE;
	}

	/**
	 * Returns true if two names are the same.
	 */
	private boolean eq(final String name1, final String name2) {
		int nameLen = name1.length();
		if (nameLen != name2.length()) {
			return false;
		}

		for (int i = nameLen - 1; i >= 0; i--) {
			char c1 = name1.charAt(i);
			char c2 = name2.charAt(i);

			if (c1 != c2) {
				if (caseSensitive) {
					return false;
				}
				if (c1 >= 'A' && c1 <= 'Z') {
					c1 += 32;
				}
				if (c2 >= 'A' && c2 <= 'Z') {
					c2 += 32;
				}
				if (c1 != c2) {
					return false;
				}
			}
		}
		return true;
	}

	// ---------------------------------------------------------------- basic

	/**
	 * Returns the number of keys. This is not the number of all elements.
	 * Not optimized.
	 */
	public int size() {
		return names().size();
	}

	/**
	 * Clears the map.
	 */
	public HttpMultiMap<V> clear() {
		for (int i = 0; i < entries.length; i++) {
			entries[i] = null;
		}
		head.before = head.after = head;
		return this;
	}

	/**
	 * Returns true if name exist.
	 */
	public boolean contains(final String name) {
		return getEntry(name) != null;
	}

	/**
	 * Returns true if map is empty.
	 */
	public boolean isEmpty() {
		return head == head.after;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		for (Map.Entry<String, V> entry : this) {
			sb.append(entry).append('\n');
		}
		return sb.toString();
	}

	// ---------------------------------------------------------------- set/add

	private HttpMultiMap<V> _set(final Iterable<Map.Entry<String, V>> map) {
		clear();
		for (Map.Entry<String, V> entry : map) {
			add(entry.getKey(), entry.getValue());
		}
		return this;
	}

	public HttpMultiMap<V> setAll(final HttpMultiMap<V> multiMap) {
		return _set(multiMap);
	}

	public HttpMultiMap<V> setAll(final Map<String, V> map) {
		return _set(map.entrySet());
	}

	public HttpMultiMap<V> set(final String name, final V value) {
		int h = hash(name);
		int i = index(h);
		_remove(h, i, name);
		_add(h, i, name, value);
		return this;
	}

	public HttpMultiMap<V> setAll(final String name, final Iterable<V> values) {
		int h = hash(name);
		int i = index(h);

		_remove(h, i, name);
		for (V v : values) {
			_add(h, i, name, v);
		}

		return this;
	}

	public HttpMultiMap<V> add(final String name, final V value) {
		int h = hash(name);
		int i = index(h);
		_add(h, i, name, value);
		return this;
	}

	public HttpMultiMap<V> addAll(final String name, final Iterable<V> values) {
		int h = hash(name);
		int i = index(h);
		for (V value : values) {
			_add(h, i, name, value);
		}
		return this;
	}

	public HttpMultiMap<V> addAll(final HttpMultiMap<V> map) {
		for (Map.Entry<String, V> entry : map.entries()) {
			add(entry.getKey(), entry.getValue());
		}
		return this;
	}

	public HttpMultiMap<V> addAll(final Map<String, V> map) {
		for (Map.Entry<String, V> entry : map.entrySet()) {
			add(entry.getKey(), entry.getValue());
		}
		return this;
	}

	private void _add(final int hash, final int index, final String name, final V value) {
		// update the hash table
		MapEntry<V> e = entries[index];
		MapEntry<V> newEntry;
		entries[index] = newEntry = new MapEntry<>(hash, name, value);
		newEntry.next = e;

		// update the linked list
		newEntry.addBefore(head);
	}

	// ---------------------------------------------------------------- remove

	public HttpMultiMap<V> remove(final String name) {
		int h = hash(name);
		int i = index(h);
		_remove(h, i, name);
		return this;
	}

	private void _remove(final int hash, final int index, final String name) {
		MapEntry<V> e = entries[index];
		if (e == null) {
			return;
		}

		for (; ; ) {
			if (e.hash == hash && eq(name, e.key)) {
				e.remove();
				MapEntry<V> next = e.next;
				if (next != null) {
					entries[index] = next;
					e = next;
				}
				else {
					entries[index] = null;
					return;
				}
			}
			else {
				break;
			}
		}

		for (; ; ) {
			MapEntry<V> next = e.next;
			if (next == null) {
				break;
			}
			if (next.hash == hash && eq(name, next.key)) {
				e.next = next.next;
				next.remove();
			}
			else {
				e = next;
			}
		}
	}

	// ---------------------------------------------------------------- get

	/**
	 * Returns the first value from the map associated with the name.
	 * Returns null if name does not exist or
	 * if associated value is null.
	 */
	public V get(final String name) {
		Map.Entry<String, V> entry = getEntry(name);

		if (entry == null) {
			return null;
		}
		return entry.getValue();
	}

	/**
	 * Returns first entry for given name. Returns null if entry
	 * does not exist.
	 */
	public Map.Entry<String, V> getEntry(final String name) {
		int h = hash(name);
		int i = index(h);
		MapEntry<V> e = entries[i];
		while (e != null) {
			if (e.hash == h && eq(name, e.key)) {
				return e;
			}

			e = e.next;
		}
		return null;
	}

	/**
	 * Returns all values associated with the name.
	 */
	public List<V> getAll(final String name) {
		LinkedList<V> values = new LinkedList<>();

		int h = hash(name);
		int i = index(h);
		MapEntry<V> e = entries[i];
		while (e != null) {
			if (e.hash == h && eq(name, e.key)) {
				values.addFirst(e.getValue());
			}
			e = e.next;
		}
		return values;
	}

	// ---------------------------------------------------------------- iterate

	/**
	 * Returns iterator of all entries.
	 */
	@Override
	public Iterator<Map.Entry<String, V>> iterator() {
		final MapEntry[] e = {head.after};

		return new Iterator<Map.Entry<String, V>>() {
			@Override
			public boolean hasNext() {
				return e[0] != head;
			}

			@Override
			@SuppressWarnings("unchecked")
			public Map.Entry<String, V> next() {
				if (!hasNext()) {
					throw new NoSuchElementException("No next() entry in the iteration");
				}
				MapEntry<V> next = e[0];
				e[0] = e[0].after;
				return next;
			}

			@Override
			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}

	public Set<String> names() {
		Set<String> names = new TreeSet<>(caseSensitive ? null : String.CASE_INSENSITIVE_ORDER);

		MapEntry e = head.after;
		while (e != head) {
			names.add(e.getKey());
			e = e.after;
		}
		return names;
	}

	/**
	 * Returns all the entries of this map. Case sensitivity does not influence
	 * the returned list, it always contains all of the values.
	 */
	public List<Map.Entry<String, V>> entries() {
		List<Map.Entry<String, V>> all = new LinkedList<>();

		MapEntry<V> e = head.after;
		while (e != head) {
			all.add(e);
			e = e.after;
		}
		return all;
	}

	private static final class MapEntry<V> implements Map.Entry<String, V> {
		final int hash;
		final String key;
		V value;
		MapEntry<V> next;
		MapEntry<V> before, after;

		private MapEntry(final int hash, final String key, final V value) {
			this.hash = hash;
			this.key = key;
			this.value = value;
		}

		void remove() {
			before.after = after;
			after.before = before;
		}

		void addBefore(final MapEntry<V> e) {
			after = e;
			before = e.before;
			before.after = this;
			after.before = this;
		}

		@Override
		public String getKey() {
			return key;
		}

		@Override
		public V getValue() {
			return value;
		}

		@Override
		public V setValue(final V value) {
			V oldValue = this.value;
			this.value = value;
			return oldValue;
		}

		@Override
		public String toString() {
			return getKey() + ": " + getValue();
		}
	}
}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// 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.
//
// 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 HOLDER 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 jodd.buffer;

import jodd.util.CharArraySequence;

import java.util.Arrays;

/**
 * Faster {@code char} buffer.
 */
public class FastCharBuffer implements CharSequence, Appendable {

	private char[] buffer;
	private int offset;

	/**
	 * Creates a new {@code char} buffer. The buffer capacity is
	 * initially 64 chars, though its size increases if necessary.
	 */
	public FastCharBuffer() {
		this.buffer = new char[64];
	}

	/**
	 * Creates a new {@code char} buffer, with a buffer capacity of
	 * the specified size, in chars.
	 *
	 * @param size the initial size.
	 * @throws IllegalArgumentException if size is negative.
	 */
	public FastCharBuffer(final int size) {
		this.buffer = new char[size];
	}

	/**
	 * Grows the buffer.
	 */
	private void grow(final int minCapacity) {
		final int oldCapacity = buffer.length;
		int newCapacity = oldCapacity << 1;
		if (newCapacity - minCapacity < 0) {
			// special case, min capacity is larger then a grow
			newCapacity = minCapacity + 512;
		}
		buffer = Arrays.copyOf(buffer, newCapacity);
	}

	/**
	 * Appends single {@code char} to buffer.
	 */
	@Override
	public FastCharBuffer append(final char element) {
		if (offset - buffer.length >= 0) {
			grow(offset);
		}

		buffer[offset++] = element;
		return this;
	}

	/**
	 * Appends {@code char} array to buffer.
	 */
	public FastCharBuffer append(final char[] array, final int off, final int len) {
		if (offset + len - buffer.length > 0) {
			grow(offset + len);
		}

		System.arraycopy(array, off, buffer, offset, len);
		offset += len;
		return this;
	}

	/**
	 * Appends {@code char} array to buffer.
	 */
	public FastCharBuffer append(final char[] array) {
		return append(array, 0, array.length);
	}

	/**
	 * Appends another fast buffer to this one.
	 */
	public FastCharBuffer append(final FastCharBuffer buff) {
		if (buff.offset == 0) {
			return this;
		}
		append(buff.buffer, 0, buff.offset);
		return this;
	}

	/**
	 * Returns buffer size.
	 */
	public int size() {
		return offset;
	}

	@Override
	public int length() {
		return offset;
	}

	/**
	 * Tests if this buffer has no elements.
	 */
	public boolean isEmpty() {
		return offset == 0;
	}

	/**
	 * Resets the buffer content.
	 */
	public void clear() {
		offset = 0;
	}

	/**
	 * Creates {@code char} array from buffered content.
	 */
	public char[] toArray() {
		return Arrays.copyOf(buffer, offset);
	}

	/**
	 * Creates {@code char} subarray from buffered content.
	 */
	public char[] toArray(final int start, final int len) {
		final char[] array = new char[len];

		if (len == 0) {
			return array;
		}

		System.arraycopy(buffer, start, array, 0, len);

		return array;
	}

	/**
	 * Returns {@code char} element at given index.
	 */
	public char get(final int index) {
		if (index >= offset) {
			throw new IndexOutOfBoundsException();
		}
		return buffer[index];
	}

	/**
	 * Appends character sequence to buffer.
	 */
	@Override
	public FastCharBuffer append(final CharSequence csq) {
		append(csq, 0, csq.length());
		return this;
	}

	/**
	 * Appends character sequence to buffer.
	 */
	@Override
	public FastCharBuffer append(final CharSequence csq, final int start, final int end) {
		for (int i = start; i < end; i++) {
			append(csq.charAt(i));
		}
		return this;
	}
	@Override
	public char charAt(final int index) {
		return get(index);
	}

	@Override
	public CharSequence subSequence(final int start, final int end) {
		return new CharArraySequence(buffer, start, end - start);
	}

	/**
	 * Returns a String of the char buffer.
	 * Please use {@code StringBuilder} instead, it is faster.
	 */
	@Override
	public String toString() {
		return new String(toArray());
	}
}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// 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.
//
// 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 HOLDER 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 jodd.proxetta.asm;

import jodd.asm.AnnotationVisitorAdapter;
import jodd.asm.AsmUtil;
import jodd.asm.EmptyClassVisitor;
import jodd.asm.EmptyMethodVisitor;
import jodd.asm7.AnnotationVisitor;
import jodd.asm7.MethodVisitor;
import jodd.proxetta.ProxettaException;
import jodd.proxetta.ProxettaNames;
import jodd.proxetta.ProxyTarget;
import jodd.proxetta.ProxyTargetReplacement;

import java.util.List;

import static jodd.asm7.Opcodes.ACC_ABSTRACT;
import static jodd.asm7.Opcodes.ACC_NATIVE;
import static jodd.asm7.Opcodes.ALOAD;
import static jodd.asm7.Opcodes.ARETURN;
import static jodd.asm7.Opcodes.ASTORE;
import static jodd.asm7.Opcodes.GETFIELD;
import static jodd.asm7.Opcodes.INVOKEINTERFACE;
import static jodd.asm7.Opcodes.INVOKESPECIAL;
import static jodd.asm7.Opcodes.INVOKESTATIC;
import static jodd.asm7.Opcodes.INVOKEVIRTUAL;
import static jodd.asm7.Opcodes.POP;
import static jodd.asm7.Opcodes.POP2;
import static jodd.proxetta.asm.ProxettaAsmUtil.adviceFieldName;
import static jodd.proxetta.asm.ProxettaAsmUtil.adviceMethodName;
import static jodd.proxetta.asm.ProxettaAsmUtil.castToReturnType;
import static jodd.proxetta.asm.ProxettaAsmUtil.checkArgumentIndex;
import static jodd.proxetta.asm.ProxettaAsmUtil.isArgumentMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isArgumentTypeMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isArgumentsCountMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isCreateArgumentsArrayMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isCreateArgumentsClassArrayMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isInfoMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isInvokeMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isReturnTypeMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isReturnValueMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isSetArgumentMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isTargetClassAnnotationMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isTargetClassMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isTargetMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isTargetMethodAnnotationMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isTargetMethodDescriptionMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isTargetMethodNameMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.isTargetMethodSignatureMethod;
import static jodd.proxetta.asm.ProxettaAsmUtil.loadSpecialMethodArguments;
import static jodd.proxetta.asm.ProxettaAsmUtil.loadStaticMethodArguments;
import static jodd.proxetta.asm.ProxettaAsmUtil.loadVirtualMethodArguments;
import static jodd.proxetta.asm.ProxettaAsmUtil.prepareReturnValue;
import static jodd.proxetta.asm.ProxettaAsmUtil.storeMethodArgumentFromObject;
import static jodd.proxetta.asm.ProxettaAsmUtil.visitReturn;

@SuppressWarnings({"AnonymousClassVariableHidesContainingMethodVariable"})
public class ProxettaMethodBuilder extends EmptyMethodVisitor {

	public static final String TARGET_CLASS_NAME = ProxyTarget.class.getSimpleName();        // extract ProxyTarget name for recognition

	protected final MethodSignatureVisitor msign;
	protected final WorkData wd;
	protected final List<ProxyAspectData> aspectList;

	public ProxettaMethodBuilder(final MethodSignatureVisitor msign, final WorkData wd, final List<ProxyAspectData> aspectList) {
		this.msign = msign;
		this.wd = wd;
		this.aspectList = aspectList;
		createFirstChainDelegate_Start();
	}

	// ---------------------------------------------------------------- visits

	/**
	 * Copies target method annotations.
	 */
	@Override
	public AnnotationVisitor visitAnnotation(final String desc, final boolean visible) {
		AnnotationVisitor destAnn = methodVisitor.visitAnnotation(desc, visible); // [A4]
		return new AnnotationVisitorAdapter(destAnn);
	}

	@Override
	public AnnotationVisitor visitAnnotationDefault() {
		AnnotationVisitor destAnn = methodVisitor.visitAnnotationDefault();
		return new AnnotationVisitorAdapter(destAnn);
	}

	@Override
	public AnnotationVisitor visitParameterAnnotation(final int parameter, final String desc, final boolean visible) {
		AnnotationVisitor destAnn = methodVisitor.visitParameterAnnotation(parameter, desc, visible);
		return new AnnotationVisitorAdapter(destAnn);
	}

	/**
	 * Finally, builds proxy methods if applied to current method.
	 */
	@Override
	public void visitEnd() {
		createFirstChainDelegate_Continue(tmd);
		for (int p = 0; p < tmd.proxyData.length; p++) {
			tmd.selectCurrentProxy(p);
			createProxyMethod(tmd);
		}
	}

	// ---------------------------------------------------------------- creating

	protected TargetMethodData tmd;
	protected MethodVisitor methodVisitor;

	/**
	 * Starts creation of first chain delegate.
	 */
	protected void createFirstChainDelegate_Start() {
		// check invalid access flags
		int access = msign.getAccessFlags();
		if (!wd.allowFinalMethods) {
			if ((access & AsmUtil.ACC_FINAL) != 0) {   // detect final
				throw new ProxettaException(
					"Unable to create proxy for final method: " + msign + ". Remove final modifier or change the pointcut definition.");
			}
		}

		// create proxy methods
		tmd = new TargetMethodData(msign, aspectList);

		access &= ~ACC_NATIVE;
		access &= ~ACC_ABSTRACT;

		methodVisitor = wd.dest.visitMethod(
				access, tmd.msign.getMethodName(), tmd.msign.getDescription(), tmd.msign.getAsmMethodSignature(), null);
	}

	/**
	 * Continues the creation of the very first method in calling chain that simply delegates invocation to the first proxy method.
	 * This method mirrors the target method.
	 */
	protected void createFirstChainDelegate_Continue(final TargetMethodData tmd) {
		methodVisitor.visitCode();

		if (tmd.msign.isStatic) {
			loadStaticMethodArguments(methodVisitor, tmd.msign);
			methodVisitor.visitMethodInsn(
				INVOKESTATIC,
				wd.thisReference,
				tmd.firstMethodName(),
				tmd.msign.getDescription(),
				false);
		} else {
			loadSpecialMethodArguments(methodVisitor, tmd.msign);
			methodVisitor.visitMethodInsn(
				INVOKESPECIAL,
				wd.thisReference,
				tmd.firstMethodName(),
				tmd.msign.getDescription(),
				false);
		}

		visitReturn(methodVisitor, tmd.msign, false);

		methodVisitor.visitMaxs(0, 0);
		methodVisitor.visitEnd();
	}

	protected boolean proxyInfoRequested;

	/**
	 * Creates proxy methods over target method, For each matched proxy, new proxy method is created
	 * by taking advice bytecode and replaces usages of {@link jodd.proxetta.ProxyTarget}.
	 * <p>
	 * Invocation chain example: {@code name -> name$p0 -> name$p1 -> name$p4 -> super}.
	 */
	public void createProxyMethod(final TargetMethodData td) {
		final ProxyAspectData aspectData = td.getProxyData();

		int access = td.msign.getAccessFlags();

		access &= ~ACC_NATIVE;
		access &= ~ACC_ABSTRACT;
		access = ProxettaAsmUtil.makePrivateFinalAccess(access);

		final MethodVisitor mv = wd.dest.visitMethod(access, td.methodName(), td.msign.getDescription(), null, null);
		mv.visitCode();

		//*** VISIT ADVICE - called for each aspect and each method
		aspectData.getAdviceClassReader().accept(new EmptyClassVisitor() {

			@Override
			public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {

				if (!name.equals(ProxettaNames.executeMethodName)) {
					return null;
				}

				return new HistoryMethodAdapter(mv) {

					@Override
					public void visitFieldInsn(final int opcode, String owner, String name, final String desc) {
						if (owner.equals(aspectData.adviceReference)) {
							owner = wd.thisReference;              // [F5]
							name = adviceFieldName(name, aspectData.aspectIndex);
						}
						super.visitFieldInsn(opcode, owner, name, desc);
					}

					@Override
					public void visitVarInsn(final int opcode, int var) {
						var += (var == 0 ? 0 : td.msign.getAllArgumentsSize());

						if (proxyInfoRequested) {
							proxyInfoRequested = false;
							if (opcode == ASTORE) {
								ProxyTargetReplacement.info(mv, td.msign, var);
							}
						}

						super.visitVarInsn(opcode, var);   // [F1]
					}

					@Override
					public void visitIincInsn(int var, final int increment) {
						var += (var == 0 ? 0 : td.msign.getAllArgumentsSize());
						super.visitIincInsn(var, increment);  // [F1]
					}

					@Override
					public void visitInsn(final int opcode) {
						if (opcode == ARETURN) {
							visitReturn(mv, td.msign, true);
							return;
						}
						if (traceNext) {
							if ((opcode == POP) || (opcode == POP2)) {      // [F3] - invoke invoked without assignment
								return;
							}
						}
						super.visitInsn(opcode);
					}

					@SuppressWarnings({"ParameterNameDiffersFromOverriddenParameter"})
					@Override
					public void visitMethodInsn(final int opcode, String string, String mname, final String mdesc, final boolean isInterface) {
						if ((opcode == INVOKEVIRTUAL) || (opcode == INVOKEINTERFACE) || (opcode == INVOKESPECIAL)) {
							if (string.equals(aspectData.adviceReference)) {
								string = wd.thisReference;
								mname = adviceMethodName(mname, aspectData.aspectIndex);
							}
						} else

						if (opcode == INVOKESTATIC) {
							if (string.equals(aspectData.adviceReference)) {
								string = wd.thisReference;
								mname = adviceMethodName(mname, aspectData.aspectIndex);
							} else

							if (string.endsWith('/' + TARGET_CLASS_NAME)) {

								if (isInvokeMethod(mname, mdesc)) {           // [R7]
									if (td.isLastMethodInChain()) {                            // last proxy method just calls super target method

										if (!wd.isWrapper()) {
											// PROXY
											loadSpecialMethodArguments(mv, td.msign);
											mv.visitMethodInsn(INVOKESPECIAL, wd.superReference, td.msign.getMethodName(), td.msign.getDescription(), isInterface);
										} else {
											// WRAPPER
											mv.visitVarInsn(ALOAD, 0);
											mv.visitFieldInsn(GETFIELD, wd.thisReference, wd.wrapperRef, wd.wrapperType);
											loadVirtualMethodArguments(mv, td.msign);
											if (wd.wrapInterface) {
												mv.visitMethodInsn(
													INVOKEINTERFACE,
													wd.wrapperType.substring(1, wd.wrapperType.length() - 1),
													td.msign.getMethodName(),
													td.msign.getDescription(),
													true);
											} else {
												mv.visitMethodInsn(
													INVOKEVIRTUAL,
													wd.wrapperType.substring(1, wd.wrapperType.length() - 1),
													td.msign.getMethodName(),
													td.msign.getDescription(),
													isInterface);
											}
										}

										prepareReturnValue(mv, td.msign, aspectData.maxLocalVarOffset);     // [F4]
										traceNext = true;
									} else {                                                    // calls next proxy method
										loadSpecialMethodArguments(mv, td.msign);
										mv.visitMethodInsn(INVOKESPECIAL, wd.thisReference, td.nextMethodName(), td.msign.getDescription(), isInterface);
										visitReturn(mv, td.msign, false);
									}
									return;
								}

								if (isArgumentsCountMethod(mname, mdesc)) {		// [R2]
									ProxyTargetReplacement.argumentsCount(mv, td.msign);
									return;
								}

								if (isArgumentTypeMethod(mname, mdesc)) {      // [R3]
									int argIndex = this.getArgumentIndex();
									ProxyTargetReplacement.argumentType(mv, td.msign, argIndex);
									return;
								}

								if (isArgumentMethod(mname, mdesc)) {           // [R4]
									int argIndex = this.getArgumentIndex();
									ProxyTargetReplacement.argument(mv, td.msign, argIndex);
									return;
								}

								if (isSetArgumentMethod(mname, mdesc)) {           // [R5]
									int argIndex = this.getArgumentIndex();
									checkArgumentIndex(td.msign, argIndex);
									mv.visitInsn(POP);
									storeMethodArgumentFromObject(mv, td.msign, argIndex);
									return;
								}

								if (isCreateArgumentsArrayMethod(mname, mdesc)) {  // [R6]
									ProxyTargetReplacement.createArgumentsArray(mv, td.msign);
									return;
								}

								if (isCreateArgumentsClassArrayMethod(mname, mdesc)) {     // [R11]
									ProxyTargetReplacement.createArgumentsClassArray(mv, td.msign);
									return;
								}

								if (isTargetMethod(mname, mdesc)) {       // [R9.1]
									mv.visitVarInsn(ALOAD, 0);
									return;
								}

								if (isTargetClassMethod(mname, mdesc)) {       // [R9]
									ProxyTargetReplacement.targetClass(mv, td.msign);
									//ProxyTargetReplacement.targetClass(mv, wd.superReference);
									return;
								}

								if (isTargetMethodNameMethod(mname, mdesc)) {  // [R10]
									ProxyTargetReplacement.targetMethodName(mv, td.msign);
									return;
								}

								if (isTargetMethodSignatureMethod(mname, mdesc)) {
									ProxyTargetReplacement.targetMethodSignature(mv, td.msign);
									return;
								}

								if (isTargetMethodDescriptionMethod(mname, mdesc)) {
									ProxyTargetReplacement.targetMethodDescription(mv, td.msign);
									return;
								}

								if (isInfoMethod(mname, mdesc)) {
									// we are NOT replacing info() here! First, we need to figure out
									// what is the operand for the very next ASTORE instructions
									// since we need to create an object and store it in this
									// register - and reuse it, in replacement code.

									//ProxyTargetReplacement.info(mv, td.msign);
									proxyInfoRequested = true;
									return;
								}

								if (isReturnTypeMethod(mname, mdesc)) {        // [R11]
									ProxyTargetReplacement.returnType(mv, td.msign);
									return;
								}

								if (isReturnValueMethod(mname, mdesc)) {
									castToReturnType(mv, td.msign);
									return;
								}

								if (isTargetMethodAnnotationMethod(mname, mdesc)) {
									String[] args = getLastTwoStringArguments();

									// pop current two args
									mv.visitInsn(POP);
									mv.visitInsn(POP);

									ProxyTargetReplacement.targetMethodAnnotation(mv, td.msign, args);
									return;
								}

								if (isTargetClassAnnotationMethod(mname, mdesc)) {
									String[] args = getLastTwoStringArguments();

									// pop current two args
									mv.visitInsn(POP);
									mv.visitInsn(POP);

									ProxyTargetReplacement.targetClassAnnotation(mv, td.msign.getClassInfo(), args);
									return;
								}
							}
						}
						super.visitMethodInsn(opcode, string, mname, mdesc, isInterface);
					}

				};
			}

		}, 0);
	}
}


// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// 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.
//
// 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 HOLDER 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 jodd.json;

import jodd.util.StringUtil;

import java.util.Objects;

/**
 * Path to a property from JSON root.
 */
public final class Path implements Cloneable {

	private CharSequence[] paths;
	private int index;
	Path altPath;

	/**
	 * Parses input dot-separated string that represents a path.
	 */
	public static Path parse(final String path) {
		return path == null ? new Path() : new Path(StringUtil.splitc(path, '.'));
	}

	public Path() {
		paths = new CharSequence[8];
	}

	/**
	 * Creates path from given path elements.
	 */
	public Path(final CharSequence... fields) {
		if (fields.length >= 8) {
			paths = fields;
		}
		else {
			paths = new CharSequence[8];
			System.arraycopy(fields, 0, paths, 0, fields.length);
			index = fields.length;
		}
	}

	private Path(CharSequence[] paths, int index, Path altPath) {
		this.paths = paths;
		this.index = index;
		this.altPath = altPath;
	}

	/**
	 * Returns alternative path.
	 */
	public Path getAltPath() {
		return altPath;
	}

	/**
	 * Push element to the path.
	 */
	public Path push(final CharSequence field) {
		_push(field);

		if (altPath != null) {
			altPath.push(field);
		}
		return this;
	}

	public Path push(final CharSequence field, final CharSequence altField) {
		_push(field);

		if (altPath != null) {
			altPath.push(altField);
		}
		return this;
	}

	private void _push(final CharSequence field) {
		if (index == paths.length) {	// ensure size
			CharSequence[] newPaths = new CharSequence[paths.length << 1];
			System.arraycopy(paths, 0, newPaths, 0, paths.length);
			paths = newPaths;
		}

		paths[index] = field;
		index++;
	}

	/**
	 * Pop last element from the path.
	 */
	public CharSequence pop(){
		if (altPath != null) {
			altPath.pop();
		}
		return paths[--index];
	}

	/**
	 * Returns path length.
	 */
	public int length() {
		return index;
	}

	/**
	 * Returns path chunk at given index.
	 */
	public CharSequence get(final int i) {
		if (i >= index) {
			throw new IndexOutOfBoundsException();
		}
		return paths[i];
	}

	@Override
	public String toString() {
		StringBuilder builder = new StringBuilder();

		builder.append('[');
		for (int i = 0; i < index; i++) {
			CharSequence current = paths[i];
			if (i > 0) {
				builder.append('.');
			}
			builder.append(current);
		}

		builder.append(']');
		return builder.toString();
	}

	@Override
	public boolean equals(final Object o) {
		if (this == o) {
			return true;
		}

		// correct, but aint gonna happen
//		if (o == null || getClass() != o.getClass()) {
//			return false;
//		}

		Path path1 = (Path) o;

		int length = path1.length();

		if (this.length() != length) {
			return false;
		}

		for (int i = 0; i < length; i++) {
			Object o1 = path1.paths[i];
			Object o2 = paths[i];

			if (!Objects.equals(o1, o2)) {
				return false;
			}
		}

		return true;
	}

	@Override
	public int hashCode() {
		int result = 1;

		for (int i = 0; i < index; i++) {
			CharSequence element = paths[i];
			result = 31 * result + (element == null ? 0 : element.hashCode());
		}

		return result;
	}

	@Override
	public Path clone() {
		CharSequence[] clonedPaths = new CharSequence[paths.length];

		System.arraycopy(paths, 0, clonedPaths, 0, paths.length);

		return new Path(clonedPaths, index, altPath != null ? altPath.clone() : null);
	}
}