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

  $Archive: /njcl_v2/src/com/novell/service/nds/naming/net/BasicNdsIterator.java $
  $Revision: 37 $
  $Modtime: 8/21/01 4:43p $

  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.io.*;

import java.util.*;

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

import com.sun.jndi.toolkit.*;

import com.novell.service.jncp.*;
import com.novell.service.jncpv2.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.nds.NdsIterator;
import com.novell.service.nds.NdsIteratorControls;
import com.novell.service.nds.NdsIteratorResult;

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

import com.novell.service.jncpv2.net.NetIterator;

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;


/**
 *
 */
public class BasicNdsIterator implements NdsIterator, Cloneable
{
   private static ContextFactory factory = new NetContextFactory ();
   private static NdsNamingExceptionFactory exceptionFactory =
       new NdsNamingExceptionFactory ();
   private static NameParser nameParser = new NdsNameParser ();

   protected NetIterator iterator;

   private NetEnvironment environment;
   private NetService service;

   private transient Object[] filterArgs;

   private transient NetFilter filter;

   private transient DirContext schemaContext;

   private boolean retObj = false;
   private boolean empty;

   private transient StreamTokenizer rfc;


   /**
    *
    */
   public BasicNdsIterator (
         String objectName,
         NetEnvironment environment,
         Attributes matchingAttributes,
         String[] attributesToReturn)
      throws NSIException
   {
      this.environment = (NetEnvironment) environment.clone ();

      try
      {
         boolean allAttrs;
         NetBuffer filterBuffer;
         NetBuffer attrNames;

         this.service = environment.getService ();

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

         filter = new NetFilter (service);

         if (attributesToReturn == null) // return all attributes
         {
            allAttrs = true;
            attrNames = null;
         }
         else if (attributesToReturn.length == 0) // return NO attributes
         {
            allAttrs = false;
            attrNames = null;
         }
         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)
                     filter.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)
                        filter.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.
                     filter.addFilterToken(NetJNI.FTOK_ANAME,
                                          attr.getID (),
                                          valueImpl.getNdsSyntaxId());

                     filter.addFilterToken(NetJNI.FTOK_EQ);

                     filter.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.
            filter.addFilterToken(NetJNI.FTOK_BASECLS);

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

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

         iterator = new NetIterator (
                                       service,
                                       objectName,
                                       filterBuffer.getHandle (),
                                       allAttrs,
                                       (attrNames != null)
                                          ? attrNames.getHandle () : 0);
      }
      catch (NamingException e)
      {
         NSIException ne = new NSIException ();

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

      empty = iterator.atEOF ();

   } /* BasicNdsIterator () */

   /**
    *
    */
   public BasicNdsIterator (
         String objectName,
         DirContext schemaContext,
         NetEnvironment environment,
         String filterExpr,
         Object [] filterArgs,
         NdsIteratorControls cons)
      throws NSIException
   {
      String indexSelect;
      String sortKey;
      int scalability;

      this.environment = (NetEnvironment) environment.clone ();

      // allow access to attribute definition's syntaxId
      this.schemaContext = schemaContext;

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

      try
      {
         boolean allAttrs;
         NetBuffer filterBuffer;
         NetBuffer attrNames;

         service = environment.getService ();

         this.filterArgs = filterArgs;

         // ----- process search constraints -----
         boolean searchAliases;

         searchAliases = cons.getDerefLinkFlag ();
         String[] attributesToReturn = cons.getReturningAttributes ();

         indexSelect = getSortIndexString (cons.getIndexSelect ());
         sortKey = getSortIndexString (cons.getSortKey ());
         scalability = cons.getScalability ();

         if (attributesToReturn == null) // return all attributes
         {
            allAttrs = true;
            attrNames = null;
         }
         else if (attributesToReturn.length == 0) // return NO attributes
         {
            allAttrs = false;
            attrNames = null;
         }
         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]);
            }
         }

         int scope;

         scope = cons.getSearchScope ();
         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 ();

         int timeout;

         timeout = cons.getTimeLimit ();

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

         // ----- process search filter -----

         rfc = new StreamTokenizer(
               new StringReader(filterExpr));
         rfc.resetSyntax();
         rfc.whitespaceChars(0, ' ');
