/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.bcel.generic;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.apache.bcel.Const;
import org.apache.bcel.classfile.Constant;
import org.apache.bcel.classfile.ConstantCP;
import org.apache.bcel.classfile.ConstantClass;
import org.apache.bcel.classfile.ConstantDouble;
import org.apache.bcel.classfile.ConstantDynamic;
import org.apache.bcel.classfile.ConstantFieldref;
import org.apache.bcel.classfile.ConstantFloat;
import org.apache.bcel.classfile.ConstantInteger;
import org.apache.bcel.classfile.ConstantInterfaceMethodref;
import org.apache.bcel.classfile.ConstantInvokeDynamic;
import org.apache.bcel.classfile.ConstantLong;
import org.apache.bcel.classfile.ConstantMethodref;
import org.apache.bcel.classfile.ConstantNameAndType;
import org.apache.bcel.classfile.ConstantPool;
import org.apache.bcel.classfile.ConstantString;
import org.apache.bcel.classfile.ConstantUtf8;
import org.apache.bcel.classfile.Utility;

/**
 * This class is used to build up a constant pool. The user adds constants via 'addXXX' methods, 'addString', 'addClass', and so on. These methods return an
 * index into the constant pool. Finally, 'getFinalConstantPool()' returns the constant pool built up. Intermediate versions of the constant pool can be
 * obtained with 'getConstantPool()'. A constant pool has capacity for Constants.MAX_SHORT entries. Note that the first (0) is used by the JVM and that Double
 * and Long constants need two slots.
 *
 * @see Constant
 */
public class ConstantPoolGen {

    private static final int DEFAULT_BUFFER_SIZE = 256;

    private static final String METHODREF_DELIM = ":";

    private static final String IMETHODREF_DELIM = "#";

    private static final String FIELDREF_DELIM = "&";

    private static final String NAT_DELIM = "%"; // Name and Type

    /**
     * @deprecated (since 6.0) will be made private; do not access directly, use getter/setter
     */
    @Deprecated
    protected int size;

    /**
     * @deprecated (since 6.0) will be made private; do not access directly, use getter/setter
     */
    @Deprecated
    protected Constant[] constants;

    /**
     * @deprecated (since 6.0) will be made private; do not access directly, use getSize()
     */
    @Deprecated
    protected int index = 1; // First entry (0) used by JVM

    private final Map<String, Integer> stringTable = new HashMap<>();

    private final Map<String, Integer> classTable = new HashMap<>();

    private final Map<String, Integer> utf8Table = new HashMap<>();

    private final Map<String, Integer> natTable = new HashMap<>();

    private final Map<String, Integer> cpTable = new HashMap<>();

    /**
     * Constructs a new empty constant pool.
     */
    public ConstantPoolGen() {
        size = DEFAULT_BUFFER_SIZE;
        constants = new Constant[size];
    }

