webwork/src/main/webwork/util ComplexException.java,NONE,1.1 SimpleTest.java,NONE,1.1

[email protected] Sun, 16 Nov 2003 15:40:33 -0800
Newsgroups gmane.comp.java.open-symphony.cvs
Message-ID <[email protected]>
Update of /cvsroot/opensymphony/webwork/src/main/webwork/util
In directory sc8-pr-cvs1:/tmp/cvs-serv2823

Added Files:
	ComplexException.java SimpleTest.java 
Log Message:
New class that is used to evaluate if expressions. It is much faster than
the javacc generated parser.

--- NEW FILE: ComplexException.java ---
/*
 * WebWork, Web Application Framework
 *
 * Distributable under Apache license.
 * See terms of license at opensource.org
 */
package webwork.util;

/**
 * This exception is thrown from the constructor of the
 * SimpleTest class when it notices that an expression
 * is too complex for it to handle.
 *
 *	@see webwork.util.SimpleTest
 *	@author Dick Zetterberg ([email protected])
 *	@version $Revision: 1.1 $
 */
public class ComplexException extends java.lang.Exception
{
	public ComplexException()
	{
	}
	
	public ComplexException(String msg)
	{
		super(msg);
	}
}

--- NEW FILE: SimpleTest.java ---
/*
 * WebWork, Web Application Framework
 *
 * Distributable under Apache license.
 * See terms of license at opensource.org
 */
package webwork.util;

import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This class is used to handle test expressions in an efficient way.
 * It is more than 5 times faster than the javacc generated Parser.java
 * Instances of the class are retrieved through the static method
 * getSimpleTest. If the method returns null it means the expression was
 * too complex for this class too handle. Complex expressions are
 * those using parenthesis to control the order of evaluation.
 * Those expressions will instead be handled by the old
 * javacc Parser.java class.
 *
 *	@see webwork.util.ValueStack
 *	@see webwork.util.ComplexException
 *	@see webwork.expr.Parser
 *	@author Dick Zetterberg ([email protected])
 *	@version $Revision: 1.1 $
 */
public class SimpleTest
{
   private static Log log = LogFactory.getLog(SimpleTest.class);

	private static Map simpleTestMap = new ConcurrentReaderHashMap();

	/**
	* This marker object is stored in the simpleTestMap to show
	* that the expression has been found to be too complex for
	* this class to handle
	*/
	private static final SimpleTest COMPLEX_TEST = new SimpleTest();
	
	private static final Object NO_CONSTANT = new Object();

	// The possible values for the operator
	protected static final int NONE = 0;
	protected static final int AND = 1;
	protected static final int OR = 2;

	// The condition characters used
	protected static final char AND_CHAR = '&';
	protected static final char OR_CHAR = '|';
	protected static final char QUOTE_CHAR = '\'';
	protected static final char EQUAL_CHAR = '=';
	protected static final char NOT_CHAR = '!';
	protected static final char GREATER_CHAR = '>';
	protected static final char LESS_CHAR = '<';
	
	// The possible values for the condition
	protected static final int CHECK_NULL = 0;
	protected static final int EQUAL = 1;
	protected static final int NOT_EQUAL = 2;
	protected static final int GREATER = 3;
	protected static final int GREATER_EQUAL = 4;
	protected static final int LESS = 5;
	protected static final int LESS_EQUAL = 6;
	
	protected int condition;
	
	protected boolean neg1;
	protected boolean neg2;
	protected Query q1;
	protected Query q2;
	protected String exp1;
	protected String exp2;
	protected Object value1;
	protected Object value2;
	protected SimpleTest nextTest;
	protected int operator = NONE;
	protected int sameVal1;
	protected int sameVal2;
	
