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

  $Archive: /njcl_v2rmi/src/com/novell/service/nds/naming/net/NetSearchEnumerator.java $
  $Revision: 25 $
  $Modtime: 1/05/01 3:59p $

  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.nds.naming.net;

import java.rmi.RemoteException;
import java.io.*;

import java.util.*;

import javax.naming.*;
import javax.naming.directory.*;

import com.sun.jndi.toolkit.ctx.*;

import com.novell.service.jncp.*;
import com.novell.service.jncpv2r.clx.ClxJNI;

import com.novell.service.nds.*;
import com.novell.service.nds.naming.*;
import com.novell.service.nds.naming.NdsDirContextWrappable;
import com.novell.service.nds.net.*;
import com.novell.service.nds.ldap.LdapAttributeValueFactory;

import com.novell.service.session.SessionException;
import com.novell.service.jncpv2r.net.*;

import com.novell.service.toolkit.jcl.NWInteger;

import com.novell.utility.naming.Environment;
import com.novell.utility.naming.directory.NAttributes;
import com.novell.utility.naming.spi.ContextFactory;
import com.novell.utility.naming.spi.ContextFactoryBuilder;


/* *
 * NetSearchEnumerator
 */
class NetSearchEnumerator
   implements NamingEnumeration, NdsNamingEnumeratorWrappable
{
   private String objectName;
   private NetEnvironment environment;
   private NetService service;

   private DirContext schemaContext;

   private NetBuffer objectInfo;
   private NetBuffer filterBuffer;
   private NetBuffer attrNames = null;
   private NetFilter ndsFilter;
   private Object [] filterArgs;
   private NetIterationHandle iterationHandle;
   private int scope = NetJNI.DS_SEARCH_SUBORDINATES;

   private boolean retObj = false;
   private boolean searchAliases = true;
   private boolean allAttrs;

   private int countObjectsToSearch = 0;
   private NWInteger countObjectsSearched;
   private int entriesLeft;
   private int parmIdx = 0;
   private StreamTokenizer rfc;
   private StringTokenizer st;

   private ContextFactory factory = new NetContextFactory ();
   private static NdsNamingExceptionFactory exceptionFactory =
       new NdsNamingExceptionFactory ();

   private long countLimit;
   private boolean sized;
   private int objectCount;
   private SearchObject nextSearchObject;


   /**
    * Constructor 1 of 2
    *
    * @param ndsContext          NetDirContext with which to operate.
    * @param matchingAttributes  Attribute criteria for search.
    * @param attributesToReturn  List of attribute names for search to
    *                            return.
    *
    */
   public NetSearchEnumerator (
         String objectName,
         NetEnvironment environment,
         Attributes matchingAttributes,
         String[] attributesToReturn)
      throws NamingException
   {
      this.objectName = objectName;
      this.environment = (NetEnvironment) environment.clone ();
      this.service = environment.getService ();

      try
      {
         iterationHandle = new NetIterationHandle (
                                 service,
                                 NetJNI.DSV_SEARCH);

         countObjectsSearched = new NWInteger ();

         objectInfo = new NetBuffer (
                              service,
                              environment.getBatchSize ());

         filterBuffer = new NetBuffer (
                                 service,
                                 NetJNI.DSV_SEARCH_FILTER,
                                 environment.getBatchSize ());

         ndsFilter = new NetFilter (service);

         if (attributesToReturn == null) // return all attributes
         {
            allAttrs = true;
         }
         else if (attributesToReturn.length == 0) // return NO attributes
         {
            allAttrs = false;
         }
         else // return specified attributes
         {
            allAttrs = false;
            attrNames = new NetBuffer (
                                 service,
                                 NetJNI.DSV_SEARCH,
                                 environment.getBatchSize ());

            for (int i = 0; i < attributesToReturn.length; i++)
            {
               attrNames.putAttributeName (attributesToReturn[i]);
            }

         }

         if (matchingAttributes != null &&
             matchingAttributes.size () > 0)
         {
            boolean attrStart = true;

            for (NamingEnumeration enum =
                 matchingAttributes.getAll ();
                 enum.hasMoreElements ();)
            {
               Attribute attr = (Attribute) enum.next();

               if (attr.size () > 0)
               {
                  NdsAttributeValue value;
                  NetAttributeValue valueImpl;
                  boolean valueStart = true;

                  if (!attrStart)
                     ndsFilter.addFilterToken(NetJNI.FTOK_AND);

                  for (Enumeration vals = attr.getAll ();
                       vals.hasMoreElements ();)
                  {
                     value = (NdsAttributeValue) vals.nextElement ();

                     if( value instanceof NetAttributeValue )
                     {
                        valueImpl = (NetAttributeValue) value;
                     }
                     else
                     {
                        valueImpl = NetAttributeValueFactory.createValue(value);
                     }

                     if (!valueStart)
                        ndsFilter.addFilterToken(NetJNI.FTOK_AND);

                     // we assume that the values of the attribute are
                     // homogeneous since we get the syntax ID from the
                     // value and not the attribute itself.
                     ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
                                          attr.getID (),
                                          valueImpl.getNdsSyntaxId());

                     ndsFilter.addFilterToken(NetJNI.FTOK_EQ);

                     ndsFilter.addFilterToken(NetJNI.FTOK_AVAL,
                                          valueImpl.toByte (),
                                          valueImpl.getNdsSyntaxId());

                     valueStart = false;
                  }

                  attrStart = false;
               }
               else
               {
                  throw new InvalidAttributeValueException ();
               }
            }

         }
         else
         {
            // By default, we want search to return all objects. BaseClass:*
            // will return all objects without the user having to authenticate.
            ndsFilter.addFilterToken(NetJNI.FTOK_BASECLS);

            ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
                                  "*",
                                  NdsSyntaxId.CLASS_NAME_ID);
         }


         ndsFilter.addFilterToken(NetJNI.FTOK_END);
         ndsFilter.putFilter(filterBuffer);

         nextSearchObject = getFirstSearchObject ();
      }
      catch (Exception e)
      {
//         throw (new NamingException ());
         NamingException ne = new NamingException ();

         ne.setRootCause (e);
         throw (ne);
      }

   } /* NetSearchEnumerator */


   /**
    * Constructor 2 of 2
    *
    * @param ndsContext NetDirContext with which to operate.
    * @param filterExpr Rfc1960 filter.
    * @param filterArgs Object to insert into search filter.
    * @param cons       Additional search constraints.
    */
   public NetSearchEnumerator (
         String objectName,
         DirContext schemaContext,
//         NetDirContext netContext,
         NetEnvironment environment,
         String filterExpr,
         Object [] filterArgs,
         SearchControls cons)
      throws NamingException
   {
      this.objectName = objectName;
//      objectName = netContext.getDistinguishedName ();
      this.environment = (NetEnvironment) environment.clone ();
//      environment = (NetEnvironment) netContext.environment.clone ();
      service = environment.getService ();

      // allow access to attribute definition's syntaxId
      this.schemaContext = schemaContext;
//      this.schemaContext = netContext.getSchema ("");

      if (cons == null)
      {
         cons = new SearchControls ();
      }

      try
      {
         iterationHandle = new NetIterationHandle (
                                 service,
                                 NetJNI.DSV_SEARCH);

         countObjectsSearched = new NWInteger ();

         this.filterArgs = filterArgs;

         objectInfo = new NetBuffer (
                              service,
                              environment.getBatchSize ());

         // ----- process search constraints -----
         countLimit = cons.getCountLimit ();
         sized = countLimit == 0 ? false : true;
//         this.countObjectsToSearch =
//            (countLimit > Integer.MAX_VALUE) ?
//               Integer.MAX_VALUE : (int) countLimit;
         this.searchAliases = cons.getDerefLinkFlag ();
         String[] attributesToReturn = cons.getReturningAttributes ();

         if (attributesToReturn == null) // return all attributes
         {
            allAttrs = true;
         }
         else if (attributesToReturn.length == 0) // return NO attributes
         {
            allAttrs = false;
         }
         else // return specified attributes
         {
            allAttrs = false;
            attrNames = new NetBuffer (
                                 service,
                                 NetJNI.DSV_SEARCH,
                                 environment.getBatchSize ());

            for(int i = 0; i < attributesToReturn.length; i++)
            {
               attrNames.putAttributeName (attributesToReturn[i]);
            }
         }

         this.scope = cons.getSearchScope ();
         this.scope =
            (scope == SearchControls.OBJECT_SCOPE) ?
               NetJNI.DS_SEARCH_ENTRY :
            (scope == SearchControls.ONELEVEL_SCOPE) ?
               NetJNI.DS_SEARCH_SUBORDINATES :
            (scope == SearchControls.SUBTREE_SCOPE) ?
               NetJNI.DS_SEARCH_SUBTREE : scope;

         this.retObj = cons.getReturningObjFlag ();

         // what to do with getTimeLimit () ?????

         // --------------------------------------

         // ----- process search filter -----
/*
         rfc = new StreamTokenizer(
               new StringReader(filterExpr));

         rfc.resetSyntax();
         rfc.whitespaceChars(0, ' ');
         rfc.wordChars('@', 'z');
         rfc.wordChars('0', '9'); // make digit chars word chars
         rfc.wordChars(':', ':');
         rfc.wordChars('-', '-');
         rfc.wordChars('_', '_');
         rfc.wordChars(' ', ' ');
         rfc.wordChars('.', '.');
		 rfc.wordChars(0x80, 0xff);
*/
		 // make sure filterExpr has enclosing parens.
		 if(filterExpr.charAt(0) != '(')
			filterExpr = "(" + filterExpr + ")";

		 st = new StringTokenizer(filterExpr, "()=", true);

         filterBuffer = new NetBuffer (
                                 service,
                                 NetJNI.DSV_SEARCH_FILTER,
                                 environment.getBatchSize ());

         ndsFilter = new NetFilter (service);

         parseFilter();

         ndsFilter.addFilterToken(NetJNI.FTOK_END);
         ndsFilter.putFilter(filterBuffer);

         // ---------------------------------

         nextSearchObject = getFirstSearchObject ();
      }
      catch (Exception e)
      {
//         throw (new NamingException ());
         NamingException ne = new NamingException ();

         ne.setRootCause (e);
         throw (ne);
      }

   } /* NetSearchEnumerator */


