r.jvmi.binding.JvmMethod.java Source code

Java tutorial

Introduction

Here is the source code for r.jvmi.binding.JvmMethod.java

Source

/*
 * R : A Computer Language for Statistical Data Analysis
 * Copyright (C) 1995, 1996  Robert Gentleman and Ross Ihaka
 * Copyright (C) 1997--2008  The R Development Core Team
 * Copyright (C) 2003, 2004  The R Foundation
 * Copyright (C) 2010 bedatadriven
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package r.jvmi.binding;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.apache.commons.math.complex.Complex;
import r.jvmi.annotations.*;
import r.jvmi.wrapper.WrapperRuntime;
import r.lang.*;
import r.lang.PairList.Node;
import r.lang.exception.ControlFlowException;
import r.lang.exception.EvalException;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;

import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;

/**
 * Wraps a {@code java.lang.reflect.Method} and provides
 * useful introspection methods.
 */
public class JvmMethod implements Comparable<JvmMethod> {
    private Method method;
    private List<Argument> arguments;
    private List<Argument> formals;
    private boolean recycle;

    private static final Class[] ATOMIC_TYPES = { Boolean.TYPE, Boolean.class, Logical.class, Integer.TYPE,
            Integer.class, Double.TYPE, Double.class, Complex.class, String.class };

    public JvmMethod(Method method) {
        this.method = method;

        ImmutableList.Builder<Argument> argumentsBuilder = ImmutableList.builder();
        for (int i = 0; i != method.getParameterTypes().length; ++i) {
            argumentsBuilder.add(new Argument(method, i));
        }
        this.arguments = argumentsBuilder.build();
        this.formals = ImmutableList.copyOf(Iterables.filter(arguments, new IsFormal()));

        computeRecycling();
    }

    private void computeRecycling() {

        // is recycling disabled for the whole method?
        Recycle methodRecycle = method.getAnnotation(Recycle.class);
        if (methodRecycle != null && !methodRecycle.value()) {
            this.recycle = false;
            return;
        }

        // determine whether Recycling is explicitly defined
        boolean implicitRecycling = true;
        for (Argument formal : formals) {
            if (formal.isAnnotatedWith(Recycle.class)) {
                implicitRecycling = false;
                break;
            }
        }

        // check whether a method with this return type is eligible at all for recycling
        if (!(isAtomic(method.getReturnType())
                || (SEXP.class.isAssignableFrom(method.getReturnType()) && !implicitRecycling))) {
            this.recycle = false;
            return;
        }

        for (Argument formal : formals) {
            formal.recycle = formal.isAnnotatedWith(Recycle.class)
                    || (implicitRecycling && formal.isAtomicElementType() && !formal.hasName());
            if (formal.recycle) {
                this.recycle = true;
            }
        }
    }

    public static List<JvmMethod> findOverloads(Class clazz, String name, String alias) {
        List<JvmMethod> methods = Lists.newArrayList();
        if (clazz != null) {
            for (Method method : clazz.getMethods()) {

                if (isPublic(method.getModifiers()) && isStatic(method.getModifiers())
                        && (method.getName().equals(alias) || method.getName().equals(name)
                                || alias(method).equals(name))) {
                    methods.add(new JvmMethod(method));
                }
            }
        }
        validate(methods);
        return methods;
    }

    public static String alias(Method method) {
        Primitive alias = method.getAnnotation(Primitive.class);
        return alias == null ? "" : alias.value();
    }

    /**
     * @return  true if this overload will handle the call itself, that is, it
     * has a signature of (EnvExp, LangExp)
     */
    public boolean acceptsCall() {
        return arguments.size() == 3 && arguments.get(0).getClazz().equals(Context.class)
                && arguments.get(1).getClazz().equals(Environment.class)
                && arguments.get(2).getClazz().equals(FunctionCall.class);
    }

