/*
 * #%L
 * fujion
 * %%
 * Copyright (C) 2018 Fujion Framework
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * #L%
 */
package org.fujion.expression;

import org.fujion.expression.MessageAccessor.MessageContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.EnvironmentAccessor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.ConstructorResolver;
import org.springframework.expression.MethodResolver;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;

/**
 * An extension of Spring's EL evaluator that supports plugin accessors, resolvers, and converters..
 */
public class ELEvaluator extends StandardEvaluationContext implements BeanPostProcessor, ApplicationContextAware {
    
    private static final ELEvaluator instance = new ELEvaluator();
    
    private final ExpressionCache cache = new ExpressionCache();
    
    private final DefaultConversionService conversionService = new DefaultConversionService();
    
    public static ELEvaluator getInstance() {
        return instance;
    }
    
    private ELEvaluator() {
        addPropertyAccessor(new EnvironmentAccessor());
        addPropertyAccessor(new MessageAccessor());
        addPropertyAccessor(new ContextAccessor());
        addMethodResolver(new ELMethodResolver());
        conversionService.addConverter(new MessageAccessor.MessageContextConverter());
        setTypeConverter(new StandardTypeConverter(conversionService));
    }
    
    /**
     * Evaluate an EL expression against the specified root object.
     *
     * @param expression An EL expression.
     * @param root The root object against which to evaluate the expression.
     * @return The result of the evaluation.
     */
    public Object evaluate(String expression, Object root) {
        Object value = cache.hasExpression(expression) ? cache.get(expression).getValue(this, root) : expression;
        return value instanceof MessageContext ? value.toString() : value;
    }
    
    /**
     * Evaluate an EL expression.
     *
     * @param expression An EL expression.
     * @return The result of the evaluation.
     */
    public Object evaluate(String expression) {
        return cache.hasExpression(expression) ? cache.get(expression).getValue(this) : expression;
    }
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
    
    /**
     * Discover and register plugin resolvers, accessors, and converters.
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof ConstructorResolver) {
            addConstructorResolver((ConstructorResolver) bean);
        } else if (bean instanceof MethodResolver) {
            addMethodResolver((MethodResolver) bean);
        } else if (bean instanceof PropertyAccessor) {
            addPropertyAccessor((PropertyAccessor) bean);
        } else if (bean instanceof Converter) {
            conversionService.addConverter((Converter<?, ?>) bean);
        } else if (bean instanceof BeanResolver) {
            setBeanResolver((BeanResolver) bean);
        }
        
        return bean;
    }
    
    /**
     * Register the application context as a bean resolver and its environment as the default root
     * object.
     *
     * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        setRootObject(applicationContext.getEnvironment());
        
        setBeanResolver((context, beanName) -> {
            return applicationContext.getBean(beanName);
        });
    }
    
}

@@ -1758,20 +1758,16 @@
 Aware %7B%0A
-    
 %0A    pri
@@ -1822,28 +1822,24 @@
 valuator();%0A
-    
 %0A    private
@@ -1888,28 +1888,24 @@
 ionCache();%0A
-    
 %0A    private
@@ -1988,20 +1988,16 @@
 vice();%0A
-    
 %0A    pub
@@ -2062,28 +2062,24 @@
 ance;%0A    %7D%0A
-    
 %0A    private
@@ -2463,36 +2463,32 @@
 ervice));%0A    %7D%0A
-    
 %0A    /**%0A     * 
@@ -2976,36 +2976,32 @@
  : value;%0A    %7D%0A
-    
 %0A    /**%0A     * 
@@ -3280,28 +3280,24 @@
 sion;%0A    %7D%0A
-    
 %0A    @Overri
@@ -3422,36 +3422,32 @@
 urn bean;%0A    %7D%0A
-    
 %0A    /**%0A     * 
@@ -3754,37 +3754,49 @@
 bean);%0A        %7D
- else
+%0A        %0A       
  if (bean instan
@@ -3873,37 +3873,49 @@
 bean);%0A        %7D
- else
+%0A        %0A       
  if (bean instan
@@ -3998,37 +3998,49 @@
 bean);%0A        %7D
- else
+%0A        %0A       
  if (bean instan
@@ -4138,13 +4138,25 @@
    %7D
- else
+%0A        %0A       
  if 
@@ -4244,32 +4244,24 @@
 ;%0A        %7D%0A
-        
 %0A        ret
@@ -4276,20 +4276,16 @@
 ;%0A    %7D%0A
-    
 %0A    /**
@@ -4723,24 +4723,16 @@
 ent());%0A
-        
 %0A       
@@ -4848,15 +4848,11 @@
 ;%0A    %7D%0A
-    
 %0A%7D%0A

Improve recognition of EL helper beans.