// --------------------------------------------------------------------------
// Start of StreamTokenizer RFC2254 parsing code. NOTE: replace with
// StringTokenizer parsing code...
// --------------------------------------------------------------------------
/*
   //
   // Helper method. Will parse an rfc1960 filter and build an NDS filter.
   //
   private void parseFilter()
      throws InvalidSearchFilterException, NamingException
   {
      try
      {
         if(rfc.nextToken() != '(')
               throw new InvalidSearchFilterException (); // missing '('
      }
      catch (IOException e)
      {
         throw new NamingException ();
      }

      parseFilterComp();

   }


   // * Rfc1960 filter helper method. Will Parse a filter component.
   private void parseFilterComp()
      throws InvalidSearchFilterException, NamingException
   {
      try
      {
         int tok = rfc.nextToken(); // get operator or attribute name
         String attrName = null;
         int syntaxId = -1;

         if (tok == StreamTokenizer.TT_WORD)
         {
            attrName = rfc.sval.trim (); // attribute name

            // get attribute syntax ID from attrName
            if(attrName.equalsIgnoreCase("name"))  // FTOK_RDN
            {
               syntaxId = NdsSyntaxId.DISTINGUISHED_NAME_ID;
            }
            else if(attrName.equalsIgnoreCase("baseclass")) // FTOK_BASECLS
            {
               syntaxId = NdsSyntaxId.CLASS_NAME_ID;
            }
            else
            {
               Attributes sAttrs =
                  schemaContext.getAttributes (attrName + ".SC=Attributes");
               Attribute syntax = sAttrs.get("Syntax ID");
               Enumeration enum = syntax.getAll();
               NdsInteger i = (NdsInteger)enum.nextElement();
               syntaxId = i.intValue();
            }
         }

         if(tok == '&')
         {
            parseFilterList(NetJNI.FTOK_AND);
         }
         else if(tok == '|')
         {
            parseFilterList(NetJNI.FTOK_OR);
         }
         else if(tok == '!')
         {
            ndsFilter.addFilterToken(NetJNI.FTOK_NOT);
            parseFilter();
            if(rfc.nextToken() != ')')
                  throw new InvalidSearchFilterException (); // missing ')'
         }
         else
         {
            // get item
            int relOp = rfc.nextToken(); // get relational operator
            String val = null;
            NetAttributeValue valueImpl = null;

            if(relOp == '>')
            {
               if(rfc.nextToken() != '=')
                  throw new InvalidSearchFilterException ();

               relOp = NetJNI.FTOK_GE;

               valueImpl = parseRfc1778Value(syntaxId);
            }

            else if(relOp == '<')
            {
               if(rfc.nextToken() != '=')
                  throw new InvalidSearchFilterException ();

               relOp = NetJNI.FTOK_LE;

               valueImpl = parseRfc1778Value(syntaxId);
            }

            else if(relOp == '~')
            {
               if(rfc.nextToken() != '=')
                  throw new InvalidSearchFilterException ();

               relOp = NetJNI.FTOK_APPROX;

               valueImpl = parseRfc1778Value(syntaxId);
            }

            else if(relOp == '=')
            {
               rfc.wordChars('*','*'); // help identify 'Present operator'

               int nextTok = rfc.nextToken();

               if((nextTok == StreamTokenizer.TT_WORD) &&
                  (rfc.sval.equals("*")))
               {
                  relOp = NetJNI.FTOK_PRESENT;

                  // consume closing paren
                  if(rfc.nextToken() != ')')
                     throw new InvalidSearchFilterException ();
               }
               else
               {
                  rfc.pushBack();

                  relOp = NetJNI.FTOK_EQ;

                  valueImpl = parseRfc1778Value(syntaxId);
               }

               rfc.ordinaryChar('*');

            }
            else
            {
               throw new InvalidSearchFilterException (); // invalid operator
            }

            // Place component into Nds Filter

            // First, check for two special cases; name & baseclass
            if(attrName.equalsIgnoreCase("name"))
            {
               ndsFilter.addFilterToken(NetJNI.FTOK_RDN);

               if(relOp == NetJNI.FTOK_PRESENT)
               {
                  ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
                                        "*",
                                        syntaxId);
               }
               else
               {
                  ndsFilter.addFilterToken(
                     NetJNI.FTOK_ANAME,
                     ((NdsDistinguishedName)valueImpl).getDistinguishedName (),
                     syntaxId);
               }
            }
            else if(attrName.equalsIgnoreCase("baseclass"))
            {
               ndsFilter.addFilterToken(NetJNI.FTOK_BASECLS);

               if(relOp == NetJNI.FTOK_PRESENT)
               {
                  ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
                                        "*",
                                        syntaxId);
               }
               else
               {
                  ndsFilter.addFilterToken(
                             NetJNI.FTOK_ANAME,
                             ((NdsClassName)valueImpl).getClassName (),
                             syntaxId);
               }
            }
            else if(relOp == NetJNI.FTOK_PRESENT)
            {
               ndsFilter.addFilterToken(NetJNI.FTOK_PRESENT);

               ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
                                          attrName,
                                          syntaxId);
            }
            else
            {
                  ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
                                             attrName,
                                             syntaxId);

                  ndsFilter.addFilterToken(relOp);

                  ndsFilter.addFilterToken(NetJNI.FTOK_AVAL,
                                             valueImpl.toByte (),
                                             syntaxId);
            }
         }
      }
      catch (IOException e)
      {
         throw (new NamingException ());
      }
      catch (Exception e)
      {
         try
         {
            // add a false node to the filter, and continue
            // add "NOT PRESENT : Object Class"
            ndsFilter.addFilterToken(NetJNI.FTOK_NOT);

            ndsFilter.addFilterToken(NetJNI.FTOK_PRESENT);

            ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
                                 "Object Class",
                                 NdsSyntaxId.CLASS_NAME_ID);
         }
         catch (Exception ex)
         {
            throw (new NamingException ());
         }
      }

   }


   // * Helper method for parsing an rfc1960 filter. This method will parse
   // * a list of components and distribute the operator for that list of
   // * components between each component as it is placed in the NetFilter.
   private void parseFilterList(int tok)
      throws InvalidSearchFilterException, NamingException
   {
      boolean again = false;

      try
      {
         ndsFilter.addFilterToken(NetJNI.FTOK_LPAREN);

         while(rfc.nextToken() == '(')
         {
            if(again)
            {
               ndsFilter.addFilterToken(tok);
            }
            parseFilterComp();
            again=true;
         }

         ndsFilter.addFilterToken(NetJNI.FTOK_RPAREN);
      }
      catch (Exception e)
      {
         throw (new NamingException ());
      }

   } 


   // * 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)
   throws NamingException {
      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);
               }
            } 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;
	  String s = null;
	  try {
		 s = new String(answer, "UTF8"); // convert UTF8 bytes to unicode
	  }
	  catch(Exception e) {
	  }
	  return s;
   }

   // * Helper method for parsing attribute values. Will process replacement
   // * arguments if needed.
   private NetAttributeValue parseRfc1778Value(int ndsSyntaxId)
      throws InvalidSearchFilterException, NamingException
   {
      NdsAttributeValue attrValue=null;
      NetAttributeValue attrValueImpl=null;
	  byte[] ba;
      String LdapValue;

      try
      {
         rfc.nextToken ();

         if(rfc.ttype == '{') // process filterArg
         {
            rfc.nextToken (); // retrieve filterArg number
            try
            {
               int i = Integer.parseInt(rfc.sval);

               attrValue = (NdsAttributeValue)
                  filterArgs[i];
            }
            catch (ArrayIndexOutOfBoundsException e)
            {
               NamingException ne = new InvalidSearchFilterException ();
               ne.setRootCause (e);
               throw ne;
            }
            catch (NumberFormatException e)
            {
               NamingException ne = new InvalidSearchFilterException ();
               ne.setRootCause (e);
               throw ne;
            }

            if(rfc.nextToken() != '}')
               throw new InvalidSearchFilterException ();
				if(rfc.nextToken() != ')') // parse to and including closing paren
					throw new InvalidSearchFilterException ();
         }
         else // process Ldap String Syntax
         {
            // parse to and including closing paren
            rfc.pushBack();
            // read to the end of this ldap value and save in a string buf
            // then we will go through the string and process escaped chars
			StringBuffer sb = new StringBuffer();
            boolean process = true;
            while(process) {
               rfc.nextToken();
               switch(rfc.ttype) {
				  case StreamTokenizer.TT_WORD:
					 System.out.println("rfc.sval: " + rfc.sval);
                     sb.append(rfc.sval);
                     break;
                  case StreamTokenizer.TT_EOF: // if eof we lack ')'
                     rfc.pushBack();
                     process = false;
                     break;
                  case ')':
                     process = false;
                     break;
                  default:
                     sb.append((char)rfc.ttype);
               }
            }

			System.out.println("sb.toString(): " + sb.toString());
			ba = sb.toString().getBytes();
			for(int i=0; i<ba.length; i++) {
			   System.out.print(ba[i] + " ");
			}
			System.out.println();
			String n = "\u9363\u9286";
			ba = n.getBytes("UTF8");
			for(int i=0; i<ba.length; i++) {
			   System.out.print(ba[i] + " ");
			}
			System.out.println();
            LdapValue = unescapeFilterValue(ba, 0, ba.length);
//            LdapValue = unescapeFilterValue(sb.toString().getBytes(), 0, sb.length());

///* old way to parse values
            rfc.ordinaryChar('\\');
            while(rfc.nextToken() != ')')
            {
               switch (rfc.ttype)
               {
                  case StreamTokenizer.TT_WORD:
                     LdapValue.append(rfc.sval);
                     break;
                  case StreamTokenizer.TT_NUMBER:
                     LdapValue.append(rfc.nval);
                     break;
                  case '\\': // handle escaped '()*' chars
                     rfc.nextToken();
                     switch (rfc.ttype)
                     {
                        case '(':
                           LdapValue.append('(');
                           break;
                        case ')':
                           if (rfc.nextToken() == ')')
                           {
                              LdapValue.append(')');
                              rfc.pushBack();
                           }
                           else
                           {
                              LdapValue.append('\\');
                           }
                           break;
                        case '*':
                           LdapValue.append('*');
                           break;
                     }
                     
                     break;
                  case StreamTokenizer.TT_EOF:
                     throw new InvalidSearchFilterException ();
                  default:
                     LdapValue.append(rfc.ttype);
               }
            }
            rfc.wordChars('\\','\\'); // not sure why '\' needs to be word
// End old way to parse

            // create NdsAttributeValue from Ldap string syntax
            attrValue = LdapAttributeValueFactory.createValue(
                           ndsSyntaxId, LdapValue);
         }

         // convert to NetAttributeValue
         if( attrValue instanceof NetAttributeValue )
         {
            attrValueImpl = (NetAttributeValue) attrValue;
         }
         else
         {
            attrValueImpl = NetAttributeValueFactory.createValue(attrValue);
         }
      }
      catch (Exception e)
      {
         throw (new NamingException ());
      }

      return (attrValueImpl);

   }
*/		  
// --------------------------------------------------------------------------
// End of StreamTokenizer RFC2254 parsing code	 
// --------------------------------------------------------------------------

