/* **************************************************************************

  $Archive: /njcl/src/com/novell/service/rfc1960/SearchStringComponent.java $
  $Revision: 4 $
  $Modtime: 5/27/99 10:54a $
 
  Copyright (c) 1998 Novell, Inc.  All Rights Reserved.

  THIS WORK IS  SUBJECT  TO  U.S.  AND  INTERNATIONAL  COPYRIGHT  LAWS  AND
  TREATIES.   NO  PART  OF  THIS  WORK MAY BE  USED,  PRACTICED,  PERFORMED
  COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED,  ABRIDGED, CONDENSED,
  EXPANDED,  COLLECTED,  COMPILED,  LINKED,  RECAST, TRANSFORMED OR ADAPTED
  WITHOUT THE PRIOR WRITTEN CONSENT OF NOVELL, INC. ANY USE OR EXPLOITATION
  OF THIS WORK WITHOUT AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO
  CRIMINAL AND CIVIL LIABILITY.

****************************************************************************/

package com.novell.service.rfc1960;

/**@Internal
 * RFC1960 (LDAP) Search String Component
 *
 * <p>This class is instantiated, and setup by the Rfc1960Parser class.  It
 * will be returned to the user through the enumeration interface in that
 * class, and only get accessors will be available to the user of this class
 *
 */

public class SearchStringComponent
{
   /**
    * Operation Type - equals
    *
    * <p>(EQUALS = 0)
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationType
    */

   public static final int EQUALS            = 0;

   /**
    * Operation Type - approximately equal
    *
    * <p>(APPROXIMATE = 1)
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationType
    */

   public static final int APPROXIMATE       = 1;

   /**
    * Operation Type - greater than or equal to
    *
    * <p>(GREATER_OR_EQUAL = 2)
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationType
    */

   public static final int GREATER_OR_EQUAL  = 2;

   /**
    * Operation Type - less than or equal to
    *
    * <p>(LESS_OR_EQUAL = 3)
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationType
    */

   public static final int LESS_OR_EQUAL     = 3;

   /**
    * Operation Type - present
    *
    * <p>(PRESENT = 4)
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationType
    */

   public static final int PRESENT           = 4;

   /**
    * Operation Type - sub-string
    *
    * <p>(SUBSTRING = 5)
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationType
    */

   public static final int SUBSTRING         = 5;


   /**
    * Operation Description - equals
    *
    * <p>(EQUALS_STRING = "equal")
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationTypeDescription
    */

   public static final String EQUALS_STRING           = "equal";

   /**
    * Operation Description - approximately equal
    *
    * <p>(APROXIMATE_STRING = "approximate")
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationTypeDescription
    */

   public static final String APPROXIMATE_STRING      = "approximate";

   /**
    * Operation Description - greater than or equal to
    *
    * <p>(GREATER_OR_EQUAL_STRING = "greaterOrEqual")
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationTypeDescription
    */

   public static final String GREATER_OR_EQUAL_STRING = "greaterOrEqual";

   /**
    * Operation Description - less than or equal to
    *
    * <p>(LESS_OR_EQUAL_STRING = "lessOrEqual")
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationTypeDescription
    */

   public static final String LESS_OR_EQUAL_STRING    = "lessOrEqual";

   /**
    * Operation Description - present
    *
    * <p>(PRESENT_STRING = "present")
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationTypeDescription
    */

   public static final String PRESENT_STRING          = "present";

   /**
    * Operation Description - sub-string
    *
    * <p>(SUBSTRING_STRING = "substring")
    * </p>
    * @see com.novell.service.rfc1960.SearchStringComponent#getOperationTypeDescription
    */

   public static final String SUBSTRING_STRING        = "substring";

   protected boolean compared;
   protected int pushCount;
   protected int popCount;

   private String component;
   private String attributeId;
   private String operation;
   private String operand;
   private int operationType;
   private int offset;
   private boolean operandReplacement;
   private Object replacementObject = null;

   private int replacementIndex = -1;

   /**
    * Returns the component string of the requested component
    *
    * <p>This method returns the component string of the requested
    * component.  If the component is "filesize>=1024", this method would
    * return "filesize>=filesize"
    * </p>
    *
    * @return                    The attribute id of the requested component
    *
    */

