All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.sitebricks.compiler.MvelEvaluatorCompiler Maven / Gradle / Ivy

The newest version!
package com.google.sitebricks.compiler;

import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.jcip.annotations.NotThreadSafe;

import org.jetbrains.annotations.Nullable;
import org.mvel2.CompileException;
import org.mvel2.MVEL;
import org.mvel2.ParserContext;
import org.mvel2.compiler.CompiledExpression;
import org.mvel2.compiler.ExpressionCompiler;

import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.sitebricks.Evaluator;
import com.google.sitebricks.Visible;
import com.google.sitebricks.conversion.generics.Generics;
import com.google.sitebricks.conversion.generics.ParameterizedTypeImpl;

/**
 * @author Dhanji R. Prasanna (dhanji@gmail com)
 * 
 * TODO make this thread-safe when pages can be compiled on demand 
 */
@NotThreadSafe
public class MvelEvaluatorCompiler implements EvaluatorCompiler {
  private final Class backingType;
  private final Map backingTypes;

  private static final String CLASS = "class";
  private final Set writeableProperties = Sets.newHashSet();
  private final Map egressTypes = Maps.newHashMap();
  private ParserContext cachedParserContext;

  public MvelEvaluatorCompiler(Class backingType) {
    this.backingType = backingType;
    this.backingTypes = null;
  }

  public MvelEvaluatorCompiler(Map backingTypes) {
    this.backingTypes = Collections.unmodifiableMap(backingTypes);
    this.backingType = null;
  }

  //memo field caches compiled expressions
  private final Map compiled =
      new HashMap();


  public Type resolveEgressType(String expression) throws ExpressionCompileException {

		// try to get the type from the cache
	    Type type = egressTypes.get(expression);
	    if (type != null) {
	    	return type;
	    }
	    
	    CompiledExpression compiled = compileExpression(expression);
		final Class egressClass = compiled.getKnownEgressType();
	    final Type[] parameters = compiled.getParserContext().getLastTypeParameters();
	    
	    if (parameters == null) {
	        // the class is not parameterised (generic)
	    	type = egressClass;
	    }
	    else {
	        // reconstruct the Type from mvel's generics details
	    	type = new ParameterizedTypeImpl(egressClass, parameters, egressClass.getEnclosingClass());
	    }
	    
	    egressTypes.put(expression, type);
	    
	    return type;
  }

  public boolean isWritable(String property) throws ExpressionCompileException {
    // Ensure we have introspected. Relying on sidefx, ugh.
    getParserContext();

    return writeableProperties.contains(property);
  }

  public Evaluator compile(String expression) throws ExpressionCompileException {

    //do *not* inline
    final CompiledExpression compiled = compileExpression(expression);

    return new Evaluator() {
      @Nullable
      public Object evaluate(String expr, Object bean) {
        return MVEL.executeExpression(compiled, bean);
      }

      public void write(String expr, Object bean, Object value) {
        //lets use mvel to store an expression
        MVEL.setProperty(bean, expr, value);
      }

      public Object read(String property, Object contextObject) {
        return MVEL.getProperty(property, contextObject);
      }
    };
  }

  private CompiledExpression compileExpression(String expression)
      throws ExpressionCompileException {
    final CompiledExpression compiledExpression = compiled.get(expression);

    //use cached copy
    if (null != compiledExpression)
      return compiledExpression;

    //otherwise compile expression and cache
    final ExpressionCompiler compiler = new ExpressionCompiler(expression, getParserContext());

    CompiledExpression tempCompiled;
    try {
      tempCompiled = compiler.compile();
    } catch (CompileException ce) {
      throw new ExpressionCompileException(expression, ce.getErrors());
    }

    //store in memo cache
    compiled.put(expression, tempCompiled);

    return tempCompiled;
  }

  private ParserContext getParserContext() throws ExpressionCompileException {
    if (null != cachedParserContext) {
      return cachedParserContext;
    }

    return cachedParserContext = (null != backingType)
        ? singleBackingTypeParserContext() : backingMapParserContext();
  }


  @SuppressWarnings({ "unchecked", "rawtypes" })
private ParserContext backingMapParserContext() {
    ParserContext context = new ParserContext();
    context.setStrongTyping(true);

    context.addInputs((Map) backingTypes);

    return context;
  }

  public List tokenizeAndCompile(String template) throws ExpressionCompileException {
    return Parsing.tokenize(template, this);
  }

  //generates a parsing context with type information from the backing type's javabean properties
  private ParserContext singleBackingTypeParserContext() throws ExpressionCompileException {
    ParserContext context = new ParserContext();
    context.setStrongTyping(true);
    context.addInput("this", backingType);

    PropertyDescriptor[] propertyDescriptors;
    try {
      propertyDescriptors = Introspector.getBeanInfo(backingType).getPropertyDescriptors();
    } catch (IntrospectionException e) {
      throw new ExpressionCompileException("Could not read class " + backingType);
    }

    // read @Visible annotated fields.
    for (Field field : backingType.getDeclaredFields()) {
      if (field.isAnnotationPresent(Visible.class)) {
        context.addInput(field.getName(), field.getType());

        if (!field.getAnnotation(Visible.class).readOnly()) {
          writeableProperties.add(field.getName());
        }
      }
    }

    // read javabean properties -- these override @Visible fields.
    for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
      // skip getClass()
      if (CLASS.equals(propertyDescriptor.getName()))
        continue;

      if (null != propertyDescriptor.getWriteMethod()) {
        writeableProperties.add(propertyDescriptor.getName());
      }

      // if this is a collection, determine its type parameter
      if (Collection.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {

        Type propertyType;
        if (propertyDescriptor.getReadMethod() != null) {
          propertyType = propertyDescriptor.getReadMethod().getGenericReturnType();
        }
        else {
          propertyType = propertyDescriptor.getWriteMethod().getGenericParameterTypes()[0];
        }

        ParameterizedType collectionType = (ParameterizedType) Generics
            .getExactSuperType(propertyType, Collection.class);

        Class[] parameterClasses = new Class[1];
        Type parameterType = collectionType.getActualTypeArguments()[0];
        parameterClasses[0] = Generics.erase(parameterType);
        
        context.addInput(propertyDescriptor.getName(), propertyDescriptor.getPropertyType(), parameterClasses);
      } else {
        context.addInput(propertyDescriptor.getName(), propertyDescriptor.getPropertyType());
      }
    }

    return context;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy