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