Java tutorial
/* * 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); } } }