SquirrelJME/SquirrelJME

View on GitHub
modules/tool-classfile/src/main/java/net/multiphasicapps/classfile/Pool.java

Summary

Maintainability
A
1 hr
Test Coverage
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
//     Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------

package net.multiphasicapps.classfile;

import cc.squirreljme.runtime.cldc.debug.Debugging;
import cc.squirreljme.runtime.cldc.io.MarkableInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.UTFDataFormatException;
import java.util.Arrays;

/**
 * This class decodes the constant pool and provides generic access to the
 * contents of it.
 *
 * @since 2017/06/08
 */
public final class Pool
{
    /** The UTF constant tag. */
    public static final int TAG_UTF8 =
        1;
    
    /** Integer constant. */
    public static final int TAG_INTEGER =
        3;
    
    /** Float constant. */
    public static final int TAG_FLOAT =
        4;
    
    /** Long constant. */
    public static final int TAG_LONG =
        5;
    
    /** Double constant. */
    public static final int TAG_DOUBLE =
        6;
    
    /** Reference to another class. */
    public static final int TAG_CLASS =
        7;
    
    /** String constant. */
    public static final int TAG_STRING =
        8;
    
    /** Field reference. */
    public static final int TAG_FIELDREF =
        9;
    
    /** Method reference. */
    public static final int TAG_METHODREF =
        10;
    
    /** Interface method reference. */
    public static final int TAG_INTERFACEMETHODREF =
        11;
    
    /** Name and type. */
    public static final int TAG_NAMEANDTYPE =
        12;
    
    /** Method handle (illegal). */
    public static final int TAG_METHODHANDLE =
        15;
    
    /** Method type (illegal). */
    public static final int TAG_METHODTYPE =
        16;
    
    /** Invoke dynamic call site (illegal). */
    public static final int TAG_INVOKEDYNAMIC =
        18;
    
    /** The top of a long/double. */
    public static final int TAG_WIDETOP =
        -1;
    
    /** Entries within the constant pool. */
    private final Object[] _entries;
    
    /** The constant pool tags. */
    private final int[] _tags;
    
    /** Raw bytes for the constant pool. */
    private final byte[] _rawBytes;
    
    /**
     * Parses and initializes the constant pool structures.
     *
     * @param __rawBytes Raw constant pool bytes.
     * @param __tags The pool tags.
     * @param __e The entries which make up the pool, this is used directly.
     * @since 2017/06/08
     */
    Pool(byte[] __rawBytes, int[] __tags, Object... __e)
    {
        this._rawBytes = __rawBytes;
        this._tags = __tags;
        this._entries = (__e == null ? new Object[0] : __e);
    }
    
    /**
     * Obtains the entry at the specified index.
     *
     * @param <C> The type of class to get.
     * @param __cl The type of class to get.
     * @param __i The index of the entry to get.
     * @return The entry at the specified position as the given class or
     * {@code null} if it does not exist.
     * @throws InvalidClassFormatException If the class type does not match or
     * the pool index is out of range.
     * @throws NullPointerException On null arguments.
     * @since 2017/06/08
     */
    public <C> C get(Class<C> __cl, int __i)
        throws InvalidClassFormatException, NullPointerException
    {
        // Check
        if (__cl == null)
            throw new NullPointerException("NARG");
        
        // Short circuit, the zero entry is always nothing
        if (__i == 0)
            return null;
        
        /* {@squirreljme.error JC3o The specified index is not within the bounds
        of the constant pool. (The index of the entry)} */
        Object[] entries = this._entries;
        if (__i < 0 || __i >= entries.length)
            throw new InvalidClassFormatException(
                String.format("JC3o %d", __i));
        
        /* {@squirreljme.error JC3p The specified entry's class is not of the
        expected class. (The index of the entry; The class the entry is; The
        expected class)} */
        Object val = entries[__i];
        if (val != null && !__cl.isInstance(val))
            throw new InvalidClassFormatException(
                String.format("JC3p %d %s %s", __i, val.getClass(), __cl));
        
        return __cl.cast(val);
    }
    
