<commit_before>/*******************************************************************************
 * This file is part of Pebble.
 * <p>
 * Copyright (c) 2014 by <NAME>
 * <p>
 * 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.Array;
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.Optional;
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.DynamicAttributeProvider;
import com.mitchellbosecke.pebble.extension.NodeVisitor;
import com.mitchellbosecke.pebble.extension.ResolvedAttribute;
import com.mitchellbosecke.pebble.node.ArgumentsNode;
import com.mitchellbosecke.pebble.node.PositionalArgumentNode;
import com.mitchellbosecke.pebble.template.EvaluationContextImpl;
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,
 * {@link DynamicAttributeProvider}, get method, is method, has method, public method,
 * public field.
 *
 * @author Mitchell
 */
public class GetAttributeExpression implements Expression<Object> {

    private final Expression<?> node;

    private final Expression<?> attributeNameExpression;

    private final ArgumentsNode args;

    private final String filename;

    private final int lineNumber;

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

    public GetAttributeExpression(Expression<?> node, Expression<?> attributeNameExpression, String filename,
                                  int lineNumber) {
        this(node, attributeNameExpression, null, filename, lineNumber);
    }

    public GetAttributeExpression(Expression<?> node, Expression<?> attributeNameExpression, ArgumentsNode args,
                                  String filename, int lineNumber) {

        this.node = node;
        this.attributeNameExpression = attributeNameExpression;
        this.args = args;
        this.filename = filename;
        this.lineNumber = lineNumber;

        /*
         * 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, EvaluationContextImpl context) throws PebbleException {
        Object object = this.node.evaluate(self, context);
        Object attributeNameValue = this.attributeNameExpression.evaluate(self, context);
        String attributeName = String.valueOf(attributeNameValue);

        Object result = null;

        Object[] argumentValues = this.getArgumentValues(self, context);

        // check if the object is able to provide the attribute dynamically
        if(object != null && object instanceof DynamicAttributeProvider) {
            DynamicAttributeProvider dynamicAttributeProvider = (DynamicAttributeProvider) object;
            if(dynamicAttributeProvider.canProvideDynamicAttribute(attributeName)) {
                return dynamicAttributeProvider.getDynamicAttribute(attributeNameValue, argumentValues);
            }
        }

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

            Optional<ResolvedAttribute> resolvedAttribute = resolveMap(object, attributeNameValue);

            if (!resolvedAttribute.isPresent()) {
                resolvedAttribute = resolveArray(object, attributeNameValue, context.isStrictVariables());
            }

            if (!resolvedAttribute.isPresent()) {
                resolvedAttribute = resolveList(object, attributeNameValue, context.isStrictVariables());
            }

            if (resolvedAttribute.isPresent()) {
                return resolvedAttribute.get().get();
            }
        }
        
        Member member = object == null ? null : this.memberCache.get(new MemberCacheKey(object.getClass(), attributeName));
        if (object != null && member == null) {

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

            for (int i = 0; i < argumentValues.length; i++) {
                Object o = argumentValues[i];
                if (o == null) {
                    argumentTypes[i] = null;
                } else {
                    argumentTypes[i] = o.getClass();
                }
            }

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

        }

        if (object != null && member != null) {
            result = this.invokeMember(object, member, argumentValues);
        } else if (context.isStrictVariables()) {
            if (object == null) {

                if (this.node instanceof ContextVariableExpression) {
                    final String rootPropertyName = ((ContextVariableExpression) this.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, this.lineNumber, this.filename);
                } else {
                    throw new RootAttributeNotFoundException(null,
                            "Attempt to get attribute of null object and strict variables is set to true.", attributeName, this.lineNumber, this.filename);
                }

            } 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, this.lineNumber, this.filename);
            }
        }
        return result;

    }

    private Optional<ResolvedAttribute> resolveList(Object object, Object attributeNameValue, boolean strictVariables) throws AttributeNotFoundException {
        // then lists
        if (object instanceof List) {
            String attributeName = String.valueOf(attributeNameValue);

            @SuppressWarnings("unchecked")
            final List<Object> list = (List<Object>) object;

            Optional<Integer> optIndex = asIndex(attributeName);
            if (optIndex.isPresent()) {
                final int index = optIndex.get();
                int length = list.size();
    <commit_msg>refactoring of attribute resolution<commit_after>/*******************************************************************************
 * This file is part of Pebble.
 * <p>
 * Copyright (c) 2014 by <NAME>
 * <p>
 * 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.Array;
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.Optional;
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.DynamicAttributeProvider;
import com.mitchellbosecke.pebble.extension.NodeVisitor;
import com.mitchellbosecke.pebble.extension.ResolvedAttribute;
import com.mitchellbosecke.pebble.node.ArgumentsNode;
import com.mitchellbosecke.pebble.node.PositionalArgumentNode;
import com.mitchellbosecke.pebble.template.EvaluationContextImpl;
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,
 * {@link DynamicAttributeProvider}, get method, is method, has method, public method,
 * public field.
 *
 * @author Mitchell
 */
public class GetAttributeExpression implements Expression<Object> {

    private final Expression<?> node;

    private final Expression<?> attributeNameExpression;

    private final ArgumentsNode args;

    private final String filename;

    private final int lineNumber;

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

    public GetAttributeExpression(Expression<?> node, Expression<?> attributeNameExpression, String filename,
                                  int lineNumber) {
        this(node, attributeNameExpression, null, filename, lineNumber);
    }

    public GetAttributeExpression(Expression<?> node, Expression<?> attributeNameExpression, ArgumentsNode args,
                                  String filename, int lineNumber) {

        this.node = node;
        this.attributeNameExpression = attributeNameExpression;
        this.args = args;
        this.filename = filename;
        this.lineNumber = lineNumber;

        /*
         * 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, EvaluationContextImpl context) throws PebbleException {
        final Object object = this.node.evaluate(self, context);
        final Object attributeNameValue = this.attributeNameExpression.evaluate(self, context);
        final String attributeName = String.valueOf(attributeNameValue);
        final Object[] argumentValues = this.getArgumentValues(self, context);


        if (object == null && context.isStrictVariables()) {
            if (this.node instanceof ContextVariableExpression) {
                final String rootPropertyName = ((ContextVariableExpression) this.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, this.lineNumber, this.filename);
            } else {
                throw new RootAttributeNotFoundException(null,
                        "Attempt to get attribute of null object and strict variables is set to true.", attributeName, this.lineNumber, this.filename);
            }
        }
        
        if (object != null) {

            Optional<ResolvedAttribute> resolvedAttribute=Optional.empty();
            
            // check if the object is able to provide the attribute dynamically
            if (!resolvedAttribute.isPresent()) {
                if(object instanceof DynamicAttributeProvider) {
                    final DynamicAttributeProvider dynamicAttributeProvider = (DynamicAttributeProvider) object;
                    if(dynamicAttributeProvider.canProvideDynamicAttribute(attributeName)) {
                        resolvedAttribute=Optional.<ResolvedAttribute>of(new ResolvedAttribute() {
                            
                            @Override
                            public Object get() throws PebbleException {
                                return dynamicAttributeProvider.getDynamicAttribute(attributeNameValue, argumentValues);
                            }
                        });
                    }
                }
            }
            
            
            if (this.args == null) {
                /*
                 * If, and only if, no arguments were provided does it make sense to
                 * check maps/arrays/lists
                 */
                if (!resolvedAttribute.isPresent()) {
                    resolvedAttribute = resolveMap(object, attributeNameValue);
                }
    
                if (!resolvedAttribute.isPresent()) {
                    resolvedAttribute = resolveArray(object, attributeNameValue, context.isStrictVariables());
                }
    
                if (!resolvedAttribute.isPresent()) {
                    resolvedAttribute = resolveList(object, attributeNameValue, context.isStrictVariables());
                }
            }
            
            if (!resolvedAttribute.isPresent()) {
                resolvedAttribute = resolveMemberCall(object, attributeName, argumentValues);
            }
            
            if (resolvedAttribute.isPresent()) {
                return resolvedAttribute.get().get();
            } 
            
            if (context.isStrictVariables()) {
                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, this.lineNumber, this.filename);
            }
        }