    /**
     * Constructs a new instance with the given array of constants.
     *
     * @param cs array of given constants, new ones will be appended.
     */
    public ConstantPoolGen(final Constant[] cs) {
        final StringBuilder sb = new StringBuilder(DEFAULT_BUFFER_SIZE);

        size = Math.min(Math.max(DEFAULT_BUFFER_SIZE, cs.length + 64), Const.MAX_CP_ENTRIES + 1);
        constants = Arrays.copyOf(cs, size);

        if (cs.length > 0) {
            index = cs.length;
        }

        for (int i = 1; i < index; i++) {
            final Constant c = constants[i];
            if (c instanceof ConstantString) {
                final ConstantString s = (ConstantString) c;
                final ConstantUtf8 u8 = (ConstantUtf8) constants[s.getStringIndex()];
                final String key = u8.getBytes();
                if (!stringTable.containsKey(key)) {
                    stringTable.put(key, Integer.valueOf(i));
                }
            } else if (c instanceof ConstantClass) {
                final ConstantClass s = (ConstantClass) c;
                final ConstantUtf8 u8 = (ConstantUtf8) constants[s.getNameIndex()];
                final String key = u8.getBytes();
                if (!classTable.containsKey(key)) {
                    classTable.put(key, Integer.valueOf(i));
                }
            } else if (c instanceof ConstantNameAndType) {
                final ConstantNameAndType n = (ConstantNameAndType) c;
                final ConstantUtf8 u8NameIdx = (ConstantUtf8) constants[n.getNameIndex()];
                final ConstantUtf8 u8SigIdx = (ConstantUtf8) constants[n.getSignatureIndex()];

                sb.append(u8NameIdx.getBytes());
                sb.append(NAT_DELIM);
                sb.append(u8SigIdx.getBytes());
                final String key = sb.toString();
                sb.delete(0, sb.length());

                if (!natTable.containsKey(key)) {
                    natTable.put(key, Integer.valueOf(i));
                }
            } else if (c instanceof ConstantUtf8) {
                final ConstantUtf8 u = (ConstantUtf8) c;
                final String key = u.getBytes();
                if (!utf8Table.containsKey(key)) {
                    utf8Table.put(key, Integer.valueOf(i));
                }
            } else if (c instanceof ConstantCP) {
                final ConstantCP m = (ConstantCP) c;
                final String className;
                ConstantUtf8 u8;

                if (c instanceof ConstantInvokeDynamic) {
                    className = Integer.toString(((ConstantInvokeDynamic) m).getBootstrapMethodAttrIndex());
                } else if (c instanceof ConstantDynamic) {
                    className = Integer.toString(((ConstantDynamic) m).getBootstrapMethodAttrIndex());
                } else {
                    final ConstantClass clazz = (ConstantClass) constants[m.getClassIndex()];
                    u8 = (ConstantUtf8) constants[clazz.getNameIndex()];
                    className = Utility.pathToPackage(u8.getBytes());
                }

                final ConstantNameAndType n = (ConstantNameAndType) constants[m.getNameAndTypeIndex()];
                u8 = (ConstantUtf8) constants[n.getNameIndex()];
                final String methodName = u8.getBytes();
                u8 = (ConstantUtf8) constants[n.getSignatureIndex()];
                final String signature = u8.getBytes();

                // Since name cannot begin with digit, we can use METHODREF_DELIM without fear of duplicates
                String delim = METHODREF_DELIM;
                if (c instanceof ConstantInterfaceMethodref) {
                    delim = IMETHODREF_DELIM;
                } else if (c instanceof ConstantFieldref) {
                    delim = FIELDREF_DELIM;
                }

                sb.append(className);
                sb.append(delim);
                sb.append(methodName);
                sb.append(delim);
                sb.append(signature);
                final String key = sb.toString();
                sb.delete(0, sb.length());

                if (!cpTable.containsKey(key)) {
                    cpTable.put(key, Integer.valueOf(i));
                }
            }
//            else if (c == null) { // entries may be null
//                // nothing to do
//            } else if (c instanceof ConstantInteger) {
//                // nothing to do
//            } else if (c instanceof ConstantLong) {
//                // nothing to do
//            } else if (c instanceof ConstantFloat) {
//                // nothing to do
//            } else if (c instanceof ConstantDouble) {
//                // nothing to do
//            } else if (c instanceof org.apache.bcel.classfile.ConstantMethodType) {
//                // TODO should this be handled somehow?
//            } else if (c instanceof org.apache.bcel.classfile.ConstantMethodHandle) {
//                // TODO should this be handled somehow?
//            } else if (c instanceof org.apache.bcel.classfile.ConstantModule) {
//                // TODO should this be handled somehow?
//            } else if (c instanceof org.apache.bcel.classfile.ConstantPackage) {
//                // TODO should this be handled somehow?
//            } else {
//                // Not helpful, should throw an exception.
//                assert false : "Unexpected constant type: " + c.getClass().getName();
//            }
        }
    }

    /**
     * Constructs a new instance with the given constant pool.
     *
     * @param cp the constant pool.
     */
    public ConstantPoolGen(final ConstantPool cp) {
        this(cp.getConstantPool());
    }

    /**
     * Add a reference to an array class (for example, String[][]) as needed by MULTIANEWARRAY instruction, for example, to the ConstantPool.
     *
     * @param type type of array class.
     * @return index of entry.
     */
    public int addArrayClass(final ArrayType type) {
        return addClass_(type.getSignature());
    }

    /**
     * Add a new Class reference to the ConstantPool for a given type.
     *
     * @param type Class to add.
     * @return index of entry.
     */
    public int addClass(final ObjectType type) {
        return addClass(type.getClassName());
    }

