<commit_before>/*******************************************************************************
 * This file is part of Pebble.
 * 
 * Copyright (c) 2014 by <NAME>
 * 
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 ******************************************************************************/
package com.mitchellbosecke.pebble.node.expression;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.mitchellbosecke.pebble.error.AttributeNotFoundException;
import com.mitchellbosecke.pebble.error.PebbleException;
import com.mitchellbosecke.pebble.error.RootAttributeNotFoundException;
import com.mitchellbosecke.pebble.extension.NodeVisitor;
import com.mitchellbosecke.pebble.node.ArgumentsNode;
import com.mitchellbosecke.pebble.node.PositionalArgumentNode;
import com.mitchellbosecke.pebble.template.EvaluationContext;
import com.mitchellbosecke.pebble.template.PebbleTemplateImpl;

/**
 * Used to get an attribute from an object. It will look up attributes in the
 * following order: map entry, array item, list item, get method, is method, has
 * method, public method, public field.
 * 
 * @author Mitchell
 * 
 */
public class GetAttributeExpression implements Expression<Object> {

    private final Expression<?> node;

    private final String attributeName;

    private final ArgumentsNode args;

    /**
     * Potentially cached on first evaluation.
     */
    private final ConcurrentHashMap<Class<?>, Member> memberCache;

    public GetAttributeExpression(Expression<?> node, String attributeName) {
        this(node, attributeName, null);
    }

    public GetAttributeExpression(Expression<?> node, String attributeName, ArgumentsNode args) {
        this.node = node;
        this.attributeName = attributeName;
        this.args = args;

        /*
         * I dont imagine that users will often give different types to the same
         * template so we will give this cache a pretty small initial capacity.
         */
        this.memberCache = new ConcurrentHashMap<>(2, 0.9f, 1);
    }

    @Override
    public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throws PebbleException {
        Object object = node.evaluate(self, context);

        Object result = null;

        Object[] argumentValues = null;

        Member member = object == null ? null : memberCache.get(object.getClass());

        if (object != null && member == null) {

            /*
             * If, and only if, no arguments were provided does it make sense to
             * check maps/arrays/lists
             */
            if (args == null) {

                // first we check maps
                if (object instanceof Map && ((Map<?, ?>) object).containsKey(attributeName)) {
                    return ((Map<?, ?>) object).get(attributeName);
                }

                try {

                    // then we check arrays
                    if (object instanceof Object[]) {
                        Integer key = Integer.valueOf(attributeName);
                        Object[] arr = ((Object[]) object);
                        return arr[key];
                    }

                    // then lists
                    if (object instanceof List) {
                        Integer key = Integer.valueOf(attributeName);
                        @SuppressWarnings("unchecked")
                        List<Object> list = (List<Object>) object;
                        return list.get(key);
                    }
                } catch (NumberFormatException ex) {
                    // do nothing
                }

            }

            /*
             * turn args into an array of types and an array of values in order
             * to use them for our reflection calls
             */
            argumentValues = getArgumentValues(self, context);
            Class<?>[] argumentTypes = new Class<?>[argumentValues.length];

            for (int i = 0; i < argumentValues.length; i++) {
                argumentTypes[i] = argumentValues[i].getClass();
            }

            member = reflect(object, attributeName, argumentTypes);
            if (member != null) {
                memberCache.put(object.getClass(), member);
            }

        }

        if (object != null && member != null) {
            if (argumentValues == null) {
                argumentValues = getArgumentValues(self, context);
            }
            result = invokeMember(object, member, argumentValues);
        } else if (context.isStrictVariables()) {
            if (object == null) {
                final String rootPropertyName = ((ContextVariableExpression) node).getName();
                throw new RootAttributeNotFoundException(null,
                        String.format(
                                "Root attribute [%s] does not exist or can not be accessed and strict variables is set to true.",
                                rootPropertyName),
                        rootPropertyName);
            } else {
                throw new AttributeNotFoundException(null,
                        String.format(
                                "Attribute [%s] of [%s] does not exist or can not be accessed and strict variables is set to true.",
                                attributeName, object.getClass().getName()),
                        attributeName);
            }
        }
        return result;

    }

    /**
     * Invoke the "Member" that was found via reflection.
     * 
     * @param object
     * @param member
     * @param argumentValues
     * @return
     */
    private Object invokeMember(Object object, Member member, Object[] argumentValues) {
        Object result = null;
        try {
            if (member != null) {

                if (member instanceof Method) {
                    result = ((Method) member).invoke(object, argumentValues);
                } else if (member instanceof Field) {
                    result = ((Field) member).get(object);
                }
            }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            e.printStackTrace();

        }
        return result;
    }