    public boolean acceptsArgumentList() {
        for (Argument formal : formals) {
            if (formal.isAnnotatedWith(ArgumentList.class)) {
                return true;
            }
        }
        return false;
    }

    public boolean isRecycle() {
        return recycle;
    }

    public boolean isStrict() {
        for (Argument formal : getFormals()) {
            if (!formal.isEvaluated() && !formal.isSymbol()) {
                return false;
            }
        }
        return true;
    }

    public boolean isGeneric() {
        return method.getAnnotation(Generic.class) != null
                || method.getDeclaringClass().getAnnotation(GroupGeneric.class) != null;
    }

    public boolean isGroupGeneric() {
        return method.getDeclaringClass().getAnnotation(GroupGeneric.class) != null
                || method.getAnnotation(GroupGeneric.class) != null;
    }

    public String getGenericGroup() {
        return method.getDeclaringClass().getSimpleName();
    }

    /**
     * Determines whether all attributes should be preserved from the arguments in the return type. 
     * <p>For example:
     * <ul>
     * <li>class(x) == class(-x)</li>
     * <li>attr(x,'foo') == attr(-x, 'foo')</li>
     * 
     * 
     * 
     * @return
     */
    public boolean shouldPreserveAllAttributes() {

        return getReturnType().equals(getFormals().get(0).getClass());
    }

    public PreserveAttributeStyle getPreserveAttributesStyle() {
        PreserveAttributes annotation = method.getAnnotation(PreserveAttributes.class);
        return annotation == null ? PreserveAttributeStyle.SPECIAL : annotation.value();
    }

    /**
     * @return the name to use for generic dispatch
     */
    public String getGenericName() {
        Primitive primitive = method.getAnnotation(Primitive.class);
        if (primitive != null && primitive.value() != null) {
            return primitive.value();
        }
        return method.getName();
    }

    public List<Argument> getAllArguments() {
        return arguments;
    }

    /**
     * @return true if this method's arguments exactly match the {@code expectedArgumentTypes}
     */
    public boolean argumentListEquals(Class... expectedArgumentTypes) {
        return Arrays.equals(method.getParameterTypes(), expectedArgumentTypes);
    }

    public Class getDeclaringClass() {
        return method.getDeclaringClass();
    }

    /**
     * Invokes the underlying static method
     *
      * @param arguments
     * @param <X>
     * @return
     */
    public <X> X invoke(Object... arguments) {
        try {
            return (X) method.invoke(null, arguments);
        } catch (IllegalAccessException e) {
            throw new EvalException("Access exception while invoking method:\n" + method.toString(), e);
        } catch (InvocationTargetException e) {
            if (e.getCause() instanceof ControlFlowException) {
                throw (ControlFlowException) e.getCause();
            } else if (e.getCause() instanceof EvalException) {
                // Rethrow eval Exceptions
                throw (EvalException) e.getCause();
            } else {
                // wrap checked exceptions
                throw new EvalException(e.getCause());
            }
        } catch (IllegalArgumentException e) {
            throw new EvalException("IllegalArgumentException while invoking " + method.toString());
        }
    }

    /**
     * Invokes the method with the given arguments and converts the return value to
     * an R {@code SEXP} and wraps the {@code SEXP} in a {@code EvalResult}.
     */
    public SEXP invokeAndWrap(Context context, Object... arguments) {
        Object result = invoke(arguments);

        if (method.getReturnType() == Void.TYPE) {
            context.setInvisibleFlag();
            return Null.INSTANCE;
        } else {
            return SEXPFactory.fromJava(result);
        }
    }

    public SEXP invokeWithContextAndWrap(Context context, Environment rho, Object[] formals) {
        return invokeAndWrap(context, assembleArgumentListWithContext(context, rho, formals));
    }

    public Object invokeWithContext(Context context, Environment rho, Object[] formals) {
        return invoke(assembleArgumentListWithContext(context, rho, formals));
    }