    /**
     * Add a new Class reference to the ConstantPool, if it is not already in there.
     *
     * @param str Class to add.
     * @return index of entry.
     */
    public int addClass(final String str) {
        return addClass_(Utility.packageToPath(str));
    }

    private int addClass_(final String clazz) {
        final int cpRet;
        if ((cpRet = lookupClass(clazz)) != -1) {
            return cpRet; // Already in CP
        }
        adjustSize();
        final ConstantClass c = new ConstantClass(addUtf8(clazz));
        final int ret = index;
        constants[index++] = c;
        return computeIfAbsent(classTable, clazz, ret);
    }

    /**
     * Adds a constant from another ConstantPool and returns the new index.
     *
     * @param constant The constant to add.
     * @param cpGen Source pool.
     * @return index of entry.
     */
    public int addConstant(final Constant constant, final ConstantPoolGen cpGen) {
        final Constant[] constants = cpGen.getConstantPool().getConstantPool();
        switch (constant.getTag()) {
        case Const.CONSTANT_String: {
            final ConstantString s = (ConstantString) constant;
            final ConstantUtf8 u8 = (ConstantUtf8) constants[s.getStringIndex()];
            return addString(u8.getBytes());
        }
        case Const.CONSTANT_Class: {
            final ConstantClass s = (ConstantClass) constant;
            final ConstantUtf8 u8 = (ConstantUtf8) constants[s.getNameIndex()];
            return addClass(u8.getBytes());
        }
        case Const.CONSTANT_NameAndType: {
            final ConstantNameAndType n = (ConstantNameAndType) constant;
            final ConstantUtf8 u8 = (ConstantUtf8) constants[n.getNameIndex()];
            final ConstantUtf8 u8_2 = (ConstantUtf8) constants[n.getSignatureIndex()];
            return addNameAndType(u8.getBytes(), u8_2.getBytes());
        }
        case Const.CONSTANT_Utf8:
            return addUtf8(((ConstantUtf8) constant).getBytes());
        case Const.CONSTANT_Double:
            return addDouble(((ConstantDouble) constant).getBytes());
        case Const.CONSTANT_Float:
            return addFloat(((ConstantFloat) constant).getBytes());
        case Const.CONSTANT_Long:
            return addLong(((ConstantLong) constant).getBytes());
        case Const.CONSTANT_Integer:
            return addInteger(((ConstantInteger) constant).getBytes());
        case Const.CONSTANT_InterfaceMethodref:
        case Const.CONSTANT_Methodref:
        case Const.CONSTANT_Fieldref: {
            final ConstantCP m = (ConstantCP) constant;
            final ConstantClass clazz = (ConstantClass) constants[m.getClassIndex()];
            final ConstantNameAndType n = (ConstantNameAndType) constants[m.getNameAndTypeIndex()];
            ConstantUtf8 u8 = (ConstantUtf8) constants[clazz.getNameIndex()];
            final String className = Utility.pathToPackage(u8.getBytes());
            u8 = (ConstantUtf8) constants[n.getNameIndex()];
            final String name = u8.getBytes();
            u8 = (ConstantUtf8) constants[n.getSignatureIndex()];
            final String signature = u8.getBytes();
            switch (constant.getTag()) {
            case Const.CONSTANT_InterfaceMethodref:
                return addInterfaceMethodref(className, name, signature);
            case Const.CONSTANT_Methodref:
                return addMethodref(className, name, signature);
            case Const.CONSTANT_Fieldref:
                return addFieldref(className, name, signature);
            default: // Never reached
                throw new IllegalArgumentException("Unknown constant type " + constant);
            }
        }
        default: // Never reached
            throw new IllegalArgumentException("Unknown constant type " + constant);
        }
    }

    /**
     * Add a new double constant to the ConstantPool, if it is not already in there.
     *
     * @param n Double number to add.
     * @return index of entry.
     */
    public int addDouble(final double n) {
        int ret;
        if ((ret = lookupDouble(n)) != -1) {
            return ret; // Already in CP
        }
        adjustSize();
        ret = index;
        constants[index] = new ConstantDouble(n);
        index += 2; // Wastes one entry according to spec
        return ret;
    }

