/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * This file is available under and governed by the GNU General Public
 * License version 2 only, as published by the Free Software Foundation.
 * However, the following notice accompanied the original version of this
 * file:
 *
 * 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 jdk.internal.org.objectweb.asm;

/**
 * The constant pool entries, the BootstrapMethods attribute entries and the (ASM specific) type
 * table entries of a class.
 *
 * @author Eric Bruneton
 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.4">JVMS
 *     4.4</a>
 * @see <a href="https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.7.23">JVMS
 *     4.7.23</a>
 */
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 <i>not</i> 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 <i>not</i> 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.classFileBuffer;
        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.classFileBuffer;
        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 <i>not</i> 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 <i>not</i> check
      * whether {@link #entries} already contains a similar entry or not, and does <i>not</i> 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.
        int numBootstrapArguments = bootstrapMethodArguments.length;
        int[] bootstrapMethodArgumentIndexes = new int[numBootstrapArguments];
        for (int i = 0; i < numBootstrapArguments; i++) {
            bootstrapMethodArgumentIndexes[i] = addConstant(bootstrapMethodArguments[i]).index;
        }

        // 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);

        bootstrapMethodsAttribute.putShort(numBootstrapArguments);
        for (int i = 0; i < numBootstrapArguments; i++) {
            bootstrapMethodsAttribute.putShort(bootstrapMethodArgumentIndexes[i]);
        }

        // 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) {
        long data =
                typeTableIndex1 < typeTableIndex2
                        ? typeTableIndex1 | (((long) typeTableIndex2) << 32)
                        : typeTableIndex2 | (((long) typeTableIndex1) << 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;
        }
    }
}