    private Object[] assembleArgumentListWithContext(Context context, Environment rho, Object[] formals) {
        Object params[] = new Object[arguments.size()];
        int formalIndex = 0;

        for (int i = 0; i != arguments.size(); i++) {
            if (arguments.get(i).isContextual()) {
                Class clazz = arguments.get(i).getClazz();
                if (clazz.equals(Environment.class)) {
                    params[i] = rho;
                } else if (clazz.equals(Context.class)) {
                    params[i] = context;
                } else {
                    throw new UnsupportedOperationException(
                            String.format("Cannot inject argument of type '%s' into method %s", clazz.getName(),
                                    this.toString()));
                }
            } else {
                params[i] = formals[formalIndex++];
            }
        }
        return params;
    }

    public Class getReturnType() {
        return method.getReturnType();
    }

    public boolean returnsVoid() {
        return method.getReturnType() == Void.class || method.getReturnType() == Void.TYPE;
    }

    public Object getName() {
        return method.getName();
    }

    public int countPositionalFormals() {
        return getPositionalFormals().size();
    }

    public List<Argument> getPositionalFormals() {
        List<Argument> list = Lists.newArrayList();
        for (Argument formal : getFormals()) {
            if (formal.isAnnotatedWith(ArgumentList.class) || formal.isAnnotatedWith(NamedFlag.class)) {
                break;
            }
            list.add(formal);
        }
        return list;
    }

    public void appendFriendlySignatureTo(StringBuilder sb) {
        appendFriendlySignatureTo(method.getName(), sb);
    }

    public void appendFriendlySignatureTo(String name, StringBuilder sb) {
        sb.append(name).append("(");
        boolean needsComma = false;
        for (Argument argument : arguments) {
            if (!argument.isContextual()) {
                if (needsComma) {
                    sb.append(", ");
                } else {
                    needsComma = true;
                }
                if (argument.isAnnotatedWith(ArgumentList.class)) {
                    sb.append("...");
                } else {
                    sb.append(FriendlyTypesNames.get().format(argument.getClazz()));
                    if (!argument.isRecycle() && argument.isAtomicElementType()) {
                        sb.append("(1)");
                    }
                }
            }
        }
        sb.append(")");
    }

    public List<Argument> getFormals() {
        return formals;
    }

    public boolean isHiddenBy(JvmMethod other) {
        if (formals.size() != other.getFormals().size()) {
            return false;
        }
        for (int i = 0; i != formals.size(); ++i) {
            if (!formals.get(i).getClazz().isAssignableFrom(other.getFormals().get(i).getClazz())) {
                return false;
            }
        }
        return true;
    }

    @Override
    public int compareTo(JvmMethod o) {
        if (formals.size() != o.getFormals().size()) {
            return formals.size() - o.getFormals().size();
        }
        if (isHiddenBy(o)) {
            return -1;
        } else if (o.isHiddenBy(this)) {
            return +1;
        }
        return 0;
    }

    @Override
    public String toString() {
        return method.toString();
    }

    public boolean isAnnotatedWith(Class<? extends Annotation> annotationClass) {
        return method.isAnnotationPresent(annotationClass);
    }

    public boolean acceptsNA() {
        return method.isAnnotationPresent(AllowNA.class);
    }

    public int getFormalIndexByName(String name) {
        Preconditions.checkNotNull(name);
        for (int i = 0; i != formals.size(); ++i) {
            if (name.equals(formals.get(i).getName())) {
                return i;
            }
        }
        return -1;
    }

    public class Argument {
        private int index;
        private Class clazz;
        private boolean contextual = false;
        private boolean evaluated = true;
        private boolean symbol;
        private String name;
        public boolean recycle;
        public boolean atomicType;
        public boolean defaultValue;

        public ExplicitConverter explicitConverter;