    /**
     * Add a new Fieldref constant to the ConstantPool, if it is not already in there.
     *
     * @param className class name string to add.
     * @param fieldName field name string to add.
     * @param signature signature string to add.
     * @return index of entry.
     */
    public int addFieldref(final String className, final String fieldName, final String signature) {
        final int cpRet;
        if ((cpRet = lookupFieldref(className, fieldName, signature)) != -1) {
            return cpRet; // Already in CP
        }
        adjustSize();
        final int classIndex = addClass(className);
        final int nameAndTypeIndex = addNameAndType(fieldName, signature);
        final int ret = index;
        constants[index++] = new ConstantFieldref(classIndex, nameAndTypeIndex);
        return computeIfAbsent(cpTable, className + FIELDREF_DELIM + fieldName + FIELDREF_DELIM + signature, ret);
    }

    /**
     * Add a new Float constant to the ConstantPool, if it is not already in there.
     *
     * @param n Float number to add.
     * @return index of entry.
     */
    public int addFloat(final float n) {
        int ret;
        if ((ret = lookupFloat(n)) != -1) {
            return ret; // Already in CP
        }
        adjustSize();
        ret = index;
        constants[index++] = new ConstantFloat(n);
        return ret;
    }

    /**
     * Add a new Integer constant to the ConstantPool, if it is not already in there.
     *
     * @param n integer number to add.
     * @return index of entry.
     */
    public int addInteger(final int n) {
        int ret;
        if ((ret = lookupInteger(n)) != -1) {
            return ret; // Already in CP
        }
        adjustSize();
        ret = index;
        constants[index++] = new ConstantInteger(n);
        return ret;
    }

    /**
     * Adds a new InterfaceMethodref constant to the ConstantPool.
     *
     * @param method the method to add.
     * @return index of entry.
     */
    public int addInterfaceMethodref(final MethodGen method) {
        return addInterfaceMethodref(method.getClassName(), method.getName(), method.getSignature());
    }

    /**
     * Add a new InterfaceMethodref constant to the ConstantPool, if it is not already in there.
     *
     * @param className class name string to add.
     * @param methodName method name string to add.
     * @param signature signature string to add.
     * @return index of entry.
     */
    public int addInterfaceMethodref(final String className, final String methodName, final String signature) {
        final int cpRet;
        if ((cpRet = lookupInterfaceMethodref(className, methodName, signature)) != -1) {
            return cpRet; // Already in CP
        }
        adjustSize();
        final int classIndex = addClass(className);
        final int nameAndTypeIndex = addNameAndType(methodName, signature);
        final int ret = index;
        constants[index++] = new ConstantInterfaceMethodref(classIndex, nameAndTypeIndex);
        return computeIfAbsent(cpTable, className + IMETHODREF_DELIM + methodName + IMETHODREF_DELIM + signature, ret);
    }

    /**
     * Add a new long constant to the ConstantPool, if it is not already in there.
     *
     * @param n Long number to add.
     * @return index of entry.
     */
    public int addLong(final long n) {
        int ret;
        if ((ret = lookupLong(n)) != -1) {
            return ret; // Already in CP
        }
        adjustSize();
        ret = index;
        constants[index] = new ConstantLong(n);
        index += 2; // Wastes one entry according to spec
        return ret;
    }

    /**
     * Adds a new Methodref constant to the ConstantPool.
     *
     * @param method the method to add.
     * @return index of entry.
     */
    public int addMethodref(final MethodGen method) {
        return addMethodref(method.getClassName(), method.getName(), method.getSignature());
    }

    /**
     * Add a new Methodref constant to the ConstantPool, if it is not already in there.
     *
     * @param className class name string to add.
     * @param methodName method name string to add.
     * @param signature method signature string to add.
     * @return index of entry.
     */
    public int addMethodref(final String className, final String methodName, final String signature) {
        final int cpRet;
        if ((cpRet = lookupMethodref(className, methodName, signature)) != -1) {
            return cpRet; // Already in CP
        }
        adjustSize();
        final int nameAndTypeIndex = addNameAndType(methodName, signature);
        final int classIndex = addClass(className);
        final int ret = index;
        constants[index++] = new ConstantMethodref(classIndex, nameAndTypeIndex);
        return computeIfAbsent(cpTable, className + METHODREF_DELIM + methodName + METHODREF_DELIM + signature, ret);
    }

