Open Source Repository

Home /spring/spring-expression-3.0.5 | Repository Home



org/springframework/expression/spel/ExpressionState.java
/*
 * Copyright 2002-2009 the original author or authors.
 *
 * 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.
 */

package org.springframework.expression.spel;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.Operation;
import org.springframework.expression.OperatorOverloader;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypeComparator;
import org.springframework.expression.TypedValue;

/**
 * An ExpressionState is for maintaining per-expression-evaluation state, any changes to it are not seen by other
 * expressions but it gives a place to hold local variables and for component expressions in a compound expression to
 * communicate state. This is in contrast to the EvaluationContext, which is shared amongst expression evaluations, and
 * any changes to it will be seen by other expressions or any code that chooses to ask questions of the context.
 
 <p>It also acts as a place for to define common utility routines that the various Ast nodes might need.
 
 @author Andy Clement
 @since 3.0
 */
public class ExpressionState {

  private final EvaluationContext relatedContext;
  
  private Stack<VariableScope> variableScopes; 

  private Stack<TypedValue> contextObjects;
  
  private final TypedValue rootObject;
  
  private SpelParserConfiguration configuration;


  public ExpressionState(EvaluationContext context) {
    this.relatedContext = context;
    this.rootObject = context.getRootObject();
  }
  
  public ExpressionState(EvaluationContext context, SpelParserConfiguration configuration) {
    this.relatedContext = context;
    this.configuration = configuration;
    this.rootObject = context.getRootObject();
  }
  
  public ExpressionState(EvaluationContext context, TypedValue rootObject) {
    this.relatedContext = context;
    this.rootObject = rootObject;
  }
  
  public ExpressionState(EvaluationContext context, TypedValue rootObject, SpelParserConfiguration configuration) {
    this.relatedContext = context;
    this.configuration = configuration;
    this.rootObject = rootObject;
  }
  

  private void ensureVariableScopesInitialized() {
    if (this.variableScopes == null) {
      this.variableScopes = new Stack<VariableScope>();
      // top level empty variable scope
      this.variableScopes.add(new VariableScope())
    }
  }

  /**
   * The active context object is what unqualified references to properties/etc are resolved against.
   */
  public TypedValue getActiveContextObject() {
    if (this.contextObjects==null || this.contextObjects.isEmpty()) {
      return this.rootObject;
    }
    
    return this.contextObjects.peek();
  }

  public void pushActiveContextObject(TypedValue obj) {
    if (this.contextObjects==null) {
      this.contextObjects =  new Stack<TypedValue>();
    }
    this.contextObjects.push(obj);
  }

  public void popActiveContextObject() {
    if (this.contextObjects==null) {
      this.contextObjects =  new Stack<TypedValue>();
    }
    this.contextObjects.pop();
  }

  public TypedValue getRootContextObject() {
    return this.rootObject;
  }

  public void setVariable(String name, Object value) {
    this.relatedContext.setVariable(name, value);
  }

  public TypedValue lookupVariable(String name) {
    Object value = this.relatedContext.lookupVariable(name);
    if (value == null) {
      return TypedValue.NULL;
    }
    else {
      return new TypedValue(value, TypeDescriptor.forObject(value));
    }
  }

  public TypeComparator getTypeComparator() {
    return this.relatedContext.getTypeComparator();
  }

  public Class<?> findType(String typethrows EvaluationException {
    return this.relatedContext.getTypeLocator().findType(type);
  }

  public Object convertValue(Object value, TypeDescriptor targetTypeDescriptorthrows EvaluationException {
    return this.relatedContext.getTypeConverter().convertValue(value, TypeDescriptor.forObject(value), targetTypeDescriptor);
  }
  
  public Object convertValue(TypedValue value, TypeDescriptor targetTypeDescriptorthrows EvaluationException {
    return this.relatedContext.getTypeConverter().convertValue(value.getValue(), TypeDescriptor.forObject(value.getValue()), targetTypeDescriptor);
  }

  /*
   * A new scope is entered when a function is invoked
   */
  
  public void enterScope(Map<String, Object> argMap) {
    ensureVariableScopesInitialized();
    this.variableScopes.push(new VariableScope(argMap));
  }

  public void enterScope(String name, Object value) {
    ensureVariableScopesInitialized();
    this.variableScopes.push(new VariableScope(name, value));
  }

  public void exitScope() {
    ensureVariableScopesInitialized();
    this.variableScopes.pop();
  }

  public void setLocalVariable(String name, Object value) {
    ensureVariableScopesInitialized();
    this.variableScopes.peek().setVariable(name, value);
  }

  public Object lookupLocalVariable(String name) {
    ensureVariableScopesInitialized();
    int scopeNumber = this.variableScopes.size() 1;
    for (int i = scopeNumber; i >= 0; i--) {
      if (this.variableScopes.get(i).definesVariable(name)) {
        return this.variableScopes.get(i).lookupVariable(name);
      }
    }
    return null;
  }

  public TypedValue operate(Operation op, Object left, Object rightthrows EvaluationException {
    OperatorOverloader overloader = this.relatedContext.getOperatorOverloader();
    if (overloader.overridesOperation(op, left, right)) {
      Object returnValue = overloader.operate(op, left, right);
      return new TypedValue(returnValue,TypeDescriptor.forObject(returnValue));
    }
    else {
      String leftType = (left==null?"null":left.getClass().getName());
      String rightType = (right==null?"null":right.getClass().getName());
      throw new SpelEvaluationException(SpelMessage.OPERATOR_NOT_SUPPORTED_BETWEEN_TYPES, op, leftType, rightType);
    }
  }

  public List<PropertyAccessor> getPropertyAccessors() {
    return this.relatedContext.getPropertyAccessors();
  }

  public EvaluationContext getEvaluationContext() {
    return this.relatedContext;
  }

  public SpelParserConfiguration getConfiguration() {
    return this.configuration;
  }

  /**
   * A new scope is entered when a function is called and it is used to hold the parameters to the function call.  If the names
   * of the parameters clash with those in a higher level scope, those in the higher level scope will not be accessible whilst
   * the function is executing.  When the function returns the scope is exited.
   */
  private static class VariableScope {

    private final Map<String, Object> vars = new HashMap<String, Object>();

    public VariableScope() { }

    public VariableScope(Map<String, Object> arguments) {
      if (arguments != null) {
        this.vars.putAll(arguments);
      }
    }
    
    public VariableScope(String name, Object value) {
      this.vars.put(name,value);
    }

    public Object lookupVariable(String name) {
      return this.vars.get(name);
    }

    public void setVariable(String name, Object value) {
      this.vars.put(name,value);
    }

    public boolean definesVariable(String name) {
      return this.vars.containsKey(name);
    }
  }

}