JavaMethodExecutor.java

/**
 * VStar: a statistical analysis tool for variable star data.
 * Copyright (C) 2009  AAVSO (http://www.aavso.org/)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 */
package org.aavso.tools.vstar.vela;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * This class executes Java methods.
 */
public class JavaMethodExecutor extends FunctionExecutor {

    private Object instance;
    private Method method;

    /**
     * Construct a Java method executor.
     * 
     * @param instance       The instance of this class on which to invoke the
     *                       method.
     * @param funcName       The function's name.
     * @param method         The corresponding Java method object.
     * @param parameterNames The function's formal parameter names.
     * @param parameterTypes The function's parameter types.
     * @param returnType     The function's optional return type.
     * @param helpString     The optional help string.
     */
    public JavaMethodExecutor(Object instance, Method method, Optional<String> funcName, List<String> parameterNames,
            List<Type> parameterTypes, Optional<Type> returnType, Optional<String> helpString) {
        super(funcName, parameterNames, parameterTypes, returnType, helpString);
        this.instance = instance;
        this.method = method;

        if (parameterTypes.size() == parameterNames.size() + 1) {
            // We will get here if there is an underlying non-static Java method executor,
            // since this will require an extra parameter.
            ArrayList<String> extendedParams = new ArrayList<String>();
            extendedParams.add("OBJ");
            extendedParams.addAll(parameterNames);
            parameterNames = extendedParams;
        }
    }

    @Override
    public Optional<Operand> apply(List<Operand> operands) throws VeLaEvalError {
        return invokeJavaMethod(method, operands, getReturnType());
    }

    private Optional<Operand> invokeJavaMethod(Method method, List<Operand> operands, Optional<Type> retType) {
        Optional<Operand> retVal = null;

        try {
            // obj is null for static methods
            Object obj = null;

            if (!Modifier.isStatic(method.getModifiers())) {
                // For non-static methods, if instance is null, assume the first
                // operand is an object instance.
                if (instance == null) {
                    Operand op = operands.get(0);
                    obj = operands.get(0).toObject(Type.vela2Java(op.getType()));
                    operands.remove(0);
                } else {
                    // ...otherwise, use what's been passed in.
                    obj = instance;
                }
            }

            Class<?>[] javaParamTypes = method.getParameterTypes();

            Object[] objParams = new Object[operands.size()];
            int paramIndex = 0;
            for (Operand op : operands) {
                objParams[paramIndex] = op.toObject(javaParamTypes[paramIndex]);
                paramIndex++;
            }

            Operand result = Operand.object2Operand(retType.get(), method.invoke(obj, objParams));

            retVal = null;

            if (result != null) {
                retVal = Optional.of(result);
            } else {
                retVal = Optional.of(Operand.NO_VALUE);
            }

        } catch (Exception e) {
            throwVeLaEvalError(e);
        }

        return retVal;
    }

    @Override
    protected String getParametersString() {
        String paramsStr;

        if (parameterTypes == ANY_FORMAL_TYPES) {
            paramsStr = "ANY";
        } else {
            StringBuffer paramsBuf = new StringBuffer();

            for (int i = 0; i < parameterTypes.size(); i++) {
                // Note: when we can get real names from Javadoc, re-enable this
//                paramsBuf.append(parameterNames.get(i - 1));
//                paramsBuf.append(":");
                paramsBuf.append(parameterTypes.get(i));
                paramsBuf.append(" ");
            }

            paramsStr = paramsBuf.toString().trim();
        }

        return paramsStr;
    }

    // Helpers

    private void throwVeLaEvalError(Exception e) throws VeLaEvalError {
        String msg = e.getLocalizedMessage();
        if (msg == null) {
            msg = "Intrinsic function invocation error";
            if (funcName.isPresent()) {
                msg += ": " + funcName.get();
            }
        }
        throw new VeLaEvalError(msg);
    }
}