// --------------------------------------------------------------------------
// Start of StringTokenizer RFC2254 parsing code	 
// --------------------------------------------------------------------------

   /**
    * Helper method. Will parse an rfc1960 filter and build an NDS filter.
    */
   private void parseFilter()
      throws InvalidSearchFilterException, NamingException
   {
	  if(!st.nextToken().equals("("))
		 throw new InvalidSearchFilterException (); // missing '('

      parseFilterComp();

   } /* parseFilter () */


   /**
    * Rfc1960 filter helper method. Will Parse a filter component.
    */
   private void parseFilterComp()
      throws InvalidSearchFilterException, NamingException
   {
      try
      {
         String tok = st.nextToken().trim(); // get operator or attribute name
         int syntaxId = -1;

         if(tok.equals("&"))
         {
            parseFilterList(NetJNI.FTOK_AND);
         }
         else if(tok.equals("|"))
         {
            parseFilterList(NetJNI.FTOK_OR);
         }
         else if(tok.equals("!"))
         {
            ndsFilter.addFilterToken(NetJNI.FTOK_NOT);
            parseFilter();
			if(!st.nextToken().equals(")"))
			   throw new InvalidSearchFilterException (); // missing ')'
         }
         else
         {
            // get attribute syntax ID from attrName
            if(tok.equalsIgnoreCase("name"))  // FTOK_RDN
            {
               syntaxId = NdsSyntaxId.DISTINGUISHED_NAME_ID;
            }
            else if(tok.equalsIgnoreCase("baseclass")) // FTOK_BASECLS
            {
               syntaxId = NdsSyntaxId.CLASS_NAME_ID;
            }
            else
            {
               Attributes sAttrs =
                  schemaContext.getAttributes (tok + ".SC=Attributes");
               Attribute syntax = sAttrs.get("Syntax ID");
               Enumeration enum = syntax.getAll();
               NdsInteger i = (NdsInteger)enum.nextElement();
               syntaxId = i.intValue();
            }

            // get item
            String filtertype = st.nextToken("><~=()").trim(); // get relational operator
			int relOp;
            String val = null;
            NetAttributeValue valueImpl = null;

            if(filtertype.equals(">"))
            {
               if(!st.nextToken().equals("="))
                  throw new InvalidSearchFilterException ();

               relOp = NetJNI.FTOK_GE;

               valueImpl = parseRfc1778Value(syntaxId, st.nextToken().trim());
            }

            else if(filtertype.equals("<"))
            {
               if(!st.nextToken().equals("="))
                  throw new InvalidSearchFilterException ();

               relOp = NetJNI.FTOK_LE;

               valueImpl = parseRfc1778Value(syntaxId, st.nextToken().trim());
            }

            else if(filtertype.equals("~"))
            {
               if(!st.nextToken().equals("="))
                  throw new InvalidSearchFilterException ();

               relOp = NetJNI.FTOK_APPROX;

               valueImpl = parseRfc1778Value(syntaxId, st.nextToken().trim());
            }

            else if(filtertype.equals("="))
            {
               String value = st.nextToken().trim();

			   if(value.equals("*"))
               {
                  relOp = NetJNI.FTOK_PRESENT;
               }
               else
               {
                  relOp = NetJNI.FTOK_EQ;

                  valueImpl = parseRfc1778Value(syntaxId, value);
               }
            }
            else
            {
               throw new InvalidSearchFilterException (); // invalid operator
            }

			// consume closing paren
			if(!st.nextToken().equals(")"))
			   throw new InvalidSearchFilterException (); // missing ')'

            // Place component into Nds Filter

            // First, check for two special cases; name & baseclass
            if(tok.equalsIgnoreCase("name"))
            {
               ndsFilter.addFilterToken(NetJNI.FTOK_RDN);

               if(relOp == NetJNI.FTOK_PRESENT)
               {
                  ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
                                           "*",
                                           syntaxId);
               }
               else
               {
                  ndsFilter.addFilterToken(
                     NetJNI.FTOK_ANAME,
                     ((NdsDistinguishedName)valueImpl).getDistinguishedName(),
                     syntaxId);
               }
            }
            else if(tok.equalsIgnoreCase("baseclass"))
            {
               ndsFilter.addFilterToken(NetJNI.FTOK_BASECLS);

               if(relOp == NetJNI.FTOK_PRESENT)
               {
                  ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
                                           "*",
                                           syntaxId);
               }
               else
               {
                  ndsFilter.addFilterToken(
                             NetJNI.FTOK_ANAME,
                             ((NdsClassName)valueImpl).getClassName (),
                             syntaxId);
               }
            }
            else if(relOp == NetJNI.FTOK_PRESENT)
            {
               ndsFilter.addFilterToken(NetJNI.FTOK_PRESENT);

               ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
                                        tok,
                                        syntaxId);
            }
            else
            {
			   ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
										tok,
										syntaxId);

			   ndsFilter.addFilterToken(relOp);

			   ndsFilter.addFilterToken(NetJNI.FTOK_AVAL,
										valueImpl.toByte (),
										syntaxId);
            }
         }
      }
      catch (Exception e)
      {
         try
         {
            // add a false node to the filter, and continue
            // add "NOT PRESENT : Object Class"
            ndsFilter.addFilterToken(NetJNI.FTOK_NOT);

            ndsFilter.addFilterToken(NetJNI.FTOK_PRESENT);

            ndsFilter.addFilterToken(NetJNI.FTOK_ANAME,
                                     "Object Class",
                                     NdsSyntaxId.CLASS_NAME_ID);
         }
         catch (Exception ex)
         {
            throw (new NamingException ());
         }
      }

   } /* parseFilterComp () */


   /**
    * Helper method for parsing an rfc1960 filter. This method will parse
    * a list of components and distribute the operator for that list of
    * components between each component as it is placed in the NetFilter.
    */
   private void parseFilterList(int tok)
      throws InvalidSearchFilterException, NamingException
   {
      boolean again = false;

      try
      {
         ndsFilter.addFilterToken(NetJNI.FTOK_LPAREN);

		 while(st.nextToken().trim().equals("("))
         {
            if(again)
            {
               ndsFilter.addFilterToken(tok);
            }
            parseFilterComp();
            again=true;
         }

         ndsFilter.addFilterToken(NetJNI.FTOK_RPAREN);
      }
      catch (Exception e)
      {
         throw (new NamingException ());
      }

   }


   /**
    * 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( char 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 );
   }

   /**
    * Replace escaped hex digits with the equivalent unicode representation.
	* Assume either V2 or V3 escape mechanisms:
	* V2: \*,  \(,  \),  \\.
	* V3: \2A, \28, \29, \5C, \00.
	*/
   private String unescapeFilterValue(String value)
	  throws NamingException
   {
	  StringBuffer sb = new StringBuffer();
      boolean escape = false, escStart = false;
      int ival;
      char ch, temp = 0;

      for(int i = 0; i < value.length(); i++)
	  {
         ch = value.charAt(i);
         if(escape)
		 {
            // Try LDAP V3 escape (\\xx)
            if((ival = hexchar2int(ch)) < 0)
			{
               if(escStart)
			   {
                  // V2 escaped "*()" chars differently: \*, \(, \)
                  escape = false;
				  sb.append(ch);
               }
			   else
			   {
                  // escaping already started but we can't find 2nd hex
                  throw new InvalidSearchFilterException("invalid escape sequence: " + value);
               }
            }
			else
			{
               if(escStart)
			   {
				  temp = (char)(ival<<4);
                  escStart = false;
               }
			   else
			   {
				  temp |= (char)(ival);
				  sb.append(temp);
                  escape = false;
               }
            }
         }
		 else if(ch != '\\')
		 {
			sb.append(ch);
            escape = false;
         }
		 else
		 {
            escStart = escape = true;
         }
      }

	  return sb.toString();
   }

   /**
	* Convert a String value into a NetAttributeValue.
	*
    * Will process replacement arguments and escaped hex values if needed.
    */
   private NetAttributeValue parseRfc1778Value(int ndsSyntaxId, String value)
      throws InvalidSearchFilterException, NamingException
   {
      NdsAttributeValue attrValue=null;
      NetAttributeValue attrValueImpl=null;

      try
      {
		 if(value.charAt(0) == '{') // process filterArg
         {
			StringTokenizer argt = new StringTokenizer(value, "{}");
            String index = argt.nextToken(); // retrieve filterArg number
            try
            {
               int i = Integer.parseInt(index);

               attrValue = (NdsAttributeValue)
                  filterArgs[i];
            }
            catch (ArrayIndexOutOfBoundsException e)
            {
               NamingException ne = new InvalidSearchFilterException ();
               ne.setRootCause (e);
               throw ne;
            }
            catch (NumberFormatException e)
            {
               NamingException ne = new InvalidSearchFilterException ();
               ne.setRootCause (e);
               throw ne;
            }
         }
         else // process Ldap String Syntax
         {
			// Convert any escaped hex digits to its equivelant unicode char.
            String LdapValue = unescapeFilterValue(value);

            // create NdsAttributeValue from Ldap string syntax
            attrValue = LdapAttributeValueFactory.createValue(
                           ndsSyntaxId, LdapValue);
         }

         // convert to NetAttributeValue
         if( attrValue instanceof NetAttributeValue )
         {
            attrValueImpl = (NetAttributeValue) attrValue;
         }
         else
         {
            attrValueImpl = NetAttributeValueFactory.createValue(attrValue);
         }
      }
      catch (Exception e)
      {
         throw (new NamingException ());
      }

      return (attrValueImpl);
   }
		  
