Package com.redhat.ceylon.compiler.java.runtime.metamodel

Source Code of com.redhat.ceylon.compiler.java.runtime.metamodel.AppliedFunction

package com.redhat.ceylon.compiler.java.runtime.metamodel;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import ceylon.language.Array;
import ceylon.language.Sequential;
import ceylon.language.empty_;

import com.redhat.ceylon.compiler.java.Util;
import com.redhat.ceylon.compiler.java.metadata.Ceylon;
import com.redhat.ceylon.compiler.java.metadata.Ignore;
import com.redhat.ceylon.compiler.java.metadata.Name;
import com.redhat.ceylon.compiler.java.metadata.Sequenced;
import com.redhat.ceylon.compiler.java.metadata.TypeInfo;
import com.redhat.ceylon.compiler.java.metadata.TypeParameter;
import com.redhat.ceylon.compiler.java.metadata.TypeParameters;
import com.redhat.ceylon.compiler.java.metadata.Variance;
import com.redhat.ceylon.compiler.java.runtime.model.ReifiedType;
import com.redhat.ceylon.compiler.java.runtime.model.TypeDescriptor;
import com.redhat.ceylon.compiler.typechecker.model.Parameter;
import com.redhat.ceylon.compiler.typechecker.model.ProducedReference;
import com.redhat.ceylon.compiler.typechecker.model.ProducedType;

@Ceylon(major = 7)
@com.redhat.ceylon.compiler.java.metadata.Class
@TypeParameters({
    @TypeParameter(value = "Type", variance = Variance.OUT),
    @TypeParameter(value = "Arguments", variance = Variance.IN, satisfies = "ceylon.language::Sequential<ceylon.language::Anything>"),
    })