        public Argument(Method method, int index) {
            clazz = method.getParameterTypes()[index];
            this.index = index;

            for (Annotation annotation : method.getParameterAnnotations()[index]) {
                if (annotation instanceof Current) {
                    contextual = true;

                } else if (annotation instanceof Evaluate) {
                    evaluated = ((Evaluate) annotation).value();

                } else if (annotation instanceof NamedFlag) {
                    name = ((NamedFlag) annotation).value();

                } else if (annotation instanceof DefaultValue) {
                    defaultValue = ((DefaultValue) annotation).value();

                } else if (annotation instanceof InvokeAsCharacter) {
                    evaluated = false;
                    explicitConverter = new AsCharacterConverter();
                }
            }

            symbol = (clazz == Symbol.class);
            atomicType = isAtomic(clazz);
        }

        public boolean isAssignableFrom(Object value) {
            return clazz.isAssignableFrom(value.getClass());
        }

        public Type getTypeArgument(int typeArgumentIndex) {
            Type argType = method.getGenericParameterTypes()[index];
            if (argType instanceof ParameterizedType) {
                return (Class) ((ParameterizedType) argType).getActualTypeArguments()[typeArgumentIndex];
            }
            return null;
        }

        public boolean isAnnotatedWith(Class<? extends Annotation> annotationClass) {
            for (Annotation annotation : method.getParameterAnnotations()[index]) {
                if (annotation.annotationType() == annotationClass) {
                    return true;
                }
            }
            return false;
        }

        public Class getClazz() {
            return clazz;
        }

        public boolean isContextual() {
            return contextual;
        }

        public boolean isEvaluated() {
            return evaluated;
        }

        public boolean isSymbol() {
            return symbol;
        }

        public boolean isAtomicElementType() {
            return atomicType;
        }

        public boolean isRecycle() {
            return recycle;
        }

        public boolean hasName() {
            return name != null;
        }

        public String getName() {
            return name;
        }

        public boolean getDefaultValue() {
            return defaultValue;
        }

        public int getIndex() {
            return index;
        }

        /**
         * 
         * @return then name of the R-language converter function
         * that will first convert provided arguments.
         */
        public ExplicitConverter getExplicitConverter() {
            return explicitConverter;
        }

        /**
         * 
         * @return true if this argument has it's own R-language converter
         * function (like "as.character")
         */
        public boolean hasExplicitConverter() {
            return explicitConverter != null;
        }

    }

    private class IsFormal implements Predicate<Argument> {
        @Override
        public boolean apply(Argument input) {
            return !input.isContextual();
        }
    }

    public static void validate(List<JvmMethod> methods) {
        for (int i = 0; i != methods.size(); ++i) {
            for (int j = 0; j != methods.size(); ++j) {
                if (i != j) {
                    JvmMethod x = methods.get(i);
                    JvmMethod y = methods.get(j);

                    if (x.isHiddenBy(y)) {
                        throw new EvalException(formatHiddenMethod(x, y));
                    }
                }
            }
        }
    }

    private static String formatHiddenMethod(JvmMethod x, JvmMethod y) {
        StringBuilder sb = new StringBuilder();
        sb.append("Primitive method\n\t");
        x.appendFriendlySignatureTo(sb);
        sb.append("\nis hidden by\n\t");
        y.appendFriendlySignatureTo(sb);
        return sb.append("\n").toString();
    }

    private boolean isAtomic(Class clazz) {
        for (int i = 0; i != ATOMIC_TYPES.length; ++i) {
            if (clazz.equals(ATOMIC_TYPES[i])) {
                return true;
            }
        }
        return false;
    }

    public interface ExplicitConverter {
        SEXP convert(Context context, Environment rho, SEXP provided);
    }

    public class AsCharacterConverter implements ExplicitConverter {

        @Override
        public SEXP convert(Context context, Environment rho, SEXP provided) {
            return WrapperRuntime.invokeAsCharacter(context, rho, provided);
        }
    }

}