// --------------------------------------------------------------------------
// End of StringTokenizer RFC2254 parsing code	 
// --------------------------------------------------------------------------

   /**
    *
    */
   public boolean hasMoreElements ()
   {
      if (sized && countLimit <= 0)
      {
         return (false);
      }
      if (null == nextSearchObject)
      {
         return (false);
      }
      return (true);
   }

   public boolean hasMore ()
      throws NamingException
   {
      return (hasMoreElements ());
   }

   /**
    * Helper method for filling the object info buffer
    */
   private int updateObjectInfo ()
      throws NSIException
   {
      try
      {
         int objectCount;
         
         // Make sure we don't exit with 0 entries and more iterations
         do
         {
            service.search (
                              objectName,
                              this.scope,
                              searchAliases,
                              filterBuffer.getHandle (),
                              NetJNI.DS_ATTRIBUTE_VALUES,
                              allAttrs,
                              (attrNames != null)
                              ? attrNames.getHandle ()
                              : 0,
                              iterationHandle,
                              countObjectsToSearch,
                              countObjectsSearched,
                              objectInfo.getHandle ());

            objectCount = objectInfo.getObjectCount ();
            
         } while ((objectCount == 0) &&
                  (iterationHandle.moreIterations ()));
                  
         return (objectCount);
      }
      catch (SessionException e)
      {
         NSIException ne = new NSIException ();

         ne.setRootCause (e);
         throw (ne);
      }
      catch (RemoteException e)
      {
         NSIException ne = new NSIException ();

         ne.setRootCause (e);
         throw (ne);
      }

   } /* updateObjectInfo () */


   /**
    *
    */
   private SearchObject getFirstSearchObject ()
      throws NamingException, NSIException
   {
      if (0 == (objectCount = updateObjectInfo ()))
      {
         return (null);
      }
      objectCount--;
      return (new SearchObject (objectInfo, environment));

   } /* getFirstSearchObject () */

   /**
    *
    */
   private SearchObject getNextSearchObject ()
      throws NamingException, NSIException
   {
      while (0 == objectCount)
      {
         if (!iterationHandle.moreIterations ())
         {
            return (null);
         }
         objectCount = updateObjectInfo ();
      }
      objectCount--;
      return (new SearchObject (objectInfo, environment));

   } /* getNextSearchObject () */

   /**
    *
    */
   public Object nextElement ()
   {
      return (next ());
   }

   /**
    *
    */
   public Object next ()
   {
      if (false == this.hasMoreElements ())
      {
         throw (new NoSuchElementException ());
      }
      try
      {
         Context context = null;
         SearchObject currentSearchObject = nextSearchObject;

         nextSearchObject = getNextSearchObject ();

         // handle search objects in multiple iterations
         while (currentSearchObject.equalsApproximate (nextSearchObject))
         {
/*
            Enumeration valueList = nextSearchObject.getAll ();

            while (valueList.hasMoreElements ())
            {
               currentSearchObject.add ((String)valueList.nextElement ());
            }
*/
            Attributes attrs = nextSearchObject.getAttributes ();
            currentSearchObject.mergeAttributes (attrs);
            nextSearchObject = getNextSearchObject ();
         }

         if (retObj)
         {
            // create DirContext
            context = factory.getContextInstance (
                                     currentSearchObject.getName (),
                                     currentSearchObject.getNdsObjectInfo (),
                                     environment);
         }

         if (sized)
            countLimit--;

         return (new SearchResult (
                        currentSearchObject.getName (),
                        currentSearchObject.getNdsObjectInfo().getBaseClass(),
                        context,
                        currentSearchObject.getAttributes (),
                        false));


      }
      catch (NSIException e)
      {
         throw (new NoSuchElementException ());
      }
      catch (NamingException e)
      {
         throw (new NoSuchElementException ());
      }

   } /* next () */


   /**
    * Returns the next element in the object info buffer.
    *
    */