public class AppliedFunction<Type, Arguments extends Sequential<? extends Object>>
    implements ceylon.language.meta.model.Function<Type, Arguments>, ReifiedType, DefaultValueProvider {

    @Ignore
    private final TypeDescriptor $reifiedType;
    @Ignore
    private final TypeDescriptor $reifiedArguments;
   
    private ceylon.language.meta.model.Type<? extends Type> type;
    protected FreeFunction declaration;
    private MethodHandle method;
    private MethodHandle[] dispatch;
    private int firstDefaulted = -1;
    private int variadicIndex = -1;
    private ceylon.language.Map<? extends ceylon.language.meta.declaration.TypeParameter, ? extends ceylon.language.meta.model.Type<?>> typeArguments;
    private Object instance;
    private ceylon.language.meta.model.Type<?> container;
    private List<ProducedType> parameterProducedTypes;
    private Sequential<? extends ceylon.language.meta.model.Type<? extends Object>> parameterTypes;
    private ProducedReference appliedFunction;

    public AppliedFunction(@Ignore TypeDescriptor $reifiedType,
                           @Ignore TypeDescriptor $reifiedArguments,
                           ProducedReference appliedFunction, FreeFunction function,
                           ceylon.language.meta.model.Type<?> container,
                           Object instance) {
        this.$reifiedType = $reifiedType;
        this.$reifiedArguments = $reifiedArguments;
        this.container = container;
        this.instance = instance;
        this.appliedFunction = appliedFunction;
       
        com.redhat.ceylon.compiler.typechecker.model.Method decl = (com.redhat.ceylon.compiler.typechecker.model.Method) function.declaration;
        List<Parameter> parameters = decl.getParameterLists().get(0).getParameters();
       
        this.firstDefaulted = Metamodel.getFirstDefaultedParameter(parameters);
        this.variadicIndex = Metamodel.getVariadicParameter(parameters);

        Method[] defaultedMethods = null;
        if(firstDefaulted != -1){
            // if we have 2 params and first is defaulted we need 2 + 1 - 0 = 3 methods:
            // f(), f(a) and f(a, b)
            this.dispatch = new MethodHandle[parameters.size() + 1 - firstDefaulted];
            defaultedMethods = new Method[dispatch.length];
        }

        this.type = Metamodel.getAppliedMetamodel(Metamodel.getFunctionReturnType(appliedFunction));
       
        this.declaration = function;
       
        this.typeArguments = Metamodel.getTypeArguments(declaration, appliedFunction);

        // get a list of produced parameter types
        this.parameterProducedTypes = Metamodel.getParameterProducedTypes(parameters, appliedFunction);
        this.parameterTypes = Metamodel.getAppliedMetamodelSequential(this.parameterProducedTypes);

        // FIXME: delay method setup for when we actually use it?
        java.lang.Class<?> javaClass = Metamodel.getJavaClass(function.declaration);
        Method found = null;
        String name = Metamodel.getJavaMethodName((com.redhat.ceylon.compiler.typechecker.model.Method) function.declaration);
       
        // special cases for some erased types
        if(javaClass == ceylon.language.Object.class
                || javaClass == ceylon.language.Basic.class
                || javaClass == ceylon.language.Identifiable.class){
            if("equals".equals(name)){
                // go fetch the method Object.equals
                try {
                    found = java.lang.Object.class.getDeclaredMethod("equals", java.lang.Object.class);
                } catch (NoSuchMethodException e) {
                    throw Metamodel.newModelError("Missing equals method in ceylon.language::Object");
                } catch (SecurityException e) {
                    throw Metamodel.newModelError("Security exception getting equals method in ceylon.language::Object");
                }
            }else{
                throw Metamodel.newModelError("Object/Basic/Identifiable member not supported: "+decl.getName());
            }
        } else if (javaClass == ceylon.language.Throwable.class) {
            if("printStackTrace".equals(decl.getName())){
                try {
                    found = java.lang.Throwable.class.getDeclaredMethod("printStackTrace");
                } catch (NoSuchMethodException e) {
                    throw Metamodel.newModelError("Missing printStackTrace method in ceylon.language::Throwable");
                } catch (SecurityException e) {
                    throw Metamodel.newModelError("Security exception getting printStackTrace method in ceylon.language::Throwable");
                }
            }
        } else{
            // FIXME: deal with Java classes and overloading
            // FIXME: faster lookup with types? but then we have to deal with erasure and stuff
            found = Metamodel.getJavaMethod((com.redhat.ceylon.compiler.typechecker.model.Method) function.declaration);
           
            int reifiedTypeParameterCount = MethodHandleUtil.isReifiedTypeSupported(found, false) ? found.getTypeParameters().length : 0;
            boolean isArray = MethodHandleUtil.isJavaArray(javaClass);
            for(Method method : javaClass.getDeclaredMethods()){
                if(!method.getName().equals(name))
                    continue;
                if(method.isBridge() || method.isSynthetic())
                    continue;
                // skip static methods if we're in array types
                if(isArray && Modifier.isStatic(method.getModifiers()))
                    continue;
                if(method.isAnnotationPresent(Ignore.class)){
                    // save method for later
                    // FIXME: proper checks
                    if(firstDefaulted != -1){
                        // do not count reified type parameters
                        int params = method.getParameterTypes().length - reifiedTypeParameterCount;
                        defaultedMethods[params - firstDefaulted] = method;
                    }
                    continue;
                }
                // FIXME: deal with private stuff?
            }
        }
        if(found != null){
            boolean variadic = found.isVarArgs();
            method = reflectionToMethodHandle(found, javaClass, instance, appliedFunction, parameterProducedTypes, variadic, false);
            if(defaultedMethods != null){
                // this won't find the last one, but its method
                int i=0;
                for(;i<defaultedMethods.length-1;i++){
                    if(defaultedMethods[i] == null)
                        throw Metamodel.newModelError("Missing defaulted method "+found.getName()
                                +" with "+(i+firstDefaulted)+" parameters in "+found.getDeclaringClass());
                    dispatch[i] = reflectionToMethodHandle(defaultedMethods[i], javaClass, instance, appliedFunction, parameterProducedTypes, variadic, false);
                }
                dispatch[i] = method;
            }else if(variadic){
                // variadic methods don't have defaulted parameters, but we will simulate one because our calling convention is that
                // we treat variadic methods as if the last parameter is optional
                firstDefaulted = parameters.size() - 1;
                dispatch = new MethodHandle[2];
                dispatch[0] = reflectionToMethodHandle(found, javaClass, instance, appliedFunction, parameterProducedTypes, variadic, true);
                dispatch[1] = method;
            }
        }
    }

    private MethodHandle reflectionToMethodHandle(Method found, java.lang.Class<?> javaClass, Object instance,
                                                  ProducedReference appliedFunction, List<ProducedType> parameterProducedTypes,
                                                  boolean variadic, boolean bindVariadicParameterToEmptyArray) {
        // save the parameter types before we mess with "found"
        java.lang.Class<?>[] parameterTypes = found.getParameterTypes();
        MethodHandle method = null;
        boolean isJavaArray = MethodHandleUtil.isJavaArray(javaClass);
        try {
            if(isJavaArray){
                if(found.getName().equals("get"))
                    method = MethodHandleUtil.getJavaArrayGetterMethodHandle(javaClass);
                else if(found.getName().equals("set"))
                    method = MethodHandleUtil.getJavaArraySetterMethodHandle(javaClass);
                else if(found.getName().equals("copyTo")){
                    found = MethodHandleUtil.getJavaArrayCopyToMethod(javaClass, found);
                }
            }
            if(method == null){
                found.setAccessible(true);
                method = MethodHandles.lookup().unreflect(found);
            }
        } catch (IllegalAccessException e) {
            throw Metamodel.newModelError("Problem getting a MH for constructor for: "+javaClass, e);
        }
        // box the return type
        method = MethodHandleUtil.boxReturnValue(method, found.getReturnType(), appliedFunction.getType());
        // we need to cast to Object because this is what comes out when calling it in $call
        if(instance != null
                && (isJavaArray || !Modifier.isStatic(found.getModifiers())))
            method = method.bindTo(instance);
        method = method.asType(MethodType.methodType(Object.class, parameterTypes));
        int typeParametersCount = found.getTypeParameters().length;
        int skipParameters = 0;
        // insert any required type descriptors
        if(typeParametersCount != 0 && MethodHandleUtil.isReifiedTypeSupported(found, false)){
            List<ProducedType> typeArguments = new ArrayList<ProducedType>();
            Map<com.redhat.ceylon.compiler.typechecker.model.TypeParameter, ProducedType> typeArgumentMap = appliedFunction.getTypeArguments();
            for (com.redhat.ceylon.compiler.typechecker.model.TypeParameter tp : ((com.redhat.ceylon.compiler.typechecker.model.Method)appliedFunction.getDeclaration()).getTypeParameters()) {
                typeArguments.add(typeArgumentMap.get(tp));
            }
            method = MethodHandleUtil.insertReifiedTypeArguments(method, 0, typeArguments);
            skipParameters = typeParametersCount;
        }
        // now convert all arguments (we may need to unbox)
        method = MethodHandleUtil.unboxArguments(method, skipParameters, 0, parameterTypes,
                                                 parameterProducedTypes, variadic, bindVariadicParameterToEmptyArray);
        return method;
    }

    @Override
    public FreeFunction getDeclaration(){
        return declaration;
    }
   
    @Override
    @TypeInfo("ceylon.language::Map<ceylon.language.meta.declaration::TypeParameter,ceylon.language.meta.model::Type<ceylon.language::Anything>>")
    public ceylon.language.Map<? extends ceylon.language.meta.declaration.TypeParameter, ? extends ceylon.language.meta.model.Type<?>> getTypeArguments() {
        return typeArguments;
    }

    private void checkMethod(){
        if(method == null)
            throw Metamodel.newModelError("No method found for: "+declaration.getName());
    }
   
    @Ignore
    @Override
    public Type $call$() {
        checkMethod();
        try {
            if(firstDefaulted == -1)
                return (Type)method.invokeExact();
            // FIXME: proper checks
            return (Type)dispatch[0].invokeExact();
        } catch (Throwable e) {
            Util.rethrow(e);
            return null;
        }
    }

    @Ignore
    @Override
    public Type $call$(Object arg0) {
        checkMethod();
        try {
            if(firstDefaulted == -1)
                return (Type)method.invokeExact(arg0);
            // FIXME: proper checks
            return (Type)dispatch[1-firstDefaulted].invokeExact(arg0);
        } catch (Throwable e) {
            Util.rethrow(e);
            return null;
        }
    }

    @Ignore
    @Override
    public Type $call$(Object arg0, Object arg1) {
        checkMethod();
        try {
            if(firstDefaulted == -1)
                return (Type)method.invokeExact(arg0, arg1);
            // FIXME: proper checks
            return (Type)dispatch[2-firstDefaulted].invokeExact(arg0, arg1);
        } catch (Throwable e) {
            Util.rethrow(e);
            return null;
        }
    }

    @Ignore
    @Override
    public Type $call$(Object arg0, Object arg1, Object arg2) {
        checkMethod();
        try {
            if(firstDefaulted == -1)
                return (Type)method.invokeExact(arg0, arg1, arg2);
            // FIXME: proper checks
            return (Type)dispatch[3-firstDefaulted].invokeExact(arg0, arg1, arg2);
        } catch (Throwable e) {
            Util.rethrow(e);
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    @Ignore
    @Override
    public Type $call$(Object... args) {
        checkMethod();
        try {
            // FIXME: this does not do invokeExact and does boxing/widening
            if(firstDefaulted == -1)
                return (Type)method.invokeWithArguments(args);
            // FIXME: proper checks
            return (Type)dispatch[args.length-firstDefaulted].invokeWithArguments(args);
        } catch (Throwable e) {
            Util.rethrow(e);
            return null;
        }
    }

    @Ignore
    @Override
    public short $getVariadicParameterIndex$() {
        return (short)variadicIndex;
    }

    @Override
    @TypeInfo("ceylon.language.meta.model::Type<Type>")
    public ceylon.language.meta.model.Type<? extends Type> getType() {
        return type;
    }

    @Override
    @Ignore
    public Type $callvariadic$() {
        return $call$();
    }
   
    @Override
    @Ignore
    public Type $callvariadic$(Sequential<?> varargs) {
        return $call$(varargs);
    }

    @Override
    @Ignore
    public Type $callvariadic$(Object arg0,
            Sequential<?> varargs) {
        return $call$(arg0, varargs);
    }

    @Override
    @Ignore
    public Type $callvariadic$(Object arg0,
            Object arg1, Sequential<?> varargs) {
        return $call$(arg0, arg1, varargs);
    }

    @Override
    @Ignore
    public Type $callvariadic$(Object arg0,
            Object arg1, Object arg2, Sequential<?> varargs) {
        return $call$(arg0, arg1, arg2, varargs);
    }

    @Override
    @Ignore
    public Type $callvariadic$(Object... argsAndVarargs) {
        return $call$((Object[])argsAndVarargs);
    }

    @Override
    @Ignore
    public Type $callvariadic$(Object arg0) {
        return $call$(arg0, empty_.get_());
    }

    @Override
    @Ignore
    public Type $callvariadic$(Object arg0, Object arg1) {
        return $call$(arg0, arg1, empty_.get_());
    }

    @Override
    @Ignore
    public Type $callvariadic$(Object arg0, Object arg1, Object arg2) {
        return $call$(arg0, arg1, arg2, empty_.get_());
    }

    @Ignore
    @Override
    public Type apply(){
        return apply(empty_.get_());
    }

    @Override
    public Type apply(@Name("arguments")
        @Sequenced
        @TypeInfo("ceylon.language::Sequential<ceylon.language::Anything>")
        Sequential<?> arguments){
       
        return Metamodel.apply(this, arguments, parameterProducedTypes, firstDefaulted, variadicIndex);
    }

    @Override
    public Type namedApply(@Name("arguments")
        @TypeInfo("ceylon.language::Iterable<ceylon.language::Entry<ceylon.language::String,ceylon.language::Anything>,ceylon.language::Null>")
        ceylon.language.Iterable<? extends ceylon.language.Entry<? extends ceylon.language.String,? extends java.lang.Object>,? extends java.lang.Object> arguments){

        return Metamodel.namedApply(this, this,
                (com.redhat.ceylon.compiler.typechecker.model.Functional)declaration.declaration,
                arguments, parameterProducedTypes);
    }

    @Override
    public Object getDefaultParameterValue(Parameter parameter, Array<Object> values, int collectedValueCount) {
        // find the right class
        java.lang.Class<?> javaClass = Metamodel.getJavaClass(declaration.declaration);
        // default method name
        String name = declaration.getName()+"$"+parameter.getName();
        Method found = null;
        // iterate to find it, rather than figure out its parameter types
        for(Method m : javaClass.getDeclaredMethods()){
            if(m.getName().equals(name)){
                found = m;
                break;
            }
        }
        if(found == null)
            throw Metamodel.newModelError("Default argument method for "+parameter.getName()+" not found");
        int parameterCount = found.getParameterTypes().length;
        if(MethodHandleUtil.isReifiedTypeSupported(found, false))
            parameterCount -= found.getTypeParameters().length;
        if(parameterCount != collectedValueCount)
            throw Metamodel.newModelError("Default argument method for "+parameter.getName()+" requires wrong number of parameters: "+parameterCount+" should be "+collectedValueCount);
       
        // AFAIK default value methods cannot be Java-variadic
        MethodHandle methodHandle = reflectionToMethodHandle(found, javaClass, instance, appliedFunction, parameterProducedTypes, false, false);
        // sucks that we have to copy the array, but that's the MH API
        java.lang.Object[] arguments = new java.lang.Object[collectedValueCount];
        System.arraycopy(values.toArray(), 0, arguments, 0, collectedValueCount);
        try {
            return methodHandle.invokeWithArguments(arguments);
        } catch (Throwable e) {
            Util.rethrow(e);
            return null;
        }
    }

    @TypeInfo("ceylon.language::Sequential<ceylon.language.meta.model::Type<ceylon.language::Anything>>")
    @Override
    public ceylon.language.Sequential<? extends ceylon.language.meta.model.Type<? extends Object>> getParameterTypes(){
        return parameterTypes;
    }

    @Override
    public int hashCode() {
        int result = 1;
        // in theory, if our instance is the same, our containing type should be the same
        // and if we don't have an instance we're a toplevel and have no containing type
        result = 37 * result + (instance == null ? 0 : instance.hashCode());
        result = 37 * result + getDeclaration().hashCode();
        result = 37 * result + getTypeArguments().hashCode();
        return result;
    }
   
    @Override
    public boolean equals(Object obj) {
        if(obj == null)
            return false;
        if(obj == this)
            return true;
        if(obj instanceof AppliedFunction == false)
            return false;
        AppliedFunction<?,?> other = (AppliedFunction<?,?>) obj;
        // in theory, if our instance is the same, our containing type should be the same
        // and if we don't have an instance we're a toplevel and have no containing type
        return getDeclaration().equals(other.getDeclaration())
                && Util.eq(instance, other.instance)
                && getTypeArguments().equals(other.getTypeArguments());
    }


    @Override
    @TypeInfo("ceylon.language.meta.model::Type<ceylon.language::Anything>|ceylon.language::Null")
    public ceylon.language.meta.model.Type<?> getContainer(){
        return container;
    }

    @Override
    public String toString() {
        return Metamodel.toTypeString(this);
    }

    @Ignore
    @Override
    public TypeDescriptor $getType$() {
        return TypeDescriptor.klass(AppliedFunction.class, $reifiedType, $reifiedArguments);
    }
}
TOP

Related Classes of com.redhat.ceylon.compiler.java.runtime.metamodel.AppliedFunction

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.