   public String getComponent()
   {
      return component;
   }

   /**
    * Returns the attribute id of the requested component
    *
    * <p>This method returns the attribute id portion of the requested
    * component.  If the component is "filesize>=1024", this method would
    * return "filesize"
    * </p>
    *
    * @return                    The attribute id of the requested component
    *
    */

   public String getAttributeId()
   {
      return attributeId;
   }

   /**
    * Returns the operation of the requested component
    *
    * <p>This method returns the operation portion of the requested
    * component.  If the component is "filesize>=1024", this method would
    * return ">=1024"
    * </p>
    *
    * @return                    The operation of the requested component
    *
    */

   public String getOperation()
   {
      return operation;
   }

   /**
    * Returns the operations operand of the requested component
    *
    * <p>This method returns the operations operand portion of the requested
    * component.  If the component is "filesize>=1024", this method would
    * return "1024"
    * </p>
    *
    * @return                    The operand of the requested component
    *
    */

   public String getOperand()
   {
      return operand;
   }

   /**
    * Returns the value of the replacement operand of the requested component
    *
    * <p>This method returns the operations operand portion of the requested
    * component.  If the component is "filesize>=%5", this method would
    * return 5
    * </p>
    *
    * @return                    The operand of the requested component
    *
    */

   public int getReplacementIndex()
   {
      if (!operandReplacement)
      {
         throw new IllegalStateException();
      }
      return replacementIndex;
   }

   /**
    * Returns the operation type of the requested component
    *
    * <p>This method returns the operation portion of the requested
    * component.  If the component is "filesize>=1024", this method would
    * return GREATER_OR_EQUAL.  Possible values are:
    * </p>
    *    <i>EQUALS</i>
    *    <i>APPROXIMATE</i>
    *    <i>GREATER_OR_EQUAL</i>
    *    <i>LESS_OR_EQUAL</i>
    *
    * @return                    The operation type of the requested component
    *
    */

   public int getOperationType()
   {
      return operationType;
   }

   /**
    * Returns true if the components operand is the replacement flag ({n})
    *
    * <p>The JNDI extension of the RFC1960 syntax allows for the replacement
    * of non-string operand values with an object (See jndi.ds.SearchFilter).
    * This parser will look for an operand of length 1, with that single
    * character operand being the '{n}'.  If this is the case for the requested
    * component, this method returns true, otherwise it returns false.
    * </p>
    *
    * @return                    true, if the operand is the replacement
    *                            flag.  false otherwise.
    *
    */

   public boolean operandReplacement()
   {
      return operandReplacement;
   }

   /**
    * Sets the operand replacement object
    *
    * <p>If operandReplancement is true, the user can set the replacement
    * object associated with this component with this method.
    * </p>
    *
    * @param obj                 The replacement object for this component.
    * @exception                 IllegalStateException
    */

   public void setReplacementObject(Object obj)
   {
      if (!operandReplacement)
         throw new IllegalStateException();
      replacementObject = obj;
   }


   /**
    * Gets the operand replacement object
    *
    * <p>If operandReplancement is true, the user can set the replacement
    * object associated with this component with the setReplacementObject
    * method.  This method returns this value.
    * </p>
    *
    * @return                    The replacement object
    */

   public Object getReplacementObject()
   {
      return replacementObject;
   }


   /**
    * Returns the components offset within the searchString
    *
    * <p>This method returns the components offset within the oringal search
    * string that was parsed by the Rfc1960Parser.
    * </p>
    *
    * @return                    The index of the component in relationship
    *                            to the original search string
    */

   public int getOffset()
   {
      return offset;
   }

   /**
    * Get the syntax description of the given operation type
    *
    * <p>Returns the Syntax description of the operationType
    * </p>
    *
    * @param operationType          The type to return the description of
    * @return                       The syntax description of operationType
    */

