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 */
017package org.apache.xbean.naming.context;
018
019import javax.naming.CompositeName;
020import javax.naming.Context;
021import javax.naming.InvalidNameException;
022import javax.naming.Name;
023import javax.naming.NameAlreadyBoundException;
024import javax.naming.NameParser;
025import javax.naming.NamingEnumeration;
026import javax.naming.NamingException;
027import javax.naming.NotContextException;
028import javax.naming.LinkRef;
029import javax.naming.NameNotFoundException;
030import javax.naming.InitialContext;
031import javax.naming.OperationNotSupportedException;
032import javax.naming.NameClassPair;
033import javax.naming.Binding;
034
035import java.io.Serializable;
036import java.util.Collections;
037import java.util.Hashtable;
038import java.util.Map;
039
040public 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}