//         rfc.wordChars('a', 'z');
         rfc.wordChars('@', 'z');
         rfc.wordChars('0', '9'); // make digit chars word chars
         rfc.wordChars(':', ':');
         rfc.wordChars('-', '-');
         rfc.wordChars('_', '_');
         rfc.wordChars(' ', ' ');
         rfc.wordChars('.', '.');

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

         filter = new NetFilter (service);

         parseFilter ();

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

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

         iterator = new NetIterator (
                                       service,
                              objectName,
                              scope,
                              searchAliases,
                              filterBuffer.getHandle (),
                              0,                            // timeFilter
                              NetJNI.DS_ATTRIBUTE_VALUES,
                              allAttrs,
                              (attrNames != null) ? attrNames.getHandle () : 0,
                              indexSelect,
                              sortKey,
                              scalability,
                              timeout);
      }
      catch (NamingException e)
      {
         NSIException ne = new NSIException ();

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

      empty = iterator.atEOF ();

   } /* BasicNdsIterator () */


   /**
    *
    */
   public NdsIteratorInfo getInfo ()
      throws NSIException
   {
      return (iterator.getInfo ());
   }


   /**
    *
    */
   public int getPosition (
         int timeout)
      throws NSIException
   {
      return (iterator.getPosition (timeout));
   }

   /**
    *
    */
   public void setPosition (int position, int timeout)
      throws NSIException
   {
      iterator.setPosition (position, timeout);
   }

   /**
    *
    */
   public void setPosition (
         NdsIterator position,
         int timeout)
      throws NSIException
   {
      iterator.setPosition (((BasicNdsIterator) position).iterator, timeout);
   }

   /**
    *
    */
   public void setPosition (
         String attribute,
         String value,
         int timeout)
      throws NSIException
   {
      if (attribute.equalsIgnoreCase ("Name"))
      {
         iterator.setPosition ("_RDN", value, timeout);
      }
      else
      {
         iterator.setPosition (attribute, value, timeout);
      }

   } /* setPosition () */

   /**
    *
    */
   public int skip (int count, int timeout)
      throws NSIException
   {
      return (iterator.skip (count, timeout));
   }

   public boolean hasNext()
      throws NSIException
   {
      return (iterator.atEOF () ? false : true);
   }

   public NamingEnumeration next (int entries, int timeout)
      throws NSIException
   {
      return (new NextEnumerator (entries, timeout));
   }

   public boolean hasPrevious()
      throws NSIException
   {
      return ((!iterator.atFirst () && !empty) ? true : false);
   }

   public NamingEnumeration previous (int entries, int timeout)
      throws NSIException
   {
      return (new PreviousEnumerator (entries, timeout));
   }

   public Object current ()
      throws NSIException
   {
      try
      {
         return ((new CurrentEnumerator ()).next ());
      }
      catch (NamingException e)
      {
         NSIException ne = new NSIException ();

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

   } /* current () */

   /**
    *
    */
   public int count (int timeout, int maxCount, boolean updatePosition)
      throws NSIException
   {
      return (iterator.count (timeout, maxCount, updatePosition));
   }


   /**
    * Closes the Iterator and frees all associated memory.
    */
   public void close ()
      throws NSIException
   {
      iterator.destroy ();
   }



   // ******************** Object Class ********************

   /**
    * Creates a new object of the same class as this object. It
    * then initializes each of the new object's fields by assigning
    * them the same value as the corresponding fields in this object.
    * No constructor is called.
    *
    * @return A clone of this object instance containing the cloned
    *         syntax.
    *
    * @exception CloneNotSupportedException The object's class does
    *            not support the Cloneable interface.
    */
   public Object clone ()
   {
      try
      {
         BasicNdsIterator iterator = (BasicNdsIterator) super.clone ();

         iterator.iterator = (NetIterator) this.iterator.clone ();
         iterator.environment = (NetEnvironment) this.environment.clone ();

         iterator.service = iterator.environment.getService ();

         iterator.retObj = this.retObj;

         return (iterator);
      }
      catch (CloneNotSupportedException e)
      {
         // this shouldn't happen, since we are Cloneable
         throw (new InternalError ());
      }
      catch (NamingException e)
      {
         NSIException ne = new NSIException ();

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

   } /* clone () */



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

   } /* parseFilter () */

   /**
    * 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 == '!')
         {
            filter.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"))
            {
               filter.addFilterToken(NetJNI.FTOK_RDN);

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

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

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

                  filter.addFilterToken(relOp);

                  filter.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"
            filter.addFilterToken(NetJNI.FTOK_NOT);

            filter.addFilterToken(NetJNI.FTOK_PRESENT);

            filter.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
      {
         filter.addFilterToken(NetJNI.FTOK_LPAREN);

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

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

   } /* parseFilterList () */

   /**
    * 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;
      StringBuffer LdapValue = new StringBuffer();

      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 ();
         }
         else // process Ldap String Syntax
         {
            // parse to and including closing paren
            LdapValue.append(rfc.sval);
            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 StreamTokenizer.TT_EOF:
                     throw new InvalidSearchFilterException ();
                  default:
                     LdapValue.append(rfc.ttype);
               }
            }

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

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

      return (attrValueImpl);

   }

    /**
     * Retrieves the sort index of these Controls.
     *
     * @return The sort index of this Controls.
     */
   public String getSortIndexString (
         String[] sortIndex)
   {
      if (sortIndex != null)
      {
         int i = 0;
         StringBuffer buffer = new StringBuffer ();

         do
         {
            String index = sortIndex[i++];

            if (index.equalsIgnoreCase ("Name"))
            {
               buffer.append ("_RDN");
            }
            else if (index.equalsIgnoreCase ("BaseClass"))
            {
               buffer.append ("_BaseClass");
            }
            else
            {
               buffer.append (index);
            }

         } while (i < sortIndex.length && buffer.append (',') != null);

         return (new String (buffer));
      }
      return ("");

   } /* getSortIndexString () */

   /**
    *
    */
   class NextEnumerator implements NamingEnumeration
   {
      private int entries;
      private int timeout;
      private NetIterationHandle iteration;
      private NetBuffer objectInfo;

      private int objectCount;
      private SearchObject nextSearchObject;

      /**
       *
       */
      public NextEnumerator (int entries, int timeout)
      {
         this.entries = entries;
         this.timeout = timeout;

         iteration = new NetIterationHandle (service, NetJNI.DSV_ITERATOR);
         objectInfo = new NetBuffer (service, environment.getBatchSize ());

         nextSearchObject = getFirstSearchObject ();
      }

      /**
       *
       */
      public boolean hasMoreElements ()
      {
         return (hasMore ());
      }

      /**
       *
       */
      public boolean hasMore ()
      {
         if (null == nextSearchObject)
         {
            return (false);
         }
         return (true);
      }

      /**
       *
       */
      private int updateObjectInfo ()
         throws NSIException
      {
         iterator.getNext (entries, timeout, iteration, objectInfo);
         return (objectInfo.getObjectCount ());
      }

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

      } /* getFirstSearchObject () */

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

      } /* getNextSearchObject () */

      /**
       *
       */
      public Object nextElement ()
      {
         try
         {
            return (next ());
         }
         catch (NamingException e)
         {
            throw (new NoSuchElementException ());
         }
      }

      /**
       *
       */
      public Object next ()
         throws NamingException
      {
         if (!hasMore ())
         {
            throw (new NoSuchElementException ());
         }
         try
         {
            Context context = null;
            SearchObject currentSearchObject = nextSearchObject;

            nextSearchObject = getNextSearchObject ();

            // handle search objects in multiple iterations
            while (currentSearchObject.equalsApproximate (nextSearchObject))
            {
               Attributes attrs = nextSearchObject.getAttributes ();

               currentSearchObject.mergeAttributes (attrs);
               nextSearchObject = getNextSearchObject ();
            }

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

            Name name = nameParser.parse (currentSearchObject.getName ());

            return (new NdsIteratorResult (
                           name.get (name.size () - 1),
                           context,
                           currentSearchObject.getAttributes (),
                           currentSearchObject.getNdsObjectInfo (),
                           true));
         }
         catch (NSIException e)
         {
            NamingException ne = exceptionFactory.getNamingException (e);
            throw (ne);
         }

      } /* next () */

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

   } /* NextEnumerator */

   /**
    *
    */
   class PreviousEnumerator implements NamingEnumeration
   {
      private int entries;
      private int timeout;
      private NetIterationHandle iteration;
      private NetBuffer objectInfo;

      private int objectCount;
      private SearchObject nextSearchObject;

      /**
       *
       */
      public PreviousEnumerator (int entries, int timeout)
      {
         this.entries = entries;
         this.timeout = timeout;

         iteration = new NetIterationHandle (service, NetJNI.DSV_ITERATOR);
         objectInfo = new NetBuffer (service, environment.getBatchSize ());

         nextSearchObject = getFirstSearchObject ();
      }

      /**
       *
       */
      public boolean hasMoreElements ()
      {
         return (hasMore ());
      }

      /**
       *
       */
      public boolean hasMore ()
      {
         if (null == nextSearchObject)
         {
            return (false);
         }
         return (true);
      }

      /**
       *
       */
      private int updateObjectInfo ()
         throws NSIException
      {
         iterator.getPrev (entries, timeout, iteration, objectInfo);
         return (objectInfo.getObjectCount ());
      }

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

      } /* getFirstSearchObject () */

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

      } /* getNextSearchObject () */

      /**
       *
       */
      public Object nextElement ()
      {
         try
         {
            return (next ());
         }
         catch (NamingException e)
         {
            throw (new NoSuchElementException ());
         }
      }

      /**
       *
       */
      public Object next ()
         throws NamingException
      {
         if (!hasMoreElements ())
         {
            throw (new NoSuchElementException ());
         }
         try
         {
            Context context;
            SearchObject currentSearchObject = nextSearchObject;

            nextSearchObject = getNextSearchObject ();

            // handle search objects in multiple iterations
            while (currentSearchObject.equalsApproximate (nextSearchObject))
            {
               Attributes attrs = nextSearchObject.getAttributes ();

               currentSearchObject.mergeAttributes (attrs);
               nextSearchObject = getNextSearchObject ();
            }

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

            Name name = nameParser.parse (currentSearchObject.getName ());

            return (new NdsIteratorResult (
                           name.get (name.size () - 1),
                           (retObj) ? context : null,
                           currentSearchObject.getAttributes (),
                           currentSearchObject.getNdsObjectInfo (),
                           true));
         }
         catch (NSIException e)
         {
            NamingException ne = exceptionFactory.getNamingException (e);
            throw (ne);
         }

      } /* next () */

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

   } /* PreviousEnumerator */

   /**
    *
    */
   class CurrentEnumerator implements NamingEnumeration
   {
      private int entries;
      private int timeout;
      private NetIterationHandle iteration;
      private NetBuffer objectInfo;

      private int objectCount;
      private SearchObject nextSearchObject;

      /**
       *
       */
      public CurrentEnumerator ()
      {
         this.entries = 1;
         this.timeout = 0;

         iteration = new NetIterationHandle (service, NetJNI.DSV_ITERATOR);
         objectInfo = new NetBuffer (service, environment.getBatchSize ());

         nextSearchObject = getFirstSearchObject ();
      }

      /**
       *
       */
      public boolean hasMoreElements ()
      {
         return (hasMore ());
      }

      /**
       *
       */
      public boolean hasMore ()
      {
         if (null == nextSearchObject)
         {
            return (false);
         }
         return (true);
      }

      /**
       *
       */
      private int updateObjectInfo ()
         throws NSIException
      {
         iterator.getCurrent (iteration, objectInfo);
         return (objectInfo.getObjectCount ());
      }

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

      } /* getFirstSearchObject () */

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

      } /* getNextSearchObject () */

      /**
       *
       */
      public Object nextElement ()
      {
         try
         {
            return (next ());
         }
         catch (NamingException e)
         {
            throw (new NoSuchElementException ());
         }
      }

      /**
       *
       */
      public Object next ()
         throws NamingException
      {
         if (!hasMoreElements ())
         {
            throw (new NoSuchElementException ());
         }
         try
         {
            Context context;
            SearchObject currentSearchObject = nextSearchObject;

            nextSearchObject = getNextSearchObject ();

            // handle search objects in multiple iterations
            while (currentSearchObject.equalsApproximate (nextSearchObject))
            {
               Attributes attrs = nextSearchObject.getAttributes ();

               currentSearchObject.mergeAttributes (attrs);
               nextSearchObject = getNextSearchObject ();
            }

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

            Name name = nameParser.parse (currentSearchObject.getName ());

            return (new NdsIteratorResult (
                           name.get (name.size () - 1),
                           (retObj) ? context : null,
                           currentSearchObject.getAttributes (),
                           currentSearchObject.getNdsObjectInfo (),
                           true));
         }
         catch (NSIException e)
         {
            NamingException ne = exceptionFactory.getNamingException (e);
            throw (ne);
         }

      } /* next () */

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

   } /* CurrentEnumerator */

} /* BasicNdsIterator */


