001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.xbean.naming.context;
018    
019    import javax.naming.CompositeName;
020    import javax.naming.Context;
021    import javax.naming.InvalidNameException;
022    import javax.naming.Name;
023    import javax.naming.NameAlreadyBoundException;
024    import javax.naming.NameParser;
025    import javax.naming.NamingEnumeration;
026    import javax.naming.NamingException;
027    import javax.naming.NotContextException;
028    import javax.naming.LinkRef;
029    import javax.naming.NameNotFoundException;
030    import javax.naming.InitialContext;
031    import javax.naming.OperationNotSupportedException;
032    import java.io.Serializable;
033    import java.util.Collections;
034    import java.util.Hashtable;
035    import java.util.Map;
036    
037    public abstract class AbstractContext implements Context, NestedContextFactory, Serializable {
038        private static final long serialVersionUID = 6481918425692261483L;
039        private final String nameInNamespace;
040        private final Name parsedNameInNamespace;
041        private final ContextAccess contextAccess;
042        private final boolean modifiable;
043        private final ThreadLocal inCall = new ThreadLocal();
044    
045        protected AbstractContext(String nameInNamespace) {
046            this(nameInNamespace, ContextAccess.MODIFIABLE);
047        }
048    
049        public AbstractContext(String nameInNamespace, ContextAccess contextAccess) {
050            this.nameInNamespace = nameInNamespace;
051            try {
052                this.parsedNameInNamespace = getNameParser().parse(nameInNamespace);
053            } catch (NamingException e) {
054                throw new RuntimeException(e);
055            }
056            this.contextAccess = contextAccess;
057            this.modifiable = contextAccess.isModifiable(getParsedNameInNamespace());
058        }
059    
060        public void close() throws NamingException {
061            //Ignore. Explicitly do not close the context
062        }
063    
064        protected ContextAccess getContextAccess() {
065            return contextAccess;
066        }
067    
068        //
069        //  Lookup Binding
070        //
071    
072        /**
073         * Gets the object bound to the name.  The name may contain slashes.
074         * @param name the name
075         * @return the object bound to the name, or null if not found
076         */
077        protected Object getDeepBinding(String name) {
078            return null;
079        }
080    
081        /**
082         * Gets the object bound to the name.  The name will not contain slashes.
083         * @param name the name
084         * @return the object bound to the name, or null if not found
085         */
086        protected Object getBinding(String name) throws NamingException {
087            Map bindings = getBindings();
088            return bindings.get(name);
089        }
090    
091        /**
092         * Finds the specified entry.  Normally there is no need to override this method; instead you should
093         * simply implement the getDeepBindings(String) and getBindings(String) method.
094         *
095         * This method will follow links except for the final element which is always just returned without
096         * inspection.  This means this method can be used to implement lookupLink.
097         *
098         * @param stringName the string version of the name; maybe null
099         * @param parsedName the parsed name; may be null
100         * @return the value bound to the name
101         * @throws NamingException if no value is bound to that name or if a problem occurs during the lookup
102         */
103        protected Object lookup(String stringName, Name parsedName) throws NamingException {
104            if (stringName == null && parsedName == null) {
105                throw new IllegalArgumentException("Both stringName and parsedName are null");
106            }
107            if (stringName == null) stringName = parsedName.toString();
108    
109            // If the name starts with our name in namespace strip it off
110            // This works because the name in namespace is assumed to be absolute
111            if (stringName.startsWith(nameInNamespace)) {
112                stringName = stringName.substring(nameInNamespace.length());
113            }
114    
115            // try to look up the name directly (this is the fastest path)
116            Object directLookup = getDeepBinding(stringName);
117            if (directLookup != null) {
118                return ContextUtil.resolve(stringName, directLookup);
119            }
120    
121            // if the parsed name has no parts, they are asking for the current context
122            if (parsedName == null) parsedName = getNameParser().parse(stringName);
123            if (parsedName.isEmpty()) {
124                return this;
125            }
126    
127            // we didn't find an entry, pop the first element off the parsed name and attempt to
128            // get a context from the bindings and delegate to that context
129            Object localValue;
130            String firstNameElement = parsedName.get(0);
131            if (firstNameElement.length() == 0) {
132                // the element is null... this is normally caused by looking up with a trailing '/' character
133                localValue = this;
134            } else {
135                localValue = getBinding(firstNameElement);
136            }
137    
138            if (localValue != null) {
139    
140                // if the name only had one part, we've looked up everything
141                if (parsedName.size() == 1) {
142                    localValue = ContextUtil.resolve(stringName, localValue);
143                    return localValue;
144                }
145    
146                // if we have a link ref, follow it
147                if (localValue instanceof LinkRef) {
148                    LinkRef linkRef = (LinkRef) localValue;
149                    localValue = lookup(linkRef.getLinkName());
150                }
151    
152                // we have more to lookup so we better have a context object
153                if (!(localValue instanceof Context)) {
154                    throw new NameNotFoundException(stringName);
155                }
156    
157                // delegate to the sub-context
158                return ((Context) localValue).lookup(parsedName.getSuffix(1));
159            }
160    
161            // if we didn't find an entry, it may be an absolute name
162            Object value = faultLookup(stringName, parsedName);
163            if (value != null) {
164                return value;
165            }
166            if (parsedName.size() > 1) {
167                throw new NotContextException(stringName);
168            } else {
169                throw new NameNotFoundException(stringName);
170            }
171        }
172    
173        /**
174         * When a value can not be found within this context, this method is called as a last ditch effort befrore
175         * thowing a null pointer exception.
176         * @param stringName the string version of the name; will not be null
177         * @param parsedName the parsed name; will not be null
178         * @return the value or null if no fault value could be found
179         */
180        protected Object faultLookup(String stringName, Name parsedName) {
181            if (!stringName.startsWith(nameInNamespace) && stringName.indexOf(':') > 0 && inCall.get() == null) {
182                inCall.set(parsedName);
183                try {
184                    Context ctx = new InitialContext();
185                    return ctx.lookup(parsedName);
186                } catch (NamingException ignored) {
187                    // thrown below
188                } finally {
189                    inCall.set(null);                
190                }
191            }
192            return null;
193        }
194    
195        protected Context lookupFinalContext(Name name) throws NamingException {
196            Object value = null;
197            try {
198                value = lookup(name.getPrefix(name.size() - 1));
199            } catch (NamingException e) {
200                throw new NotContextException("The intermediate context " + name.get(name.size() - 1) + " does not exist");
201            }
202    
203            if (value == null) {
204                throw new NotContextException("The intermediate context " + name.get(name.size() - 1) + " does not exist");
205            } else if (!(value instanceof Context)) {
206                throw new NotContextException("The intermediate context " + name.get(name.size() - 1) + " does is not a context");
207            } else {
208                return (Context) value;
209            }
210        }
211    
212        //
213        //  List Bindings
214        //
215    
216        /**
217         * Gets a map of the bindings for the current node (i.e., no names with slashes).
218         * This method must not return null.
219         *
220         * @return a Map from binding name to binding value
221         * @throws NamingException if a problem occurs while getting the bindigns
222         */
223        protected abstract Map getBindings() throws NamingException;
224    
225        //
226        //  Add Binding
227        //
228    
229        protected void addDeepBinding(Name name, Object value, boolean rebind, boolean createIntermediateContexts) throws NamingException {
230            if (name == null) throw new NullPointerException("name is null");
231            if (value == null) throw new NullPointerException("value is null");
232    
233            if (name.isEmpty()) {
234                throw new InvalidNameException("Name is empty");
235            }
236    
237            if (name.size() == 1) {
238                addBinding(name.get(0), value, rebind);
239                return;
240            }
241    
242            if (!createIntermediateContexts) {
243                Context context = lookupFinalContext(name);
244    
245                String lastSegment = name.get(name.size() - 1);
246                addBinding(context, lastSegment, value, rebind);
247            } else {
248                Context currentContext = this;
249                for (int i = 0; i < name.size(); i++) {
250                    String part = name.get(i);
251    
252                    // empty path parts are not allowed
253                    if (part.length() == 0) {
254                        // this could be supported but it would be tricky
255                        throw new InvalidNameException("Name part " + i + " is empty: " + name);
256                    }
257    
258                    // Is this the last element in the name?
259                    if (i == name.size() - 1) {
260                        // we're at the end... (re)bind the value into the parent context
261                        addBinding(currentContext, part, value, rebind);
262    
263                        // all done... this is redundant but makes the code more readable
264                        break;
265                    } else {
266                        Object currentValue = getBinding(currentContext, part);
267                        if (currentValue == null) {
268                            // the next step in the tree is not present, so create everything down
269                            // and add it to the current bindings
270                            Context subcontext = createSubcontextTree(name.getPrefix(i).toString(), name.getSuffix(i), value);
271                            addBinding(currentContext, part, subcontext, rebind);
272    
273                            // all done
274                            break;
275                        } else {
276                            // the current value must be a nested subcontext
277                            if (!(currentContext instanceof Context)) {
278                                throw new NotContextException("Expected an instance of context to be bound at " +
279                                        part + " but found an instance of " + currentValue.getClass().getName());
280                            }
281                            currentContext = (Context) currentValue;
282                            // now we recurse into the current context
283                        }
284                    }
285                }
286            }
287        }
288    
289        /**
290         * Gets the value bound to the specified name within the specified context.  If the specified context is an
291         * AbstractContext this method will call the faster getBinding method, otherwise it will call lookup.
292         *
293         * @param context the context to get the binding from
294         * @param name the binding name
295         * @return the bound value or null if no value was bound
296         */
297        private static Object getBinding(Context context, String name) {
298            try {
299                if (context instanceof AbstractContext) {
300                    AbstractContext abstractContext = (AbstractContext) context;
301                    Object value = abstractContext.getBinding(name);
302                    return value;
303                } else {
304                    Object value = context.lookup(name);
305                    return value;
306                }
307            } catch (NamingException e) {
308                return null;
309            }
310        }
311    
312        /**
313         * Binds the specified value to the specified name within the specified context.  If the specified context is an
314         * AbstractContext and is a nested subcontext, this method will call the direct addBinding method, otherwise it
315         * will call public (re)bind method.
316         *
317         * @param context the context to add the binding to
318         * @param name the binding name
319         * @param value the value to bind
320         * @param rebind if true, this method will replace any exsiting binding, otherwise a NamingException will be thrown
321         * @throws NamingException if a problem occurs while (re)binding
322         */
323        protected void addBinding(Context context, String name, Object value, boolean rebind) throws NamingException {
324            if (context == this || (context instanceof AbstractContext && isNestedSubcontext(context))) {
325                AbstractContext abstractContext = (AbstractContext) context;
326                abstractContext.addBinding(name, value, rebind);
327            } else {
328                if (rebind) {
329                    context.rebind(name, value);
330                } else {
331                    context.bind(name, value);
332                }
333            }
334        }
335    
336        protected abstract boolean addBinding(String name, Object value, boolean rebind) throws NamingException;
337    
338        /**
339         * Creates a context tree which will be rooted at the specified path and contain a single entry located down
340         * a path specified by the name.  All necessary intermediate contexts will be created using the createContext method.
341         * @param path the path to the context that will contains this context
342         * @param name the name under which the value should be bound
343         * @param value the vale
344         * @return a context with the value bound at the specified name
345         * @throws NamingException if a problem occurs while creating the subcontext tree
346         */
347        protected Context createSubcontextTree(String path, Name name, Object value) throws NamingException {
348            if (path == null) throw new NullPointerException("path is null");
349            if (name == null) throw new NullPointerException("name is null");
350            if (name.size() < 2) throw new InvalidNameException("name must have at least 2 parts " + name);
351    
352            if (!path.endsWith("/")) path += "/";
353    
354            for (int i = name.size() - 2; i >= 0; i--) {
355                String fullPath = path + name.getSuffix(i);
356                String key = name.get(i + 1);
357                value = createNestedSubcontext(fullPath, Collections.singletonMap(key, value));
358            }
359            return (Context) value;
360        }
361    
362    
363        //
364        //  Remove Binding
365        //
366    
367        /**
368         * Removes the binding from the context.  The name will not contain a path and the value will not
369         * be a nested context although it may be a foreign context.
370         * @param name name under which the value should be bound
371         * @param removeNotEmptyContext
372         * @throws NamingException if a problem occurs during the bind such as a value already being bound
373         */
374        protected abstract boolean removeBinding(String name, boolean removeNotEmptyContext) throws NamingException;
375    
376        protected void removeDeepBinding(Name name, boolean pruneEmptyContexts) throws NamingException {
377            removeDeepBinding(name, pruneEmptyContexts, false);
378        }
379    
380        protected void removeDeepBinding(Name name, boolean pruneEmptyContexts, boolean removeNotEmptyContext) throws NamingException {
381            if (name == null) throw new NullPointerException("name is null");
382            if (name.isEmpty()) {
383                throw new InvalidNameException("Name is empty");
384            }
385    
386            if (name.size() == 1) {
387                removeBinding(name.get(0), removeNotEmptyContext);
388                return;
389            }
390    
391            if (!pruneEmptyContexts) {
392                Context context = lookupFinalContext(name);
393                context.unbind(name.getSuffix(name.size() - 1));
394            } else {
395                // we serch the tree for a target context and name to remove
396                // this is normally the last context in the tree and the final name part, but
397                // it may be farther up the path if the intervening nodes are empty
398                Context targetContext = this;
399                String targetName = name.get(0);
400    
401                Context currentContext = this;
402                for (int i = 0; i < name.size(); i++) {
403                    String part = name.get(i);
404    
405                    // empty path parts are not allowed
406                    if (part.length() == 0) {
407                        throw new InvalidNameException("Name part " + i + " is empty: " + name);
408                    }
409    
410                    // update targets
411                    if (getSize(currentContext) > 1) {
412                        targetContext = currentContext;
413                        targetName = part;
414                    }
415    
416    
417                    // Is this the last element in the name?
418                    if (i == name.size() - 1) {
419                        // we're at the end... unbind value
420                        unbind(targetContext, targetName, true);
421    
422                        // all done... this is redundant but makes the code more readable
423                        break;
424                    } else {
425                        Object currentValue = getBinding(currentContext, part);
426                        if (currentValue == null) {
427                            // path not found we are done, but first prune the empty contexts
428                            if (targetContext != currentContext) {
429                                unbind(targetContext, targetName, false);
430                            }
431                            break;
432                        } else {
433                            // the current value must be a context
434                            if (!(currentValue instanceof Context)) {
435                                throw new NotContextException("Expected an instance of context to be bound at " +
436                                        part + " but found an instance of " + currentValue.getClass().getName());
437                            }
438                            currentContext = (Context) currentValue;
439                            // now we recurse into the current context
440                        }
441                    }
442                }
443            }
444        }
445    
446        protected static boolean isEmpty(Context context) throws NamingException {
447            if (context instanceof AbstractContext) {
448                AbstractContext abstractContext = (AbstractContext) context;
449                Map currentBindings = abstractContext.getBindings();
450                return currentBindings.isEmpty();
451            } else {
452                NamingEnumeration namingEnumeration = context.list("");
453                return namingEnumeration.hasMore();
454            }
455        }
456    
457        protected static int getSize(Context context) throws NamingException {
458            if (context instanceof AbstractContext) {
459                AbstractContext abstractContext = (AbstractContext) context;
460                Map currentBindings = abstractContext.getBindings();
461                return currentBindings.size();
462            } else {
463                NamingEnumeration namingEnumeration = context.list("");
464                int size = 0;
465                while (namingEnumeration.hasMore()) size++;
466                return size;
467            }
468        }
469    
470        /**
471         * Unbinds any value bound to the specified name within the specified context.  If the specified context is an
472         * AbstractContext and is a nested context, this method will call the direct removeBinding method, otherwise it
473         * will call public unbind.
474         *
475         * @param context the context to remove the binding from
476         * @param name the binding name
477         * @param removeNotEmptyContext
478         * @throws NamingException if a problem occurs while unbinding
479         */
480        private void unbind(Context context, String name, boolean removeNotEmptyContext) throws NamingException {
481            if (context == this || (context instanceof AbstractContext && isNestedSubcontext(context))) {
482                AbstractContext abstractContext = (AbstractContext) context;
483                abstractContext.removeBinding(name, removeNotEmptyContext);
484            } else {
485                context.unbind(name);
486            }
487        }
488    
489        //
490        // Environment
491        //
492    
493        /**
494         * Always returns a new (empty) Hashtable.
495         * @return a new (empty) Hashtable
496         */
497        public Hashtable getEnvironment() {
498            return new Hashtable();
499        }
500    
501        public Object addToEnvironment(String propName, Object propVal) throws NamingException {
502            if (propName == null) throw new NullPointerException("propName is null");
503            if (propVal == null) throw new NullPointerException("propVal is null");
504    
505            Map env = getEnvironment();
506            return env.put(propName, propVal);
507        }
508    
509        public Object removeFromEnvironment(String propName) throws NamingException {
510            if (propName == null) throw new NullPointerException("propName is null");
511    
512            Map env = getEnvironment();
513            return env.remove(propName);
514        }
515    
516        //
517        // Name handling
518        //
519    
520        /**
521         * Gets the name of this context withing the global namespace.  This method may return null
522         * if the location of the node in the global namespace is not known
523         * @return the name of this context within the global namespace or null if unknown.
524         */
525        public String getNameInNamespace() {
526            return nameInNamespace;
527        }
528    
529        /**
530         * Gets the name of this context withing the global namespace.  This method may return null
531         * if the location of the node in the global namespace is not known
532         * @return the name of this context within the global namespace or null if unknown.
533         */
534        protected Name getParsedNameInNamespace() {
535            return parsedNameInNamespace;
536        }
537    
538        /**
539         * Gets the name of a path withing the global namespace context.
540         */
541        protected String getNameInNamespace(String path) {
542            String nameInNamespace = getNameInNamespace();
543            if (nameInNamespace == null || nameInNamespace.length() == 0) {
544                return path;
545            } else {
546                return nameInNamespace + "/" + path;
547            }
548        }
549    
550        /**
551         * Gets the name of a path withing the global namespace context.
552         */
553        protected Name getNameInNamespace(Name path) throws NamingException {
554            Name nameInNamespace = getParsedNameInNamespace();
555            if (nameInNamespace == null || nameInNamespace.size() == 0) {
556                return path;
557            } else {
558                return composeName(nameInNamespace, path);
559            }
560        }
561    
562        /**
563         * A parser that can turn Strings into javax.naming.Name objects.
564         * @return ContextUtil.NAME_PARSER
565         */
566        protected NameParser getNameParser() {
567            return ContextUtil.NAME_PARSER;
568        }
569    
570        public NameParser getNameParser(Name name) {
571            return getNameParser();
572        }
573    
574        public NameParser getNameParser(String name) {
575            return getNameParser();
576        }
577    
578        public Name composeName(Name name, Name prefix) throws NamingException {
579            if (name == null) throw new NullPointerException("name is null");
580            if (prefix == null) throw new NullPointerException("prefix is null");
581    
582            Name result = (Name) prefix.clone();
583            result.addAll(name);
584            return result;
585        }
586    
587        public String composeName(String name, String prefix) throws NamingException {
588            if (name == null) throw new NullPointerException("name is null");
589            if (prefix == null) throw new NullPointerException("prefix is null");
590    
591            CompositeName result = new CompositeName(prefix);
592            result.addAll(new CompositeName(name));
593            return result.toString();
594        }
595    
596        //
597        // Lookup
598        //
599    
600        public Object lookup(String name) throws NamingException {
601            if (name == null) throw new NullPointerException("name is null");
602    
603            Object value = lookup(name, null);
604    
605            // if we got a link back we need to resolve it
606            if (value instanceof LinkRef) {
607                LinkRef linkRef = (LinkRef) value;
608                value = lookup(linkRef.getLinkName());
609            }
610    
611            return value;
612        }
613    
614        public Object lookup(Name name) throws NamingException {
615            if (name == null) throw new NullPointerException("name is null");
616    
617            Object value = lookup(null, name);
618    
619    
620            // if we got a link back we need to resolve it
621            if (value instanceof LinkRef) {
622                LinkRef linkRef = (LinkRef) value;
623                value = lookup(linkRef.getLinkName());
624            }
625    
626            return value;
627        }
628    
629        public Object lookupLink(String name) throws NamingException {
630            if (name == null) throw new NullPointerException("name is null");
631            return lookup(name, null);
632        }
633    
634        public Object lookupLink(Name name) throws NamingException {
635            if (name == null) throw new NullPointerException("name is null");
636            return lookup(null, name);
637        }
638    
639        //
640        // Bind, rebind, rename and unbind
641        //
642    
643        public void bind(String name, Object obj) throws NamingException {
644            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
645            if (name == null) throw new NullPointerException("name is null");
646            if (name.length() == 0) {
647                throw new NameAlreadyBoundException("Cannot bind to an empty name (this context)");
648            }
649            bind(new CompositeName(name), obj);
650        }
651    
652        public void bind(Name name, Object obj) throws NamingException {
653            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
654            if (name == null) throw new NullPointerException("name is null");
655            if (name.isEmpty()) {
656                throw new NameAlreadyBoundException("Cannot bind to an empty name (this context)");
657            }
658            addDeepBinding(name, obj, false, false);
659        }
660    
661        public void rebind(String name, Object obj) throws NamingException {
662            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
663            if (name == null) throw new NullPointerException("name is null");
664            rebind(new CompositeName(name), obj);
665        }
666    
667        public void rebind(Name name, Object obj) throws NamingException {
668            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
669            if (name == null) throw new NullPointerException("name is null");
670            if (name.isEmpty()) {
671                throw new NameAlreadyBoundException("Cannot rebind an empty name (this context)");
672            }
673            addDeepBinding(name, obj, true, false);
674        }
675    
676        public void rename(String oldName, String newName) throws NamingException {
677            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
678            if (oldName == null) throw new NullPointerException("oldName is null");
679            if (newName == null) throw new NullPointerException("newName is null");
680            rename(new CompositeName(oldName), new CompositeName(newName));
681        }
682    
683        public void rename(Name oldName, Name newName) throws NamingException {
684            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
685            if (oldName == null || newName == null) {
686                throw new NullPointerException("name is null");
687            } else if (oldName.isEmpty() || newName.isEmpty()) {
688                throw new NameAlreadyBoundException("Name cannot be empty");
689            }
690            this.bind(newName, this.lookup(oldName));
691            this.unbind(oldName);
692        }
693    
694        public void unbind(String name) throws NamingException {
695            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
696            if (name == null) throw new NullPointerException("name is null");
697            unbind(new CompositeName(name));
698        }
699    
700        public void unbind(Name name) throws NamingException {
701            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
702            if (name == null) throw new NullPointerException("name is null");
703            if (name.isEmpty()) {
704                throw new InvalidNameException("Cannot unbind empty name");
705            }
706            removeDeepBinding(name, false);
707        }
708    
709        //
710        // List
711        //
712    
713        protected NamingEnumeration list() throws NamingException {
714            Map bindings = getBindings();
715            return new ContextUtil.ListEnumeration(bindings);
716        }
717    
718        protected NamingEnumeration listBindings() throws NamingException {
719            Map bindings = getBindings();
720            return new ContextUtil.ListBindingEnumeration(bindings);
721        }
722    
723        public NamingEnumeration list(String name) throws NamingException {
724            if (name == null) throw new NullPointerException("name is null");
725    
726            // if the name is empty, list the current context
727            if (name.length() == 0) {
728                return list();
729            }
730    
731            // lookup the target context
732            Object target = null;
733            try {
734                target = lookup(name);
735            } catch (NamingException e) {
736                throw new NotContextException(name);
737            }
738    
739            if (target == this) {
740                return list();
741            } else if (target instanceof Context) {
742                return ((Context) target).list("");
743            } else {
744                throw new NotContextException("The name " + name + " cannot be listed");
745            }
746        }
747    
748        public NamingEnumeration list(Name name) throws NamingException {
749            if (name == null) throw new NullPointerException("name is null");
750    
751            // if the name is empty, list the current context
752            if (name.isEmpty()) {
753                return list();
754            }
755    
756            // lookup the target context
757            Object target = null;
758            try {
759                target = lookup(name);
760            } catch (NamingException e) {
761                throw new NotContextException(name.toString());
762            }
763    
764            if (target == this) {
765                return list();
766            } else if (target instanceof Context) {
767                return ((Context) target).list("");
768            } else {
769                throw new NotContextException("The name " + name + " cannot be listed");
770            }
771        }
772    
773        public NamingEnumeration listBindings(String name) throws NamingException {
774            if (name == null) throw new NullPointerException("name is null");
775    
776            // if the name is empty, list the current context
777            if (name.length() == 0) {
778                return listBindings();
779            }
780    
781            // lookup the target context
782            Object target = null;
783            try {
784                target = lookup(name);
785            } catch (NamingException e) {
786                throw new NotContextException(name.toString());
787            }
788    
789            if (target == this) {
790                return listBindings();
791            } else if (target instanceof Context) {
792                return ((Context) target).listBindings("");
793            } else {
794                throw new NotContextException("The name " + name + " cannot be listed");
795            }
796        }
797    
798        public NamingEnumeration listBindings(Name name) throws NamingException {
799            if (name == null) throw new NullPointerException("name is null");
800    
801            // if the name is empty, list the current context
802            if (name.isEmpty()) {
803                return listBindings();
804            }
805    
806            // lookup the target context
807            Object target = null;
808            try {
809                target = lookup(name);
810            } catch (NamingException e) {
811                throw new NotContextException(name.toString());
812            }
813    
814            if (target == this) {
815                return listBindings();
816            } else if (target instanceof Context) {
817                return ((Context) target).listBindings("");
818            } else {
819                throw new NotContextException("The name " + name + " cannot be listed");
820            }
821        }
822    
823        //
824        // Subcontexts
825        //
826    
827        public Context createSubcontext(String name) throws NamingException {
828            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
829            if (name == null) throw new NullPointerException("name is null");
830            return createSubcontext(new CompositeName(name));
831        }
832    
833        public Context createSubcontext(Name name) throws NamingException {
834            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
835            if (name == null) throw new NullPointerException("name is null");
836            if (name.isEmpty()) {
837                throw new NameAlreadyBoundException("Cannot create a subcontext if the name is empty");
838            }
839            Context abstractContext = createNestedSubcontext(name.toString(), Collections.EMPTY_MAP);
840            addDeepBinding(name, abstractContext, false, false);
841            return abstractContext;
842        }
843    
844        public void destroySubcontext(String name) throws NamingException {
845            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
846            if (name == null) throw new NullPointerException("name is null");
847            destroySubcontext(new CompositeName(name));
848        }
849    
850        public void destroySubcontext(Name name) throws NamingException {
851            if (!modifiable) throw new OperationNotSupportedException("Context is read only");
852            if (name == null) throw new NullPointerException("name is null");
853            if (name.isEmpty()) {
854                throw new InvalidNameException("Cannot destroy subcontext with empty name");
855            }
856            unbind(name);
857        }
858    }