SetterMethod.java

package com.mackenziehigh.autumn.lang.compiler.utils;

import com.google.common.base.Preconditions;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IDeclaredType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IMethod;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IVariableType;

/**
 * An instance of this class represents a bytecode setter method in a record.
 *
 * @author Mackenzie High
 */
public final class SetterMethod
{
    /**
     * This is the type that declares the getter.
     */
    public final IDeclaredType owner;

    /**
     * This is the type of the type of record returned by the setter method.
     */
    public final IDeclaredType returns;

    /**
     * This is the name of the element that the setter sets.
     */
    public final String name;

    /**
     * This is the type of value that the setter accepts as an argument.
     */
    public final IVariableType parameter;

    /**
     * Constructor.
     *
     * @param owner is the type that declares the setter.
     * @param returns is the return-type of the setter.
     * @param name is the name of the setter.
     * @param parameter is the type of the setter's only formal-parameter.
     */
    public SetterMethod(final IDeclaredType owner,
                        final IDeclaredType returns,
                        final String name,
                        final IVariableType parameter)
    {
        Preconditions.checkNotNull(owner);
        Preconditions.checkNotNull(returns);
        Preconditions.checkNotNull(name);
        Preconditions.checkNotNull(parameter);

        this.owner = owner;
        this.returns = returns;
        this.name = name;
        this.parameter = parameter;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode()
    {
        int hash = 7;
        hash = 23 * hash + (this.owner != null ? this.owner.hashCode() : 0);
        hash = 23 * hash + (this.returns != null ? this.returns.hashCode() : 0);
        hash = 23 * hash + (this.name != null ? this.name.hashCode() : 0);
        hash = 23 * hash + (this.parameter != null ? this.parameter.hashCode() : 0);
        return hash;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object obj)
    {
        if (obj == null)
        {
            return false;
        }
        if (getClass() != obj.getClass())
        {
            return false;
        }
        final SetterMethod other = (SetterMethod) obj;
        if (this.owner != other.owner && (this.owner == null || !this.owner.equals(other.owner)))
        {
            return false;
        }
        if (this.returns != other.returns && (this.returns == null || !this.returns.equals(other.returns)))
        {
            return false;
        }
        if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name))
        {
            return false;
        }
        if (this.parameter != other.parameter && (this.parameter == null || !this.parameter.equals(other.parameter)))
        {
            return false;
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString()
    {
        return "setter " + Utils.simpleName(owner) + "." + name + "(" + Utils.simpleName(parameter) + ") : " + Utils.simpleName(returns);
    }

    /**
     * This method determines whether this setter method is declared in its owner.
     *
     * @return true, iff the owner currently directly declares this setter method.
     */
    public boolean isDeclared()
    {
        for (IMethod method : owner.getMethods())
        {
            /**
             * If the name is different, continue.
             */
            if (method.getName().equals(name) == false)
            {
                continue;
            }

            /**
             * If the number of parameters is different, continue.
             */
            if (method.getParameters().size() != 1)
            {
                continue;
            }

            /**
             * If the parameter-type is different, continue.
             */
            if (method.getParameters().get(0).getType().equals(parameter) == false)
            {
                continue;
            }

            /**
             * If the return-type is different, continue.
             */
            if (method.getReturnType().equals(returns) == false)
            {
                continue;
            }

            /**
             * Since the owner, name, parameter-list, and return-type match,
             * we have found a method declaration that describes the setter method.
             */
            return true;
        }

        /**
         * None of the method declarations in the owner describe the setter method.
         */
        return false;
    }

    /**
     * If this setter is a bridge-method, this method determines which setter to invoke.
     *
     * @return setter method that this setter method invokes at runtime.
     */
    public IMethod findBridgeTarget()
    {
        /**
         * If this setter method is the target, it should cannot target itself.
         */
        assert returns.equals(owner) == false;

        IMethod result = null;

        final IMethod self = this.findSelf();

        for (IMethod method : owner.getMethods())
        {
            if (method == self)
            {
                continue;
            }

            final boolean match1 = method.getName().equals(name);

            final boolean match2 = method.getReturnType().equals(owner);

            final boolean match3 = method.getParameters().size() == 1;

            final boolean matches = match1 && match2 && match3 && match3;

            if (matches && result == null)
            {
                result = method;
                continue;
            }

            if (matches == false || result == null)
            {
                continue;
            }

            final IVariableType other_parameter = method.getParameters().get(0).getType();

            final IVariableType result_parameter = result.getParameters().get(0).getType();

            final boolean subtype = other_parameter.isSubtypeOf(result_parameter);

            final boolean equals = other_parameter.equals(result_parameter);

            final boolean proper_subtype = subtype && !equals;

            /**
             * If the method is more specific than the previous result, use the new result.
             */
            if (proper_subtype)
            {
                result = method;
            }
        }

        /**
         * This method should only be invoked, when the result is known to exist.
         */
        assert result != null;

        return result;
    }

    /**
     * This method retrieves the type-system representation of this method.
     *
     * @return the member of the owner-type that represents this setter method.
     */
    public IMethod findSelf()
    {
        IMethod result = null;

        for (IMethod method : owner.getMethods())
        {
            final boolean match1 = method.getName().equals(name);

            final boolean match2 = method.getReturnType().equals(returns);

            final boolean match3 = method.getParameters().size() == 1;

            final boolean match4 = match3 && method.getParameters().get(0).getType().equals(parameter);

            if (match1 && match2 && match3 && match4)
            {
                result = method;
                break;
            }
        }

        /**
         * This method should only be invoked, when the result is known to exist.
         */
        assert result != null;

        return result;
    }
}