/**
   * Creates an initial context using the specified environment properties.
   *
   * <p>If an InitialContextFactoryBuilder has been installed, it is used to create the factory for
   * creating the initial context. Otherwise, the class specified in the
   * <tt>Context.INITIAL_CONTEXT_FACTORY</tt> environment property is used. Note that an initial
   * context factory (an object that implements the InitialContextFactory interface) must be public
   * and must have a public constructor that accepts no arguments.
   *
   * @param env The possibly null environment properties used when creating the context.
   * @return A non-null initial context.
   * @exception NoInitialContextException If the <tt>Context.INITIAL_CONTEXT_FACTORY</tt> property
   *     is not found or names a nonexistent class or a class that cannot be instantiated, or if the
   *     initial context could not be created for some other reason.
   * @exception NamingException If some other naming exception was encountered.
   * @see javax.naming.InitialContext
   * @see javax.naming.directory.InitialDirContext
   */
  public static Context getInitialContext(Hashtable<?, ?> env) throws NamingException {
    InitialContextFactory factory;

    InitialContextFactoryBuilder builder = getInitialContextFactoryBuilder();
    if (builder == null) {
      // No factory installed, use property
      // Get initial context factory class name

      String className = env != null ? (String) env.get(Context.INITIAL_CONTEXT_FACTORY) : null;
      if (className == null) {
        NoInitialContextException ne =
            new NoInitialContextException(
                "Need to specify class name in environment or system "
                    + "property, or as an applet parameter, or in an "
                    + "application resource file:  "
                    + Context.INITIAL_CONTEXT_FACTORY);
        throw ne;
      }

      try {
        factory = (InitialContextFactory) helper.loadClass(className).newInstance();
      } catch (Exception e) {
        NoInitialContextException ne =
            new NoInitialContextException("Cannot instantiate class: " + className);
        ne.setRootCause(e);
        throw ne;
      }
    } else {
      factory = builder.createInitialContextFactory(env);
    }

    return factory.getInitialContext(env);
  }
  /**
   * {@collect.stats} Creates an extended response object that corresponds to the LDAP StartTLS
   * extended request.
   *
   * <p>The result must be a concrete subclass of StartTlsResponse and must have a public
   * zero-argument constructor.
   *
   * <p>This method locates the implementation class by locating configuration files that have the
   * name:
   *
   * <blockquote>
   *
   * <tt> META-INF/services/javax.naming.ldap.StartTlsResponse </tt>
   *
   * </blockquote>
   *
   * The configuration files and their corresponding implementation classes must be accessible to
   * the calling thread's context class loader.
   *
   * <p>Each configuration file should contain a list of fully-qualified class names, one per line.
   * Space and tab characters surrounding each name, as well as blank lines, are ignored. The
   * comment character is <tt>'#'</tt> (<tt>0x23</tt>); on each line all characters following the
   * first comment character are ignored. The file must be encoded in UTF-8.
   *
   * <p>This method will return an instance of the first implementation class that it is able to
   * load and instantiate successfully from the list of class names collected from the configuration
   * files. This method uses the calling thread's context classloader to find the configuration
   * files and to load the implementation class.
   *
   * <p>If no class can be found in this way, this method will use an implementation-specific way to
   * locate an implementation. If none is found, a NamingException is thrown.
   *
   * @param id The object identifier of the extended response. Its value must be
   *     "1.3.6.1.4.1.1466.20037" or null. Both values are equivalent.
   * @param berValue The possibly null ASN.1 BER encoded value of the extended response. This is the
   *     raw BER bytes including the tag and length of the response value. It does not include the
   *     response OID. Its value is ignored because a Start TLS response is not expected to contain
   *     any response value.
   * @param offset The starting position in berValue of the bytes to use. Its value is ignored
   *     because a Start TLS response is not expected to contain any response value.
   * @param length The number of bytes in berValue to use. Its value is ignored because a Start TLS
   *     response is not expected to contain any response value.
   * @return The StartTLS extended response object.
   * @exception NamingException If a naming exception was encountered while creating the StartTLS
   *     extended response object.
   */
  public ExtendedResponse createExtendedResponse(String id, byte[] berValue, int offset, int length)
      throws NamingException {

    // Confirm that the object identifier is correct
    if ((id != null) && (!id.equals(OID))) {
      throw new ConfigurationException(
          "Start TLS received the following response instead of " + OID + ": " + id);
    }

    StartTlsResponse resp = null;

    ServiceLoader<StartTlsResponse> sl =
        ServiceLoader.load(StartTlsResponse.class, getContextClassLoader());
    Iterator<StartTlsResponse> iter = sl.iterator();

    while (resp == null && privilegedHasNext(iter)) {
      resp = iter.next();
    }
    if (resp != null) {
      return resp;
    }
    try {
      VersionHelper helper = VersionHelper.getVersionHelper();
      Class clas = helper.loadClass("com.sun.jndi.ldap.ext.StartTlsResponseImpl");

      resp = (StartTlsResponse) clas.newInstance();

    } catch (IllegalAccessException e) {
      throw wrapException(e);

    } catch (InstantiationException e) {
      throw wrapException(e);

    } catch (ClassNotFoundException e) {
      throw wrapException(e);
    }

    return resp;
  }
  /**
   * Retrieves the ObjectFactory for the object identified by a reference, using the reference's
   * factory class name and factory codebase to load in the factory's class.
   *
   * @param ref The non-null reference to use.
   * @param factoryName The non-null class name of the factory.
   * @return The object factory for the object identified by ref; null if unable to load the
   *     factory.
   */
  static ObjectFactory getObjectFactoryFromReference(Reference ref, String factoryName)
      throws IllegalAccessException, InstantiationException, MalformedURLException {
    Class<?> clas = null;

    // Try to use current class loader
    try {
      clas = helper.loadClass(factoryName);
    } catch (ClassNotFoundException e) {
      // ignore and continue
      // e.printStackTrace();
    }
    // All other exceptions are passed up.

    // Not in class path; try to use codebase
    String codebase;
    if (clas == null && (codebase = ref.getFactoryClassLocation()) != null) {
      try {
        clas = helper.loadClass(factoryName, codebase);
      } catch (ClassNotFoundException e) {
      }
    }

    return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
  }
