/*
 * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
 * 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.
 */

package sun.tools.java;

import java.io.IOException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.util.Vector;
import java.util.Hashtable;

/**
 * This class is used to represent a constant table once
 * it is read from a class file.
 *
 * WARNING: The contents of this source file are not part of any
 * supported API.  Code that depends on them does so at its own risk:
 * they are subject to change or removal without notice.
 */
public final
class BinaryConstantPool implements Constants {
    private byte types[];
    private Object cpool[];

    /**
     * Constructor
     */
    BinaryConstantPool(DataInputStream in) throws IOException {
        // JVM 4.1 ClassFile.constant_pool_count
        types = new byte[in.readUnsignedShort()];
        cpool = new Object[types.length];
        for (int i = 1 ; i < cpool.length ; i++) {
            int j = i;
            // JVM 4.4 cp_info.tag
            switch(types[i] = in.readByte()) {
              case CONSTANT_UTF8:
                cpool[i] = in.readUTF();
                break;

              case CONSTANT_INTEGER:
                cpool[i] = in.readInt();
                break;
              case CONSTANT_FLOAT:
                cpool[i] = new Float(in.readFloat());
                break;
              case CONSTANT_LONG:
                cpool[i++] = in.readLong();
                break;
              case CONSTANT_DOUBLE:
                cpool[i++] = new Double(in.readDouble());
                break;

              case CONSTANT_CLASS:
              case CONSTANT_STRING:
                // JVM 4.4.3 CONSTANT_String_info.string_index
                // or JVM 4.4.1 CONSTANT_Class_info.name_index
                cpool[i] =in.readUnsignedShort();
                break;

              case CONSTANT_FIELD:
              case CONSTANT_METHOD:
              case CONSTANT_INTERFACEMETHOD:
              case CONSTANT_NAMEANDTYPE:
                // JVM 4.4.2 CONSTANT_*ref_info.class_index & name_and_type_index
                cpool[i] = (in.readUnsignedShort() << 16) | in.readUnsignedShort();
                break;

              case CONSTANT_METHODHANDLE:
                cpool[i] = readBytes(in, 3);
                break;
              case CONSTANT_METHODTYPE:
                cpool[i] = readBytes(in, 2);
                break;
              case CONSTANT_INVOKEDYNAMIC:
                cpool[i] = readBytes(in, 4);
                break;

              case 0:
              default:
                throw new ClassFormatError("invalid constant type: " + (int)types[i]);
            }
        }
    }

    private byte[] readBytes(DataInputStream in, int cnt) throws IOException {
        byte[] b = new byte[cnt];
        in.readFully(b);
        return b;
    }

    /**
     * get a integer
     */
    public int getInteger(int n) {
        return (n == 0) ? 0 : ((Number)cpool[n]).intValue();
    }

    /**
     * get a value
     */
    public Object getValue(int n) {
        return (n == 0) ? null : cpool[n];
    }

    /**
     * get a string
     */
    public String getString(int n) {
        return (n == 0) ? null : (String)cpool[n];
    }

    /**
     * get an identifier
     */
    public Identifier getIdentifier(int n) {
        return (n == 0) ? null : Identifier.lookup(getString(n));
    }

    /**
     * get class declaration
     */
    public ClassDeclaration getDeclarationFromName(Environment env, int n) {
        return (n == 0) ? null : env.getClassDeclaration(Identifier.lookup(getString(n).replace('/','.')));
    }

    /**
     * get class declaration
     */
    public ClassDeclaration getDeclaration(Environment env, int n) {
        return (n == 0) ? null : getDeclarationFromName(env, getInteger(n));
    }

    /**
     * get a type from a type signature
     */
    public Type getType(int n) {
        return Type.tType(getString(n));
    }

    /**
     * get the type of constant given an index
     */
    public int getConstantType(int n) {
        return types[n];
    }

    /**
     * get the n-th constant from the constant pool
     */
    public Object getConstant(int n, Environment env) {
        int constant_type = getConstantType(n);
        switch (constant_type) {
            case CONSTANT_INTEGER:
            case CONSTANT_FLOAT:
            case CONSTANT_LONG:
            case CONSTANT_DOUBLE:
            case CONSTANT_METHODHANDLE:
            case CONSTANT_METHODTYPE:
            case CONSTANT_INVOKEDYNAMIC:
                return getValue(n);

            case CONSTANT_CLASS:
                return getDeclaration(env, n);

            case CONSTANT_STRING:
                return getString(getInteger(n));

            case CONSTANT_FIELD:
            case CONSTANT_METHOD:
            case CONSTANT_INTERFACEMETHOD:
                try {
                    int key = getInteger(n);
                    ClassDefinition clazz =
                        getDeclaration(env, key >> 16).getClassDefinition(env);
                    int name_and_type = getInteger(key & 0xFFFF);
                    Identifier id = getIdentifier(name_and_type >> 16);
                    Type type = getType(name_and_type & 0xFFFF);

                    for (MemberDefinition field = clazz.getFirstMatch(id);
                         field != null;
                         field = field.getNextMatch()) {
                        Type field_type = field.getType();
                        if ((constant_type == CONSTANT_FIELD)
                            ? (field_type == type)
                            : (field_type.equalArguments(type)))
                            return field;
                    }
                } catch (ClassNotFound e) {
                }
                return null;

            default:
                throw new ClassFormatError("invalid constant type: " +
                                              constant_type);
        }
    }