   public static String getOperationTypeDescription(int operationType)
   {
      switch (operationType)
      {
         case EQUALS:
            return EQUALS_STRING;
         case APPROXIMATE:
            return APPROXIMATE_STRING;
         case GREATER_OR_EQUAL:
            return GREATER_OR_EQUAL_STRING;
         case LESS_OR_EQUAL:
            return LESS_OR_EQUAL_STRING;
         case PRESENT:
            return PRESENT_STRING;
         case SUBSTRING:
            return SUBSTRING_STRING;
         default:
            return null;
      }
   }

   /**
    * Do Sub-String compare.
    *
    * <p>This method will find all of the sub-strings in the operand
    * parameter and check them against the "against" parameter.  This method
    * will return true if all of the various sub-strings of operand are
    * found in the against string.  These compares will be case sensitive.
    * If either string is null or empty, this method will return false.  If
    * the search string has no wild card characters ("*") in it, the whole
    * string will be used as the sub-string.
    * </p>
    *
    * @param operand             The string containing the sub-strings to
    *                            look for.  For example "Sub1*Sub2*Sub3"
    *
    * @param against             The string to check against.  Look for the
    *                            various sub-strings from the operand in
    *                            this string
    *
    * @param ignoreCase          True if String.equalsIgnoreCase is to be
    *                            used for the compares.
    *
    * @return                    True if all of the sub-strings of the
    *                            operand string are found in the against
    *                            string.  False otherwise.
    */

   public static boolean compareSubString(
                           String operand,
                           String against,
                           boolean ignoreCase)
   {
      int operandOffset = 0;
      int againstOffset = 0;
      boolean firstPass = true;
      boolean moreSubs = true;

      if (operand == null || against == null)
         return false;

      if (operand.length() == 0 || against.length() == 0)
         return false;

      if (ignoreCase)
      {
         operand = operand.toLowerCase();
         against = against.toLowerCase();
      }

      do
      {
         String substring;
         int wildOffset = operand.indexOf('*', operandOffset);
         if (wildOffset == -1)
         {
            substring = operand.substring(operandOffset);
            if (!firstPass)
               moreSubs = false;
         }
         else
            substring = operand.substring(operandOffset, wildOffset);

         if (firstPass)
         {
            firstPass = false;
            if (wildOffset == 0)
            {
               operandOffset = wildOffset + 1;
               continue;   // no sub string to match, go back to the top
            }
         }

         if (substring.length() == 0)
            return true;   // ended in a wild card

         int foundOffset = against.indexOf(substring, againstOffset);
         if (foundOffset == -1)
            return false;

         againstOffset = foundOffset + substring.length();
         operandOffset = wildOffset + 1;
      }while(moreSubs);
      return true;
   }

   /**
    * Compares the String operand string to the against string
    *
    * <p>Based on the operation type, the operand string is compared to the
    * against string.  For a terminology example, in the following search
    * string, "(a=*test*)" a is the against string and *test* is the operand
    * string.  The various type comparisions are as follows:
    * </p>
    *
    * EQUALS                     if both are null, empty or identical,
    *                            returns true, any mismatch returns false.
    *
    * APPROXIMATE                Throws IllegalArgumentException.
    *
    * GREATER_OR_EQUAL           If the Unicode ordering of against is
    *                            greater than or equal to the Unicode
    *                            ordering of operand, returns true, otherwise
    *                            returns false.  A null string is considered
    *                            to be the lowest possible Unicode ordering
    *                            value.
    *
    * LESS_OR_EQUAL              If the Unicode ordering of against is less
    *                            than or equal to the Unicode ordering of
    *                            operand, returns true, otherwise returns
    *                            false. A null string is considered to be the
    *                            lowest possible Unicode ordering value.
    *
    * PRESENT                    If the against string is not null return
    *                            true, otherwise return false.
    *
    * SUBSTRING                  Use the operand string as the wild card
    *                            substring source.  Look for all substrings
    *                            of the operand string in the against string.
    *                            If all substrings are found, return true,
    *                            otherwise return false.  If either string is
    *                            null, return false.
    *
    * @param type                The compare operation type to apply
    * @param operand             The operand string
    * @param against             The compared string
    * @param ignoreCase          If true use String.equalsIgnoreCase compare.
    *
    * @return                    Compare result based on type and values as
    *                            outlined above.
    *
    * @exception                 IllegalArgumentException
    */