/*
   public Object next ()
   {
      int ccode;
      String className;

      if (false == this.hasMoreElements ())
      {
         throw (new NoSuchElementException ());
      }

      if (0 == entriesLeft)
      {
         doSearch ();
      }

      StringBuffer objectName = new StringBuffer ();
      StringBuffer attributeName = new StringBuffer ();
      NWInteger attributeCount = new NWInteger (0);
      NWInteger attributeValueCount = new NWInteger (0);
      NWInteger syntaxID = new NWInteger (0);
      NWInteger attributeValueSize = new NWInteger ();
      String attrIds[];
      DirContext dirContext;

      try
      {
         NetObjectInfo ndsObjectInfo = new NetObjectInfo ();

         // accumulate identical objects
         // get objects name
         objectInfo.getObjectName (
                         objectName,
                         attributeCount,
                         ndsObjectInfo);

         // create NdsDirContext
         dirContext = (DirContext) factory.getContextInstance (
                                                new String (objectName),
                                                ndsObjectInfo,
                                                environment);

         attrIds = new String[attributeCount.getValue ()];

         // get attributes for the object
         for(int i = 0; i < attributeCount.getValue (); i++)
         {
            objectInfo.getAttrName (attributeName,
                                    attributeValueCount,
                                    syntaxID);

            attrIds[i] = attributeName.toString ();
            attributeName.setLength (0);
         }

         entriesLeft--;

         return (new SearchResult(objectName.toString (),
                 (retObj) ? dirContext : null,
                 dirContext.getAttributes("", attrIds)));

      }
      catch (NSIException e)
      {
         throw (new NoSuchElementException ());
      }
      catch (NamingException e)
      {
         throw (new NoSuchElementException ());
      }


   } // next ()
*/

   /**
    *
    */
   public void close ()
      throws NamingException
   {
      try
      {
         iterationHandle.closeIteration ();
      }
      catch (NSIException e)
      {
         NamingException ne = exceptionFactory.getNamingException (e);
         throw (ne);
      }
   }

} /* NetSearchEnumerator */


