org.netxilia.spi.impl.formula.function.MethodWrapper.java Source code

Java tutorial

Introduction

Here is the source code for org.netxilia.spi.impl.formula.function.MethodWrapper.java

Source

/*******************************************************************************
 * 
 * Copyright 2010 Alexandru Craciun, and individual contributors as indicated
 * by the @authors tag. 
 * 
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 * 
 * This software 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 ******************************************************************************/
package org.netxilia.spi.impl.formula.function;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Iterator;

import org.apache.commons.beanutils.ConvertUtils;
import org.netxilia.api.formula.IFormulaContext;
import org.netxilia.api.reference.AreaReference;
import org.netxilia.api.value.ErrorValue;
import org.netxilia.api.value.ErrorValueType;
import org.netxilia.api.value.GenericValueUtils;
import org.netxilia.api.value.IDelegateValue;
import org.netxilia.api.value.IGenericValue;
import org.netxilia.api.value.ReferenceValue;
import org.netxilia.api.value.RichValue;
import org.netxilia.spi.formula.Function;
import org.netxilia.spi.impl.formula.parser.ASTBaseNode;

/**
 * This implements the interface {@link IFunction} using a given method from an object containing a list of different
 * functions. This wrapper does the proper type conversions between the ASTNodes and basic types.
 * 
 * @author <a href='mailto:ax.craciun@gmail.com'>Alexandru Craciun</a>
 * 
 */
public class MethodWrapper implements IFunction {
    private static org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(MethodWrapper.class);

    private final Object instance;
    private final Method method;
    private final IParamConverter[] paramConverters;
    private final boolean cacheable;

    public MethodWrapper(Object instance, Method method) {
        this.instance = instance;
        this.method = method;
        Class<?>[] paramTypes = method.getParameterTypes();
        paramConverters = new IParamConverter[paramTypes.length];

        Type[] genericParameterTypes = method.getGenericParameterTypes();

        for (int i = 0; i < genericParameterTypes.length; ++i) {
            Type genericParameterType = genericParameterTypes[i];
            if (genericParameterType instanceof ParameterizedType) {
                ParameterizedType aType = (ParameterizedType) genericParameterType;

                if (Iterator.class.isAssignableFrom((Class<?>) aType.getRawType())) {
                    Class<?> componentType = (Class<?>) aType.getActualTypeArguments()[0];
                    paramConverters[i] = new IteratorConverter(componentType);
                }
            } else {
                Class<?> paramType = (Class<?>) genericParameterType;
                if (IGenericValue.class == paramType) {
                    paramConverters[i] = new LazyParamConverter();
                } else if (RichValue.class == paramType) {
                    paramConverters[i] = new RichValueParamConverter();
                } else {
                    paramConverters[i] = new BasicTypeConverter(paramType);
                }
            }
        }

        Function annotation = this.method.getAnnotation(Function.class);
        cacheable = annotation == null || annotation.cacheable();
    }

    @Override
    public IGenericValue eval(IFormulaContext context, ASTBaseNode... paramNodes) {
        Object[] params = new Object[paramConverters.length];
        for (int i = 0; i < paramConverters.length; ++i) {
            params[i] = paramConverters[i].convert(context, paramNodes, i);
        }

        Object returnValue;
        try {
            returnValue = method.invoke(instance, params);
        } catch (Exception e) {
            log.error("Exception executing method:" + method + " with params:" + Arrays.toString(params) + ":" + e,
                    e);
            return new ErrorValue(ErrorValueType.VALUE);
        }
        if (returnValue == null) {
            return null;
        }
        if (returnValue instanceof IGenericValue) {
            return (IGenericValue) returnValue;
        }
        if (returnValue instanceof AreaReference) {
            return new ReferenceValue((AreaReference) returnValue, context);
        }
        return GenericValueUtils.objectAsValue(returnValue);
    }

    @Override
    public String getDocumentation() {
        // TODO Auto-generated method stub
        return null;
    }

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

    @Override
    public String getParamNames() {
        // TODO Auto-generated method stub
        return null;
    }

    private interface IParamConverter {
        public Object convert(IFormulaContext context, ASTBaseNode[] nodes, int pos);
    }

    private class LazyParamConverter implements IParamConverter {
        @Override
        public Object convert(IFormulaContext context, ASTBaseNode[] nodes, int pos) {
            return new LazyGenericValue(nodes[pos], context);
        }

    }

    private class BasicTypeConverter implements IParamConverter {
        private Class<?> paramType;

        public BasicTypeConverter(Class<?> paramType) {
            this.paramType = paramType;
        }

        @Override
        public Object convert(IFormulaContext context, ASTBaseNode[] nodes, int pos) {
            if (pos >= nodes.length) {
                // i have less parameters in the formula than in the function definition
                // use default values
                // get the default value as specified by the converters
                return ConvertUtils.convert((String) null, paramType);
            }
            IGenericValue value = nodes[pos].eval(context);
            return GenericValueUtils.convert(value, paramType);
        }
    }

    private class RichValueParamConverter implements IParamConverter {
        public RichValueParamConverter() {
        }

        @Override
        public Object convert(IFormulaContext context, ASTBaseNode[] nodes, int pos) {
            IGenericValue value = nodes[pos].eval(context);
            if (value == null) {
                return null;
            }
            while (value instanceof IDelegateValue) {
                if (value instanceof RichValue) {
                    return value;
                }
                value = ((IDelegateValue) value).getGenericValue();
            }
            return new RichValue(value);
        }
    }

    private class IteratorConverter implements IParamConverter {
        private final Class<?> elementType;

        public IteratorConverter(Class<?> elementType) {
            this.elementType = elementType;
        }

        @SuppressWarnings("unchecked")
        @Override
        public Object convert(IFormulaContext context, ASTBaseNode[] nodes, int pos) {
            return new ParameterIterator(elementType, context, nodes, pos);
        }
    }

    @Override
    public boolean isCacheble() {
        return cacheable;
    }
}