/**
 * This class contains methods for creating context objects and objects referred to by location
 * information in the naming or directory service.
 *
 * <p>This class cannot be instantiated. It has only static methods.
 *
 * <p>The mention of URL in the documentation for this class refers to a URL string as defined by
 * RFC 1738 and its related RFCs. It is any string that conforms to the syntax described therein,
 * and may not always have corresponding support in the java.net.URL class or Web browsers.
 *
 * <p>NamingManager is safe for concurrent access by multiple threads.
 *
 * <p>Except as otherwise noted, a <tt>Name</tt> or environment parameter passed to any method is
 * owned by the caller. The implementation will not modify the object or keep a reference to it,
 * although it may keep a reference to a clone or copy.
 *
 * @author Rosanna Lee
 * @author Scott Seligman
 * @since 1.3
 */
public class NamingManager {

  /*
   * Disallow anyone from creating one of these.
   * Made package private so that DirectoryManager can subclass.
   */

  NamingManager() {}

  // should be protected and package private
  static final VersionHelper helper = VersionHelper.getVersionHelper();

  // --------- object factory stuff

  /** Package-private; used by DirectoryManager and NamingManager. */
  private static ObjectFactoryBuilder object_factory_builder = null;

  /**
   * The ObjectFactoryBuilder determines the policy used when trying to load object factories. See
   * getObjectInstance() and class ObjectFactory for a description of the default policy.
   * setObjectFactoryBuilder() overrides this default policy by installing an ObjectFactoryBuilder.
   * Subsequent object factories will be loaded and created using the installed builder.
   *
   * <p>The builder can only be installed if the executing thread is allowed (by the security
   * manager's checkSetFactory() method) to do so. Once installed, the builder cannot be replaced.
   *
   * <p>
   *
   * @param builder The factory builder to install. If null, no builder is installed.
   * @exception SecurityException builder cannot be installed for security reasons.
   * @exception NamingException builder cannot be installed for a non-security-related reason.
   * @exception IllegalStateException If a factory has already been installed.
   * @see #getObjectInstance
   * @see ObjectFactory
   * @see ObjectFactoryBuilder
   * @see java.lang.SecurityManager#checkSetFactory
   */
  public static synchronized void setObjectFactoryBuilder(ObjectFactoryBuilder builder)
      throws NamingException {
    if (object_factory_builder != null)
      throw new IllegalStateException("ObjectFactoryBuilder already set");

    SecurityManager security = System.getSecurityManager();
    if (security != null) {
      security.checkSetFactory();
    }
    object_factory_builder = builder;
  }

