SquirrelJME/SquirrelJME

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

Summary

Maintainability
A
0 mins
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 java.util.ArrayList;
import java.util.List;

/**
 * This represents the type descriptor of a method.
 *
 * @since 2017/06/12
 */
public final class MethodDescriptor
    implements Comparable<MethodDescriptor>, MemberDescriptor
{
    /** String representation of the descriptor. */
    protected final String string;
    
    /** The return value, null is void. */
    protected final FieldDescriptor rvalue;
    
    /** The arguments in the method. */
    private final FieldDescriptor[] _args;
    
    /**
     * Initializes the descriptor with the given descriptors.
     *
     * @param __rv The return value, may be {@code null} for {@code void}.
     * @param __args Arguments to the descriptor.
     * @throws InvalidClassFormatException If the descriptor is not valid.
     * @throws NullPointerException On null arguments.
     * @since 2018/12/08
     */
    public MethodDescriptor(FieldDescriptor __rv, FieldDescriptor... __args)
        throws InvalidClassFormatException, NullPointerException
    {
        if (__args == null)
            throw new NullPointerException("NARG");
        
        // Need to build the string representation
        StringBuilder sb = new StringBuilder();
        sb.append('(');
        
        // Check for more nulls and build the string
        __args = __args.clone();
        for (FieldDescriptor f : __args)
        {
            if (f == null)
                throw new NullPointerException("NARG");
            
            sb.append(f.toString());
        }
        
        // Finish with the return value
        sb.append(')');
        sb.append((__rv == null ? "V" : __rv.toString()));
        
        // Set because these are copies
        this.rvalue = __rv;
        this._args = __args;
        this.string = sb.toString();
    }
    
    /**
     * Initializes the method descriptor.
     *
     * @param __n The method descriptor to decode.
     * @throws InvalidClassFormatException If it is not a valid method
     * descriptor.
     * @throws NullPointerException On null arguments.
     * @since 2017/06/12
     */
    public MethodDescriptor(String __n)
        throws InvalidClassFormatException, NullPointerException
    {
        // Check
        if (__n == null)
            throw new NullPointerException("NARG");
        
        // Set
        this.string = __n;
        
        /* {@squirreljme.error JC3h Method descriptors must start with an open
        parenthesis. (The method descriptor)} */
        if (!__n.startsWith("("))
            throw new InvalidClassFormatException(
                String.format("JC3h %s", __n));
        
        // Parse all input arguments
        List<FieldDescriptor> args = new ArrayList<>();
        int i = 1, n = __n.length();
        for (; i < n;)
        {
            char c = __n.charAt(i);
            
            // End of descriptor arguments
            if (c == ')')
                break;
            
            // Skip array markers
            int at = i;
            for (; at < n; at++)
                if ('[' != __n.charAt(at))
                    break;
            
            /* {@squirreljme.error JC3i Reached end of descriptor parsing
            arguments. (The method descriptor)} */
            if (at >= n)
                throw new InvalidClassFormatException(
                    String.format("JC3i %s", __n));
            
            // Find end sequence
            switch (__n.charAt(at))
            {
                    // Primitive
                case 'B':
                case 'C':
                case 'D':
                case 'F':
                case 'I':
                case 'J':
                case 'S':
                case 'Z':
                    break;
                    
                    // Class, find ;
                case 'L':
                    for (; at < n; at++)
                        if (';' == __n.charAt(at))
                            break;
                    break;
                
                    /* {@squirreljme.error JC3j Unknown field descriptor in
                    method descriptor argument. (The descriptor)} */
                default:
                    throw new InvalidClassFormatException(
                        String.format("JC3j %s", __n));
            }
            
            // Parse field
            args.add(new FieldDescriptor(__n.substring(i, at + 1)));
            
            // Go to next field
            i = at + 1;
        }
        this._args = args.<FieldDescriptor>toArray(
            new FieldDescriptor[args.size()]);
        
        // Skip the ending parenthesis
        i++;
        
        /* {@squirreljme.error JC3k The method descriptor has no return
        value. (The method descriptor)} */
        if (i >= n)
            throw new InvalidClassFormatException(
                String.format("JC3k %s", __n));
        
        // No return value?
        char c = __n.charAt(i);
        if (c == 'V' && (i + 1) == n)
            this.rvalue = null;
        
        // Parse as a field
        else
            this.rvalue = new FieldDescriptor(__n.substring(i));
    }
    
    /**
     * Returns the argument of the given parameter number.
     *
     * @param __i The parameter to get the argument for.
     * @throws IndexOutOfBoundsException If the argument is not within bounds.
     * @since 2017/07/28
     */
    public FieldDescriptor argument(int __i)
        throws IndexOutOfBoundsException
    {
        return this._args[__i];
    }
    
    /**
     * Returns the slot count for arguments.
     * 
     * @return The slot count for arguments.
     * @since 2022/09/21
     */
    public int argumentSlotCount()
    {
        int total = 0;
        for (FieldDescriptor desc : this._args)
            total += (desc.isWide() ? 2 : 1);
        
        return total;
    }
    
    /**
     * Returns all the arguments.
     *
     * @return The arguments.
     * @since 2019/04/14
     */
    public FieldDescriptor[] arguments()
    {
        return this._args.clone();
    }
    
    /**
     * Returns the number of arguments this descriptor has.
     *
     * @return The number of arguments this descriptor has.
     * @since 2017/07/28
     */
    public int argumentCount()
    {
        return this._args.length;
    }
    
    /**
     * {@inheritDoc}
     * @since 2022/08/27
     */
    @Override
    public int compareTo(MethodDescriptor __other)
    {
        if (__other == null)
            throw new NullPointerException("NARG");
        if (__other == this)
            return 0;
        
        FieldDescriptor[] aList = this._args;
        FieldDescriptor[] bList = __other._args;
        
        // Compare by each index
        int aLen = aList.length;
        int bLen = bList.length;
        for (int i = 0, n = Math.min(aLen, bLen); i < n; i++)
        {
            FieldDescriptor a = aList[i];
            FieldDescriptor b = bList[i];
            
            // If these are different, then use that difference
            int compare = a.compareTo(b);
            if (compare != 0)
                return compare;
        }
        
        // Shorter list comes first
        if (aLen != bLen)
            return bLen - aLen;
        
        // Compare return types
        FieldDescriptor aReturn = this.rvalue;
        FieldDescriptor bReturn = __other.rvalue;
        
        // Null first
        if ((aReturn == null) != (bReturn == null))
        {
            if (aReturn == null)
                return -1;
            else
                return 1;
        }
        
        // Do the compare if neither are null
        if (aReturn != null)
            return aReturn.compareTo(bReturn);
        
        // These are equal
        return 0;
    }
    
    /**
     * {@inheritDoc}
     * @since 2017/06/12
     */
    @Override
    public boolean equals(Object __o)
    {
        // Check
        if (__o == this)
            return true;
        if (!(__o instanceof MethodDescriptor))
            return false;
        
        return this.string.equals(((MethodDescriptor)__o).string);
    }
    
    /**
     * {@inheritDoc}
     * @since 2017/06/12
     */
    @Override
    public int hashCode()
    {
        return this.string.hashCode();
    }
    
    /**
     * Does this method have a return value?
     *
     * @return If the method has a return value.
     * @since 2018/09/16
     */
    public final boolean hasReturnValue()
    {
        return null != this.rvalue;
    }
    
    /**
     * Returns the Java type stack for this descriptor.
     * 
     * @return The descriptor as it appears on the Java Stack.
     * @since 2017/09/16
     */
    public JavaType[] javaStack()
    {
        // Handle all arguments now
        int n = this.argumentCount();
        List<JavaType> rv = new ArrayList<>(n * 2);
        for (int i = 0; i < n; i++)
        {
            FieldDescriptor a;
            JavaType j;
            rv.add(j = new JavaType(this.argument(i)));
            
            // Add top of long/double but with unique distinct types
            if (j.isWide())
                rv.add(j.topType());
        }
        
        return rv.<JavaType>toArray(new JavaType[rv.size()]);
    }
    
    /**
     * Returns the return value of this descriptor.
     *
     * @return The value returned in this descriptor or {@code null} if there
     * is no return value.
     * @since 2017/09/22
     */
    public FieldDescriptor returnValue()
    {
        return this.rvalue;
    }
    
    /**
     * {@inheritDoc}
     * @since 2017/06/12
     */
    @Override
    public String toString()
    {
        return this.string;
    }
    
    /**
     * Creates a descriptor from the given input strings.
     * 
     * @param __rv The return value of the method, may be {@code null}.
     * @param __args The arguments of the method.
     * @return The descriptor.
     * @throws NullPointerException On null arguments.
     * @since 2020/09/15
     */
    public static MethodDescriptor ofArguments(String __rv, String... __args)
        throws NullPointerException
    {
        if (__args == null)
            throw new NullPointerException("NARG");
        
        int n = __args.length;
        FieldDescriptor[] args = new FieldDescriptor[n];
        for (int i = 0; i < n; i++)
            args[i] = new FieldDescriptor(__args[i]);
        
        return new MethodDescriptor(
            (__rv == null ? null : new FieldDescriptor(__rv)), args);
    }
    
    /**
     * Creates a descriptor from the given input strings.
     * 
     * @param __rv The return value of the method, may be {@code null}.
     * @param __args The arguments of the method.
     * @return The descriptor.
     * @throws NullPointerException On null arguments.
     * @since 2023/07/16
     */
    public static MethodDescriptor ofArguments(FieldDescriptor __rv,
        FieldDescriptor... __args)
        throws NullPointerException
    {
        if (__args == null)
            throw new NullPointerException("NARG");
        
        return new MethodDescriptor(__rv, __args.clone());
    }
}