	/**
	* Get a SimpleTest object for the expression.
	* If the expression is too complex then null is returned
	* If the expression has been handled before then a cached SimpleTest
	* object is returned. Otherwise a new one is created
	*/
	public static SimpleTest getSimpleTest(String expression)
	{
		SimpleTest test = (SimpleTest) simpleTestMap.get(expression);
		if (test != null)
		{
			if (test != COMPLEX_TEST)
				return test;
			else
				return null;
		}
		// The expression has not been handled before, try creating a SimpleTest now
		try
		{
//			log.debug("Creating SimpleTest for expression: " + expression);
			test = new SimpleTest(expression);
			// Put the expression in the map
			simpleTestMap.put(expression, test);
			return test;
		}
		catch(ComplexException e)
		{
//			log.debug("SimpleTest not created because expression is too complex: " + expression, e);
			// Put the expression in the map so we do not try to handle it again
			simpleTestMap.put(expression, COMPLEX_TEST);
			// Return null to show that no SimpleTest could be created
			return null;
		}
		// Consider catching throwable to give better error message
/*
		catch(Throwable t)
		{
			log.error("SimpleTest not created because unexpected throwable, expression: " + expression, t);
			// Put the expression in the map so we do not try to handle it again
			simpleTestMap.put(expression, COMPLEX_TEST);
			// Return null to show that no SimpleTest could be created
			return null;
		}		
*/
	}
	
	public SimpleTest()
	{}
	
	/**
	* Create a SimpleTest for the expression exp, and make use of the values
	* found in the previously evaluated expression prevText if possible
	*/
	public SimpleTest(SimpleTest prevTest, String exp) throws ComplexException
	{
		// First evaluate the expression exp
		this(exp);
		
		// If the first expression is not a constant then
		// check if it already exists in the previous test
		if (value1 == NO_CONSTANT)
		{
			if (exp1.equals(prevTest.exp1))
				sameVal1 = 1;
			else if (exp1.equals(prevTest.exp2))
				sameVal1 = 2;
		}
		
		// If we have a second non constant expression check if it exists in the previous test
		if (exp2 != null && value2 == NO_CONSTANT)
		{
			if (exp2.equals(prevTest.exp1))
				sameVal2 = 1;
			else if (exp2.equals(prevTest.exp2))
				sameVal2 = 2;			
		}
	}
	
	public SimpleTest(String exp) throws ComplexException
	{
		exp = exp.trim();
		
		String nextExp = null;

		// Get the index of any && or || operators
		// The method also check for conditions like == and != and
		// if found, sets the exp1 and exp2 variables
		int opIndex = checkOperator(exp);
		
		// If there is an operator it means that the expression consists of
		// several expressions. Get the index of the next one
		if (operator != NONE)
		{
			nextExp = exp.substring(opIndex+2).trim();
			exp = exp.substring(0, opIndex).trim();
		}
		
		if (condition == CHECK_NULL)
		{
			// The expression is only to check for null value
		   if (exp.charAt(0)=='!')
		   {
		      neg1 = true;
		      exp1 = exp.substring(1).trim();
		   }
		   else
		      exp1 = exp;
			// Check that expression does not start with (
			if (exp1.charAt(0) == '(')
				throw new ComplexException("Expression too complex because it starts with ( : " + exp);
		   value1 = getConstant(exp1);
		}
		else
		{
			// The expression consists of 2 parts with a condition: ==, !=, <, > etc
	      // Check that the expressions do not start with negation, in that case it is an
	      // illegal expression
		   if (exp1.charAt(0)=='!' || exp2.charAt(0)=='!')
		   	throw new IllegalArgumentException("Invalid expression, misplaced \'!\' : " + exp);
		      
			// Check that expression does not start with (
			if (exp1.charAt(0) == '(' || exp2.charAt(0) == '(')
				throw new ComplexException("Expression too complex because it starts with ( : " + exp);

		   value1 = getConstant(exp1);
		   value2 = getConstant(exp2);
		}
		if (value1 == NO_CONSTANT)
			q1 = Query.getQuery(exp1);
		if (condition != CHECK_NULL && value2 == NO_CONSTANT)
			q2 = Query.getQuery(exp2);
			
		if (nextExp != null)
			nextTest = new SimpleTest(this, nextExp);
	}
	