    /**
     * Returns the raw constant pool bytes.
     *
     * @return The raw bytes for the constant pool.
     * @since 2024/01/20
     */
    public byte[] rawData()
    {
        return this._rawBytes;
    }
    
    /**
     * This is similar to {@link #get(Class, int)} except that it is not valid
     * if the entry is the {@code null} entry (the first one).
     *
     * @param <C> The type of class to get.
     * @param __cl The type of class to get.
     * @param __i The index of the entry to get.
     * @return The entry at the specified position as the given class.
     * @throws InvalidClassFormatException If the class type does not match,
     * the pool index is out of range, or the entry is not valid.
     * @throws NullPointerException On null arguments.
     * @since 2017/06/14
     */
    public <C> C require(Class<C> __cl, int __i)
        throws InvalidClassFormatException, NullPointerException
    {
        /* {@squirreljme.error JC3q Expected the specified constant pool entry
        to not be the null entry. (The index; The expected class)} */
        C rv = this.<C>get(__cl, __i);
        if (rv == null)
            throw new InvalidClassFormatException(
                String.format("JC3q %d %s", __i, __cl));
        return rv;
    }
    
    /**
     * Returns the size of the constant pool.
     *
     * @return The constant pool size.
     * @since 2023/08/09
     */
    public int size()
    {
        return this._entries.length;
    }
    
    /**
     * Returns the constant pool tags.
     *
     * @return The tags used for the constant pool.
     * @since 2023/08/09
     */
    public int[] tags()
    {
        return this._tags.clone();
    }
    
    /**
     * Decodes the constant pool.
     *
     * @param __in The input stream.
     * @return The read constant pool.
     * @throws InvalidClassFormatException If the constant pool is not valid.
     * @throws IOException On read errors.
     * @throws NullPointerException On null arguments.
     * @since 2017/09/27
     */
    public static Pool decode(DataInputStream __in)
        throws InvalidClassFormatException, IOException, NullPointerException
    {
        return Pool.decode(__in, -1);
    }
    