    /**
     * Add a new NameAndType constant to the ConstantPool if it is not already in there.
     *
     * @param name Name string to add.
     * @param signature signature string to add.
     * @return index of entry.
     */
    public int addNameAndType(final String name, final String signature) {
        int ret;
        if ((ret = lookupNameAndType(name, signature)) != -1) {
            return ret; // Already in CP
        }
        adjustSize();
        final int nameIndex = addUtf8(name);
        final int signatureIndex = addUtf8(signature);
        ret = index;
        constants[index++] = new ConstantNameAndType(nameIndex, signatureIndex);
        return computeIfAbsent(natTable, name + NAT_DELIM + signature, ret);
    }

    /**
     * Add a new String constant to the ConstantPool, if it is not already in there.
     *
     * @param str String to add.
     * @return index of entry.
     */
    public int addString(final String str) {
        int ret;
        if ((ret = lookupString(str)) != -1) {
            return ret; // Already in CP
        }
        final int utf8 = addUtf8(str);
        adjustSize();
        final ConstantString s = new ConstantString(utf8);
        ret = index;
        constants[index++] = s;
        return computeIfAbsent(stringTable, str, ret);
    }

    /**
     * Add a new Utf8 constant to the ConstantPool, if it is not already in there.
     *
     * @param n Utf8 string to add.
     * @return index of entry.
     */
    public int addUtf8(final String n) {
        int ret;
        if ((ret = lookupUtf8(n)) != -1) {
            return ret; // Already in CP
        }
        adjustSize();
        ret = index;
        constants[index++] = new ConstantUtf8(n);
        return computeIfAbsent(utf8Table, n, ret);
    }

    /**
     * Resize internal array of constants.
     */
    protected void adjustSize() {
        // 3 extra spaces are needed as some entries may take 3 slots
        if (index + 3 >= Const.MAX_CP_ENTRIES + 1) {
            throw new IllegalStateException("The number of constants " + (index + 3)
                    + " is over the size of the constant pool: "
                    + Const.MAX_CP_ENTRIES);
        }

        if (index + 3 >= size) {
            final Constant[] tmp = constants;
            size *= 2;
            // the constant array shall not exceed the size of the constant pool
            size = Math.min(size, Const.MAX_CP_ENTRIES + 1);
            constants = new Constant[size];
            System.arraycopy(tmp, 0, constants, 0, index);
        }
    }

    private int computeIfAbsent(final Map<String, Integer> map, final String key, final int value) {
        return map.computeIfAbsent(key, k -> Integer.valueOf(value));
    }

    /**
     * Gets a constant pool entry at the specified index.
     *
     * @param i index in constant pool.
     * @return constant pool entry at index i.
     */
    public Constant getConstant(final int i) {
        return constants[i];
    }

    /**
     * Gets the intermediate constant pool.
     *
     * @return intermediate constant pool.
     */
    public ConstantPool getConstantPool() {
        return new ConstantPool(constants);
    }

    /**
     * Gets the constant pool with proper length.
     *
     * @return constant pool with proper length.
     */
    public ConstantPool getFinalConstantPool() {
        return new ConstantPool(Arrays.copyOf(constants, index));
    }

    private int getIndex(final Map<String, Integer> map, final String key) {
        return toIndex(map.get(key));
    }

    /**
     * Gets the current size of constant pool.
     *
     * @return current size of constant pool.
     */
    public int getSize() {
        return index;
    }

    /**
     * Look for ConstantClass in ConstantPool named 'str'.
     *
     * @param str String to search for.
     * @return index on success, -1 otherwise.
     */
    public int lookupClass(final String str) {
        return getIndex(classTable, Utility.packageToPath(str));
    }