   public static boolean compareString(
                           int type,
                           String operand,
                           String against,
                           boolean ignoreCase)
   {
      if (type == APPROXIMATE)
         throw new IllegalArgumentException(
                     getOperationTypeDescription(type));

      // check equals
      if (type == EQUALS)
      {
         if (operand == null)
         {
            if (against == null)
               return true;
            return false;
         }

         if (against == null)
         {
            if (operand == null)
               return true;
            return false;
         }
         if (ignoreCase)
            return operand.equalsIgnoreCase(against);
         return operand.equals(against);
      }

      // check greater than or equal

      if (type == GREATER_OR_EQUAL)
      {
         if (operand == null)
         {
            /*
                If against is null they are equal, if against has anything
                it would be greater
            */
            return true;
         }

         if (against == null)
         {
            if (operand == null)
               return true;      // they are equal
            // if operand has anything, against is less than
            return false;
         }

         int order = against.compareTo(operand);
         if (order >= 0)
            return true;
         return false;
      }

      // check less than or equal

      if (type == LESS_OR_EQUAL)
      {
         if (operand == null)
         {
            if (against == null)
               return true;      // they are equal

            // if against has anything, it is greater
            return false;
         }

         if (against == null)
         {
            /*
               If operand is null they are equal, otherwise, operand would
               always be greater than against
            */

            return true;
         }

         int order = against.compareTo(operand);
         if (order <= 0)
            return true;
         return false;
      }

      // check present

      if (type == PRESENT)
      {
         if (against != null)
            return true;
         return false;
      }

      // must be substring, check it

      return compareSubString(operand, against, ignoreCase);
   }

   /**
    * Compares the Integer operand to the Integer against
    *
    * <p>Based on the operation type, the Integer operand is compared to the
    * Integer against.  For a terminology example, in the following search
    * string, "(a=5)" a is the against Integer and 5 is the operand
    * Integer.  The various type comparisions are as follows:
    * </p>
    *
    * EQUALS                     if the values are equal return true,
    *                            otherwise returns false.
    *
    * APPROXIMATE                Throws IllegalArgumentException.
    *
    * GREATER_OR_EQUAL           if the value of against is greater than or
    *                            equal to the value of operand, return true,
    *                            otherwise, return false.
    *
    * LESS_OR_EQUAL              If the value of against is less than or
    *                            equal to the value of operand, return true,
    *                            otherwise, return false.
    *
    * PRESENT                    Always returns true.
    *
    * SUBSTRING                  Throws IllegalArgumentException.
    *
    * @param type                The compare operation type to apply
    * @param operand             The operand Integer
    * @param against             The compared Integer
    *
    * @return                    Compare result based on type and values as
    *                            outlined above.
    *
    * @exception                 IllegalArgumentException
    */

   public static boolean compareInt(int type, int operand, int against)
   {
      if (type == APPROXIMATE || type == SUBSTRING)
         throw new IllegalArgumentException(
                     getOperationTypeDescription(type));

      // check equals

      if (type == EQUALS)
      {
         if (operand == against)
            return true;
         return false;
      }

      // check greater than or equal

      if (type == GREATER_OR_EQUAL)
      {
         if (against >= operand)
            return true;
         return false;
      }

      // check less than or equal

      if (type == LESS_OR_EQUAL)
      {
         if (against <= operand)
            return true;
         return false;
      }

      // must be present check

      return true;
   }

   /**
    * Compares the Integer operand to the boolean against
    *
    * <p>Based on the operation type, the boolean operand is compared to the
    * boolean against.  For a terminology example, in the following search
    * string, "(a=0)" a is the against boolean and 0 is the operand
    * Integer.  The various type comparisions are as follows:
    * </p>
    *
    * EQUALS                     The operand is considered equal to false if
    *                            zero, and true if non-zero.  It's converted
    *                            true/false state is then compared with
    *                            against and the result returned.
    *
    * APPROXIMATE                Throws IllegalArgumentException.
    *
    * GREATER_OR_EQUAL           Throws IllegalArgumentException.
    *
    * LESS_OR_EQUAL              Throws IllegalArgumentException.
    *
    * PRESENT                    Always returns true.
    *
    * SUBSTRING                  Throws IllegalArgumentException.
    *
    * @param type                The compare operation type to apply
    * @param operand             The operand Integer
    * @param against             The compared boolean
    *
    * @return                    Compare result based on type and values as
    *                            outlined above.
    *
    * @exception                 IllegalArgumentException
    */