    /**
     * Get a list of dependencies, ie: all the classes referenced in this
     * constant pool.
     */
    public Vector<ClassDeclaration> getDependencies(Environment env) {
        Vector<ClassDeclaration> v = new Vector<>();
        for (int i = 1 ; i < cpool.length ; i++) {
            switch(types[i]) {
              case CONSTANT_CLASS:
                v.addElement(getDeclarationFromName(env, getInteger(i)));
                break;
            }
        }
        return v;
    }

    Hashtable<Object, Integer> indexHashObject;
    Hashtable<Object, Integer> indexHashAscii;
    Vector<String> MoreStuff;

    /**
     * Find the index of an Object in the constant pool
     */
    public int indexObject(Object obj, Environment env) {
        if (indexHashObject == null)
            createIndexHash(env);
        Integer result = indexHashObject.get(obj);
        if (result == null)
            throw new IndexOutOfBoundsException("Cannot find object " + obj + " of type " +
                                obj.getClass() + " in constant pool");
        return result.intValue();
    }

    /**
     * Find the index of an ascii string in the constant pool.  If it's not in
     * the constant pool, then add it at the end.
     */
    public int indexString(String string, Environment env) {
        if (indexHashObject == null)
            createIndexHash(env);
        Integer result = indexHashAscii.get(string);
        if (result == null) {
            if (MoreStuff == null) MoreStuff = new Vector<>();
            result = cpool.length + MoreStuff.size();
            MoreStuff.addElement(string);
            indexHashAscii.put(string, result);
        }
        return result.intValue();
    }

    /**
     * Create a hash table of all the items in the constant pool that could
     * possibly be referenced from the outside.
     */

    public void createIndexHash(Environment env) {
        indexHashObject = new Hashtable<>();
        indexHashAscii = new Hashtable<>();
        for (int i = 1; i < cpool.length; i++) {
            if (types[i] == CONSTANT_UTF8) {
                indexHashAscii.put(cpool[i], i);
            } else {
                try {
                    indexHashObject.put(getConstant(i, env), i);
                } catch (ClassFormatError e) { }
            }
        }
    }


    /**
     * Write out the contents of the constant pool, including any additions
     * that have been added.
     */
    public void write(DataOutputStream out, Environment env) throws IOException {
        int length = cpool.length;
        if (MoreStuff != null)
            length += MoreStuff.size();
        out.writeShort(length);
        for (int i = 1 ; i < cpool.length; i++) {
            int type = types[i];
            Object x = cpool[i];
            out.writeByte(type);
            switch (type) {
                case CONSTANT_UTF8:
                    out.writeUTF((String) x);
                    break;
                case CONSTANT_INTEGER:
                    out.writeInt(((Number)x).intValue());
                    break;
                case CONSTANT_FLOAT:
                    out.writeFloat(((Number)x).floatValue());
                    break;
                case CONSTANT_LONG:
                    out.writeLong(((Number)x).longValue());
                    i++;
                    break;
                case CONSTANT_DOUBLE:
                    out.writeDouble(((Number)x).doubleValue());
                    i++;
                    break;
                case CONSTANT_CLASS:
                case CONSTANT_STRING:
                    out.writeShort(((Number)x).intValue());
                    break;
                case CONSTANT_FIELD:
                case CONSTANT_METHOD:
                case CONSTANT_INTERFACEMETHOD:
                case CONSTANT_NAMEANDTYPE: {
                    int value = ((Number)x).intValue();
                    out.writeShort(value >> 16);
                    out.writeShort(value & 0xFFFF);
                    break;
                }
                case CONSTANT_METHODHANDLE:
                case CONSTANT_METHODTYPE:
                case CONSTANT_INVOKEDYNAMIC:
                    out.write((byte[])x, 0, ((byte[])x).length);
                    break;
                default:
                     throw new ClassFormatError("invalid constant type: "
                                                   + (int)types[i]);
            }
        }
        for (int i = cpool.length; i < length; i++) {
            String string = MoreStuff.elementAt(i - cpool.length);
            out.writeByte(CONSTANT_UTF8);
            out.writeUTF(string);
        }
    }

}
