net.sf.jasperreports.compilers.GroovyEvaluator.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jasperreports.compilers.GroovyEvaluator.java

Source

/*
 * JasperReports - Free Java Reporting Library.
 * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
 * http://www.jaspersoft.com
 *
 * Unless you have purchased a commercial license agreement from Jaspersoft,
 * the following license terms apply:
 *
 * This program is part of JasperReports.
 *
 * JasperReports 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.
 *
 * JasperReports 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 JasperReports. If not, see <http://www.gnu.org/licenses/>.
 */
package net.sf.jasperreports.compilers;

import groovy.lang.ExpandoMetaClass;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyRuntimeException;
import groovy.lang.MetaClass;
import groovy.lang.MetaMethod;
import groovy.lang.MissingMethodException;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.sf.jasperreports.engine.JRExpression;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.fill.JREvaluator;
import net.sf.jasperreports.engine.fill.JRExpressionEvalException;
import net.sf.jasperreports.engine.fill.JasperReportsContextAware;
import net.sf.jasperreports.functions.FunctionSupport;
import net.sf.jasperreports.functions.FunctionsUtil;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.codehaus.groovy.runtime.MethodClosure;
import org.codehaus.groovy.runtime.metaclass.ClosureMetaMethod;

/**
 * Groovy expression evaluator that compiles expressions at fill time.
 * 
 * @author Lucian Chirita (lucianc@users.sourceforge.net)
 * @see JRGroovyCompiler
 */
public abstract class GroovyEvaluator extends JREvaluator implements JasperReportsContextAware {

    private static final Log log = LogFactory.getLog(GroovyEvaluator.class);
    public static final String EXCEPTION_MESSAGE_KEY_FUNCTION_NOT_FOUND = "compilers.groovy.function.not.found";

    //copied from groovy.lang.MetaClassImpl.chooseMostSpecificParams()
    private static final Pattern GROOVY_EXCEPTION_PATTERN_AMBIGUOUS_NULL = Pattern.compile(
            "Ambiguous method overloading for method.*Cannot resolve which method to invoke for \\[null\\] due to overlapping prototypes between.*",
            Pattern.DOTALL);

    private FunctionsUtil functionsUtil;

    private List<ClosureMetaMethod> functionMethods = new ArrayList<ClosureMetaMethod>();

    @Override
    public void setJasperReportsContext(JasperReportsContext context) {
        this.functionsUtil = FunctionsUtil.getInstance(context);
    }

    @Override
    protected Object handleEvaluationException(JRExpression expression, Throwable e)
            throws JRExpressionEvalException {
        if (ignoreNPE && e instanceof GroovyRuntimeException && e.getMessage() != null) {
            //in Groovy 2.0.1, 1 + null (and other e.g. BigDecimal * null) was throwing NPE
            //in 2.4.3, it fails with "Ambiguous method overloading..."
            //we're catching this exception (by message) and treating it like a NPE
            Matcher matcher = GROOVY_EXCEPTION_PATTERN_AMBIGUOUS_NULL.matcher(e.getMessage());
            if (matcher.matches()) {
                //evaluating the expression to null to match Groovy 2.0.1 behaviour
                return null;
            }
        }

        //throw the exception
        return super.handleEvaluationException(expression, e);
    }

    protected Object functionCall(String methodName, Object[] args) {
        Method functionMethod = functionsUtil.getMethod4Function(methodName);
        if (functionMethod == null) {
            throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_FUNCTION_NOT_FOUND, new Object[] { methodName });
        }

        // we're relying on this, if we'll ever resolve functions to methods 
        // that have different names we need to adapt the code
        assert functionMethod.getName().equals(methodName);

        Class<?> functionClass = functionMethod.getDeclaringClass();
        MetaClass functionMetaClass = DefaultGroovyMethods.getMetaClass(functionClass);

        MethodClosure functionMethodClosure = null;
        if (FunctionSupport.class.isAssignableFrom(functionClass)) {
            // search for an instance method that applies to the arguments
            MetaMethod metaMethod = functionMetaClass.getMetaMethod(methodName, args);
            if (metaMethod != null && metaMethod.isPublic()) {
                @SuppressWarnings("unchecked")
                FunctionSupport functionObject = getFunctionSupport(
                        (Class<? extends FunctionSupport>) functionClass);
                functionMethodClosure = new MethodClosure(functionObject, methodName);

                if (log.isDebugEnabled()) {
                    log.debug("found public instance method " + metaMethod + " in class " + functionClass);
                }
            }
        }

        if (functionMethodClosure == null) {
            // searching for a static method that applies to the arguments
            MetaMethod metaMethod = functionMetaClass.getStaticMetaMethod(methodName, args);
            if (metaMethod != null && metaMethod.isPublic()) {
                // creating a static method closure
                functionMethodClosure = new MethodClosure(functionClass, methodName);

                if (log.isDebugEnabled()) {
                    log.debug("found public static method " + metaMethod + " in class " + functionClass);
                }
            }
        }

        if (functionMethodClosure == null) {
            // we didn't find a public instance/static method that applies to the arguments
            throw new MissingMethodException(methodName, functionMetaClass.getTheClass(), args);
        }

        // adding the function methods for the name to the list of registered methods.
        // we need to add all the methods with the same name in the beginning because once we add one method 
        // methodMissing might no longer be called.
        // we need to reregister all methods on each new method since we create ExpandoMetaClass from scratch.
        addFunctionClosureMethods(functionMethodClosure, methodName);

        // adding the methods to the evaluator MetaClass so that it doesn't go again into methodMissing
        ExpandoMetaClass extendedMetaClass = new ExpandoMetaClass(getClass(), false);
        registerMethods(extendedMetaClass);
        extendedMetaClass.initialize();
        DefaultGroovyMethods.setMetaClass((GroovyObject) this, extendedMetaClass);

        // returning what we have to return
        return functionMethodClosure.call(args);
    }

    protected void addFunctionClosureMethods(MethodClosure methodClosure, String functionName) {
        // calling registerInstanceMethod(String, Closure) would register all methods, but we only want public methods
        List<MetaMethod> closureMethods = ClosureMetaMethod.createMethodList(functionName, getClass(),
                methodClosure);
        for (MetaMethod metaMethod : closureMethods) {
            if (!(metaMethod instanceof ClosureMetaMethod)) {
                // should not happen
                log.warn("Got unexpected closure method " + metaMethod + " of type "
                        + metaMethod.getClass().getName());
                continue;
            }

            ClosureMetaMethod closureMethod = (ClosureMetaMethod) metaMethod;
            if (!closureMethod.getDoCall().isPublic()) {
                if (log.isDebugEnabled()) {
                    log.debug("method " + closureMethod.getDoCall() + " is not public, not registering");
                }
                continue;
            }

            if (log.isDebugEnabled()) {
                log.debug("creating closure method for " + closureMethod.getDoCall());
            }

            functionMethods.add(closureMethod);
        }
    }

    protected void registerMethods(ExpandoMetaClass extendedMetaClass) {
        for (ClosureMetaMethod closureMethod : functionMethods) {
            extendedMetaClass.registerInstanceMethod(closureMethod);
        }
    }
}