   public static boolean compareBoolean(
      int type,
      int operand,
      boolean against)
   {
      if (type == APPROXIMATE || type == SUBSTRING ||
          type == GREATER_OR_EQUAL || type == LESS_OR_EQUAL)
         throw new IllegalArgumentException(
                     getOperationTypeDescription(type));

      if (type == PRESENT)
         return true;

      boolean bValue;
      if (operand == 0)
         bValue = false;
      else
         bValue = true;

      if (type == EQUALS)
      {
         if (bValue == against)
            return true;
         return false;
      }
      // should never happen
      return false;
   }

   /**
    * Compares the String operand to the boolean against
    *
    * <p>Based on the operation type, the String operand is compared to the
    * boolean against.  For a terminology example, in the following search
    * string, "(a=true)" a is the against boolean and "true" is the operand
    * String.  The following shows the valid operand Strings and there
    * assumed boolean values:
    *
    * ignore case for all
    *
    * "t"      = true
    * "true"   = true
    * "f"      = false
    * "false"  = false
    *
    * The various type comparisions are as follows:
    * </p>
    *
    * EQUALS                     If the operand string is not equal to one of
    *                            those listed above, return false.  Otherwise
    *                            make operand correlation and make the
    *                            comparision, returning the result.
    *
    * APPROXIMATE                Throws IllegalArgumentException.
    *
    * GREATER_OR_EQUAL           Throws IllegalArgumentException.
    *
    * LESS_OR_EQUAL              Throws IllegalArgumentException.
    *
    * PRESENT                    Always returns true.
    *
    * SUBSTRING                  Throws IllegalArgumentException.
    *
    * @param type                The compare operation type to apply
    * @param operand             The operand Integer
    * @param against             The compared boolean
    *
    * @return                    Compare result based on type and values as
    *                            outlined above.
    *
    * @exception                 IllegalArgumentException
    */

   public static boolean compareBoolean(
      int type,
      String operand,
      boolean against)
   {
      if (type == APPROXIMATE || type == SUBSTRING ||
          type == GREATER_OR_EQUAL || type == LESS_OR_EQUAL)
         throw new IllegalArgumentException(
                     getOperationTypeDescription(type));

      if (type == PRESENT)
         return true;

      boolean found = false;
      boolean bValue = false;
      if ("t".equalsIgnoreCase(operand))
      {
         bValue = true;
         found = true;
      }else
         if ("true".equalsIgnoreCase(operand))
         {
            bValue = true;
            found = true;
         }else
            if ("f".equalsIgnoreCase(operand))
            {
               bValue = false;
               found = true;
            }else
               if ("false".equalsIgnoreCase(operand))
               {
                  bValue = false;
                  found = true;
               }

      if (!found)
         return false;

      // check equals

      if (type == EQUALS)
      {
         if (bValue == against)
            return true;
         return false;
      }

      // should never happen
      return false;
   }

   /**
    * Compares two compareStrings for equality
    *
    * <p>This method will check two strings to see if they are equal.  If 
    * both of the strings are null, they are considered equal.
    *
    * @return                    True if the values are equal, false 
    *                            otherwise.
    */

   public static boolean compareStringsEqual(String a, String b)
   {
      if (a == null && b == null)
         return true;

      if (a == null)
         return false;

      if (b == null)
         return false;

      return a.equals(b);
   }

/* **************************************************************************
*  Protected and Private methods
****************************************************************************/

   // can only be instantiated by a Rfc1960Parser object

