/*
 * Copyright (c) 1999, 2012, 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.cosnaming;

import javax.naming.*;
import javax.naming.spi.NamingManager;
import javax.naming.spi.ResolveResult;

import java.util.Hashtable;
import java.net.MalformedURLException;
import java.net.URL;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.security.AccessController;
import java.security.PrivilegedAction;

import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

import com.sun.jndi.toolkit.corba.CorbaUtils;

// Needed for creating default ORB
import java.applet.Applet;

/**
  * Provides a bridge to the CosNaming server provided by
  * JavaIDL. This class provides the InitialContext from CosNaming.
  *
  * @author Raj Krishnamurthy
  * @author Rosanna Lee
  */

public class CNCtx implements javax.naming.Context {

    private final static boolean debug = false;

    /*
     * Implement one shared ORB among all CNCtx.  However, there is a public constructor
     * accepting an ORB, so we need the option of using a given ORB.
     */
    private static ORB _defaultOrb;
    ORB _orb;                   // used by ExceptionMapper and RMI/IIOP factory
    public NamingContext _nc;   // public for accessing underlying NamingContext

    private synchronized static ORB getDefaultOrb() {
        if (_defaultOrb == null) {
            _defaultOrb = CorbaUtils.getOrb(null, -1,
               new Hashtable<String, java.lang.Object>());
        }
        return _defaultOrb;
    }

    private NameComponent[] _name = null;

    Hashtable<String, java.lang.Object> _env; // used by ExceptionMapper
    static final CNNameParser parser = new CNNameParser();

    private static final String FED_PROP = "com.sun.jndi.cosnaming.federation";
    boolean federation = false;

    /**
     * Determines whether classes may be loaded from an arbitrary URL code base.
     */
    public static final boolean trustURLCodebase;
    static {
        // System property to control whether classes may be loaded from an
        // arbitrary URL code base
        PrivilegedAction<String> act = () -> System.getProperty(
            "com.sun.jndi.cosnaming.object.trustURLCodebase", "false");
        String trust = AccessController.doPrivileged(act);
        trustURLCodebase = "true".equalsIgnoreCase(trust);
    }

    // Reference counter for tracking _orb references
    OrbReuseTracker orbTracker = null;
    int enumCount;
    boolean isCloseCalled = false;

    /**
      * Create a CNCtx object. Gets the initial naming
      * reference for the COS Naming Service from the ORB.
      * The ORB can be passed in via the java.naming.corba.orb property
      * or be created using properties in the environment properties.
      * @param env Environment properties for initializing name service.
      * @exception NamingException Cannot initialize ORB or naming context.
      */
    @SuppressWarnings("unchecked")
    CNCtx(Hashtable<?,?> env) throws NamingException {
        if (env != null) {
            env = (Hashtable<?,?>)env.clone();
        }
        _env = (Hashtable<String, java.lang.Object>)env;
        federation = "true".equals(env != null ? env.get(FED_PROP) : null);
        initOrbAndRootContext(env);
    }

    private CNCtx() {
    }

    /**
     * This method is used by the iiop and iiopname URL Context factories.
     */
    @SuppressWarnings("unchecked")
    public static ResolveResult createUsingURL(String url, Hashtable<?,?> env)
    throws NamingException {
        CNCtx ctx = new CNCtx();
        if (env != null) {
            env = (Hashtable<?,?>) env.clone();
        }
        ctx._env = (Hashtable<String, java.lang.Object>)env;
        String rest = ctx.initUsingUrl(
            env != null ?
                (org.omg.CORBA.ORB) env.get("java.naming.corba.orb")
                : null,
            url, env);

        // rest is the INS name
        // Return the parsed form to prevent subsequent lookup
        // from parsing the string as a composite name
        // The caller should be aware that a toString() of the name,
        // which came from the environment will yield its INS syntax,
        // rather than a composite syntax
        return new ResolveResult(ctx, parser.parse(rest));
    }

    /**
      * Creates a CNCtx object which supports the javax.naming
      * apis given a COS Naming Context object.
      * @param orb The ORB used by this context
      * @param tracker The ORB reuse tracker for tracking references to the
      *  orb object
      * @param nctx The COS NamingContext object associated with this context
      * @param name The name of this context relative to the root
      */

    CNCtx(ORB orb, OrbReuseTracker tracker, NamingContext nctx,
          Hashtable<String, java.lang.Object> env, NameComponent[]name)
        throws NamingException {
            if (orb == null || nctx == null)
                throw new ConfigurationException(
                    "Must supply ORB or NamingContext");
            if (orb != null) {
                _orb = orb;
            } else {
                _orb = getDefaultOrb();
            }
            _nc = nctx;
            _env = env;
            _name = name;
            federation = "true".equals(env != null ? env.get(FED_PROP) : null);
    }