    /**
     * Decodes the constant pool.
     *
     * @param __in The input stream.
     * @param __count The pool count, may be negative if it should be read.
     * @return The read constant pool.
     * @throws InvalidClassFormatException If the constant pool is not valid.
     * @throws IOException On read errors.
     * @throws NullPointerException On null arguments.
     * @since 2017/09/27
     */
    public static Pool decode(DataInputStream __in, int __count)
        throws InvalidClassFormatException, IOException, NullPointerException
    {
        // Check
        if (__in == null)
            throw new NullPointerException("NARG");
        
        // Raw byte output
        ByteArrayOutputStream rawBytes = new ByteArrayOutputStream();
        DataOutputStream raw = new DataOutputStream(rawBytes);
        
        // Does the count need to be read?
        int count;
        if (__count < 0)
            count = __in.readUnsignedShort();
        else
            count = __count;
        
        // Read the raw constant pool contents first
        int[] tags = new int[count];
        Object[] rawdata = new Object[count];
        for (int i = 1; i < count; i++)
        {
            // Read tag
            int tag = __in.readUnsignedByte();
            tags[i] = tag;
            
            // Send to raw
            raw.writeByte(tag);
            
            // Parse tag data
            Object data;
            switch (tag)
            {
                    // UTF-8 String
                    // The string is wrapped in a wrapper class so that String
                    // constants are actual String references. It is illegal for
                    // UTF-8 constants to be directly used by the byte code so
                    // this prevents their usage from occuring by causing a
                    // class cast exception
                case Pool.TAG_UTF8:
                    try
                    {
                        data = new UTFConstantEntry(__in.readUTF());
                        
                        raw.writeUTF(data.toString());
                    }
                    
                    /* {@squirreljme.error JC3r Modified UTF-8 data is not in
                    the correct format.} */
                    catch (UTFDataFormatException e)
                    {
                        throw new InvalidClassFormatException("JC3r", e);
                    }
                    break;
                    
                    // Reference to two entries
                case Pool.TAG_FIELDREF:
                case Pool.TAG_METHODREF:
                case Pool.TAG_INTERFACEMETHODREF:
                case Pool.TAG_NAMEANDTYPE:
                    data = new int[]{__in.readUnsignedShort(),
                        __in.readUnsignedShort()};
                    
                    raw.writeShort(((int[])data)[0]);
                    raw.writeShort(((int[])data)[1]);
                    break;
                    
                    // References to single entry
                case Pool.TAG_CLASS:
                case Pool.TAG_STRING:
                    data = new int[]{__in.readUnsignedShort()};
                    
                    raw.writeShort(((int[])data)[0]);
                    break;
                    
                    // Integer
                case Pool.TAG_INTEGER:
                    data = new ConstantValueInteger(
                        Integer.valueOf(__in.readInt()));
                    
                    raw.writeInt(((ConstantValueInteger)data).intValue());
                    break;
                    
                    // Long
                case Pool.TAG_LONG:
                    data = new ConstantValueLong(
                        Long.valueOf(__in.readLong()));
                    
                    raw.writeLong(((ConstantValueLong)data).longValue());
                    break;
                    
                    // Float
                case Pool.TAG_FLOAT:
                    data = new ConstantValueFloat(
                        Float.valueOf(__in.readFloat()));
                    
                    raw.writeFloat(((ConstantValueFloat)data).floatValue());
                    break;
                    
                    // Double
                case Pool.TAG_DOUBLE:
                    data = new ConstantValueDouble(
                        Double.valueOf(__in.readDouble()));
                    
                    raw.writeDouble(((ConstantValueDouble)data).doubleValue());
                    break;
                    
                    // Invoke dynamic method handle
                case Pool.TAG_METHODHANDLE:
                    __in.readByte();
                    __in.readShort();
                    
                    data = new UnsupportedInvokeDynamic();
                    break;
                    
                    // Invoke dynamic method type
                case Pool.TAG_METHODTYPE:
                    __in.readShort();
                    
                    data = new UnsupportedInvokeDynamic();
                    break;
                    
                    // Invoke dynamic
                case Pool.TAG_INVOKEDYNAMIC:
                    __in.readShort();
                    __in.readShort();
                    
                    data = new UnsupportedInvokeDynamic();
                    break;
                
                    /* {@squirreljme.error JC3t Unknown tag type in the
                    constant pool. (The constant pool tag)} */
                default:
                    throw new InvalidClassFormatException(
                        String.format("JC3t %d", tag));
            }
            rawdata[i] = data;
            
            // Skip long/double?
            if (tag == Pool.TAG_LONG || tag == Pool.TAG_DOUBLE)
            {
                rawdata[++i] = new WideConstantTopEntry();
                tags[i] = Pool.TAG_WIDETOP;
            }
        }
        
        // Go through tags again and initialize their raw data into type-safe
        // classes 
        Object[] entries = new Object[count];
        try
        {
            Pool.__initializeEntries(entries, tags, rawdata);
        }
        
        /* {@squirreljme.error JC3u The constant pool is not valid.} */
        catch (ClassCastException|IndexOutOfBoundsException|
            NullPointerException e)
        {
            throw new InvalidClassFormatException("JC3u", e);
        }
        
        // Setup
        return new Pool(rawBytes.toByteArray(), tags, entries);
    }
    
