org.renjin.invoke.model.JvmMethod.java Source code

Java tutorial

Introduction

Here is the source code for org.renjin.invoke.model.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 org.renjin.invoke.model;

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 org.apache.commons.math.special.Gamma;
import org.renjin.eval.EvalException;
import org.renjin.invoke.annotations.*;
import org.renjin.sexp.Logical;
import org.renjin.sexp.Symbol;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
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 dataParallel;
    private boolean passNA;

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

    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()));

        DataParallel dpAnnotation = method.getAnnotation(DataParallel.class);

        // we special case the methods in java.lang.Math since we can't add
        // annotations to them
        this.dataParallel = dpAnnotation != null || method.getDeclaringClass().equals(Math.class)
                || method.getDeclaringClass().equals(Gamma.class);

        if (this.dataParallel) {
            this.passNA = dpAnnotation != null && dpAnnotation.passNA();

            // determine which arguments are looped over (recycled in
            // R lingo), and which hold a fixed value
            boolean implicitRecycling = isArgumentRecyclingImplicit();
            for (Argument arg : formals) {
                Recycle recycleAnnotation = arg.getAnnotation(Recycle.class);
                arg.recycle = arg.isAtomicElementType()
                        && (implicitRecycling || (recycleAnnotation == null || recycleAnnotation.value()));
            }
        }

    }

    /**
     * For @DataParallel methods, if none of the arguments are annotated with @Recycle
     * then we consider that we implicitly recycle over all of the arguments
     */
    private boolean isArgumentRecyclingImplicit() {
        for (Argument formal : formals) {
            if (formal.getAnnotation(Recycle.class) != null) {
                return false;
            }
        }
        return 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) {
        Builtin builtin = method.getAnnotation(Builtin.class);
        if (builtin != null) {
            return builtin.value();
        }
        Internal internal = method.getAnnotation(Internal.class);
        if (internal != null) {
            return internal.value();
        }
        return "";
    }

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

    public boolean isDataParallel() {
        return dataParallel;
    }

    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();
    }

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

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

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

    public Method getMethod() {
        return method;
    }

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

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

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

    public String 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 isDeferrable() {
        return isAnnotatedWith(Deferrable.class) || method.getDeclaringClass().equals(Math.class);
    }

    public boolean isPassNA() {
        return passNA;
    }

    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 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 Unevaluated) {
                    evaluated = false;

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

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

                } else if (annotation instanceof InvokeAsCharacter) {
                    evaluated = true;
                }
            }

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

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

        public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
            for (Annotation annotation : method.getParameterAnnotations()[index]) {
                if (annotation.annotationType() == annotationClass) {
                    return (T) annotation;
                }
            }
            return null;
        }

        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;
        }

        public boolean isVarArg() {
            return isAnnotatedWith(org.renjin.invoke.annotations.ArgumentList.class);
        }

        public boolean isNamedFlag() {
            return isAnnotatedWith(NamedFlag.class);
        }

        public CastStyle getCastStyle() {
            Cast cast = getAnnotation(Cast.class);
            if (cast == null) {
                return CastStyle.IMPLICIT;
            } else {
                return cast.value();
            }
        }
    }

    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;
    }
}