   protected SearchStringComponent(
               String component, int pushCount, int offset)
   {
      this.component = component;
      this.pushCount = pushCount;
      this.offset = offset;
      popCount = 0;
      compared = false;

      int index = component.indexOf("~=", 0);
      if (index != -1)
      {
         operationType = APPROXIMATE;
         breakItUp(component, index, 2);
         return;
      }

      index = component.indexOf(">=", 0);
      if (index != -1)
      {
         operationType = GREATER_OR_EQUAL;
         breakItUp(component, index, 2);
         return;
      }
      index = component.indexOf("<=", 0);
      if (index != -1)
      {
         operationType = LESS_OR_EQUAL;
         breakItUp(component, index, 2);
         return;
      }
      index = component.indexOf("=", 0);
      if (index != -1)
      {
         operationType = EQUALS;
         breakItUp(component, index, 1);
         return;
      }
      throw new IllegalArgumentException();
   }

   /**
    * convert character 'c' that represents a hexadecimal digit to an integer.
    * if 'c' is not a hexidecimal digit [0-9A-Fa-f], -1 is returned.
    * otherwise the converted value is returned.
    */
   private static int hexchar2int( byte c ) {
      if ( c >= '0' && c <= '9' ) {
         return( c - '0' );
      }
      if ( c >= 'A' && c <= 'F' ) {
         return( c - 'A' + 10 );
      }
      if ( c >= 'a' && c <= 'f' ) {
         return( c - 'a' + 10 );
      }
      return( -1 );
   }

   private static String unescapeFilterValue(byte[] orig, int start, int end)
   {
      boolean escape = false, escStart = false;
      int ival;
      byte ch;

      int len = end - start;
      byte tbuf[] = new byte[len];
      int j = 0;
      for (int i = start; i < end; i++) {
         ch = orig[i];
         if (escape) {
            // Try LDAP V3 escape (\\xx)
            if ((ival = hexchar2int(ch)) < 0) {
               if (escStart) {
                  // V2: \* \( \)
                  escape = false;
                  tbuf[j++] = ch;
               } else {
                  // escaping already started but we can't find 2nd hex
                  // throw new InvalidSearchFilterException("invalid escape sequence: " + orig);
                  throw new IllegalArgumentException();
               }
            } else {
               if (escStart) {
                  tbuf[j] = (byte)(ival<<4);
                  escStart = false;
               } else {
                  tbuf[j++] |= (byte)ival;
                  escape = false;
               }
            }
         } else if (ch != '\\') {
            tbuf[j++] = ch;
            escape = false;
         } else {
            escStart = escape = true;
         }
      }
      byte[] answer = new byte[j];
      System.arraycopy(tbuf, 0, answer, 0, j);

      //return answer;
      return new String(answer);
   }

   private void breakItUp(String component, int opIndex, int operandOffset)
   {
      int size = component.length();

      // strip trailing white space from attributeId
      int i;
      for (i=opIndex-1; i >= 0; i--)
      {
         char c = component.charAt(i);
         if (c == ' ' || c== '\t')
            ;
         else
         {
            attributeId = component.substring(0, i + 1);
            break;
         }
      }

      if (i < 0)
         throw new IllegalArgumentException();

      operation = component.substring(opIndex, size);

/*
      // strip leading white space from operand
      for (i = opIndex + operandOffset; i < size; i++)
      {
         char c = component.charAt(i);
         if (c == ' ' || c== '\t')
            ;
         else
         {
            operand = component.substring(i, size);
            break;
         }
      }
      if (i == size)
         throw new IllegalArgumentException();
*/
      // Get operand
      operand = component.substring(opIndex + operandOffset, size);

      operand = unescapeFilterValue(operand.getBytes(), 0, operand.length());

      // look for replacement filter
      operandReplacement = false;
      if (operand.charAt(0) == '{')
      {
         int lio = operand.lastIndexOf('}');
         if (lio != -1)
         {
            String theNumber = operand.substring(1, lio);

            try
            {
               replacementIndex = Integer.parseInt(theNumber);
               operandReplacement = true;
            } catch (NumberFormatException nfe)
            {
            // its not a valid replacement number, consider it just a string
            }
         }
      }

      //look for present filter
      if (operationType == EQUALS &&
          operand.length() == 1 &&
          operand.charAt(0) == '*')
      {
         operationType = PRESENT;
      }

      //look for substring filter
      if (operationType == EQUALS && operand.length() > 1)
      {
         if (operand.indexOf('*') != -1)
            operationType = SUBSTRING;
      }
   }
}