    NameComponent[] makeFullName(NameComponent[] child) {
        if (_name == null || _name.length == 0) {
            return child;
        }
        NameComponent[] answer = new NameComponent[_name.length+child.length];

        // parent
        System.arraycopy(_name, 0, answer, 0, _name.length);

        // child
        System.arraycopy(child, 0, answer, _name.length, child.length);
        return answer;
    }


    public String getNameInNamespace() throws NamingException {
        if (_name == null || _name.length == 0) {
            return "";
        }
        return CNNameParser.cosNameToInsString(_name);
    }

    /**
     * These are the URL schemes that need to be processed.
     * IOR and corbaloc URLs can be passed directly to ORB.string_to_object()
     */
    private static boolean isCorbaUrl(String url) {
        return url.startsWith("iiop://")
            || url.startsWith("iiopname://")
            || url.startsWith("corbaname:")
            ;
    }

    /**
      * Initializes the COS Naming Service.
      * This method initializes the three instance fields:
      * _nc : The root naming context.
      * _orb: The ORB to use for connecting RMI/IIOP stubs and for
      *       getting the naming context (_nc) if one was not specified
      *       explicitly via PROVIDER_URL.
      * _name: The name of the root naming context.
      *<p>
      * _orb is obtained from java.naming.corba.orb if it has been set.
      * Otherwise, _orb is created using the host/port from PROVIDER_URL
      * (if it contains an "iiop" or "iiopname" URL), or from initialization
      * properties specified in env.
      *<p>
      * _nc is obtained from the IOR stored in PROVIDER_URL if it has been
      * set and does not contain an "iiop" or "iiopname" URL. It can be
      * a stringified IOR, "corbaloc" URL, "corbaname" URL,
      * or a URL (such as file/http/ftp) to a location
      * containing a stringified IOR. If PROVIDER_URL has not been
      * set in this way, it is obtained from the result of
      *     ORB.resolve_initial_reference("NameService");
      *<p>
      * _name is obtained from the "iiop", "iiopname", or "corbaname" URL.
      * It is the empty name by default.
      *
      * @param env Environment The possibly null environment.
      * @exception NamingException When an error occurs while initializing the
      * ORB or the naming context.
      */
    private void initOrbAndRootContext(Hashtable<?,?> env) throws NamingException {
        org.omg.CORBA.ORB inOrb = null;
        String ncIor = null;

        if (inOrb == null && env != null) {
            inOrb = (org.omg.CORBA.ORB) env.get("java.naming.corba.orb");
        }

        if (inOrb == null)
            inOrb = getDefaultOrb(); // will create a default ORB if none exists

        // Extract PROVIDER_URL from environment
        String provUrl = null;
        if (env != null) {
            provUrl = (String)env.get(javax.naming.Context.PROVIDER_URL);
        }

        if (provUrl != null && !isCorbaUrl(provUrl)) {
            // Initialize the root naming context by using the IOR supplied
            // in the PROVIDER_URL
            ncIor = getStringifiedIor(provUrl);
            setOrbAndRootContext(inOrb, ncIor);
        } else if (provUrl != null) {
            // Initialize the root naming context by using the URL supplied
            // in the PROVIDER_URL
            String insName = initUsingUrl(inOrb, provUrl, env);

            // If name supplied in URL, resolve it to a NamingContext
            if (insName.length() > 0) {
                _name = CNNameParser.nameToCosName(parser.parse(insName));
                try {
                    org.omg.CORBA.Object obj = _nc.resolve(_name);
                    _nc = NamingContextHelper.narrow(obj);
                    if (_nc == null) {
                        throw new ConfigurationException(insName +
                            " does not name a NamingContext");
                    }
                } catch (org.omg.CORBA.BAD_PARAM e) {
                    throw new ConfigurationException(insName +
                        " does not name a NamingContext");
                } catch (Exception e) {
                    throw ExceptionMapper.mapException(e, this, _name);
                }
            }
        } else {
            // No PROVIDER_URL supplied; initialize using defaults
            if (debug) {
                System.err.println("Getting default ORB: " + inOrb + env);
            }
            setOrbAndRootContext(inOrb, (String)null);
        }
    }


    private String initUsingUrl(ORB orb, String url, Hashtable<?,?> env)
        throws NamingException {
        if (url.startsWith("iiop://") || url.startsWith("iiopname://")) {
            return initUsingIiopUrl(orb, url, env);
        } else {
            return initUsingCorbanameUrl(orb, url, env);
        }
    }