    /**
     * This initializes the entries in the constant pool.
     *
     * @param __entries Output pool entries.
     * @param __tags Constant pool tags for the entries.
     * @param __rawdata The raw pool data.
     * @throws InvalidClassFormatException If the entries are not valid.
     * @throws NullPointerException On null arguments.
     * @since 2017/06/12
     */
    private static void __initializeEntries(Object[] __entries, int[] __tags,
        Object[] __rawdata)
        throws InvalidClassFormatException, NullPointerException
    {
        // Check
        if (__entries == null || __tags == null || __rawdata == null)
            throw new NullPointerException("NARG");
        
        // Instead of having a nested loop which goes through every entry
        // multiple times, the sequence entries should be parsed in is very
        // known with a direct line of dependencies. Entries with lower
        // sequences are only depended on. This saves an extra loop and makes
        // the code much cleaner for the most part.
        int count = __entries.length;
        int[] order = new int[count];
        for (int i = 0; i < count; i++)
        {
            int tag = __tags[i];
            int sequence;
            
            // Determine the sequence based on the tag
            switch (tag)
            {
                case 0:
                case Pool.TAG_UTF8:
                case Pool.TAG_INTEGER:
                case Pool.TAG_FLOAT:
                case Pool.TAG_LONG:
                case Pool.TAG_DOUBLE:
                case Pool.TAG_WIDETOP:
                    sequence = 0;
                    break;
                
                case Pool.TAG_CLASS:
                case Pool.TAG_STRING:
                    sequence = 1;
                    break;
                
                case Pool.TAG_NAMEANDTYPE:
                    sequence = 2;
                    break;
                
                case Pool.TAG_FIELDREF:
                case Pool.TAG_METHODREF:
                case Pool.TAG_INTERFACEMETHODREF:
                    sequence = 3;
                    break;
                    
                    // Invoke dynamics which are ignored
                case Pool.TAG_METHODHANDLE:
                case Pool.TAG_METHODTYPE:
                case Pool.TAG_INVOKEDYNAMIC:
                    sequence = 3;
                    break;
                    
                default:
                    throw Debugging.oops();
            }
            
            // Add to order
            order[i] = (sequence << 16) | i;
        }
        Arrays.sort(order);
        
        // Go through and process all of the entries now that their major
        // sequence order is known.
        for (int j = 0; j < count; j++)
        {
            int i = order[j] & 0xFFFF;
            int tag = __tags[i];
            
            // Process tags
            Object in = __rawdata[i];
            Object out;
            switch (tag)
            {
                    // These are copied directly
                case 0:
                case Pool.TAG_UTF8:
                case Pool.TAG_INTEGER:
                case Pool.TAG_FLOAT:
                case Pool.TAG_LONG:
                case Pool.TAG_DOUBLE:
                case Pool.TAG_WIDETOP:
                    out = in;
                    break;
                    
                    // Name of a class
                case Pool.TAG_CLASS:
                    out = new ClassName(__entries[((int[])in)[0]].toString());
                    break;
                    
                    // String constant
                case Pool.TAG_STRING:
                    out = new ConstantValueString(
                        __entries[((int[])in)[0]].
                            toString());
                    break;
                    
                    // Name and type information
                case Pool.TAG_NAMEANDTYPE:
                    {
                        int[] ina = (int[])in;
                        out = new NameAndType(
                            __entries[ina[0]].toString(),
                            __entries[ina[1]].toString());
                    }
                    break;
                    
                    // Field and method references
                case Pool.TAG_FIELDREF:
                case Pool.TAG_METHODREF:
                case Pool.TAG_INTERFACEMETHODREF:
                    {
                        int[] ina = (int[])in;
                        ClassName cn = (ClassName)__entries[ina[0]];
                        NameAndType nat = (NameAndType)__entries[ina[1]];
                        
                        if (tag == Pool.TAG_FIELDREF)
                            out = new FieldReference(cn,
                                new FieldName(nat.name()),
                                new FieldDescriptor(nat.type()));
                        else
                            out = new MethodReference(cn,
                                new MethodName(nat.name()),
                                new MethodDescriptor(nat.type()),
                                tag == Pool.TAG_INTERFACEMETHODREF);
                    }
                    break;
                    
                    // Invoke dynamics which are ignored
                case Pool.TAG_METHODHANDLE:
                case Pool.TAG_METHODTYPE:
                case Pool.TAG_INVOKEDYNAMIC:
                    out = in;
                    break;
                
                    // Unhandled, should not happen
                default:
                    throw new RuntimeException(String.format("OOPS %d", tag));
            }
            
            __entries[i] = out;
        }
    }
}