        return null;

    }

    private Optional<ResolvedAttribute> resolveMemberCall(final Object object, String attributeName, final Object[] argumentValues) {
        
        final Member member = memberOf(object, attributeName, argumentValues);

        if (object != null && member != null) {
            return Optional.<ResolvedAttribute>of(new ResolvedAttribute() {
                
                @Override
                public Object get() throws PebbleException {
                    return invokeMember(object, member, argumentValues);
                }
            });
        }
        
        return Optional.empty();
    }

    private Member memberOf(final Object object, String attributeName, final Object[] argumentValues) {
        Member member = object == null ? null : this.memberCache.get(new MemberCacheKey(object.getClass(), attributeName));
        if (object != null && member == null) {

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

            for (int i = 0; i < argumentValues.length; i++) {
                Object o = argumentValues[i];
                if (o == null) {
                    argumentTypes[i] = null;
                } else {
                    argumentTypes[i] = o.getClass();
                }
            }

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

        }
        
        final Member resultingMember = member;
        return resultingMember;
    }

    private Optional<ResolvedAttribute> resolveList(Object object, Object attributeNameValue, boolean strictVariables) throws AttributeNotFoundException {
        // then lists
        if (object instanceof List) {
            String attributeName = String.valueOf(attributeNameValue);

            @SuppressWarnings("unchecked")
            final List<Object> list = (List<Object>) object;

            Optional<Integer> optIndex = asIndex(attributeName);
            if (optIndex.isPresent()) {
                final int index = optIndex.get();
                int length = list.size();
    
{'fraction_non_alphanumeric': 0.10095169256716058, 'fraction_numerical': 0.0010842067220816769, 'mean_word_length': 2.4022540983606557, 'pattern_counts': {'":': 0, '<': 46, '<?xml version=': 0, '>': 44, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5567169', 'n_tokens_mistral': 3900, 'n_tokens_neox': 3758, 'n_words': 1083}