    /**
     * Handles "iiop" and "iiopname" URLs (INS 98-10-11)
     */
    private String initUsingIiopUrl(ORB defOrb, String url, Hashtable<?,?> env)
        throws NamingException {

        if (defOrb == null)
            defOrb = getDefaultOrb();

        try {
            IiopUrl parsedUrl = new IiopUrl(url);

            NamingException savedException = null;

            for (IiopUrl.Address addr : parsedUrl.getAddresses()) {

                try {
                    try {
                        String tmpUrl = "corbaloc:iiop:" + addr.host
                            + ":" + addr.port + "/NameService";
                        if (debug) {
                            System.err.println("Using url: " + tmpUrl);
                        }
                        org.omg.CORBA.Object rootCtx =
                            defOrb.string_to_object(tmpUrl);
                        setOrbAndRootContext(defOrb, rootCtx);
                        return parsedUrl.getStringName();
                    } catch (Exception e) {} // keep going

                    // Get ORB
                    if (debug) {
                        System.err.println("Getting ORB for " + addr.host
                            + " and port " + addr.port);
                    }

                    // Assign to fields
                    setOrbAndRootContext(defOrb, (String)null);
                    return parsedUrl.getStringName();

                } catch (NamingException ne) {
                    savedException = ne;
                }
            }
            if (savedException != null) {
                throw savedException;
            } else {
                throw new ConfigurationException("Problem with URL: " + url);
            }
        } catch (MalformedURLException e) {
            throw new ConfigurationException(e.getMessage());
        }
    }

    /**
     * Initializes using "corbaname" URL (INS 99-12-03)
     */
    private String initUsingCorbanameUrl(ORB orb, String url, Hashtable<?,?> env)
        throws NamingException {

        if (orb == null)
                orb = getDefaultOrb();

        try {
            CorbanameUrl parsedUrl = new CorbanameUrl(url);

            String corbaloc = parsedUrl.getLocation();
            String cosName = parsedUrl.getStringName();

            setOrbAndRootContext(orb, corbaloc);

            return parsedUrl.getStringName();
        } catch (MalformedURLException e) {
            throw new ConfigurationException(e.getMessage());
        }
    }

    private void setOrbAndRootContext(ORB orb, String ncIor)
        throws NamingException {
        _orb = orb;
        try {
            org.omg.CORBA.Object ncRef;
            if (ncIor != null) {
                if (debug) {
                    System.err.println("Passing to string_to_object: " + ncIor);
                }
                ncRef = _orb.string_to_object(ncIor);
            } else {
                ncRef = _orb.resolve_initial_references("NameService");
            }
            if (debug) {
                System.err.println("Naming Context Ref: " + ncRef);
            }
            _nc = NamingContextHelper.narrow(ncRef);
            if (_nc == null) {
                if (ncIor != null) {
                    throw new ConfigurationException(
                        "Cannot convert IOR to a NamingContext: " + ncIor);
                } else {
                    throw new ConfigurationException(
"ORB.resolve_initial_references(\"NameService\") does not return a NamingContext");
                }
            }
        } catch (org.omg.CORBA.ORBPackage.InvalidName in) {
            NamingException ne =
                new ConfigurationException(
"COS Name Service not registered with ORB under the name 'NameService'");
            ne.setRootCause(in);
            throw ne;
        } catch (org.omg.CORBA.COMM_FAILURE e) {
            NamingException ne =
                new CommunicationException("Cannot connect to ORB");
            ne.setRootCause(e);
            throw ne;
        } catch (org.omg.CORBA.BAD_PARAM e) {
            NamingException ne = new ConfigurationException(
                "Invalid URL or IOR: " + ncIor);
            ne.setRootCause(e);
            throw ne;
        } catch (org.omg.CORBA.INV_OBJREF e) {
            NamingException ne = new ConfigurationException(
                "Invalid object reference: " + ncIor);
            ne.setRootCause(e);
            throw ne;
        }
    }

    private void setOrbAndRootContext(ORB orb, org.omg.CORBA.Object ncRef)
        throws NamingException {
        _orb = orb;
        try {
            _nc = NamingContextHelper.narrow(ncRef);
            if (_nc == null) {
                throw new ConfigurationException(
                    "Cannot convert object reference to NamingContext: " + ncRef);
            }
        } catch (org.omg.CORBA.COMM_FAILURE e) {
            NamingException ne =
                new CommunicationException("Cannot connect to ORB");
            ne.setRootCause(e);
            throw ne;
        }
    }

