gt:         if (envprops != null) {
context: /*
   * Copyright (c) 1999, 2017, Oracle and/or its affiliates. All rights reserved.
   * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   *
   * This code is free software; you can redistribute it and/or modify it
   * under the terms of the GNU General Public License version 2 only, as
   * published by the Free Software Foundation.  Oracle designates this
   * particular file as subject to the "Classpath" exception as provided
   * by Oracle in the LICENSE file that accompanied this code.
   *
   * This code is distributed in the hope that it will be useful, but WITHOUT
   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   * version 2 for more details (a copy is included in the LICENSE file that
   * accompanied this code).
   *
   * You should have received a copy of the GNU General Public License version
   * 2 along with this work; if not, write to the Free Software Foundation,
   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
   *
   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
   * or visit www.oracle.com if you need additional information or have any
   * questions.
   */
  
  package com.sun.jndi.ldap;
  
  import javax.naming.*;
  import javax.naming.directory.*;
  import javax.naming.spi.*;
  import javax.naming.event.*;
  import javax.naming.ldap.*;
  import javax.naming.ldap.LdapName;
  import javax.naming.ldap.Rdn;
  
  import java.util.Locale;
  import java.util.Vector;
  import java.util.Hashtable;
  import java.util.List;
  import java.util.StringTokenizer;
  import java.util.Enumeration;
  
  import java.io.IOException;
  import java.io.OutputStream;
  
  import com.sun.jndi.toolkit.ctx.*;
  import com.sun.jndi.toolkit.dir.HierMemDirCtx;
  import com.sun.jndi.toolkit.dir.SearchFilter;
  import com.sun.jndi.ldap.ext.StartTlsResponseImpl;
  
  /**
   * The LDAP context implementation.
   *
   * Implementation is not thread-safe. Caller must sync as per JNDI spec.
   * Members that are used directly or indirectly by internal worker threads
   * (Connection, EventQueue, NamingEventNotifier) must be thread-safe.
   * Connection - calls LdapClient.processUnsolicited(), which in turn calls
   *   LdapCtx.convertControls() and LdapCtx.fireUnsolicited().
   *   convertControls() - no sync; reads envprops and 'this'
   *   fireUnsolicited() - sync on eventSupport for all references to 'unsolicited'
   *      (even those in other methods);  don't sync on LdapCtx in case caller
   *      is already sync'ing on it - this would prevent Unsol events from firing
   *      and the Connection thread to block (thus preventing any other data
   *      from being read from the connection)
   *      References to 'eventSupport' need not be sync'ed because these
   *      methods can only be called after eventSupport has been set first
   *      (via addNamingListener()).
   * EventQueue - no direct or indirect calls to LdapCtx
   * NamingEventNotifier - calls newInstance() to get instance for run() to use;
   *      no sync needed for methods invoked on new instance;
   *
   * LdapAttribute links to LdapCtx in order to process getAttributeDefinition()
   * and getAttributeSyntaxDefinition() calls. It invokes LdapCtx.getSchema(),
   * which uses schemaTrees (a Hashtable - already sync). Potential conflict
   * of duplicating construction of tree for same subschemasubentry
   * but no inconsistency problems.
   *
   * NamingEnumerations link to LdapCtx for the following:
   * 1. increment/decrement enum count so that ctx doesn't close the
   *    underlying connection
   * 2. LdapClient handle to get next batch of results
   * 3. Sets LdapCtx's response controls
   * 4. Process return code
   * 5. For narrowing response controls (using ctx's factories)
   * Since processing of NamingEnumeration by client is treated the same as method
   * invocation on LdapCtx, caller is responsible for locking.
   *
   * @author Vincent Ryan
   * @author Rosanna Lee
   */
  
  final public class LdapCtx extends ComponentDirContext
      implements EventDirContext, LdapContext {
  
      /*
       * Used to store arguments to the search method.
       */
      final static class SearchArgs {
          Name name;
          String filter;
          SearchControls cons;
          String[] reqAttrs; // those attributes originally requested
  
          SearchArgs(Name name, String filter, SearchControls cons, String[] ra) {
              this.name = name;
              this.filter = filter;
              this.cons = cons;
              this.reqAttrs = ra;
          }
      }
  
      private static final boolean debug = false;
  
      private static final boolean HARD_CLOSE = true;
      private static final boolean SOFT_CLOSE = false;
  
      // -----------------  Constants  -----------------
  
      public static final int DEFAULT_PORT = 389;
      public static final int DEFAULT_SSL_PORT = 636;
      public static final String DEFAULT_HOST = "localhost";
  
      private static final boolean DEFAULT_DELETE_RDN = true;
      private static final boolean DEFAULT_TYPES_ONLY = false;
      private static final int DEFAULT_DEREF_ALIASES = 3; // always deref
      private static final int DEFAULT_LDAP_VERSION = LdapClient.LDAP_VERSION3_VERSION2;
      private static final int DEFAULT_BATCH_SIZE = 1;
      private static final int DEFAULT_REFERRAL_MODE = LdapClient.LDAP_REF_IGNORE;
      private static final char DEFAULT_REF_SEPARATOR = '#';
  
          // Used by LdapPoolManager
      static final String DEFAULT_SSL_FACTORY =
          "javax.net.ssl.SSLSocketFactory";       // use Sun's SSL
      private static final int DEFAULT_REFERRAL_LIMIT = 10;
      private static final String STARTTLS_REQ_OID = "1.3.6.1.4.1.1466.20037";
  
      // schema operational and user attributes
      private static final String[] SCHEMA_ATTRIBUTES =
          { "objectClasses", "attributeTypes", "matchingRules", "ldapSyntaxes" };
  
      // --------------- Environment property names ----------
  
      // LDAP protocol version: "2", "3"
      private static final String VERSION = "java.naming.ldap.version";
  
      // Binary-valued attributes. Space separated string of attribute names.
      private static final String BINARY_ATTRIBUTES =
                                          "java.naming.ldap.attributes.binary";
  
      // Delete old RDN during modifyDN: "true", "false"
      private static final String DELETE_RDN = "java.naming.ldap.deleteRDN";
  
      // De-reference aliases: "never", "searching", "finding", "always"
      private static final String DEREF_ALIASES = "java.naming.ldap.derefAliases";
  
      // Return only attribute types (no values)
      private static final String TYPES_ONLY = "java.naming.ldap.typesOnly";
  
      // Separator character for encoding Reference's RefAddrs; default is '#'
      private static final String REF_SEPARATOR = "java.naming.ldap.ref.separator";
  
      // Socket factory
      private static final String SOCKET_FACTORY = "java.naming.ldap.factory.socket";
  
      // Bind Controls (used by LdapReferralException)
      static final String BIND_CONTROLS = "java.naming.ldap.control.connect";
  
      private static final String REFERRAL_LIMIT =
          "java.naming.ldap.referral.limit";
  
      // trace BER (java.io.OutputStream)
      private static final String TRACE_BER = "com.sun.jndi.ldap.trace.ber";
  
      // Get around Netscape Schema Bugs
      private static final String NETSCAPE_SCHEMA_BUG =
          "com.sun.jndi.ldap.netscape.schemaBugs";
      // deprecated
      private static final String OLD_NETSCAPE_SCHEMA_BUG =
          "com.sun.naming.netscape.schemaBugs";   // for backward compatibility
  
      // Timeout for socket connect
      private static final String CONNECT_TIMEOUT =
          "com.sun.jndi.ldap.connect.timeout";
  
       // Timeout for reading responses
      private static final String READ_TIMEOUT =
          "com.sun.jndi.ldap.read.timeout";
  
      // Environment property for connection pooling
      private static final String ENABLE_POOL = "com.sun.jndi.ldap.connect.pool";
  
      // Environment property for the domain name (derived from this context's DN)
      private static final String DOMAIN_NAME = "com.sun.jndi.ldap.domainname";
  
      // Block until the first search reply is received
      private static final String WAIT_FOR_REPLY =
          "com.sun.jndi.ldap.search.waitForReply";
  
      // Size of the queue of unprocessed search replies
      private static final String REPLY_QUEUE_SIZE =
          "com.sun.jndi.ldap.search.replyQueueSize";
  
      // ----------------- Fields that don't change -----------------------
      private static final NameParser parser = new LdapNameParser();
  
      // controls that Provider needs
      private static final ControlFactory myResponseControlFactory =
          new DefaultResponseControlFactory();
      private static final Control manageReferralControl =
          new ManageReferralControl(false);
  
      private static final HierMemDirCtx EMPTY_SCHEMA = new HierMemDirCtx();
      static {
          EMPTY_SCHEMA.setReadOnly(
              new SchemaViolationException("Cannot update schema object"));
      }
  
      // ------------ Package private instance variables ----------------
      // Cannot be private; used by enums
  
          // ------- Inherited by derived context instances
  
      int port_number;                    // port number of server
      String hostname = null;             // host name of server (no brackets
                                          //   for IPv6 literals)
      LdapClient clnt = null;             // connection handle
      private boolean reconnect = false;  // indicates that re-connect requested
      Hashtable<String, java.lang.Object> envprops = null; // environment properties of context
      int handleReferrals = DEFAULT_REFERRAL_MODE; // how referral is handled
      boolean hasLdapsScheme = false;     // true if the context was created
                                          //  using an LDAPS URL.
  
          // ------- Not inherited by derived context instances
  
      String currentDN;                   // DN of this context
      Name currentParsedDN;               // DN of this context
      Vector<Control> respCtls = null;    // Response controls read
      Control[] reqCtls = null;           // Controls to be sent with each request
  
  
      // ------------- Private instance variables ------------------------
  
          // ------- Inherited by derived context instances
  
      private OutputStream trace = null;  // output stream for BER debug output
      private boolean netscapeSchemaBug = false;       // workaround
      private Control[] bindCtls = null;  // Controls to be sent with LDAP "bind"
      private int referralHopLimit = DEFAULT_REFERRAL_LIMIT;  // max referral
      private Hashtable<String, DirContext> schemaTrees = null; // schema root of this context
      private int batchSize = DEFAULT_BATCH_SIZE;      // batch size for search results
      private boolean deleteRDN = DEFAULT_DELETE_RDN;  // delete the old RDN when modifying DN
      private boolean typesOnly = DEFAULT_TYPES_ONLY;  // return attribute types (no values)
      private int derefAliases = DEFAULT_DEREF_ALIASES;// de-reference alias entries during searching
      private char addrEncodingSeparator = DEFAULT_REF_SEPARATOR;  // encoding RefAddr
  
      private Hashtable<String, Boolean> binaryAttrs = null; // attr values returned as byte[]
      private int connectTimeout = -1;         // no timeout value
      private int readTimeout = -1;            // no timeout value
      private boolean waitForReply = true;     // wait for search response
      private int replyQueueSize  = -1;        // unlimited queue size
      private boolean useSsl = false;          // true if SSL protocol is active
      private boolean useDefaultPortNumber = false; // no port number was supplied
  
          // ------- Not inherited by derived context instances
  
      // True if this context was created by another LdapCtx.
      private boolean parentIsLdapCtx = false; // see composeName()
  
      private int hopCount = 1;                // current referral hop count
      private String url = null;               // URL of context; see getURL()
      private EventSupport eventSupport;       // Event support helper for this ctx
      private boolean unsolicited = false;     // if there unsolicited listeners
      private boolean sharable = true;         // can share connection with other ctx
  
      // -------------- Constructors  -----------------------------------
  
      @SuppressWarnings("unchecked")
      public LdapCtx(String dn, String host, int port_number,
              Hashtable<?,?> props,
              boolean useSsl) throws NamingException {
  
          this.useSsl = this.hasLdapsScheme = useSsl;
  
          if (props != null) {
              envprops = (Hashtable<String, java.lang.Object>) props.clone();
  
              // SSL env prop overrides the useSsl argument
              if ("ssl".equals(envprops.get(Context.SECURITY_PROTOCOL))) {
                  this.useSsl = true;
              }
  
              // %%% These are only examined when the context is created
              // %%% because they are only for debugging or workaround purposes.
              trace = (OutputStream)envprops.get(TRACE_BER);
  
              if (props.get(NETSCAPE_SCHEMA_BUG) != null ||
                  props.get(OLD_NETSCAPE_SCHEMA_BUG) != null) {
                  netscapeSchemaBug = true;
              }
          }
  
          currentDN = (dn != null) ? dn : "";
          currentParsedDN = parser.parse(currentDN);
  
          hostname = (host != null && host.length() > 0) ? host : DEFAULT_HOST;
          if (hostname.charAt(0) == '[') {
              hostname = hostname.substring(1, hostname.length() - 1);
          }
  
          if (port_number > 0) {
              this.port_number = port_number;
          } else {
              this.port_number = this.useSsl ? DEFAULT_SSL_PORT : DEFAULT_PORT;
              this.useDefaultPortNumber = true;
          }
  
          schemaTrees = new Hashtable<>(11, 0.75f);
          initEnv();
          try {
              connect(false);
          } catch (NamingException e) {
              try {
                  close();
              } catch (Exception e2) {
                  // Nothing
              }
              throw e;
          }
      }
  
      LdapCtx(LdapCtx existing, String newDN) throws NamingException {
          useSsl = existing.useSsl;
          hasLdapsScheme = existing.hasLdapsScheme;
          useDefaultPortNumber = existing.useDefaultPortNumber;
  
          hostname = existing.hostname;
          port_number = existing.port_number;
          currentDN = newDN;
          if (existing.currentDN == currentDN) {
              currentParsedDN = existing.currentParsedDN;
          } else {
              currentParsedDN = parser.parse(currentDN);
          }
  
          envprops = existing.envprops;
          schemaTrees = existing.schemaTrees;
  
          clnt = existing.clnt;
          clnt.incRefCount();
  
          parentIsLdapCtx = ((newDN == null || newDN.equals(existing.currentDN))
                             ? existing.parentIsLdapCtx
                             : true);
  
          // inherit these debugging/workaround flags
          trace = existing.trace;
          netscapeSchemaBug = existing.netscapeSchemaBug;
  
          initEnv();
      }
  
      public LdapContext newInstance(Control[] reqCtls) throws NamingException {
  
          LdapContext clone = new LdapCtx(this, currentDN);
  
          // Connection controls are inherited from environment
  
          // Set clone's request controls
          // setRequestControls() will clone reqCtls
          clone.setRequestControls(reqCtls);
          return clone;
      }
  
      // --------------- Namespace Updates ---------------------
      // -- bind/rebind/unbind
      // -- rename
      // -- createSubcontext/destroySubcontext
  
      protected void c_bind(Name name, Object obj, Continuation cont)
              throws NamingException {
          c_bind(name, obj, null, cont);
      }
  
      /*
       * attrs == null
       *      if obj is DirContext, attrs = obj.getAttributes()
       * if attrs == null && obj == null
       *      disallow (cannot determine objectclass to use)
       * if obj == null
       *      just create entry using attrs
       * else
       *      objAttrs = create attributes for representing obj
       *      attrs += objAttrs
       *      create entry using attrs
       */
      protected void c_bind(Name name, Object obj, Attributes attrs,
                            Continuation cont)
              throws NamingException {
  
          cont.setError(this, name);
  
          Attributes inputAttrs = attrs; // Attributes supplied by caller
          try {
              ensureOpen();
  
              if (obj == null) {
                  if (attrs == null) {
                      throw new IllegalArgumentException(
                          "cannot bind null object with no attributes");
                  }
              } else {
                  attrs = Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs,
                      false, name, this, envprops); // not cloned
              }
  
              String newDN = fullyQualifiedName(name);
              attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);
              LdapEntry entry = new LdapEntry(newDN, attrs);
  
              LdapResult answer = clnt.add(entry, reqCtls);
              respCtls = answer.resControls; // retrieve response controls
  
              if (answer.status != LdapClient.LDAP_SUCCESS) {
                  processReturnCode(answer, name);
              }
  
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      refCtx.bind(name, obj, inputAttrs);
                      return;
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
  
          } catch (IOException e) {
              NamingException e2 = new CommunicationException(e.getMessage());
              e2.setRootCause(e);
              throw cont.fillInException(e2);
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      protected void c_rebind(Name name, Object obj, Continuation cont)
              throws NamingException {
          c_rebind(name, obj, null, cont);
      }
  
  
      /*
       * attrs == null
       *    if obj is DirContext, attrs = obj.getAttributes().
       * if attrs == null
       *    leave any existing attributes alone
       *    (set attrs = {objectclass=top} if object doesn't exist)
       * else
       *    replace all existing attributes with attrs
       * if obj == null
       *      just create entry using attrs
       * else
       *      objAttrs = create attributes for representing obj
       *      attrs += objAttrs
       *      create entry using attrs
       */
      protected void c_rebind(Name name, Object obj, Attributes attrs,
          Continuation cont) throws NamingException {
  
          cont.setError(this, name);
  
          Attributes inputAttrs = attrs;
  
          try {
              Attributes origAttrs = null;
  
              // Check if name is bound
              try {
                  origAttrs = c_getAttributes(name, null, cont);
              } catch (NameNotFoundException e) {}
  
              // Name not bound, just add it
              if (origAttrs == null) {
                  c_bind(name, obj, attrs, cont);
                  return;
              }
  
              // there's an object there already, need to figure out
              // what to do about its attributes
  
              if (attrs == null && obj instanceof DirContext) {
                  attrs = ((DirContext)obj).getAttributes("");
              }
              Attributes keepAttrs = (Attributes)origAttrs.clone();
  
              if (attrs == null) {
                  // we're not changing any attrs, leave old attributes alone
  
                  // Remove Java-related object classes from objectclass attribute
                  Attribute origObjectClass =
                      origAttrs.get(Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS]);
  
                  if (origObjectClass != null) {
                      // clone so that keepAttrs is not affected
                      origObjectClass = (Attribute)origObjectClass.clone();
                      for (int i = 0; i < Obj.JAVA_OBJECT_CLASSES.length; i++) {
                          origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES_LOWER[i]);
                          origObjectClass.remove(Obj.JAVA_OBJECT_CLASSES[i]);
                      }
                      // update;
                      origAttrs.put(origObjectClass);
                  }
  
                  // remove all Java-related attributes except objectclass
                  for (int i = 1; i < Obj.JAVA_ATTRIBUTES.length; i++) {
                      origAttrs.remove(Obj.JAVA_ATTRIBUTES[i]);
                  }
  
                  attrs = origAttrs;
              }
              if (obj != null) {
                  attrs =
                      Obj.determineBindAttrs(addrEncodingSeparator, obj, attrs,
                          inputAttrs != attrs, name, this, envprops);
              }
  
              String newDN = fullyQualifiedName(name);
              // remove entry
              LdapResult answer = clnt.delete(newDN, reqCtls);
              respCtls = answer.resControls; // retrieve response controls
  
              if (answer.status != LdapClient.LDAP_SUCCESS) {
                  processReturnCode(answer, name);
                  return;
              }
  
              Exception addEx = null;
              try {
                  attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);
  
                  // add it back using updated attrs
                  LdapEntry entry = new LdapEntry(newDN, attrs);
                  answer = clnt.add(entry, reqCtls);
                  if (answer.resControls != null) {
                      respCtls = appendVector(respCtls, answer.resControls);
                  }
              } catch (NamingException | IOException ae) {
                  addEx = ae;
              }
  
              if ((addEx != null && !(addEx instanceof LdapReferralException)) ||
                  answer.status != LdapClient.LDAP_SUCCESS) {
                  // Attempt to restore old entry
                  LdapResult answer2 =
                      clnt.add(new LdapEntry(newDN, keepAttrs), reqCtls);
                  if (answer2.resControls != null) {
                      respCtls = appendVector(respCtls, answer2.resControls);
                  }
  
                  if (addEx == null) {
                      processReturnCode(answer, name);
                  }
              }
  
              // Rethrow exception
              if (addEx instanceof NamingException) {
                  throw (NamingException)addEx;
              } else if (addEx instanceof IOException) {
                  throw (IOException)addEx;
              }
  
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      refCtx.rebind(name, obj, inputAttrs);
                      return;
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
  
          } catch (IOException e) {
              NamingException e2 = new CommunicationException(e.getMessage());
              e2.setRootCause(e);
              throw cont.fillInException(e2);
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      protected void c_unbind(Name name, Continuation cont)
              throws NamingException {
          cont.setError(this, name);
  
          try {
              ensureOpen();
  
              String fname = fullyQualifiedName(name);
              LdapResult answer = clnt.delete(fname, reqCtls);
              respCtls = answer.resControls; // retrieve response controls
  
              adjustDeleteStatus(fname, answer);
  
              if (answer.status != LdapClient.LDAP_SUCCESS) {
                  processReturnCode(answer, name);
              }
  
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      refCtx.unbind(name);
                      return;
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
  
          } catch (IOException e) {
              NamingException e2 = new CommunicationException(e.getMessage());
              e2.setRootCause(e);
              throw cont.fillInException(e2);
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      protected void c_rename(Name oldName, Name newName, Continuation cont)
              throws NamingException
      {
          Name oldParsed, newParsed;
          Name oldParent, newParent;
          String newRDN = null;
          String newSuperior = null;
  
          // assert (oldName instanceOf CompositeName);
  
          cont.setError(this, oldName);
  
          try {
              ensureOpen();
  
              // permit oldName to be empty (for processing referral contexts)
              if (oldName.isEmpty()) {
                  oldParent = parser.parse("");
              } else {
                  oldParsed = parser.parse(oldName.get(0)); // extract DN & parse
                  oldParent = oldParsed.getPrefix(oldParsed.size() - 1);
              }
  
              if (newName instanceof CompositeName) {
                  newParsed = parser.parse(newName.get(0)); // extract DN & parse
              } else {
                  newParsed = newName; // CompoundName/LdapName is already parsed
              }
              newParent = newParsed.getPrefix(newParsed.size() - 1);
  
              if(!oldParent.equals(newParent)) {
                  if (!clnt.isLdapv3) {
                      throw new InvalidNameException(
                                    "LDAPv2 doesn't support changing " +
                                    "the parent as a result of a rename");
                  } else {
                      newSuperior = fullyQualifiedName(newParent.toString());
                  }
              }
  
              newRDN = newParsed.get(newParsed.size() - 1);
  
              LdapResult answer = clnt.moddn(fullyQualifiedName(oldName),
                                      newRDN,
                                      deleteRDN,
                                      newSuperior,
                                      reqCtls);
              respCtls = answer.resControls; // retrieve response controls
  
              if (answer.status != LdapClient.LDAP_SUCCESS) {
                  processReturnCode(answer, oldName);
              }
  
          } catch (LdapReferralException e) {
  
              // Record the new RDN (for use after the referral is followed).
              e.setNewRdn(newRDN);
  
              // Cannot continue when a referral has been received and a
              // newSuperior name was supplied (because the newSuperior is
              // relative to a naming context BEFORE the referral is followed).
              if (newSuperior != null) {
                  PartialResultException pre = new PartialResultException(
                      "Cannot continue referral processing when newSuperior is " +
                      "nonempty: " + newSuperior);
                  pre.setRootCause(cont.fillInException(e));
                  throw cont.fillInException(pre);
              }
  
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      refCtx.rename(oldName, newName);
                      return;
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
  
          } catch (IOException e) {
              NamingException e2 = new CommunicationException(e.getMessage());
              e2.setRootCause(e);
              throw cont.fillInException(e2);
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      protected Context c_createSubcontext(Name name, Continuation cont)
              throws NamingException {
          return c_createSubcontext(name, null, cont);
      }
  
      protected DirContext c_createSubcontext(Name name, Attributes attrs,
                                              Continuation cont)
              throws NamingException {
          cont.setError(this, name);
  
          Attributes inputAttrs = attrs;
          try {
              ensureOpen();
              if (attrs == null) {
                    // add structural objectclass; name needs to have "cn"
                    Attribute oc = new BasicAttribute(
                        Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS],
                        Obj.JAVA_OBJECT_CLASSES[Obj.STRUCTURAL]);
                    oc.add("top");
                    attrs = new BasicAttributes(true); // case ignore
                    attrs.put(oc);
              }
              String newDN = fullyQualifiedName(name);
              attrs = addRdnAttributes(newDN, attrs, inputAttrs != attrs);
  
              LdapEntry entry = new LdapEntry(newDN, attrs);
  
              LdapResult answer = clnt.add(entry, reqCtls);
              respCtls = answer.resControls; // retrieve response controls
  
              if (answer.status != LdapClient.LDAP_SUCCESS) {
                  processReturnCode(answer, name);
                  return null;
              }
  
              // creation successful, get back live object
              return new LdapCtx(this, newDN);
  
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      return refCtx.createSubcontext(name, inputAttrs);
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
  
          } catch (IOException e) {
              NamingException e2 = new CommunicationException(e.getMessage());
              e2.setRootCause(e);
              throw cont.fillInException(e2);
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      protected void c_destroySubcontext(Name name, Continuation cont)
          throws NamingException {
          cont.setError(this, name);
  
          try {
              ensureOpen();
  
              String fname = fullyQualifiedName(name);
              LdapResult answer = clnt.delete(fname, reqCtls);
              respCtls = answer.resControls; // retrieve response controls
  
              adjustDeleteStatus(fname, answer);
  
              if (answer.status != LdapClient.LDAP_SUCCESS) {
                  processReturnCode(answer, name);
              }
  
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      refCtx.destroySubcontext(name);
                      return;
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
          } catch (IOException e) {
              NamingException e2 = new CommunicationException(e.getMessage());
              e2.setRootCause(e);
              throw cont.fillInException(e2);
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      /**
       * Adds attributes from RDN to attrs if not already present.
       * Note that if attrs already contains an attribute by the same name,
       * or if the distinguished name is empty, then leave attrs unchanged.
       *
       * @param dn The non-null DN of the entry to add
       * @param attrs The non-null attributes of entry to add
       * @param directUpdate Whether attrs can be updated directly
       * @return Non-null attributes with attributes from the RDN added
       */
      private static Attributes addRdnAttributes(String dn, Attributes attrs,
          boolean directUpdate) throws NamingException {
  
              // Handle the empty name
              if (dn.equals("")) {
                  return attrs;
              }
  
              // Parse string name into list of RDNs
              List<Rdn> rdnList = (new LdapName(dn)).getRdns();
  
              // Get leaf RDN
              Rdn rdn = rdnList.get(rdnList.size() - 1);
              Attributes nameAttrs = rdn.toAttributes();
  
              // Add attributes of RDN to attrs if not already there
              NamingEnumeration<? extends Attribute> enum_ = nameAttrs.getAll();
              Attribute nameAttr;
              while (enum_.hasMore()) {
                  nameAttr = enum_.next();
  
                  // If attrs already has the attribute, don't change or add to it
                  if (attrs.get(nameAttr.getID()) ==  null) {
  
                      /**
                       * When attrs.isCaseIgnored() is false, attrs.get() will
                       * return null when the case mis-matches for otherwise
                       * equal attrIDs.
                       * As the attrIDs' case is irrelevant for LDAP, ignore
                       * the case of attrIDs even when attrs.isCaseIgnored() is
                       * false. This is done by explicitly comparing the elements in
                       * the enumeration of IDs with their case ignored.
                       */
                      if (!attrs.isCaseIgnored() &&
                              containsIgnoreCase(attrs.getIDs(), nameAttr.getID())) {
                          continue;
                      }
  
                      if (!directUpdate) {
                          attrs = (Attributes)attrs.clone();
                          directUpdate = true;
                      }
                      attrs.put(nameAttr);
                  }
              }
  
              return attrs;
      }
  
  
      private static boolean containsIgnoreCase(NamingEnumeration<String> enumStr,
                                  String str) throws NamingException {
          String strEntry;
  
          while (enumStr.hasMore()) {
               strEntry = enumStr.next();
               if (strEntry.equalsIgnoreCase(str)) {
                  return true;
               }
          }
          return false;
      }
  
  
      private void adjustDeleteStatus(String fname, LdapResult answer) {
          if (answer.status == LdapClient.LDAP_NO_SUCH_OBJECT &&
              answer.matchedDN != null) {
              try {
                  // %%% RL: are there any implications for referrals?
  
                  Name orig = parser.parse(fname);
                  Name matched = parser.parse(answer.matchedDN);
                  if ((orig.size() - matched.size()) == 1)
                      answer.status = LdapClient.LDAP_SUCCESS;
              } catch (NamingException e) {}
          }
      }
  
      /*
       * Append the second Vector onto the first Vector
       * (v2 must be non-null)
       */
      private static <T> Vector<T> appendVector(Vector<T> v1, Vector<T> v2) {
          if (v1 == null) {
              v1 = v2;
          } else {
              for (int i = 0; i < v2.size(); i++) {
                  v1.addElement(v2.elementAt(i));
              }
          }
          return v1;
      }
  
      // ------------- Lookups and Browsing -------------------------
      // lookup/lookupLink
      // list/listBindings
  
      protected Object c_lookupLink(Name name, Continuation cont)
              throws NamingException {
          return c_lookup(name, cont);
      }
  
      protected Object c_lookup(Name name, Continuation cont)
              throws NamingException {
          cont.setError(this, name);
          Object obj = null;
          Attributes attrs;
  
          try {
              SearchControls cons = new SearchControls();
              cons.setSearchScope(SearchControls.OBJECT_SCOPE);
              cons.setReturningAttributes(null); // ask for all attributes
              cons.setReturningObjFlag(true); // need values to construct obj
  
              LdapResult answer = doSearchOnce(name, "(objectClass=*)", cons, true);
              respCtls = answer.resControls; // retrieve response controls
  
              // should get back 1 SearchResponse and 1 SearchResult
  
              if (answer.status != LdapClient.LDAP_SUCCESS) {
                  processReturnCode(answer, name);
              }
  
              if (answer.entries == null || answer.entries.size() != 1) {
                  // found it but got no attributes
                  attrs = new BasicAttributes(LdapClient.caseIgnore);
              } else {
                  LdapEntry entry = answer.entries.elementAt(0);
                  attrs = entry.attributes;
  
                  Vector<Control> entryCtls = entry.respCtls; // retrieve entry controls
                  if (entryCtls != null) {
                      appendVector(respCtls, entryCtls); // concatenate controls
                  }
              }
  
              if (attrs.get(Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME]) != null) {
                  // serialized object or object reference
                  obj = Obj.decodeObject(attrs);
              }
              if (obj == null) {
                  obj = new LdapCtx(this, fullyQualifiedName(name));
              }
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
                  // repeat the original operation at the new context
                  try {
  
                      return refCtx.lookup(name);
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
  
          try {
              return DirectoryManager.getObjectInstance(obj, name,
                  this, envprops, attrs);
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
  
          } catch (Exception e) {
              NamingException e2 = new NamingException(
                      "problem generating object using object factory");
              e2.setRootCause(e);
              throw cont.fillInException(e2);
          }
      }
  
      protected NamingEnumeration<NameClassPair> c_list(Name name, Continuation cont)
              throws NamingException {
          SearchControls cons = new SearchControls();
          String[] classAttrs = new String[2];
  
          classAttrs[0] = Obj.JAVA_ATTRIBUTES[Obj.OBJECT_CLASS];
          classAttrs[1] = Obj.JAVA_ATTRIBUTES[Obj.CLASSNAME];
          cons.setReturningAttributes(classAttrs);
  
          // set this flag to override the typesOnly flag
          cons.setReturningObjFlag(true);
  
          cont.setError(this, name);
  
          LdapResult answer = null;
  
          try {
              answer = doSearch(name, "(objectClass=*)", cons, true, true);
  
              // list result may contain continuation references
              if ((answer.status != LdapClient.LDAP_SUCCESS) ||
                  (answer.referrals != null)) {
                  processReturnCode(answer, name);
              }
  
              return new LdapNamingEnumeration(this, answer, name, cont);
  
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      return refCtx.list(name);
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
  
          } catch (LimitExceededException e) {
              LdapNamingEnumeration res =
                  new LdapNamingEnumeration(this, answer, name, cont);
  
              res.setNamingException(
                      (LimitExceededException)cont.fillInException(e));
              return res;
  
          } catch (PartialResultException e) {
              LdapNamingEnumeration res =
                  new LdapNamingEnumeration(this, answer, name, cont);
  
              res.setNamingException(
                      (PartialResultException)cont.fillInException(e));
              return res;
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      protected NamingEnumeration<Binding> c_listBindings(Name name, Continuation cont)
              throws NamingException {
  
          SearchControls cons = new SearchControls();
          cons.setReturningAttributes(null); // ask for all attributes
          cons.setReturningObjFlag(true); // need values to construct obj
  
          cont.setError(this, name);
  
          LdapResult answer = null;
  
          try {
              answer = doSearch(name, "(objectClass=*)", cons, true, true);
  
              // listBindings result may contain continuation references
              if ((answer.status != LdapClient.LDAP_SUCCESS) ||
                  (answer.referrals != null)) {
                  processReturnCode(answer, name);
              }
  
              return new LdapBindingEnumeration(this, answer, name, cont);
  
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
                  @SuppressWarnings("unchecked")
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      return refCtx.listBindings(name);
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
          } catch (LimitExceededException e) {
              LdapBindingEnumeration res =
                  new LdapBindingEnumeration(this, answer, name, cont);
  
              res.setNamingException(cont.fillInException(e));
              return res;
  
          } catch (PartialResultException e) {
              LdapBindingEnumeration res =
                  new LdapBindingEnumeration(this, answer, name, cont);
  
              res.setNamingException(cont.fillInException(e));
              return res;
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      // --------------- Name-related Methods -----------------------
      // -- getNameParser/getNameInNamespace/composeName
  
      protected NameParser c_getNameParser(Name name, Continuation cont)
              throws NamingException
      {
          // ignore name, always return same parser
          cont.setSuccess();
          return parser;
      }
  
      public String getNameInNamespace() {
          return currentDN;
      }
  
      public Name composeName(Name name, Name prefix)
          throws NamingException
      {
          Name result;
  
          // Handle compound names.  A pair of LdapNames is an easy case.
          if ((name instanceof LdapName) && (prefix instanceof LdapName)) {
              result = (Name)(prefix.clone());
              result.addAll(name);
              return new CompositeName().add(result.toString());
          }
          if (!(name instanceof CompositeName)) {
              name = new CompositeName().add(name.toString());
          }
          if (!(prefix instanceof CompositeName)) {
              prefix = new CompositeName().add(prefix.toString());
          }
  
          int prefixLast = prefix.size() - 1;
  
          if (name.isEmpty() || prefix.isEmpty() ||
                  name.get(0).equals("") || prefix.get(prefixLast).equals("")) {
              return super.composeName(name, prefix);
          }
  
          result = (Name)(prefix.clone());
          result.addAll(name);
  
          if (parentIsLdapCtx) {
              String ldapComp = concatNames(result.get(prefixLast + 1),
                                            result.get(prefixLast));
              result.remove(prefixLast + 1);
              result.remove(prefixLast);
              result.add(prefixLast, ldapComp);
          }
          return result;
      }
  
      private String fullyQualifiedName(Name rel) {
          return rel.isEmpty()
                  ? currentDN
                  : fullyQualifiedName(rel.get(0));
      }
  
      private String fullyQualifiedName(String rel) {
          return (concatNames(rel, currentDN));
      }
  
      // used by LdapSearchEnumeration
      private static String concatNames(String lesser, String greater) {
          if (lesser == null || lesser.equals("")) {
              return greater;
          } else if (greater == null || greater.equals("")) {
              return lesser;
          } else {
              return (lesser + "," + greater);
          }
      }
  
     // --------------- Reading and Updating Attributes
     // getAttributes/modifyAttributes
  
      protected Attributes c_getAttributes(Name name, String[] attrIds,
                                        Continuation cont)
              throws NamingException {
          cont.setError(this, name);
  
          SearchControls cons = new SearchControls();
          cons.setSearchScope(SearchControls.OBJECT_SCOPE);
          cons.setReturningAttributes(attrIds);
  
          try {
              LdapResult answer =
                  doSearchOnce(name, "(objectClass=*)", cons, true);
              respCtls = answer.resControls; // retrieve response controls
  
              if (answer.status != LdapClient.LDAP_SUCCESS) {
                  processReturnCode(answer, name);
              }
  
              if (answer.entries == null || answer.entries.size() != 1) {
                  return new BasicAttributes(LdapClient.caseIgnore);
              }
  
              // get attributes from result
              LdapEntry entry = answer.entries.elementAt(0);
  
              Vector<Control> entryCtls = entry.respCtls; // retrieve entry controls
              if (entryCtls != null) {
                  appendVector(respCtls, entryCtls); // concatenate controls
              }
  
              // do this so attributes can find their schema
              setParents(entry.attributes, (Name) name.clone());
  
              return (entry.attributes);
  
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      return refCtx.getAttributes(name, attrIds);
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      protected void c_modifyAttributes(Name name, int mod_op, Attributes attrs,
                                        Continuation cont)
              throws NamingException {
  
          cont.setError(this, name);
  
          try {
              ensureOpen();
  
              if (attrs == null || attrs.size() == 0) {
                  return; // nothing to do
              }
              String newDN = fullyQualifiedName(name);
              int jmod_op = convertToLdapModCode(mod_op);
  
              // construct mod list
              int[] jmods = new int[attrs.size()];
              Attribute[] jattrs = new Attribute[attrs.size()];
  
              NamingEnumeration<? extends Attribute> ae = attrs.getAll();
              for(int i = 0; i < jmods.length && ae.hasMore(); i++) {
                  jmods[i] = jmod_op;
                  jattrs[i] = ae.next();
              }
  
              LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls);
              respCtls = answer.resControls; // retrieve response controls
  
              if (answer.status != LdapClient.LDAP_SUCCESS) {
                  processReturnCode(answer, name);
                  return;
              }
  
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      refCtx.modifyAttributes(name, mod_op, attrs);
                      return;
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
  
          } catch (IOException e) {
              NamingException e2 = new CommunicationException(e.getMessage());
              e2.setRootCause(e);
              throw cont.fillInException(e2);
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      protected void c_modifyAttributes(Name name, ModificationItem[] mods,
                                        Continuation cont)
              throws NamingException {
          cont.setError(this, name);
  
          try {
              ensureOpen();
  
              if (mods == null || mods.length == 0) {
                  return; // nothing to do
              }
              String newDN = fullyQualifiedName(name);
  
              // construct mod list
              int[] jmods = new int[mods.length];
              Attribute[] jattrs = new Attribute[mods.length];
              ModificationItem mod;
              for (int i = 0; i < jmods.length; i++) {
                  mod = mods[i];
                  jmods[i] = convertToLdapModCode(mod.getModificationOp());
                  jattrs[i] = mod.getAttribute();
              }
  
              LdapResult answer = clnt.modify(newDN, jmods, jattrs, reqCtls);
              respCtls = answer.resControls; // retrieve response controls
  
              if (answer.status != LdapClient.LDAP_SUCCESS) {
                  processReturnCode(answer, name);
              }
  
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  LdapReferralContext refCtx =
                      (LdapReferralContext)e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      refCtx.modifyAttributes(name, mods);
                      return;
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
  
          } catch (IOException e) {
              NamingException e2 = new CommunicationException(e.getMessage());
              e2.setRootCause(e);
              throw cont.fillInException(e2);
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      private static int convertToLdapModCode(int mod_op) {
          switch (mod_op) {
          case DirContext.ADD_ATTRIBUTE:
              return(LdapClient.ADD);
  
          case DirContext.REPLACE_ATTRIBUTE:
              return (LdapClient.REPLACE);
  
          case DirContext.REMOVE_ATTRIBUTE:
              return (LdapClient.DELETE);
  
          default:
              throw new IllegalArgumentException("Invalid modification code");
          }
      }
  
     // ------------------- Schema -----------------------
  
      protected DirContext c_getSchema(Name name, Continuation cont)
              throws NamingException {
          cont.setError(this, name);
          try {
              return getSchemaTree(name);
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      protected DirContext c_getSchemaClassDefinition(Name name,
                                                      Continuation cont)
              throws NamingException {
          cont.setError(this, name);
  
          try {
              // retrieve the objectClass attribute from LDAP
              Attribute objectClassAttr = c_getAttributes(name,
                  new String[]{"objectclass"}, cont).get("objectclass");
              if (objectClassAttr == null || objectClassAttr.size() == 0) {
                  return EMPTY_SCHEMA;
              }
  
              // retrieve the root of the ObjectClass schema tree
              Context ocSchema = (Context) c_getSchema(name, cont).lookup(
                  LdapSchemaParser.OBJECTCLASS_DEFINITION_NAME);
  
              // create a context to hold the schema objects representing the object
              // classes
              HierMemDirCtx objectClassCtx = new HierMemDirCtx();
              DirContext objectClassDef;
              String objectClassName;
              for (Enumeration<?> objectClasses = objectClassAttr.getAll();
                  objectClasses.hasMoreElements(); ) {
                  objectClassName = (String)objectClasses.nextElement();
                  // %%% Should we fail if not found, or just continue?
                  objectClassDef = (DirContext)ocSchema.lookup(objectClassName);
                  objectClassCtx.bind(objectClassName, objectClassDef);
              }
  
              // Make context read-only
              objectClassCtx.setReadOnly(
                  new SchemaViolationException("Cannot update schema object"));
              return (DirContext)objectClassCtx;
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
      /*
       * getSchemaTree first looks to see if we have already built a
       * schema tree for the given entry. If not, it builds a new one and
       * stores it in our private hash table
       */
      private DirContext getSchemaTree(Name name) throws NamingException {
          String subschemasubentry = getSchemaEntry(name, true);
  
          DirContext schemaTree = schemaTrees.get(subschemasubentry);
  
          if(schemaTree==null) {
              if(debug){System.err.println("LdapCtx: building new schema tree " + this);}
              schemaTree = buildSchemaTree(subschemasubentry);
              schemaTrees.put(subschemasubentry, schemaTree);
          }
  
          return schemaTree;
      }
  
      /*
       * buildSchemaTree builds the schema tree corresponding to the
       * given subschemasubentree
       */
      private DirContext buildSchemaTree(String subschemasubentry)
          throws NamingException {
  
          // get the schema entry itself
          // DO ask for return object here because we need it to
          // create context. Since asking for all attrs, we won't
          // be transmitting any specific attrIDs (like Java-specific ones).
          SearchControls constraints = new
              SearchControls(SearchControls.OBJECT_SCOPE,
                  0, 0, /* count and time limits */
                  SCHEMA_ATTRIBUTES /* return schema attrs */,
                  true /* return obj */,
                  false /*deref link */ );
  
          Name sse = (new CompositeName()).add(subschemasubentry);
          NamingEnumeration<SearchResult> results =
              searchAux(sse, "(objectClass=subschema)", constraints,
              false, true, new Continuation());
  
          if(!results.hasMore()) {
              throw new OperationNotSupportedException(
                  "Cannot get read subschemasubentry: " + subschemasubentry);
          }
          SearchResult result = results.next();
          results.close();
  
          Object obj = result.getObject();
          if(!(obj instanceof LdapCtx)) {
              throw new NamingException(
                  "Cannot get schema object as DirContext: " + subschemasubentry);
          }
  
          return LdapSchemaCtx.createSchemaTree(envprops, subschemasubentry,
              (LdapCtx)obj /* schema entry */,
              result.getAttributes() /* schema attributes */,
              netscapeSchemaBug);
     }
  
      /*
       * getSchemaEntree returns the DN of the subschemasubentree for the
       * given entree. It first looks to see if the given entry has
       * a subschema different from that of the root DIT (by looking for
       * a "subschemasubentry" attribute). If it doesn't find one, it returns
       * the one for the root of the DIT (by looking for the root's
       * "subschemasubentry" attribute).
       *
       * This function is called regardless of the server's version, since
       * an administrator may have setup the server to support client schema
       * queries. If this function tries a search on a v2 server that
       * doesn't support schema, one of these two things will happen:
       * 1) It will get an exception when querying the root DSE
       * 2) It will not find a subschemasubentry on the root DSE
       * If either of these things occur and the server is not v3, we
       * throw OperationNotSupported.
       *
       * the relative flag tells whether the given name is relative to this
       * context.
       */
      private String getSchemaEntry(Name name, boolean relative)
          throws NamingException {
  
          // Asks for operational attribute "subschemasubentry"
          SearchControls constraints = new SearchControls(SearchControls.OBJECT_SCOPE,
              0, 0, /* count and time limits */
              new String[]{"subschemasubentry"} /* attr to return */,
              false /* returning obj */,
              false /* deref link */);
  
          NamingEnumeration<SearchResult> results;
          try {
              results = searchAux(name, "objectclass=*", constraints, relative,
                  true, new Continuation());
  
          } catch (NamingException ne) {
              if (!clnt.isLdapv3 && currentDN.length() == 0 && name.isEmpty()) {
                  // we got an error looking for a root entry on an ldapv2
                  // server. The server must not support schema.
                  throw new OperationNotSupportedException(
                      "Cannot get schema information from server");
              } else {
                  throw ne;
              }
          }
  
          if (!results.hasMoreElements()) {
              throw new ConfigurationException(
                  "Requesting schema of nonexistent entry: " + name);
          }
  
          SearchResult result = results.next();
          results.close();
  
          Attribute schemaEntryAttr =
              result.getAttributes().get("subschemasubentry");
          //System.err.println("schema entry attrs: " + schemaEntryAttr);
  
          if (schemaEntryAttr == null || schemaEntryAttr.size() < 0) {
              if (currentDN.length() == 0 && name.isEmpty()) {
                  // the server doesn't have a subschemasubentry in its root DSE.
                  // therefore, it doesn't support schema.
                  throw new OperationNotSupportedException(
                      "Cannot read subschemasubentry of root DSE");
              } else {
                  return getSchemaEntry(new CompositeName(), false);
              }
          }
  
          return (String)(schemaEntryAttr.get()); // return schema entry name
      }
  
      // package-private; used by search enum.
      // Set attributes to point to this context in case some one
      // asked for their schema
      void setParents(Attributes attrs, Name name) throws NamingException {
          NamingEnumeration<? extends Attribute> ae = attrs.getAll();
          while(ae.hasMore()) {
              ((LdapAttribute) ae.next()).setParent(this, name);
          }
      }
  
      /*
       * Returns the URL associated with this context; used by LdapAttribute
       * after deserialization to get pointer to this context.
       */
      String getURL() {
          if (url == null) {
              url = LdapURL.toUrlString(hostname, port_number, currentDN,
                  hasLdapsScheme);
          }
  
          return url;
      }
  
     // --------------------- Searches -----------------------------
      protected NamingEnumeration<SearchResult> c_search(Name name,
                                           Attributes matchingAttributes,
                                           Continuation cont)
              throws NamingException {
          return c_search(name, matchingAttributes, null, cont);
      }
  
      protected NamingEnumeration<SearchResult> c_search(Name name,
                                           Attributes matchingAttributes,
                                           String[] attributesToReturn,
                                           Continuation cont)
              throws NamingException {
          SearchControls cons = new SearchControls();
          cons.setReturningAttributes(attributesToReturn);
          String filter;
          try {
              filter = SearchFilter.format(matchingAttributes);
          } catch (NamingException e) {
              cont.setError(this, name);
              throw cont.fillInException(e);
          }
          return c_search(name, filter, cons, cont);
      }
  
      protected NamingEnumeration<SearchResult> c_search(Name name,
                                           String filter,
                                           SearchControls cons,
                                           Continuation cont)
              throws NamingException {
          return searchAux(name, filter, cloneSearchControls(cons), true,
                   waitForReply, cont);
      }
  
      protected NamingEnumeration<SearchResult> c_search(Name name,
                                           String filterExpr,
                                           Object[] filterArgs,
                                           SearchControls cons,
                                           Continuation cont)
              throws NamingException {
          String strfilter;
          try {
              strfilter = SearchFilter.format(filterExpr, filterArgs);
          } catch (NamingException e) {
              cont.setError(this, name);
              throw cont.fillInException(e);
          }
          return c_search(name, strfilter, cons, cont);
      }
  
          // Used by NamingNotifier
      NamingEnumeration<SearchResult> searchAux(Name name,
          String filter,
          SearchControls cons,
          boolean relative,
          boolean waitForReply, Continuation cont) throws NamingException {
  
          LdapResult answer = null;
          String[] tokens = new String[2];    // stores ldap compare op. values
          String[] reqAttrs;                  // remember what was asked
  
          if (cons == null) {
              cons = new SearchControls();
          }
          reqAttrs = cons.getReturningAttributes();
  
          // if objects are requested then request the Java attributes too
          // so that the objects can be constructed
          if (cons.getReturningObjFlag()) {
              if (reqAttrs != null) {
  
                  // check for presence of "*" (user attributes wildcard)
                  boolean hasWildcard = false;
                  for (int i = reqAttrs.length - 1; i >= 0; i--) {
                      if (reqAttrs[i].equals("*")) {
                          hasWildcard = true;
                          break;
                      }
                  }
                  if (! hasWildcard) {
                      String[] totalAttrs =
                          new String[reqAttrs.length +Obj.JAVA_ATTRIBUTES.length];
                      System.arraycopy(reqAttrs, 0, totalAttrs, 0,
                          reqAttrs.length);
                      System.arraycopy(Obj.JAVA_ATTRIBUTES, 0, totalAttrs,
                          reqAttrs.length, Obj.JAVA_ATTRIBUTES.length);
  
                      cons.setReturningAttributes(totalAttrs);
                  }
              }
          }
  
          LdapCtx.SearchArgs args =
              new LdapCtx.SearchArgs(name, filter, cons, reqAttrs);
  
          cont.setError(this, name);
          try {
              // see if this can be done as a compare, otherwise do a search
              if (searchToCompare(filter, cons, tokens)){
                  //System.err.println("compare triggered");
                  answer = compare(name, tokens[0], tokens[1]);
                  if (! (answer.compareToSearchResult(fullyQualifiedName(name)))){
                      processReturnCode(answer, name);
                  }
              } else {
                  answer = doSearch(name, filter, cons, relative, waitForReply);
                  // search result may contain referrals
                  processReturnCode(answer, name);
              }
              return new LdapSearchEnumeration(this, answer,
                                               fullyQualifiedName(name),
                                               args, cont);
  
          } catch (LdapReferralException e) {
              if (handleReferrals == LdapClient.LDAP_REF_THROW)
                  throw cont.fillInException(e);
  
              // process the referrals sequentially
              while (true) {
  
                  @SuppressWarnings("unchecked")
                  LdapReferralContext refCtx = (LdapReferralContext)
                          e.getReferralContext(envprops, bindCtls);
  
                  // repeat the original operation at the new context
                  try {
  
                      return refCtx.search(name, filter, cons);
  
                  } catch (LdapReferralException re) {
                      e = re;
                      continue;
  
                  } finally {
                      // Make sure we close referral context
                      refCtx.close();
                  }
              }
  
          } catch (LimitExceededException e) {
              LdapSearchEnumeration res =
                  new LdapSearchEnumeration(this, answer, fullyQualifiedName(name),
                                            args, cont);
              res.setNamingException(e);
              return res;
  
          } catch (PartialResultException e) {
              LdapSearchEnumeration res =
                  new LdapSearchEnumeration(this, answer, fullyQualifiedName(name),
                                            args, cont);
  
              res.setNamingException(e);
              return res;
  
          } catch (IOException e) {
              NamingException e2 = new CommunicationException(e.getMessage());
              e2.setRootCause(e);
              throw cont.fillInException(e2);
  
          } catch (NamingException e) {
              throw cont.fillInException(e);
          }
      }
  
  
      LdapResult getSearchReply(LdapClient eClnt, LdapResult res)
              throws NamingException {
          // ensureOpen() won't work here because
          // session was associated with previous connection
  
          // %%% RL: we can actually allow the enumeration to continue
          // using the old handle but other weird things might happen
          // when we hit a referral
          if (clnt != eClnt) {
              throw new CommunicationException(
                  "Context's connection changed; unable to continue enumeration");
          }
  
          try {
              return eClnt.getSearchReply(batchSize, res, binaryAttrs);
          } catch (IOException e) {
              NamingException e2 = new CommunicationException(e.getMessage());
              e2.setRootCause(e);
              throw e2;
          }
      }
  
      // Perform a search. Expect 1 SearchResultEntry and the SearchResultDone.
      private LdapResult doSearchOnce(Name name, String filter,
          SearchControls cons, boolean relative) throws NamingException {
  
          int savedBatchSize = batchSize;
          batchSize = 2; // 2 protocol elements
  
          LdapResult answer = doSearch(name, filter, cons, relative, true);
  
          batchSize = savedBatchSize;
          return answer;
      }
  
      private LdapResult doSearch(Name name, String filter, SearchControls cons,
          boolean relative, boolean waitForReply) throws NamingException {
              ensureOpen();
              try {
                  int scope;
  
                  switch (cons.getSearchScope()) {
                  case SearchControls.OBJECT_SCOPE:
                      scope = LdapClient.SCOPE_BASE_OBJECT;
                      break;
                  default:
                  case SearchControls.ONELEVEL_SCOPE:
                      scope = LdapClient.SCOPE_ONE_LEVEL;
                      break;
                  case SearchControls.SUBTREE_SCOPE:
                      scope = LdapClient.SCOPE_SUBTREE;
                      break;
                  }
  
                  // If cons.getReturningObjFlag() then caller should already
                  // have make sure to request the appropriate attrs
  
                  String[] retattrs = cons.getReturningAttributes();
                  if (retattrs != null && retattrs.length == 0) {
                      // Ldap treats null and empty array the same
                      // need to replace with single element array
                      retattrs = new String[1];
                      retattrs[0] = "1.1";
                  }
  
                  String nm = (relative
                               ? fullyQualifiedName(name)
                               : (name.isEmpty()
                                  ? ""
                                  : name.get(0)));
  
                  // JNDI unit is milliseconds, LDAP unit is seconds.
                  // Zero means no limit.
                  int msecLimit = cons.getTimeLimit();
                  int secLimit = 0;
  
                  if (msecLimit > 0) {
                      secLimit = (msecLimit / 1000) + 1;
                  }
  
                  LdapResult answer =
                      clnt.search(nm,
                          scope,
                          derefAliases,
                          (int)cons.getCountLimit(),
                          secLimit,
                          cons.getReturningObjFlag() ? false : typesOnly,
                          retattrs,
                          filter,
                          batchSize,
                          reqCtls,
                          binaryAttrs,
                          waitForReply,
                          replyQueueSize);
                  respCtls = answer.resControls; // retrieve response controls
                  return answer;
  
              } catch (IOException e) {
                  NamingException e2 = new CommunicationException(e.getMessage());
                  e2.setRootCause(e);
                  throw e2;
              }
      }
  
  
      /*
       * Certain simple JNDI searches are automatically converted to
       * LDAP compare operations by the LDAP service provider. A search
       * is converted to a compare iff:
       *
       *    - the scope is set to OBJECT_SCOPE
       *    - the filter string contains a simple assertion: "<type>=<value>"
       *    - the returning attributes list is present but empty
       */
  
      // returns true if a search can be carried out as a compare, and sets
      // tokens[0] and tokens[1] to the type and value respectively.
      // e.g. filter "cn=Jon Ruiz" becomes, type "cn" and value "Jon Ruiz"
      // This function uses the documents JNDI Compare example as a model
      // for when to turn a search into a compare.
  
      private static boolean searchToCompare(
                                      String filter,
                                      SearchControls cons,
                                      String tokens[]) {
  
          // if scope is not object-scope, it's really a search
          if (cons.getSearchScope() != SearchControls.OBJECT_SCOPE) {
              return false;
          }
  
          // if attributes are to be returned, it's really a search
          String[] attrs = cons.getReturningAttributes();
          if (attrs == null || attrs.length != 0) {
              return false;
          }
  
          // if the filter not a simple assertion, it's really a search
          if (! filterToAssertion(filter, tokens)) {
              return false;
          }
  
          // it can be converted to a compare
          return true;
      }
  
      // If the supplied filter is a simple assertion i.e. "<type>=<value>"
      // (enclosing parentheses are permitted) then
      // filterToAssertion will return true and pass the type and value as
      // the first and second elements of tokens respectively.
      // precondition: tokens[] must be initialized and be at least of size 2.
  
      private static boolean filterToAssertion(String filter, String tokens[]) {
  
          // find the left and right half of the assertion
          StringTokenizer assertionTokenizer = new StringTokenizer(filter, "=");
  
          if (assertionTokenizer.countTokens() != 2) {
              return false;
          }
  
          tokens[0] = assertionTokenizer.nextToken();
          tokens[1] = assertionTokenizer.nextToken();
  
          // make sure the value does not contain a wildcard
          if (tokens[1].indexOf('*') != -1) {
              return false;
          }
  
          // test for enclosing parenthesis
          boolean hasParens = false;
          int len = tokens[1].length();
  
          if ((tokens[0].charAt(0) == '(') &&
              (tokens[1].charAt(len - 1) == ')')) {
              hasParens = true;
  
          } else if ((tokens[0].charAt(0) == '(') ||
              (tokens[1].charAt(len - 1) == ')')) {
              return false; // unbalanced
          }
  
          // make sure the left and right half are not expressions themselves
          StringTokenizer illegalCharsTokenizer =
              new StringTokenizer(tokens[0], "()&|!=~><*", true);
  
          if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {
              return false;
          }
  
          illegalCharsTokenizer =
              new StringTokenizer(tokens[1], "()&|!=~><*", true);
  
          if (illegalCharsTokenizer.countTokens() != (hasParens ? 2 : 1)) {
              return false;
          }
  
          // strip off enclosing parenthesis, if present
          if (hasParens) {
              tokens[0] = tokens[0].substring(1);
              tokens[1] = tokens[1].substring(0, len - 1);
          }
  
          return true;
      }
  
      private LdapResult compare(Name name, String type, String value)
          throws IOException, NamingException {
  
          ensureOpen();
          String nm = fullyQualifiedName(name);
  
          LdapResult answer = clnt.compare(nm, type, value, reqCtls);
          respCtls = answer.resControls; // retrieve response controls
  
          return answer;
      }
  
      private static SearchControls cloneSearchControls(SearchControls cons) {
          if (cons == null) {
              return null;
          }
          String[] retAttrs = cons.getReturningAttributes();
          if (retAttrs != null) {
              String[] attrs = new String[retAttrs.length];
              System.arraycopy(retAttrs, 0, attrs, 0, retAttrs.length);
              retAttrs = attrs;
          }
          return new SearchControls(cons.getSearchScope(),
                                    cons.getCountLimit(),
                                    cons.getTimeLimit(),
                                    retAttrs,
                                    cons.getReturningObjFlag(),
                                    cons.getDerefLinkFlag());
      }
  
     // -------------- Environment Properties ------------------
  
      /**
       * Override with noncloning version.
       */
      protected Hashtable<String, Object> p_getEnvironment() {
          return envprops;
      }
  
      @SuppressWarnings("unchecked") // clone()
      public Hashtable<String, Object> getEnvironment() throws NamingException {
          return (envprops == null
                  ? new Hashtable<String, Object>(5, 0.75f)
                  : (Hashtable<String, Object>)envprops.clone());
      }
  
      @SuppressWarnings("unchecked") // clone()
      public Object removeFromEnvironment(String propName)
          throws NamingException {
  
          // not there; just return
          if (envprops == null || envprops.get(propName) == null) {
              return null;
          }
          switch (propName) {
              case REF_SEPARATOR:
                  addrEncodingSeparator = DEFAULT_REF_SEPARATOR;
                  break;
              case TYPES_ONLY:
                  typesOnly = DEFAULT_TYPES_ONLY;
                  break;
              case DELETE_RDN:
                  deleteRDN = DEFAULT_DELETE_RDN;
                  break;
              case DEREF_ALIASES:
                  derefAliases = DEFAULT_DEREF_ALIASES;
                  break;
              case Context.BATCHSIZE:
                  batchSize = DEFAULT_BATCH_SIZE;
                  break;
              case REFERRAL_LIMIT:
                  referralHopLimit = DEFAULT_REFERRAL_LIMIT;
                  break;
              case Context.REFERRAL:
                  setReferralMode(null, true);
                  break;
              case BINARY_ATTRIBUTES:
                  setBinaryAttributes(null);
                  break;
              case CONNECT_TIMEOUT:
                  connectTimeout = -1;
                  break;
              case READ_TIMEOUT:
                  readTimeout = -1;
                  break;
              case WAIT_FOR_REPLY:
                  waitForReply = true;
                  break;
              case REPLY_QUEUE_SIZE:
                  replyQueueSize = -1;
                  break;
  
              // The following properties affect the connection
  
              case Context.SECURITY_PROTOCOL:
                  closeConnection(SOFT_CLOSE);
                  // De-activate SSL and reset the context's url and port number
                  if (useSsl && !hasLdapsScheme) {
                      useSsl = false;
                      url = null;
                      if (useDefaultPortNumber) {
                          port_number = DEFAULT_PORT;
                      }
                  }
                  break;
              case VERSION:
              case SOCKET_FACTORY:
                  closeConnection(SOFT_CLOSE);
                  break;
              case Context.SECURITY_AUTHENTICATION:
              case Context.SECURITY_PRINCIPAL:
              case Context.SECURITY_CREDENTIALS:
                  sharable = false;
                  break;
          }
  
          // Update environment; reconnection will use new props
          envprops = (Hashtable<String, Object>)envprops.clone();
          return envprops.remove(propName);
      }
  
      @SuppressWarnings("unchecked") // clone()
      public Object addToEnvironment(String propName, Object propVal)
          throws NamingException {
  
              // If adding null, call remove
              if (propVal == null) {
                  return removeFromEnvironment(propName);
              }
              switch (propName) {
                  case REF_SEPARATOR:
                      setRefSeparator((String)propVal);
                      break;
                  case TYPES_ONLY:
                      setTypesOnly((String)propVal);
                      break;
                  case DELETE_RDN:
                      setDeleteRDN((String)propVal);
                      break;
                  case DEREF_ALIASES:
                      setDerefAliases((String)propVal);
                      break;
                  case Context.BATCHSIZE:
                      setBatchSize((String)propVal);
                      break;
                  case REFERRAL_LIMIT:
                      setReferralLimit((String)propVal);
                      break;
                  case Context.REFERRAL:
                      setReferralMode((String)propVal, true);
                      break;
                  case BINARY_ATTRIBUTES:
                      setBinaryAttributes((String)propVal);
                      break;
                  case CONNECT_TIMEOUT:
                      setConnectTimeout((String)propVal);
                      break;
                  case READ_TIMEOUT:
                      setReadTimeout((String)propVal);
                      break;
                  case WAIT_FOR_REPLY:
                      setWaitForReply((String)propVal);
                      break;
                  case REPLY_QUEUE_SIZE:
                      setReplyQueueSize((String)propVal);
                      break;
  
              // The following properties affect the connection
  
                  case Context.SECURITY_PROTOCOL:
                      closeConnection(SOFT_CLOSE);
                      // Activate SSL and reset the context's url and port number
                      if ("ssl".equals(propVal)) {
                          useSsl = true;
                          url = null;
                          if (useDefaultPortNumber) {
                              port_number = DEFAULT_SSL_PORT;
                          }
                      }
                      break;
                  case VERSION:
                  case SOCKET_FACTORY:
                      closeConnection(SOFT_CLOSE);
                      break;
                  case Context.SECURITY_AUTHENTICATION:
                  case Context.SECURITY_PRINCIPAL:
                  case Context.SECURITY_CREDENTIALS:
                      sharable = false;
                      break;
              }
  
              // Update environment; reconnection will use new props
              envprops = (envprops == null
                  ? new Hashtable<String, Object>(5, 0.75f)
                  : (Hashtable<String, Object>)envprops.clone());
              return envprops.put(propName, propVal);
      }
  
      /**
       * Sets the URL that created the context in the java.naming.provider.url
       * property.
       */
      void setProviderUrl(String providerUrl) { // called by LdapCtxFactory
  