DelegateToHandler.java

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

import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IClassType;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IMethod;
import com.mackenziehigh.autumn.lang.compiler.typesystem.design.IVariableType;
import java.util.List;

/**
 * An instance of this class is used to map a delegate to handler function.
 *
 * @author mackenzie
 */
public final class DelegateToHandler
{
    public static enum Errors
    {
        NO_SUCH_METHOD,
        OVERLOADED_METHOD,
        INCOMPATIBLE_METHOD,
    }

    /**
     * This field will be null, unless an error was detected.
     */
    public final Errors error;

    /**
     * This is the type of the delegate's invoke(*) method.
     */
    public final IMethod delegate;

    /**
     * This is the type of the method that the delegate refers to.
     */
    public final IMethod handler;

    /**
     * Sole Constructor.
     *
     * @param error is the type of error, if any.
     * @param member is the delegate.
     * @param handler is the handler function.
     */
    private DelegateToHandler(final Errors error,
                              final IMethod member,
                              final IMethod handler)
    {
        this.error = error;
        this.delegate = member;
        this.handler = handler;
    }

    public static DelegateToHandler find(final IClassType functor,
                                         final IClassType owner,
                                         final String name)
    {
        final IMethod invoke = TypeSystemUtils.find(functor.getMethods(), "invoke");

        final List<IMethod> overloads = TypeSystemUtils.findFunctions(owner, name);

        /**
         * An overload must exist in order to map the delegate to a handler.
         */
        if (overloads.isEmpty())
        {
            return new DelegateToHandler(Errors.NO_SUCH_METHOD, invoke, null);
        }

        /**
         * Per the specification, a delegate can not target an overloaded function.
         */
        if (overloads.size() > 1)
        {
            return new DelegateToHandler(Errors.OVERLOADED_METHOD, invoke, null);
        }

        /**
         * Get the type of the function that is referred to by the delegate.
         */
        final IMethod handler = overloads.get(0);

        /**
         * The delegate must be compatible with the function that it refers to.
         */
        if (checkCompatability(invoke, handler) == false)
        {
            return new DelegateToHandler(Errors.INCOMPATIBLE_METHOD, invoke, handler);
        }

        /**
         * Everything appear to be OK.
         */
        return new DelegateToHandler(null, invoke, handler);
    }

    /**
     * This method determines whether a delegate is compatible with the method that it refers to.
     *
     * @param functor is the type of the functor's invoke() method.
     * @param handler is the type of the function that the delegate refers to.
     * @return true, iff the delegate is compatible with the function that it refers to.
     */
    private static boolean checkCompatability(final IMethod functor,
                                              final IMethod handler)
    {
        /**
         * Per the specification, the handler's return-type must
         * be a subtype of the functor's return-type.
         */
        if (handler.getReturnType().isSubtypeOf(functor.getReturnType()) == false)
        {
            return false;
        }

        /**
         * Per the specification, the handler and the functor must
         * take the same number of arguments.
         */
        if (functor.getParameters().size() != handler.getParameters().size())
        {
            return false;
        }

        /**
         * Per the specification, the type of parameter[i] of the functor
         * must be a subtype of parameter[i] of the handler.
         */
        for (int i = 0; i < handler.getParameters().size(); i++)
        {
            final IVariableType functor_parameter = functor.getParameters().get(i).getType();

            final IVariableType handler_parameter = handler.getParameters().get(i).getType();

            if (functor_parameter.isSubtypeOf(handler_parameter) == false)
            {
                return false;
            }
        }

        /**
         * Everything appears to be OK.
         */
        return true;
    }
}