/** * AnalyzerBeans * Copyright (C) 2014 Neopost - Customer Information Management * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * 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 Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.eobjects.analyzer.beans.transform; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.el.ValueExpression; import org.eobjects.analyzer.beans.api.Categorized; import org.eobjects.analyzer.beans.api.Configured; import org.eobjects.analyzer.beans.api.Description; import org.eobjects.analyzer.beans.api.Initialize; import org.eobjects.analyzer.beans.api.OutputColumns; import org.eobjects.analyzer.beans.api.StringProperty; import org.eobjects.analyzer.beans.api.Transformer; import org.eobjects.analyzer.beans.api.TransformerBean; import org.eobjects.analyzer.beans.categories.NumbersCategory; import org.eobjects.analyzer.data.InputColumn; import org.eobjects.analyzer.data.InputRow; import org.eobjects.analyzer.util.StringUtils; import de.odysseus.el.ExpressionFactoryImpl; import de.odysseus.el.util.SimpleContext; @TransformerBean("Math formula") @Description("Provide a mathematical formula to perform arbitrary calculations.\n" + "Formulas support basic operators like plus (+), minus (-), divide (/), multiply (*) and modulus (%).") @Categorized({ NumbersCategory.class }) public class MathFormulaTransformer implements Transformer<Number> { @Configured InputColumn<Number>[] _input; @Configured @StringProperty(emptyString = false, mimeType = "text/groovy") String _formula = "(col1 + col2) / col3"; private ExpressionFactoryImpl _factory; private Map<String, List<String>> _columnAliases; @Override public OutputColumns getOutputColumns() { return new OutputColumns("Formula result"); } @Initialize public void init() { _factory = new ExpressionFactoryImpl(); _columnAliases = new HashMap<String, List<String>>(); int i = 1; for (InputColumn<Number> inputColumn : _input) { final String name = inputColumn.getName(); final List<String> list = new ArrayList<String>(3); final String variableName1 = StringUtils.replaceWhitespaces(name.toLowerCase(), "_"); final String variableName2 = StringUtils.replaceWhitespaces(name.toLowerCase(), ""); list.add(variableName1); list.add(variableName2); list.add("col" + i); i++; _columnAliases.put(name, list); } } @Override public Number[] transform(InputRow inputRow) { final SimpleContext context = new SimpleContext(); for (InputColumn<Number> inputColumn : _input) { Number value = inputRow.getValue(inputColumn); ValueExpression valueExpression = _factory.createValueExpression(value, Number.class); List<String> aliases = _columnAliases.get(inputColumn.getName()); for (String alias : aliases) { context.setVariable(alias, valueExpression); } } final String expression = "#{" + _formula.toLowerCase() + "}"; final ValueExpression valueExpression = _factory.createValueExpression(context, expression, Number.class); try { final Object value = valueExpression.getValue(context); assert value instanceof Number; if (value instanceof Double) { double dbl = ((Double) value).doubleValue(); if (Double.isNaN(dbl) || Double.isInfinite(dbl)) { // we don't want to present infinity and NaN - null is a // better replacement. return new Number[] { null }; } } return new Number[] { (Number) value }; } catch (ArithmeticException e) { return new Number[] { null }; } } }