    /**
     * Fully evaluates the individual arguments.
     * <commit_msg>Throwing a runtime exception if failing to invoke a member found via reflection. Fixes #81<commit_after>/*******************************************************************************
 * This file is part of Pebble.
 * 
 * Copyright (c) 2014 by <NAME>
 * 
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 ******************************************************************************/
package com.mitchellbosecke.pebble.node.expression;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import com.mitchellbosecke.pebble.error.AttributeNotFoundException;
import com.mitchellbosecke.pebble.error.PebbleException;
import com.mitchellbosecke.pebble.error.RootAttributeNotFoundException;
import com.mitchellbosecke.pebble.extension.NodeVisitor;
import com.mitchellbosecke.pebble.node.ArgumentsNode;
import com.mitchellbosecke.pebble.node.PositionalArgumentNode;
import com.mitchellbosecke.pebble.template.EvaluationContext;
import com.mitchellbosecke.pebble.template.PebbleTemplateImpl;

/**
 * Used to get an attribute from an object. It will look up attributes in the
 * following order: map entry, array item, list item, get method, is method, has
 * method, public method, public field.
 * 
 * @author Mitchell
 * 
 */
public class GetAttributeExpression implements Expression<Object> {

    private final Expression<?> node;

    private final String attributeName;

    private final ArgumentsNode args;

    /**
     * Potentially cached on first evaluation.
     */
    private final ConcurrentHashMap<Class<?>, Member> memberCache;

    public GetAttributeExpression(Expression<?> node, String attributeName) {
        this(node, attributeName, null);
    }

    public GetAttributeExpression(Expression<?> node, String attributeName, ArgumentsNode args) {
        this.node = node;
        this.attributeName = attributeName;
        this.args = args;

        /*
         * I dont imagine that users will often give different types to the same
         * template so we will give this cache a pretty small initial capacity.
         */
        this.memberCache = new ConcurrentHashMap<>(2, 0.9f, 1);
    }

    @Override
    public Object evaluate(PebbleTemplateImpl self, EvaluationContext context) throws PebbleException {
        Object object = node.evaluate(self, context);

        Object result = null;

        Object[] argumentValues = null;

        Member member = object == null ? null : memberCache.get(object.getClass());

        if (object != null && member == null) {

            /*
             * If, and only if, no arguments were provided does it make sense to
             * check maps/arrays/lists
             */
            if (args == null) {

                // first we check maps
                if (object instanceof Map && ((Map<?, ?>) object).containsKey(attributeName)) {
                    return ((Map<?, ?>) object).get(attributeName);
                }

                try {

                    // then we check arrays
                    if (object instanceof Object[]) {
                        Integer key = Integer.valueOf(attributeName);
                        Object[] arr = ((Object[]) object);
                        return arr[key];
                    }

                    // then lists
                    if (object instanceof List) {
                        Integer key = Integer.valueOf(attributeName);
                        @SuppressWarnings("unchecked")
                        List<Object> list = (List<Object>) object;
                        return list.get(key);
                    }
                } catch (NumberFormatException ex) {
                    // do nothing
                }

            }

            /*
             * turn args into an array of types and an array of values in order
             * to use them for our reflection calls
             */
            argumentValues = getArgumentValues(self, context);
            Class<?>[] argumentTypes = new Class<?>[argumentValues.length];

            for (int i = 0; i < argumentValues.length; i++) {
                argumentTypes[i] = argumentValues[i].getClass();
            }

            member = reflect(object, attributeName, argumentTypes);
            if (member != null) {
                memberCache.put(object.getClass(), member);
            }

        }

        if (object != null && member != null) {
            if (argumentValues == null) {
                argumentValues = getArgumentValues(self, context);
            }
            result = invokeMember(object, member, argumentValues);
        } else if (context.isStrictVariables()) {
            if (object == null) {
                final String rootPropertyName = ((ContextVariableExpression) node).getName();
                throw new RootAttributeNotFoundException(null,
                        String.format(
                                "Root attribute [%s] does not exist or can not be accessed and strict variables is set to true.",
                                rootPropertyName),
                        rootPropertyName);
            } else {
                throw new AttributeNotFoundException(null,
                        String.format(
                                "Attribute [%s] of [%s] does not exist or can not be accessed and strict variables is set to true.",
                                attributeName, object.getClass().getName()),
                        attributeName);
            }
        }
        return result;

    }

    /**
     * Invoke the "Member" that was found via reflection.
     * 
     * @param object
     * @param member
     * @param argumentValues
     * @return
     */
    private Object invokeMember(Object object, Member member, Object[] argumentValues) {
        Object result = null;
        try {
            if (member != null) {

                if (member instanceof Method) {
                    result = ((Method) member).invoke(object, argumentValues);
                } else if (member instanceof Field) {
                    result = ((Field) member).get(object);
                }
            }
        } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * Fully evaluates the individual arguments.
     * 
{'fraction_non_alphanumeric': 0.11056455274708642, 'fraction_numerical': 0.0015135462388375966, 'mean_word_length': 1.9510942384993302, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4157964', 'n_tokens_mistral': 3181, 'n_tokens_neox': 3024, 'n_words': 940}