    private String getStringifiedIor(String url) throws NamingException {
        if (url.startsWith("IOR:") || url.startsWith("corbaloc:")) {
            return url;
        } else {
            InputStream in = null;
            try {
                URL u = new URL(url);
                in = u.openStream();
                if (in != null) {
                    BufferedReader bufin =
                        new BufferedReader(new InputStreamReader(in, "8859_1"));
                    String str;
                    while ((str = bufin.readLine()) != null) {
                        if (str.startsWith("IOR:")) {
                            return str;
                        }
                    }
                }
            } catch (IOException e) {
                NamingException ne =
                    new ConfigurationException("Invalid URL: " + url);
                ne.setRootCause(e);
                throw ne;
            } finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                } catch (IOException e) {
                    NamingException ne =
                        new ConfigurationException("Invalid URL: " + url);
                    ne.setRootCause(e);
                    throw ne;
                }
            }
            throw new ConfigurationException(url + " does not contain an IOR");
        }
    }


    /**
      * Does the job of calling the COS Naming API,
      * resolve, and performs the exception mapping. If the resolved
      * object is a COS Naming Context (sub-context), then this function
      * returns a new JNDI naming context object.
      * @param path the NameComponent[] object.
      * @exception NotFound No objects under the name.
      * @exception CannotProceed Unable to obtain a continuation context
      * @exception InvalidName Name not understood.
      * @return Resolved object returned by the COS Name Server.
      */
    java.lang.Object callResolve(NameComponent[] path)
        throws NamingException {
            try {
                org.omg.CORBA.Object obj = _nc.resolve(path);
                try {
                    NamingContext nc =
                        NamingContextHelper.narrow(obj);
                    if (nc != null) {
                        return new CNCtx(_orb, orbTracker, nc, _env,
                                        makeFullName(path));
                    } else {
                        return obj;
                    }
                } catch (org.omg.CORBA.SystemException e) {
                    return obj;
                }
            } catch (Exception e) {
                throw ExceptionMapper.mapException(e, this, path);
            }
    }

    /**
      * Converts the "String" name into a CompositeName
      * returns the object resolved by the COS Naming api,
      * resolve. Returns the current context if the name is empty.
      * Returns either an org.omg.CORBA.Object or javax.naming.Context object.
      * @param name string used to resolve the object.
      * @exception NamingException See callResolve.
      * @return the resolved object
      */
    public java.lang.Object lookup(String name) throws NamingException {
        if (debug) {
            System.out.println("Looking up: " + name);
        }
        return lookup(new CompositeName(name));
    }

    /**
      * Converts the "Name" name into a NameComponent[] object and
      * returns the object resolved by the COS Naming api,
      * resolve. Returns the current context if the name is empty.
      * Returns either an org.omg.CORBA.Object or javax.naming.Context object.
      * @param name JNDI Name used to resolve the object.
      * @exception NamingException See callResolve.
      * @return the resolved object
      */
    public java.lang.Object lookup(Name name)
        throws NamingException {
            if (_nc == null)
                throw new ConfigurationException(
                    "Context does not have a corresponding NamingContext");
            if (name.size() == 0 )
                return this; // %%% should clone() so that env can be changed
            NameComponent[] path = CNNameParser.nameToCosName(name);
            java.lang.Object answer = null;

            try {
                answer = callResolve(path);
                try {
                    // Check whether object factory codebase is trusted
                    if (CorbaUtils.isObjectFactoryTrusted(answer)) {
                        answer = NamingManager.getObjectInstance(
                            answer, name, this, _env);
                    }
                } catch (NamingException e) {
                    throw e;
                } catch (Exception e) {
                    NamingException ne = new NamingException(
                        "problem generating object using object factory");
                    ne.setRootCause(e);
                    throw ne;
                }
            } catch (CannotProceedException cpe) {
                javax.naming.Context cctx = getContinuationContext(cpe);
                return cctx.lookup(cpe.getRemainingName());
            }
            return answer;
    }

    /**
      * Performs bind or rebind in the context depending on whether the
      * flag rebind is set. The only objects allowed to be bound are of
      * types org.omg.CORBA.Object, org.omg.CosNaming.NamingContext.
      * You can use a state factory to turn other objects (such as
      * Remote) into these acceptable forms.
      *
      * Uses the COS Naming apis bind/rebind or
      * bind_context/rebind_context.
      * @param pth NameComponent[] object
      * @param obj Object to be bound.
      * @param rebind perform rebind ? if true performs a rebind.
      * @exception NotFound No objects under the name.
      * @exception CannotProceed Unable to obtain a continuation context
      * @exception AlreadyBound An object is already bound to this name.
      */
    private void callBindOrRebind(NameComponent[] pth, Name name,
        java.lang.Object obj, boolean rebind) throws NamingException {
            if (_nc == null)
                throw new ConfigurationException(
                    "Context does not have a corresponding NamingContext");
            try {
                // Call state factories to convert
                obj = NamingManager.getStateToBind(obj, name, this, _env);

                if (obj instanceof CNCtx) {
                    // Use naming context object reference
                    obj = ((CNCtx)obj)._nc;
                }

                if ( obj instanceof org.omg.CosNaming.NamingContext) {
                    NamingContext nobj =
                        NamingContextHelper.narrow((org.omg.CORBA.Object)obj);
                    if (rebind)
                        _nc.rebind_context(pth,nobj);
                    else
                        _nc.bind_context(pth,nobj);

                } else if (obj instanceof org.omg.CORBA.Object) {
                    if (rebind)
                        _nc.rebind(pth,(org.omg.CORBA.Object)obj);
                    else
                        _nc.bind(pth,(org.omg.CORBA.Object)obj);
                }
                else
                    throw new IllegalArgumentException(
                "Only instances of org.omg.CORBA.Object can be bound");
            } catch (BAD_PARAM e) {
                // probably narrow() failed?
                NamingException ne = new NotContextException(name.toString());
                ne.setRootCause(e);
                throw ne;
            } catch (Exception e) {
                throw ExceptionMapper.mapException(e, this, pth);
            }
    }

    /**
      * Converts the "Name" name into a NameComponent[] object and
      * performs the bind operation. Uses callBindOrRebind. Throws an
      * invalid name exception if the name is empty. We need a name to
      * bind the object even when we work within the current context.
      * @param name JNDI Name object
      * @param obj Object to be bound.
      * @exception NamingException See callBindOrRebind
      */
    public  void bind(Name name, java.lang.Object obj)
        throws NamingException {
            if (name.size() == 0 ) {
                throw new InvalidNameException("Name is empty");
            }

            if (debug) {
                System.out.println("Bind: " + name);
            }
            NameComponent[] path = CNNameParser.nameToCosName(name);

            try {
                callBindOrRebind(path, name, obj, false);
            } catch (CannotProceedException e) {
                javax.naming.Context cctx = getContinuationContext(e);
                cctx.bind(e.getRemainingName(), obj);
            }
    }

    static private javax.naming.Context
        getContinuationContext(CannotProceedException cpe)
        throws NamingException {
        try {
            return NamingManager.getContinuationContext(cpe);
        } catch (CannotProceedException e) {
            java.lang.Object resObj = e.getResolvedObj();
            if (resObj instanceof Reference) {
                Reference ref = (Reference)resObj;
                RefAddr addr = ref.get("nns");
                if (addr.getContent() instanceof javax.naming.Context) {
                    NamingException ne = new NameNotFoundException(
                        "No object reference bound for specified name");
                    ne.setRootCause(cpe.getRootCause());
                    ne.setRemainingName(cpe.getRemainingName());
                    throw ne;
                }
            }
            throw e;
        }
    }

    /**
      * Converts the "String" name into a CompositeName object and
      * performs the bind operation. Uses callBindOrRebind. Throws an
      * invalid name exception if the name is empty.
      * @param name string
      * @param obj Object to be bound.
      * @exception NamingException See callBindOrRebind
      */
    public void bind(String name, java.lang.Object obj) throws NamingException {
        bind(new CompositeName(name), obj);
    }

    /**
      * Converts the "Name" name into a NameComponent[] object and
      * performs the rebind operation. Uses callBindOrRebind. Throws an
      * invalid name exception if the name is empty. We must have a name
      * to rebind the object to even if we are working within the current
      * context.
      * @param name string
      * @param obj Object to be bound.
      * @exception NamingException See callBindOrRebind
      */
    public  void rebind(Name name, java.lang.Object obj)
        throws NamingException {
            if (name.size() == 0 ) {
                throw new InvalidNameException("Name is empty");
            }
            NameComponent[] path = CNNameParser.nameToCosName(name);
            try {
                callBindOrRebind(path, name, obj, true);
            } catch (CannotProceedException e) {
                javax.naming.Context cctx = getContinuationContext(e);
                cctx.rebind(e.getRemainingName(), obj);
            }
    }

    /**
      * Converts the "String" name into a CompositeName object and
      * performs the rebind operation. Uses callBindOrRebind. Throws an
      * invalid name exception if the name is an empty string.
      * @param name string
      * @param obj Object to be bound.
      * @exception NamingException See callBindOrRebind
      */
    public  void rebind(String name, java.lang.Object obj)
        throws NamingException {
            rebind(new CompositeName(name), obj);
    }

    /**
      * Calls the unbind api of COS Naming and uses the exception mapper
      * class  to map the exceptions
      * @param path NameComponent[] object
      * @exception NotFound No objects under the name. If leaf
      * is not found, that's OK according to the JNDI spec
      * @exception CannotProceed Unable to obtain a continuation context
      * @exception InvalidName Name not understood.
      */
    private void callUnbind(NameComponent[] path) throws NamingException {
            if (_nc == null)
                throw new ConfigurationException(
                    "Context does not have a corresponding NamingContext");
            try {
                _nc.unbind(path);
            } catch (NotFound e) {
                // If leaf is the one missing, return success
                // as per JNDI spec

                if (leafNotFound(e, path[path.length-1])) {
                    // do nothing
                } else {
                    throw ExceptionMapper.mapException(e, this, path);
                }
            } catch (Exception e) {
                throw ExceptionMapper.mapException(e, this, path);
            }
    }

    private boolean leafNotFound(NotFound e, NameComponent leaf) {

        // This test is not foolproof because some name servers
        // always just return one component in rest_of_name
        // so you might not be able to tell whether that is
        // the leaf (e.g. aa/aa/aa, which one is missing?)

        NameComponent rest;
        return e.why.value() == NotFoundReason._missing_node &&
            e.rest_of_name.length == 1 &&
            (rest=e.rest_of_name[0]).id.equals(leaf.id) &&
            (rest.kind == leaf.kind ||
             (rest.kind != null && rest.kind.equals(leaf.kind)));
    }

    /**
      * Converts the "String" name into a CompositeName object and
      * performs the unbind operation. Uses callUnbind. If the name is
      * empty, throws an invalid name exception. Do we unbind the
      * current context (JNDI spec says work with the current context if
      * the name is empty) ?
      * @param name string
      * @exception NamingException See callUnbind
      */
    public  void unbind(String name) throws NamingException {
        unbind(new CompositeName(name));
    }

    /**
      * Converts the "Name" name into a NameComponent[] object and
      * performs the unbind operation. Uses callUnbind. Throws an
      * invalid name exception if the name is empty.
      * @param name string
      * @exception NamingException See callUnbind
      */
    public  void unbind(Name name)
        throws NamingException {
            if (name.size() == 0 )
                throw new InvalidNameException("Name is empty");
            NameComponent[] path = CNNameParser.nameToCosName(name);
            try {
                callUnbind(path);
            } catch (CannotProceedException e) {
                javax.naming.Context cctx = getContinuationContext(e);
                cctx.unbind(e.getRemainingName());
            }
    }

    /**
      * Renames an object. Since COS Naming does not support a rename
      * api, this method unbinds the object with the "oldName" and
      * creates a new binding.
      * @param oldName string, existing name for the binding.
      * @param newName string, name used to replace.
      * @exception NamingException See bind
      */
    public  void rename(String oldName,String newName)
        throws NamingException {
            rename(new CompositeName(oldName), new CompositeName(newName));
    }

    /**
      * Renames an object. Since COS Naming does not support a rename
      * api, this method unbinds the object with the "oldName" and
      * creates a new binding.
      * @param oldName JNDI Name, existing name for the binding.
      * @param newName JNDI Name, name used to replace.
      * @exception NamingException See bind
      */
    public  void rename(Name oldName,Name newName)
        throws NamingException {
            if (_nc == null)
                throw new ConfigurationException(
                    "Context does not have a corresponding NamingContext");
            if (oldName.size() == 0 || newName.size() == 0)
                throw new InvalidNameException("One or both names empty");
            java.lang.Object obj = lookup(oldName);
            bind(newName,obj);
            unbind(oldName);
    }

    /**
      * Returns a NameClassEnumeration object which has a list of name
      * class pairs. Lists the current context if the name is empty.
      * @param name string
      * @exception NamingException All exceptions thrown by lookup
      * with a non-null argument
      * @return a list of name-class objects as a NameClassEnumeration.
      */
    public  NamingEnumeration<NameClassPair> list(String name) throws NamingException {
            return list(new CompositeName(name));
    }

    /**
      * Returns a NameClassEnumeration object which has a list of name
      * class pairs. Lists the current context if the name is empty.
      * @param name JNDI Name
      * @exception NamingException All exceptions thrown by lookup
      * @return a list of name-class objects as a NameClassEnumeration.
      */
    @SuppressWarnings("unchecked")
    public  NamingEnumeration<NameClassPair> list(Name name)
        throws NamingException {
            return (NamingEnumeration)listBindings(name);
    }

    /**
      * Returns a BindingEnumeration object which has a list of name
      * object pairs. Lists the current context if the name is empty.
      * @param name string
      * @exception NamingException all exceptions returned by lookup
      * @return a list of bindings as a BindingEnumeration.
      */
    public  NamingEnumeration<javax.naming.Binding> listBindings(String name)
        throws NamingException {
            return listBindings(new CompositeName(name));
    }

    /**
      * Returns a BindingEnumeration object which has a list of name
      * class pairs. Lists the current context if the name is empty.
      * @param name JNDI Name
      * @exception NamingException all exceptions returned by lookup.
      * @return a list of bindings as a BindingEnumeration.
      */
    public  NamingEnumeration<javax.naming.Binding> listBindings(Name name)
        throws NamingException {
            if (_nc == null)
                throw new ConfigurationException(
                    "Context does not have a corresponding NamingContext");
            if (name.size() > 0) {
                try {
                    java.lang.Object obj = lookup(name);
                    if (obj instanceof CNCtx) {
                        return new CNBindingEnumeration(
                                        (CNCtx) obj, true, _env);
                    } else {
                        throw new NotContextException(name.toString());
                    }
                } catch (NamingException ne) {
                    throw ne;
                } catch (BAD_PARAM e) {
                    NamingException ne =
                        new NotContextException(name.toString());
                    ne.setRootCause(e);
                    throw ne;
                }
            }
            return new CNBindingEnumeration(this, false, _env);
    }

    /**
      * Calls the destroy on the COS Naming Server
      * @param nc The NamingContext object to use.
      * @exception NotEmpty when the context is not empty and cannot be destroyed.
      */
    private void callDestroy(NamingContext nc)
        throws NamingException {
            if (_nc == null)
                throw new ConfigurationException(
                    "Context does not have a corresponding NamingContext");
            try {
                nc.destroy();
            } catch (Exception e) {
                throw ExceptionMapper.mapException(e, this, null);
            }
    }

    /**
      * Uses the callDestroy function to destroy the context. If name is
      * empty destroys the current context.
      * @param name string
      * @exception OperationNotSupportedException when list is invoked
      * with a non-null argument
      */
    public  void destroySubcontext(String name) throws NamingException {
        destroySubcontext(new CompositeName(name));
    }

    /**
      * Uses the callDestroy function to destroy the context. Destroys
      * the current context if name is empty.
      * @param name JNDI Name
      * @exception OperationNotSupportedException when list is invoked
      * with a non-null argument
      */
    public  void destroySubcontext(Name name)
        throws NamingException {
            if (_nc == null)
                throw new ConfigurationException(
                    "Context does not have a corresponding NamingContext");
            NamingContext the_nc = _nc;
            NameComponent[] path = CNNameParser.nameToCosName(name);
            if ( name.size() > 0) {
                try {
                    javax.naming.Context ctx =
                        (javax.naming.Context) callResolve(path);
                    CNCtx cnc = (CNCtx)ctx;
                    the_nc = cnc._nc;
                    cnc.close(); //remove the reference to the context
                } catch (ClassCastException e) {
                    throw new NotContextException(name.toString());
                } catch (CannotProceedException e) {
                    javax.naming.Context cctx = getContinuationContext(e);
                    cctx.destroySubcontext(e.getRemainingName());
                    return;
                } catch (NameNotFoundException e) {
                    // If leaf is the one missing, return success
                    // as per JNDI spec

                    if (e.getRootCause() instanceof NotFound &&
                        leafNotFound((NotFound)e.getRootCause(),
                            path[path.length-1])) {
                        return; // leaf missing OK
                    }
                    throw e;
                } catch (NamingException e) {
                    throw e;
                }
            }
            callDestroy(the_nc);
            callUnbind(path);
    }

    /**
      * Calls the bind_new_context COS naming api to create a new subcontext.
      * @param path NameComponent[] object
      * @exception NotFound No objects under the name.
      * @exception CannotProceed Unable to obtain a continuation context
      * @exception InvalidName Name not understood.
      * @exception AlreadyBound An object is already bound to this name.
      * @return the new context object.
      */
    private javax.naming.Context callBindNewContext(NameComponent[] path)
        throws NamingException {
            if (_nc == null)
                throw new ConfigurationException(
                    "Context does not have a corresponding NamingContext");
            try {
                NamingContext nctx = _nc.bind_new_context(path);
                return new CNCtx(_orb, orbTracker, nctx, _env,
                                        makeFullName(path));
            } catch (Exception e) {
                throw ExceptionMapper.mapException(e, this, path);
            }
    }

    /**
      * Uses the callBindNewContext convenience function to create a new
      * context. Throws an invalid name exception if the name is empty.
      * @param name string
      * @exception NamingException See callBindNewContext
      * @return the new context object.
      */
    public  javax.naming.Context createSubcontext(String name)
        throws NamingException {
            return createSubcontext(new CompositeName(name));
    }

    /**
      * Uses the callBindNewContext convenience function to create a new
      * context. Throws an invalid name exception if the name is empty.
      * @param name string
      * @exception NamingException See callBindNewContext
      * @return the new context object.
      */
    public  javax.naming.Context createSubcontext(Name name)
        throws NamingException {
            if (name.size() == 0 )
                throw new InvalidNameException("Name is empty");
            NameComponent[] path = CNNameParser.nameToCosName(name);
            try {
                return callBindNewContext(path);
            } catch (CannotProceedException e) {
                javax.naming.Context cctx = getContinuationContext(e);
                return cctx.createSubcontext(e.getRemainingName());
            }
    }

    /**
      * Is mapped to resolve in the COS Naming api.
      * @param name string
      * @exception NamingException See lookup.
      * @return the resolved object.
      */
    public  java.lang.Object lookupLink(String name) throws NamingException {
            return lookupLink(new CompositeName(name));
    }

    /**
      * Is mapped to resolve in the COS Naming api.
      * @param name string
      * @exception NamingException See lookup.
      * @return the resolved object.
      */
    public  java.lang.Object lookupLink(Name name) throws NamingException {
            return lookup(name);
    }

    /**
      * Allow access to the name parser object.
      * @param String JNDI name, is ignored since there is only one Name
      * Parser object.
      * @exception NamingException --
      * @return NameParser object
      */
    public  NameParser getNameParser(String name) throws NamingException {
        return parser;
    }

    /**
      * Allow access to the name parser object.
      * @param Name JNDI name, is ignored since there is only one Name
      * Parser object.
      * @exception NamingException --
      * @return NameParser object
      */
    public  NameParser getNameParser(Name name) throws NamingException {
        return parser;
    }

    /**
      * Returns the current environment.
      * @return Environment.
      */
    @SuppressWarnings("unchecked")
    public  Hashtable<String, java.lang.Object> getEnvironment() throws NamingException {
        if (_env == null) {
            return new Hashtable<>(5, 0.75f);
        } else {
            return (Hashtable<String, java.lang.Object>)_env.clone();
        }
    }

    public String composeName(String name, String prefix) throws NamingException {
        return composeName(new CompositeName(name),
            new CompositeName(prefix)).toString();
    }

    public Name composeName(Name name, Name prefix) throws NamingException {
        Name result = (Name)prefix.clone();
        return result.addAll(name);
    }

    /**
      * Adds to the environment for the current context.
      * Record change but do not reinitialize ORB.
      *
      * @param propName The property name.
      * @param propVal  The ORB.
      * @return the previous value of this property if any.
      */
    @SuppressWarnings("unchecked")
    public java.lang.Object addToEnvironment(String propName,
        java.lang.Object propValue)
        throws NamingException {
            if (_env == null) {
                _env = new Hashtable<>(7, 0.75f);
            } else {
                // copy-on-write
                _env = (Hashtable<String, java.lang.Object>)_env.clone();
            }

            return _env.put(propName, propValue);
    }

    // Record change but do not reinitialize ORB
    @SuppressWarnings("unchecked")
    public java.lang.Object removeFromEnvironment(String propName)
        throws NamingException {
            if (_env != null  && _env.get(propName) != null) {
                // copy-on-write
                _env = (Hashtable<String, java.lang.Object>)_env.clone();
                return _env.remove(propName);
            }
            return null;
    }

    synchronized public void incEnumCount() {
        enumCount++;
        if (debug) {
            System.out.println("incEnumCount, new count:" + enumCount);
        }
    }

    synchronized public void decEnumCount()
            throws NamingException {
        enumCount--;
        if (debug) {
            System.out.println("decEnumCount, new count:" + enumCount +
                        "    isCloseCalled:" + isCloseCalled);
        }
        if ((enumCount == 0) && isCloseCalled) {
            close();
        }
    }

    synchronized public void close() throws NamingException {

        if (enumCount > 0) {
            isCloseCalled = true;
            return;
        }

        // Never destroy an orb in CNCtx.
        // The orb we have is either the shared/default orb, or one passed in to a constructor
        // from elsewhere, so that orb is somebody else's reponsibility.
    }

    protected void finalize() {
        try {
            close();
        } catch (NamingException e) {
            // ignore failures
        }
    }
}