  /** Used for accessing object factory builder. */
  static synchronized ObjectFactoryBuilder getObjectFactoryBuilder() {
    return object_factory_builder;
  }

  /**
   * Retrieves the ObjectFactory for the object identified by a reference, using the reference's
   * factory class name and factory codebase to load in the factory's class.
   *
   * @param ref The non-null reference to use.
   * @param factoryName The non-null class name of the factory.
   * @return The object factory for the object identified by ref; null if unable to load the
   *     factory.
   */
  static ObjectFactory getObjectFactoryFromReference(Reference ref, String factoryName)
      throws IllegalAccessException, InstantiationException, MalformedURLException {
    Class<?> clas = null;

    // Try to use current class loader
    try {
      clas = helper.loadClass(factoryName);
    } catch (ClassNotFoundException e) {
      // ignore and continue
      // e.printStackTrace();
    }
    // All other exceptions are passed up.

    // Not in class path; try to use codebase
    String codebase;
    if (clas == null && (codebase = ref.getFactoryClassLocation()) != null) {
      try {
        clas = helper.loadClass(factoryName, codebase);
      } catch (ClassNotFoundException e) {
      }
    }

    return (clas != null) ? (ObjectFactory) clas.newInstance() : null;
  }

  /**
   * Creates an object using the factories specified in the <tt>Context.OBJECT_FACTORIES</tt>
   * property of the environment or of the provider resource file associated with <tt>nameCtx</tt>.
   *
   * @return factory created; null if cannot create
   */
  private static Object createObjectFromFactories(
      Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {

    FactoryEnumeration factories =
        ResourceManager.getFactories(Context.OBJECT_FACTORIES, environment, nameCtx);

    if (factories == null) return null;

    // Try each factory until one succeeds
    ObjectFactory factory;
    Object answer = null;
    while (answer == null && factories.hasMore()) {
      factory = (ObjectFactory) factories.next();
      answer = factory.getObjectInstance(obj, name, nameCtx, environment);
    }
    return answer;
  }

  private static String getURLScheme(String str) {
    int colon_posn = str.indexOf(':');
    int slash_posn = str.indexOf('/');

    if (colon_posn > 0 && (slash_posn == -1 || colon_posn < slash_posn))
      return str.substring(0, colon_posn);
    return null;
  }

  /**
   * Creates an instance of an object for the specified object and environment.
   *
   * <p>If an object factory builder has been installed, it is used to create a factory for creating
   * the object. Otherwise, the following rules are used to create the object:
   *
   * <ol>
   *   <li>If <code>refInfo</code> is a <code>Reference</code> or <code>Referenceable</code>
   *       containing a factory class name, use the named factory to create the object. Return
   *       <code>refInfo</code> if the factory cannot be created. Under JDK 1.1, if the factory
   *       class must be loaded from a location specified in the reference, a
   *       <tt>SecurityManager</tt> must have been installed or the factory creation will fail. If
   *       an exception is encountered while creating the factory, it is passed up to the caller.
   *   <li>If <tt>refInfo</tt> is a <tt>Reference</tt> or <tt>Referenceable</tt> with no factory
   *       class name, and the address or addresses are <tt>StringRefAddr</tt>s with address type
   *       "URL", try the URL context factory corresponding to each URL's scheme id to create the
   *       object (see <tt>getURLContext()</tt>). If that fails, continue to the next step.
   *   <li>Use the object factories specified in the <tt>Context.OBJECT_FACTORIES</tt> property of
   *       the environment, and of the provider resource file associated with <tt>nameCtx</tt>, in
   *       that order. The value of this property is a colon-separated list of factory class names
   *       that are tried in order, and the first one that succeeds in creating an object is the one
   *       used. If none of the factories can be loaded, return <code>refInfo</code>. If an
   *       exception is encountered while creating the object, the exception is passed up to the
   *       caller.
   * </ol>
   *
   * <p>Service providers that implement the <tt>DirContext</tt> interface should use
   * <tt>DirectoryManager.getObjectInstance()</tt>, not this method. Service providers that
   * implement only the <tt>Context</tt> interface should use this method.
   *
   * <p>Note that an object factory (an object that implements the ObjectFactory interface) must be
   * public and must have a public constructor that accepts no arguments.
   *
   * <p>The <code>name</code> and <code>nameCtx</code> parameters may optionally be used to specify
   * the name of the object being created. <code>name</code> is the name of the object, relative to
   * context <code>nameCtx</code>. This information could be useful to the object factory or to the
   * object implementation. If there are several possible contexts from which the object could be
   * named -- as will often be the case -- it is up to the caller to select one. A good rule of
   * thumb is to select the "deepest" context available. If <code>nameCtx</code> is null, <code>name
   * </code> is relative to the default initial context. If no name is being specified, the <code>
   * name</code> parameter should be null.
   *
   * @param refInfo The possibly null object for which to create an object.
   * @param name The name of this object relative to <code>nameCtx</code>. Specifying a name is
   *     optional; if it is omitted, <code>name</code> should be null.
   * @param nameCtx The context relative to which the <code>name</code> parameter is specified. If
   *     null, <code>name</code> is relative to the default initial context.
   * @param environment The possibly null environment to be used in the creation of the object
   *     factory and the object.
   * @return An object created using <code>refInfo</code>; or <code>refInfo</code> if an object
   *     cannot be created using the algorithm described above.
   * @exception NamingException if a naming exception was encountered while attempting to get a URL
   *     context, or if one of the factories accessed throws a NamingException.
   * @exception Exception if one of the factories accessed throws an exception, or if an error was
   *     encountered while loading and instantiating the factory and object classes. A factory
   *     should only throw an exception if it does not want other factories to be used in an attempt
   *     to create an object. See ObjectFactory.getObjectInstance().
   * @see #getURLContext
   * @see ObjectFactory
   * @see ObjectFactory#getObjectInstance
   */
  public static Object getObjectInstance(
      Object refInfo, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {

    ObjectFactory factory;

    // Use builder if installed
    ObjectFactoryBuilder builder = getObjectFactoryBuilder();
    if (builder != null) {
      // builder must return non-null factory
      factory = builder.createObjectFactory(refInfo, environment);
      return factory.getObjectInstance(refInfo, name, nameCtx, environment);
    }

    // Use reference if possible
    Reference ref = null;
    if (refInfo instanceof Reference) {
      ref = (Reference) refInfo;
    } else if (refInfo instanceof Referenceable) {
      ref = ((Referenceable) (refInfo)).getReference();
    }

    Object answer;

    if (ref != null) {
      String f = ref.getFactoryClassName();
      if (f != null) {
        // if reference identifies a factory, use exclusively

        factory = getObjectFactoryFromReference(ref, f);
        if (factory != null) {
          return factory.getObjectInstance(ref, name, nameCtx, environment);
        }
        // No factory found, so return original refInfo.
        // Will reach this point if factory class is not in
        // class path and reference does not contain a URL for it
        return refInfo;

      } else {
        // if reference has no factory, check for addresses
        // containing URLs

        answer = processURLAddrs(ref, name, nameCtx, environment);
        if (answer != null) {
          return answer;
        }
      }
    }

    // try using any specified factories
    answer = createObjectFromFactories(refInfo, name, nameCtx, environment);
    return (answer != null) ? answer : refInfo;
  }

  /*
   * Ref has no factory.  For each address of type "URL", try its URL
   * context factory.  Returns null if unsuccessful in creating and
   * invoking a factory.
   */
  static Object processURLAddrs(
      Reference ref, Name name, Context nameCtx, Hashtable<?, ?> environment)
      throws NamingException {

    for (int i = 0; i < ref.size(); i++) {
      RefAddr addr = ref.get(i);
      if (addr instanceof StringRefAddr && addr.getType().equalsIgnoreCase("URL")) {

        String url = (String) addr.getContent();
        Object answer = processURL(url, name, nameCtx, environment);
        if (answer != null) {
          return answer;
        }
      }
    }
    return null;
  }

  private static Object processURL(
      Object refInfo, Name name, Context nameCtx, Hashtable<?, ?> environment)
      throws NamingException {
    Object answer;

    // If refInfo is a URL string, try to use its URL context factory
    // If no context found, continue to try object factories.
    if (refInfo instanceof String) {
      String url = (String) refInfo;
      String scheme = getURLScheme(url);
      if (scheme != null) {
        answer = getURLObject(scheme, refInfo, name, nameCtx, environment);
        if (answer != null) {
          return answer;
        }
      }
    }

    // If refInfo is an array of URL strings,
    // try to find a context factory for any one of its URLs.
    // If no context found, continue to try object factories.
    if (refInfo instanceof String[]) {
      String[] urls = (String[]) refInfo;
      for (int i = 0; i < urls.length; i++) {
        String scheme = getURLScheme(urls[i]);
        if (scheme != null) {
          answer = getURLObject(scheme, refInfo, name, nameCtx, environment);
          if (answer != null) return answer;
        }
      }
    }
    return null;
  }

  /**
   * Retrieves a context identified by <code>obj</code>, using the specified environment. Used by
   * ContinuationContext.
   *
   * @param obj The object identifying the context.
   * @param name The name of the context being returned, relative to <code>nameCtx</code>, or null
   *     if no name is being specified. See the <code>getObjectInstance</code> method for details.
   * @param nameCtx The context relative to which <code>name</code> is specified, or null for the
   *     default initial context. See the <code>getObjectInstance</code> method for details.
   * @param environment Environment specifying characteristics of the resulting context.
   * @return A context identified by <code>obj</code>.
   * @see #getObjectInstance
   */
  static Context getContext(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
      throws NamingException {
    Object answer;

    if (obj instanceof Context) {
      // %%% Ignore environment for now.  OK since method not public.
      return (Context) obj;
    }

    try {
      answer = getObjectInstance(obj, name, nameCtx, environment);
    } catch (NamingException e) {
      throw e;
    } catch (Exception e) {
      NamingException ne = new NamingException();
      ne.setRootCause(e);
      throw ne;
    }

    return (answer instanceof Context) ? (Context) answer : null;
  }

  // Used by ContinuationContext
  static Resolver getResolver(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment)
      throws NamingException {
    Object answer;

    if (obj instanceof Resolver) {
      // %%% Ignore environment for now.  OK since method not public.
      return (Resolver) obj;
    }

    try {
      answer = getObjectInstance(obj, name, nameCtx, environment);
    } catch (NamingException e) {
      throw e;
    } catch (Exception e) {
      NamingException ne = new NamingException();
      ne.setRootCause(e);
      throw ne;
    }

    return (answer instanceof Resolver) ? (Resolver) answer : null;
  }

  /** *************** URL Context implementations ************** */

  /**
   * Creates a context for the given URL scheme id.
   *
   * <p>The resulting context is for resolving URLs of the scheme <code>scheme</code>. The resulting
   * context is not tied to a specific URL. It is able to handle arbitrary URLs with the specified
   * scheme.
   *
   * <p>The class name of the factory that creates the resulting context has the naming convention
   * <i>scheme-id</i>URLContextFactory (e.g. "ftpURLContextFactory" for the "ftp" scheme-id), in the
   * package specified as follows. The <tt>Context.URL_PKG_PREFIXES</tt> environment property (which
   * may contain values taken from applet parameters, system properties, or application resource
   * files) contains a colon-separated list of package prefixes. Each package prefix in the property
   * is tried in the order specified to load the factory class. The default package prefix is
   * "com.sun.jndi.url" (if none of the specified packages work, this default is tried). The
   * complete package name is constructed using the package prefix, concatenated with the scheme id.
   *
   * <p>For example, if the scheme id is "ldap", and the <tt>Context.URL_PKG_PREFIXES</tt> property
   * contains "com.widget:com.wiz.jndi", the naming manager would attempt to load the following
   * classes until one is successfully instantiated:
   *
   * <ul>
   *   <li>com.widget.ldap.ldapURLContextFactory
   *   <li>com.wiz.jndi.ldap.ldapURLContextFactory
   *   <li>com.sun.jndi.url.ldap.ldapURLContextFactory
   * </ul>
   *
   * If none of the package prefixes work, null is returned.
   *
   * <p>If a factory is instantiated, it is invoked with the following parameters to produce the
   * resulting context.
   *
   * <p><code>factory.getObjectInstance(null, environment);</code>
   *
   * <p>For example, invoking getObjectInstance() as shown above on a LDAP URL context factory would
   * return a context that can resolve LDAP urls (e.g. "ldap://ldap.wiz.com/o=wiz,c=us",
   * "ldap://ldap.umich.edu/o=umich,c=us", ...).
   *
   * <p>Note that an object factory (an object that implements the ObjectFactory interface) must be
   * public and must have a public constructor that accepts no arguments.
   *
   * @param scheme The non-null scheme-id of the URLs supported by the context.
   * @param environment The possibly null environment properties to be used in the creation of the
   *     object factory and the context.
   * @return A context for resolving URLs with the scheme id <code>scheme</code>; <code>null</code>
   *     if the factory for creating the context is not found.
   * @exception NamingException If a naming exception occurs while creating the context.
   * @see #getObjectInstance
   * @see ObjectFactory#getObjectInstance
   */
  public static Context getURLContext(String scheme, Hashtable<?, ?> environment)
      throws NamingException {
    // pass in 'null' to indicate creation of generic context for scheme
    // (i.e. not specific to a URL).

    Object answer = getURLObject(scheme, null, null, null, environment);
    if (answer instanceof Context) {
      return (Context) answer;
    } else {
      return null;
    }
  }

  private static final String defaultPkgPrefix = "com.sun.jndi.url";

  /**
   * Creates an object for the given URL scheme id using the supplied urlInfo.
   *
   * <p>If urlInfo is null, the result is a context for resolving URLs with the scheme id 'scheme'.
   * If urlInfo is a URL, the result is a context named by the URL. Names passed to this context is
   * assumed to be relative to this context (i.e. not a URL). For example, if urlInfo is
   * "ldap://ldap.wiz.com/o=Wiz,c=us", the resulting context will be that pointed to by "o=Wiz,c=us"
   * on the server 'ldap.wiz.com'. Subsequent names that can be passed to this context will be LDAP
   * names relative to this context (e.g. cn="Barbs Jensen"). If urlInfo is an array of URLs, the
   * URLs are assumed to be equivalent in terms of the context to which they refer. The resulting
   * context is like that of the single URL case. If urlInfo is of any other type, that is handled
   * by the context factory for the URL scheme.
   *
   * @param scheme the URL scheme id for the context
   * @param urlInfo information used to create the context
   * @param name name of this object relative to <code>nameCtx</code>
   * @param nameCtx Context whose provider resource file will be searched for package prefix values
   *     (or null if none)
   * @param environment Environment properties for creating the context
   * @see javax.naming.InitialContext
   */
  private static Object getURLObject(
      String scheme, Object urlInfo, Name name, Context nameCtx, Hashtable<?, ?> environment)
      throws NamingException {

    // e.g. "ftpURLContextFactory"
    ObjectFactory factory =
        (ObjectFactory)
            ResourceManager.getFactory(
                Context.URL_PKG_PREFIXES,
                environment,
                nameCtx,
                "." + scheme + "." + scheme + "URLContextFactory",
                defaultPkgPrefix);

    if (factory == null) return null;

    // Found object factory
    try {
      return factory.getObjectInstance(urlInfo, name, nameCtx, environment);
    } catch (NamingException e) {
      throw e;
    } catch (Exception e) {
      NamingException ne = new NamingException();
      ne.setRootCause(e);
      throw ne;
    }
  }

  // ------------ Initial Context Factory Stuff
  private static InitialContextFactoryBuilder initctx_factory_builder = null;

  /**
   * Use this method for accessing initctx_factory_builder while inside an unsynchronized method.
   */
  private static synchronized InitialContextFactoryBuilder getInitialContextFactoryBuilder() {
    return initctx_factory_builder;
  }

  /**
   * Creates an initial context using the specified environment properties.
   *
   * <p>If an InitialContextFactoryBuilder has been installed, it is used to create the factory for
   * creating the initial context. Otherwise, the class specified in the
   * <tt>Context.INITIAL_CONTEXT_FACTORY</tt> environment property is used. Note that an initial
   * context factory (an object that implements the InitialContextFactory interface) must be public
   * and must have a public constructor that accepts no arguments.
   *
   * @param env The possibly null environment properties used when creating the context.
   * @return A non-null initial context.
   * @exception NoInitialContextException If the <tt>Context.INITIAL_CONTEXT_FACTORY</tt> property
   *     is not found or names a nonexistent class or a class that cannot be instantiated, or if the
   *     initial context could not be created for some other reason.
   * @exception NamingException If some other naming exception was encountered.
   * @see javax.naming.InitialContext
   * @see javax.naming.directory.InitialDirContext
   */
  public static Context getInitialContext(Hashtable<?, ?> env) throws NamingException {
    InitialContextFactory factory;

    InitialContextFactoryBuilder builder = getInitialContextFactoryBuilder();
    if (builder == null) {
      // No factory installed, use property
      // Get initial context factory class name

      String className = env != null ? (String) env.get(Context.INITIAL_CONTEXT_FACTORY) : null;
      if (className == null) {
        NoInitialContextException ne =
            new NoInitialContextException(
                "Need to specify class name in environment or system "
                    + "property, or as an applet parameter, or in an "
                    + "application resource file:  "
                    + Context.INITIAL_CONTEXT_FACTORY);
        throw ne;
      }

      try {
        factory = (InitialContextFactory) helper.loadClass(className).newInstance();
      } catch (Exception e) {
        NoInitialContextException ne =
            new NoInitialContextException("Cannot instantiate class: " + className);
        ne.setRootCause(e);
        throw ne;
      }
    } else {
      factory = builder.createInitialContextFactory(env);
    }

    return factory.getInitialContext(env);
  }

  /**
   * Sets the InitialContextFactory builder to be builder.
   *
   * <p>The builder can only be installed if the executing thread is allowed by the security manager
   * to do so. Once installed, the builder cannot be replaced.
   *
   * @param builder The initial context factory builder to install. If null, no builder is set.
   * @exception SecurityException builder cannot be installed for security reasons.
   * @exception NamingException builder cannot be installed for a non-security-related reason.
   * @exception IllegalStateException If a builder was previous installed.
   * @see #hasInitialContextFactoryBuilder
   * @see java.lang.SecurityManager#checkSetFactory
   */
  public static synchronized void setInitialContextFactoryBuilder(
      InitialContextFactoryBuilder builder) throws NamingException {
    if (initctx_factory_builder != null)
      throw new IllegalStateException("InitialContextFactoryBuilder already set");

    SecurityManager security = System.getSecurityManager();
    if (security != null) {
      security.checkSetFactory();
    }
    initctx_factory_builder = builder;
  }

  /**
   * Determines whether an initial context factory builder has been set.
   *
   * @return true if an initial context factory builder has been set; false otherwise.
   * @see #setInitialContextFactoryBuilder
   */
  public static boolean hasInitialContextFactoryBuilder() {
    return (getInitialContextFactoryBuilder() != null);
  }

  // -----  Continuation Context Stuff

  /**
   * Constant that holds the name of the environment property into which
   * <tt>getContinuationContext()</tt> stores the value of its <tt>CannotProceedException</tt>
   * parameter. This property is inherited by the continuation context, and may be used by that
   * context's service provider to inspect the fields of the exception.
   *
   * <p>The value of this constant is "java.naming.spi.CannotProceedException".
   *
   * @see #getContinuationContext
   * @since 1.3
   */
  public static final String CPE = "java.naming.spi.CannotProceedException";

  /**
   * Creates a context in which to continue a context operation.
   *
   * <p>In performing an operation on a name that spans multiple namespaces, a context from one
   * naming system may need to pass the operation on to the next naming system. The context
   * implementation does this by first constructing a <code>CannotProceedException</code> containing
   * information pinpointing how far it has proceeded. It then obtains a continuation context from
   * JNDI by calling <code>getContinuationContext</code>. The context implementation should then
   * resume the context operation by invoking the same operation on the continuation context, using
   * the remainder of the name that has not yet been resolved.
   *
   * <p>Before making use of the <tt>cpe</tt> parameter, this method updates the environment
   * associated with that object by setting the value of the property <a
   * href="#CPE"><tt>CPE</tt></a> to <tt>cpe</tt>. This property will be inherited by the
   * continuation context, and may be used by that context's service provider to inspect the fields
   * of this exception.
   *
   * @param cpe The non-null exception that triggered this continuation.
   * @return A non-null Context object for continuing the operation.
   * @exception NamingException If a naming exception occurred.
   */
  @SuppressWarnings("unchecked")
  public static Context getContinuationContext(CannotProceedException cpe) throws NamingException {

    Hashtable<Object, Object> env = (Hashtable<Object, Object>) cpe.getEnvironment();
    if (env == null) {
      env = new Hashtable<>(7);
    } else {
      // Make a (shallow) copy of the environment.
      env = (Hashtable<Object, Object>) env.clone();
    }
    env.put(CPE, cpe);

    ContinuationContext cctx = new ContinuationContext(cpe, env);
    return cctx.getTargetContext();
  }

  // ------------ State Factory Stuff

  /**
   * Retrieves the state of an object for binding.
   *
   * <p>Service providers that implement the <tt>DirContext</tt> interface should use
   * <tt>DirectoryManager.getStateToBind()</tt>, not this method. Service providers that implement
   * only the <tt>Context</tt> interface should use this method.
   *
   * <p>This method uses the specified state factories in the <tt>Context.STATE_FACTORIES</tt>
   * property from the environment properties, and from the provider resource file associated with
   * <tt>nameCtx</tt>, in that order. The value of this property is a colon-separated list of
   * factory class names that are tried in order, and the first one that succeeds in returning the
   * object's state is the one used. If no object's state can be retrieved in this way, return the
   * object itself. If an exception is encountered while retrieving the state, the exception is
   * passed up to the caller.
   *
   * <p>Note that a state factory (an object that implements the StateFactory interface) must be
   * public and must have a public constructor that accepts no arguments.
   *
   * <p>The <code>name</code> and <code>nameCtx</code> parameters may optionally be used to specify
   * the name of the object being created. See the description of "Name and Context Parameters" in
   * {@link ObjectFactory#getObjectInstance ObjectFactory.getObjectInstance()} for details.
   *
   * <p>This method may return a <tt>Referenceable</tt> object. The service provider obtaining this
   * object may choose to store it directly, or to extract its reference (using
   * <tt>Referenceable.getReference()</tt>) and store that instead.
   *
   * @param obj The non-null object for which to get state to bind.
   * @param name The name of this object relative to <code>nameCtx</code>, or null if no name is
   *     specified.
   * @param nameCtx The context relative to which the <code>name</code> parameter is specified, or
   *     null if <code>name</code> is relative to the default initial context.
   * @param environment The possibly null environment to be used in the creation of the state
   *     factory and the object's state.
   * @return The non-null object representing <tt>obj</tt>'s state for binding. It could be the
   *     object (<tt>obj</tt>) itself.
   * @exception NamingException If one of the factories accessed throws an exception, or if an error
   *     was encountered while loading and instantiating the factory and object classes. A factory
   *     should only throw an exception if it does not want other factories to be used in an attempt
   *     to create an object. See <tt>StateFactory.getStateToBind()</tt>.
   * @see StateFactory
   * @see StateFactory#getStateToBind
   * @see DirectoryManager#getStateToBind
   * @since 1.3
   */
  public static Object getStateToBind(
      Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws NamingException {

    FactoryEnumeration factories =
        ResourceManager.getFactories(Context.STATE_FACTORIES, environment, nameCtx);

    if (factories == null) {
      return obj;
    }

    // Try each factory until one succeeds
    StateFactory factory;
    Object answer = null;
    while (answer == null && factories.hasMore()) {
      factory = (StateFactory) factories.next();
      answer = factory.getStateToBind(obj, name, nameCtx, environment);
    }

    return (answer != null) ? answer : obj;
  }
}