	/**
	* Look through the expression to find any operators like && or ||, <, > etc.
	* If found then the operator variable is updated.
	*/
	protected int checkOperator(String str) throws ComplexException
	{
		int length = str.length();
		int i = 0;
		int exp2Index = -1;
		while(i < length)
		{
			char c = str.charAt(i++);
			// Check if the current characters is the && expression
			if (c == AND_CHAR && i < length && str.charAt(i) == AND_CHAR)
			{
				operator = AND;
				// If exp2Index is set we must truncate the exp2 expression now
				if (exp2Index >= 0)
				{
					exp2 = exp2.substring(0, i-exp2Index-1).trim();
				}
				return i-1;
			}
			// Check if the current characters is the || expression
			if (c == OR_CHAR && i < length && str.charAt(i) == OR_CHAR)
			{
				operator = OR;
				if (exp2Index >= 0)
				{
					exp2 = exp2.substring(0, i-exp2Index-1).trim();
				}
				return i-1;
			}
			// Check if the current character starts a quote
			if (c == QUOTE_CHAR && i < length)
			{
				// Find the next quote character and skip forward to it
				i = str.indexOf(QUOTE_CHAR, i);
				// If no other quote found do not handle it
				if (i < 0)
					throw new IllegalArgumentException("Invalid expression, no matching quote found: " + str);
				// Set i to the next character
				i++;
				continue;
			}
			// Perhaps add check if condition is already set and in that case throw exception
			if (c == EQUAL_CHAR && i < length && str.charAt(i) == EQUAL_CHAR)
			{
	      	condition = EQUAL;
				// Set exp1 to the first part of the expression and exp2 to the second
	      	exp1 = str.substring(0, i-1).trim();
	      	// Do not trim exp2 now, because we might have to truncate it later
	      	exp2 = str.substring(++i);
	      	exp2Index = i;
      		continue;
			}
			if (c == NOT_CHAR && i < length && str.charAt(i) == EQUAL_CHAR)
			{
	      	condition = NOT_EQUAL;
				// Set exp1 to the first part of the expression and exp2 to the second
	      	exp1 = str.substring(0, i-1).trim();
	      	// Do not trim exp2 now, because we might have to truncate it later
	      	exp2 = str.substring(++i);
	      	exp2Index = i;
      		continue;
			}
			if (c == LESS_CHAR && i < length && str.charAt(i) == EQUAL_CHAR)
			{
	      	condition = LESS_EQUAL;
				// Set exp1 to the first part of the expression and exp2 to the second
	      	exp1 = str.substring(0, i-1).trim();
	      	// Do not trim exp2 now, because we might have to truncate it later
	      	exp2 = str.substring(++i);
	      	exp2Index = i;
      		continue;
			}
			if (c == LESS_CHAR)
			{
	      	condition = LESS;
				// Set exp1 to the first part of the expression and exp2 to the second
	      	exp1 = str.substring(0, i-1).trim();
	      	// Do not trim exp2 now, because we might have to truncate it later
	      	exp2 = str.substring(i);
	      	exp2Index = i;
      		continue;
			}
			if (c == GREATER_CHAR && i < length && str.charAt(i) == EQUAL_CHAR)
			{
	      	condition = GREATER_EQUAL;
				// Set exp1 to the first part of the expression and exp2 to the second
	      	exp1 = str.substring(0, i-1).trim();
	      	// Do not trim exp2 now, because we might have to truncate it later
	      	exp2 = str.substring(++i);
	      	exp2Index = i;
      		continue;
			}
			if (c == GREATER_CHAR)
			{
	      	condition = GREATER;
				// Set exp1 to the first part of the expression and exp2 to the second
	      	exp1 = str.substring(0, i-1).trim();
	      	// Do not trim exp2 now, because we might have to truncate it later
	      	exp2 = str.substring(i);
	      	exp2Index = i;
      		continue;
			}
		}
		if (exp2 != null)
			exp2 = exp2.trim();
		return -1;
	}
	
	/**
	* This method checks if the expression is a constant value.
	* If it is not a constant then the method returns the object NO_CONSTANT
	* If it is a constant then it returns the constant value which may be null
	*/
	protected Object getConstant(String exp)
	{
		Query q = Query.getQuery(exp);
		QuerySegment[] segments = q.getSegments();
		QuerySegment segment = segments[0];
						
      switch (segment.getType())
      {
         case QuerySegment.STRING:
            return segment.getId();

            // the integer is the first value and only value in the segment
         case QuerySegment.NUMBER:
            return segment.getValues().get(0);

            // the reserved keyword "true"
         case QuerySegment.TRUE:
            return Boolean.TRUE;

            // the reserved keyword "false"
         case QuerySegment.FALSE:
            return Boolean.FALSE;
            
            // the reserved keyword "null"
         case QuerySegment.NULL:
         	return null;
            
         default:
         	return NO_CONSTANT;
		}
	}		

	public boolean test(ValueStack stack)
	{
		return test(stack, null, null);
	}
	