    /**
     * Look for ConstantDouble in ConstantPool.
     *
     * @param n Double number to look for.
     * @return index on success, -1 otherwise.
     */
    public int lookupDouble(final double n) {
        final long bits = Double.doubleToLongBits(n);
        for (int i = 1; i < index; i++) {
            if (constants[i] instanceof ConstantDouble) {
                final ConstantDouble c = (ConstantDouble) constants[i];
                if (Double.doubleToLongBits(c.getBytes()) == bits) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * Look for ConstantFieldref in ConstantPool.
     *
     * @param className Where to find method.
     * @param fieldName Guess what.
     * @param signature return and argument types.
     * @return index on success, -1 otherwise.
     */
    public int lookupFieldref(final String className, final String fieldName, final String signature) {
        return getIndex(cpTable, className + FIELDREF_DELIM + fieldName + FIELDREF_DELIM + signature);
    }

    /**
     * Look for ConstantFloat in ConstantPool.
     *
     * @param n Float number to look for.
     * @return index on success, -1 otherwise.
     */
    public int lookupFloat(final float n) {
        final int bits = Float.floatToIntBits(n);
        for (int i = 1; i < index; i++) {
            if (constants[i] instanceof ConstantFloat) {
                final ConstantFloat c = (ConstantFloat) constants[i];
                if (Float.floatToIntBits(c.getBytes()) == bits) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * Look for ConstantInteger in ConstantPool.
     *
     * @param n integer number to look for.
     * @return index on success, -1 otherwise.
     */
    public int lookupInteger(final int n) {
        for (int i = 1; i < index; i++) {
            if (constants[i] instanceof ConstantInteger) {
                final ConstantInteger c = (ConstantInteger) constants[i];
                if (c.getBytes() == n) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * Looks up an InterfaceMethodref in the ConstantPool.
     *
     * @param method the method to look for.
     * @return index on success, -1 otherwise.
     */
    public int lookupInterfaceMethodref(final MethodGen method) {
        return lookupInterfaceMethodref(method.getClassName(), method.getName(), method.getSignature());
    }

    /**
     * Look for ConstantInterfaceMethodref in ConstantPool.
     *
     * @param className Where to find method.
     * @param methodName Guess what.
     * @param signature return and argument types.
     * @return index on success, -1 otherwise.
     */
    public int lookupInterfaceMethodref(final String className, final String methodName, final String signature) {
        return getIndex(cpTable, className + IMETHODREF_DELIM + methodName + IMETHODREF_DELIM + signature);
    }

    /**
     * Look for ConstantLong in ConstantPool.
     *
     * @param n Long number to look for.
     * @return index on success, -1 otherwise.
     */
    public int lookupLong(final long n) {
        for (int i = 1; i < index; i++) {
            if (constants[i] instanceof ConstantLong) {
                final ConstantLong c = (ConstantLong) constants[i];
                if (c.getBytes() == n) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * Looks up a Methodref in the ConstantPool.
     *
     * @param method the method to look for.
     * @return index on success, -1 otherwise.
     */
    public int lookupMethodref(final MethodGen method) {
        return lookupMethodref(method.getClassName(), method.getName(), method.getSignature());
    }

    /**
     * Look for ConstantMethodref in ConstantPool.
     *
     * @param className Where to find method.
     * @param methodName Guess what.
     * @param signature return and argument types.
     * @return index on success, -1 otherwise.
     */
    public int lookupMethodref(final String className, final String methodName, final String signature) {
        return getIndex(cpTable, className + METHODREF_DELIM + methodName + METHODREF_DELIM + signature);
    }

    /**
     * Look for ConstantNameAndType in ConstantPool.
     *
     * @param name of variable/method.
     * @param signature of variable/method.
     * @return index on success, -1 otherwise.
     */
    public int lookupNameAndType(final String name, final String signature) {
        return getIndex(natTable, name + NAT_DELIM + signature);
    }

    /**
     * Look for ConstantString in ConstantPool containing String 'str'.
     *
     * @param str String to search for.
     * @return index on success, -1 otherwise.
     */
    public int lookupString(final String str) {
        return getIndex(stringTable, str);
    }

    /**
     * Look for ConstantUtf8 in ConstantPool.
     *
     * @param n Utf8 string to look for.
     * @return index on success, -1 otherwise.
     */
    public int lookupUtf8(final String n) {
        return getIndex(utf8Table, n);
    }

    /**
     * Use with care!
     *
     * @param i index in constant pool.
     * @param c new constant pool entry at index i.
     */
    public void setConstant(final int i, final Constant c) {
        constants[i] = c;
    }

    private int toIndex(final Integer index) {
        return index != null ? index.intValue() : -1;
    }

    /**
     * @return String representation.
     */
    @Override
    public String toString() {
        final StringBuilder buf = new StringBuilder();
        for (int i = 1; i < index; i++) {
            buf.append(i).append(")").append(constants[i]).append("\n");
        }
        return buf.toString();
    }
}
