<commit_before>/**
 * This file is part of AMEE.
 *
 * AMEE is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * AMEE is free software and 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Created by http://www.dgen.net.
 * Website http://www.amee.cc
 */
package com.amee.calculation.service;

import com.amee.domain.*;
import com.amee.domain.algorithm.Algorithm;
import com.amee.domain.data.ItemDefinition;
import com.amee.domain.data.ItemValueDefinition;
import com.amee.domain.data.ItemValueMap;
import com.amee.domain.item.BaseItem;
import com.amee.domain.item.BaseItemValue;
import com.amee.domain.item.UsableValuePredicate;
import com.amee.domain.item.data.DataItem;
import com.amee.domain.item.profile.BaseProfileItemValue;
import com.amee.domain.item.profile.ProfileItem;
import com.amee.domain.item.profile.ProfileItemNumberValue;
import com.amee.domain.item.profile.ProfileItemTextValue;
import com.amee.domain.profile.CO2CalculationService;
import com.amee.domain.sheet.Choices;
import com.amee.platform.science.*;
import com.amee.service.item.ItemService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.org.mozilla.javascript.internal.JavaScriptException;

import javax.script.ScriptException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class CalculationService implements CO2CalculationService, BeanFactoryAware {

    private final Log log = LogFactory.getLog(getClass());
    private final Log scienceLog = LogFactory.getLog("science");

    @Autowired
    private AMEEStatistics ameeStatistics;

    @Autowired
    private IItemService dataItemService;

    @Autowired
    private IItemService profileItemService;

    private AlgorithmRunner algorithmRunner = new AlgorithmRunner();

    // Set by Spring context. The BeanFactory used to retrieve ProfileFinder and DataFinder instances.
    private BeanFactory beanFactory;

    /**
     * Calculate and always set the GHG amounts for a ProfileItem.
     *
     * @param profileItem - the ProfileItem for which to calculate GHG amounts
     */
    public void calculate(ProfileItem profileItem) {

        // End marker ProfileItems can only have zero amounts.
        // Calculate amounts for ProfileItem if an Algorithm is available.
        // Some ProfileItems are from ItemDefinitions which do not have Algorithms and
        // hence do not support calculations.
        if (!profileItem.isEnd() && profileItem.supportsCalculation()) {
            Algorithm algorithm = profileItem.getItemDefinition().getAlgorithm(Algorithm.DEFAULT);
            if (algorithm != null) {
                Map<String, Object> values = getValues(profileItem);
                profileItem.setAmounts(calculate(algorithm, values));
            }
        }
    }

    /**
     * Calculate and return the GHG amounts for a DataItem and a set of user specified values.
     * <p/>
     * Note: I am unsure if this is in active use (SM)
     *
     * @param dataItem         - the DataItem for the calculation
     * @param userValueChoices - user supplied value choices
     * @param version          - the APIVersion. This is used to determine the correct ItemValueDefinitions to load into the calculation
     * @return the calculated GHG amounts
     */
    public ReturnValues calculate(DataItem dataItem, Choices userValueChoices, APIVersion version) {
        Algorithm algorithm = dataItem.getItemDefinition().getAlgorithm(Algorithm.DEFAULT);
        if (algorithm != null) {
            Map<String, Object> values = getValues(dataItem, userValueChoices, version);
            return calculate(algorithm, values);
        }
        return new ReturnValues();
    }

    /**
     * Calculate and return the GHG amounts for given the provided algorithm and input values.
     * <p/>
     * Intended to be used publicly in test harnesses when passing the modified algorithm content and input values
     * for execution is desirable.
     *
     * @param algorithm the algorithm to use
     * @param values    input values for the algorithm
     * @return the algorithm result
     */
    public ReturnValues calculate(Algorithm algorithm, Map<String, Object> values) {

        if (log.isDebugEnabled()) {
            log.debug("calculate()");
            log.debug("calculate() - algorithm uid: " + algorithm.getUid());
            log.debug("calculate() - input values: " + values);
            log.debug("calculate() - starting calculation");
        }

        ReturnValues returnValues;
        final long startTime = System.nanoTime();

        try {
            returnValues = algorithmRunner.evaluate(algorithm, values);
        } catch (ScriptException e) {

            // Bubble up parameter missing or format exceptions from the
            // algorithms (the only place where these validations can be performed.
            IllegalArgumentException iae = AlgorithmRunner.getIllegalArgumentException(e);
            if (iae != null) {
                throw iae;
            }

            // Throw CalculationException for Exceptions from the JavaScript 'throw' keyword.
            if ((e.getCause() != null) && e.getCause() instanceof JavaScriptException) {

                // Writing java programs that rely on sun.* is risky: they are not portable, and are not supported.
                // http://java.sun.com/products/jdk/faq/faq-sun-packages.html
                JavaScriptException jse = (JavaScriptException) e.getCause();
                throw new CalculationException(
                        "Caught Exception in Algorithm (" +
                                algorithm.getItemDefinition().getName() +
                                ", " +
                                algorithm.getName() +
                                ", " +
                                jse.lineNumber() +
                                ", " +
                                jse.columnNumber() +
                                "): " + jse.getValue());

            }

            // Log all other errors to the science log...
            scienceLog.warn(
                    "Caught ScriptException in Algorithm (" +
                            algorithm.getItemDefinition().getName() +
                            ", " +
                            algorithm.getName() +
                            "): " + e.getMessage());

            // ...and return an empty result by default.
            returnValues = new ReturnValues();
        } finally {
            ameeStatistics.addToThreadCalculationDuration(System.nanoTime() - startTime);
        }

        if (log.isDebugEnabled()) {
            log.debug("calculate() - finished calculation");
            log.debug("calculate() - Amounts: " + returnValues);
        }
<commit_msg>PL-9526 - Return a MARV error response when algorithm fails.<commit_after>/**
 * This file is part of AMEE.
 *
 * AMEE is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * AMEE is free software and 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Created by http://www.dgen.net.
 * Website http://www.amee.cc
 */
package com.amee.calculation.service;

import com.amee.domain.*;
import com.amee.domain.algorithm.Algorithm;
import com.amee.domain.data.ItemDefinition;
import com.amee.domain.data.ItemValueDefinition;
import com.amee.domain.data.ItemValueMap;
import com.amee.domain.item.BaseItem;
import com.amee.domain.item.BaseItemValue;
import com.amee.domain.item.UsableValuePredicate;
import com.amee.domain.item.data.DataItem;
import com.amee.domain.item.profile.BaseProfileItemValue;
import com.amee.domain.item.profile.ProfileItem;
import com.amee.domain.item.profile.ProfileItemNumberValue;
import com.amee.domain.item.profile.ProfileItemTextValue;
import com.amee.domain.profile.CO2CalculationService;
import com.amee.domain.sheet.Choices;
import com.amee.platform.science.*;
import com.amee.service.item.ItemService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import sun.org.mozilla.javascript.internal.JavaScriptException;

import javax.script.ScriptException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class CalculationService implements CO2CalculationService, BeanFactoryAware {

    private final Log log = LogFactory.getLog(getClass());
    private final Log scienceLog = LogFactory.getLog("science");

    @Autowired
    private AMEEStatistics ameeStatistics;

    @Autowired
    private IItemService dataItemService;

    @Autowired
    private IItemService profileItemService;

    private AlgorithmRunner algorithmRunner = new AlgorithmRunner();

    // Set by Spring context. The BeanFactory used to retrieve ProfileFinder and DataFinder instances.
    private BeanFactory beanFactory;

    /**
     * Calculate and always set the GHG amounts for a ProfileItem.
     *
     * @param profileItem - the ProfileItem for which to calculate GHG amounts
     */
    public void calculate(ProfileItem profileItem) {

        // End marker ProfileItems can only have zero amounts.
        // Calculate amounts for ProfileItem if an Algorithm is available.
        // Some ProfileItems are from ItemDefinitions which do not have Algorithms and
        // hence do not support calculations.
        if (!profileItem.isEnd() && profileItem.supportsCalculation()) {
            Algorithm algorithm = profileItem.getItemDefinition().getAlgorithm(Algorithm.DEFAULT);
            if (algorithm != null) {
                Map<String, Object> values = getValues(profileItem);
                profileItem.setAmounts(calculate(algorithm, values));
            }
        }
    }

    /**
     * Calculate and return the GHG amounts for a DataItem and a set of user specified values.
     * <p/>
     * Note: I am unsure if this is in active use (SM)
     *
     * @param dataItem         - the DataItem for the calculation
     * @param userValueChoices - user supplied value choices
     * @param version          - the APIVersion. This is used to determine the correct ItemValueDefinitions to load into the calculation
     * @return the calculated GHG amounts
     */
    public ReturnValues calculate(DataItem dataItem, Choices userValueChoices, APIVersion version) {
        Algorithm algorithm = dataItem.getItemDefinition().getAlgorithm(Algorithm.DEFAULT);
        if (algorithm != null) {
            Map<String, Object> values = getValues(dataItem, userValueChoices, version);
            return calculate(algorithm, values);
        }
        return new ReturnValues();
    }

    /**
     * Calculate and return the GHG amounts for given the provided algorithm and input values.
     * <p/>
     * Intended to be used publicly in test harnesses when passing the modified algorithm content and input values
     * for execution is desirable.
     *
     * @param algorithm the algorithm to use
     * @param values    input values for the algorithm
     * @return the algorithm result
     */
    public ReturnValues calculate(Algorithm algorithm, Map<String, Object> values) {

        if (log.isDebugEnabled()) {
            log.debug("calculate()");
            log.debug("calculate() - algorithm uid: " + algorithm.getUid());
            log.debug("calculate() - input values: " + values);
            log.debug("calculate() - starting calculation");
        }

        ReturnValues returnValues;
        final long startTime = System.nanoTime();

        try {
            returnValues = algorithmRunner.evaluate(algorithm, values);
        } catch (ScriptException e) {

            // Bubble up parameter missing or format exceptions from the
            // algorithms (the only place where these validations can be performed.
            IllegalArgumentException iae = AlgorithmRunner.getIllegalArgumentException(e);
            if (iae != null) {
                throw iae;
            }

            // Throw CalculationException for Exceptions from the JavaScript 'throw' keyword.
            if ((e.getCause() != null) && e.getCause() instanceof JavaScriptException) {

                // Writing java programs that rely on sun.* is risky: they are not portable, and are not supported.
                // http://java.sun.com/products/jdk/faq/faq-sun-packages.html
                JavaScriptException jse = (JavaScriptException) e.getCause();
                throw new CalculationException(
                        "Caught Exception in Algorithm (" +
                                algorithm.getItemDefinition().getName() +
                                ", " +
                                algorithm.getName() +
                                ", " +
                                jse.lineNumber() +
                                ", " +
                                jse.columnNumber() +
                                "): " + jse.getValue());

            }

            // Log all other errors to the science log...
            scienceLog.warn(
                    "Caught ScriptException in Algorithm (" +
                            algorithm.getItemDefinition().getName() +
                            ", " +
                            algorithm.getName() +
                            "): " + e.getMessage());

            // ...and return an empty result by default.
            returnValues = new ReturnValues();
            returnValues.addNote("error", "Algorithm error.");
        } finally {
            ameeStatistics.addToThreadCalculationDuration(System.nanoTime() - startTime);
        }

        if (log.isDebugEnabled()) {
            log.debug("calculate() - finished calculation");
            log.debug("calculate() - Amounts: " + returnValues);
        }

{'fraction_non_alphanumeric': 0.08768850754030161, 'fraction_numerical': 0.0006500260010400416, 'mean_word_length': 2.4449171518137036, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1565158', 'n_tokens_mistral': 3877, 'n_tokens_neox': 3735, 'n_words': 1183}