	/**
	* The values in prevVal1 and prevVal2 will always be sent in. They can be null
	* because they are not used or because the value found was null. This does not matter.
	* The expression after this one will only care about the value IF the same expression
	* existed in this test as in the next test.
	*/
	protected boolean test(ValueStack stack, Object prevVal1, Object prevVal2)
	{
	   Object val1;
	   Object val2 = null;
		boolean result = false;	      	

		// Check if the first expression is not a constant
	   if (value1 == NO_CONSTANT)
	   {
	   	// Check if it is the same value as one already evaluated. Otherwise look
	   	// it up on the value stack
	   	if (sameVal1 == 0)
	      	val1 = stack.findValue(q1);
	      else if (sameVal1 == 1)
	      	val1 = prevVal1;
	      else
	      	val1 = prevVal2;
	   }
	   // The value is a constant so just assign it
	   else
	   {
	   	val1 = value1;
	   }
	      	
	   if (condition == CHECK_NULL)
	   {
	      // No equals expression. Just check if the value is null or not
	      result = (val1 == null ? false : true);
	      if (neg1)
	      	result = !result;	      	
	   }
	   // The expression contains a condition like == or !=
	   else
	   {
		   if (value2 == NO_CONSTANT)
		   {
		   	if (sameVal2 == 0)
		      	val2 = stack.findValue(q2);
		   	else if (sameVal2 == 1)
		   		val2 = prevVal1;
		   	else
		   		val2 = prevVal2;
		   }
	      else
		      val2 = value2;

			int comparison = -1;	// less than
         // We resolve nulls by hand
         if ( val1 == null || val2 == null )
         {
            if ( val1 == null && val2 == null ) // equal to
            {
            	comparison = 0;	// equal to
            }
            else if (val2 == null) // greater than
            	comparison = 1;
            
         }
         else if ( val1 instanceof Number && val2 instanceof Number )
         {
            double number1 = ((Number)val1).doubleValue();
            double number2 = ((Number)val2).doubleValue();
				
				if (number1 > number2)
					comparison = 1;		// greater than
            else if ( number1 == number2 )
            {
               long longBits1 = Double.doubleToLongBits(number1);
               long longBits2 = Double.doubleToLongBits(number2);
               if ( longBits1 > longBits2 )
                  comparison = 1;
               else if ( longBits1 == longBits2 )
                  comparison = 0;
            }
			}

         // If operands are of the same type, do a direct comparison
         else if ( val1.getClass().equals(val2.getClass()) && (val1 instanceof Comparable)) {
             comparison = ((Comparable)val1).compareTo((Comparable)val2);
         }
         else
			{
            // If the operands aren't the same type or aren't comparible, then
            // convert them to strings and do a comparison
            // Mo: I think that this could lead to some difficult to predict or unpredictable behavior
           	comparison = ((Comparable)val1.toString()).compareTo((Comparable)val2.toString());
			}
			// Now call resolve to determine the resulting value
			result = resolve(comparison, condition);
 		}

		switch(operator)
		{
			case NONE:
				return result;
			case AND:
				if (result)
					return nextTest.test(stack, val1, val2);
				else
					return false;
			case OR:
				if (result)
					return true;
				else
					return nextTest.test(stack, val1, val2);
			default:
				return false;
		}		
	}

   /**
    * determine true or false by comparing the comparison result
    * with the operator.
    *
    * @param   comp  the comparison result
    * @param   operatr  the operator
    * @return     the boolean result
    *
    */
   protected boolean resolve(int comp, int condition) {
      //log.debug( "comp: " + comp + ", operatr: " + operatr);
      if ( comp == 0 )
      {
         switch (condition) {
            case EQUAL:
            case GREATER_EQUAL:
            case LESS_EQUAL:
               return true;
            default:
               return false;
         }
      }

      if ( comp > 0 )
      {
         switch (condition) {
            case GREATER:
            case GREATER_EQUAL:
            case NOT_EQUAL:
               return true;
            default:
               return false;
         }
      }

      switch (condition) {
         case LESS:
         case LESS_EQUAL:
         case NOT_EQUAL:
            return true;
      }
      
      return false;
   }
}




-------------------------------------------------------
This SF. Net email is sponsored by: GoToMyPC
GoToMyPC is the fast, easy and secure way to access your computer from
any Web browser or wireless device. Click here to Try it Free!
https://www.gotomypc.com/tr/OSDN/AW/Q4_2003/t/g22lp?Target=mm/g22lp.tmpl