1
#------------------------------------------------------------------------------
2
# Copyright (c) 2005, Enthought, Inc.
5
# This software is provided without warranty under the terms of the BSD
6
# license included in enthought/LICENSE.txt and may be redistributed only
7
# under the conditions described in the aforementioned license. The license
8
# is also available online at http://www.enthought.com/licenses/BSD.txt
9
# Thanks for using Enthought open source!
11
# Author: Enthought, Inc.
12
# Description: <Enthought naming package component>
13
#------------------------------------------------------------------------------
14
""" The base class for all naming contexts. """
17
# Enthought library imports.
18
from traits.api import Any, Dict, Event, HasTraits, Instance
19
from traits.api import Property, Str
20
from apptools.type_manager.api import TypeManager
23
from binding import Binding
24
from exception import InvalidNameError, NameAlreadyBoundError
25
from exception import NameNotFoundError, NotContextError
26
from exception import OperationNotSupportedError
27
from naming_event import NamingEvent
28
from naming_manager import naming_manager
29
from object_factory import ObjectFactory
30
from state_factory import StateFactory
31
from unique_name import make_unique_name
34
# Constants for environment property keys.
35
INITIAL_CONTEXT_FACTORY = "apptools.naming.factory.initial"
36
OBJECT_FACTORIES = "apptools.naming.factory.object"
37
STATE_FACTORIES = "apptools.naming.factory.state"
40
TYPE_MANAGER = "apptools.naming.factory.type.manager"
43
# The default environment.
45
# 'Context' properties.
46
OBJECT_FACTORIES : [],
54
class Context(HasTraits):
55
""" The base class for all naming contexts. """
57
# Keys for environment properties.
58
INITIAL_CONTEXT_FACTORY = INITIAL_CONTEXT_FACTORY
59
OBJECT_FACTORIES = OBJECT_FACTORIES
60
STATE_FACTORIES = STATE_FACTORIES
63
TYPE_MANAGER = TYPE_MANAGER
65
#### 'Context' interface ##################################################
67
# The naming environment in effect for this context.
68
environment = Dict(ENVIRONMENT)
70
# The name of the context within its own namespace.
71
namespace_name = Property(Str)
73
# The type manager in the context's environment (used to create context
76
# fixme: This is an experimental 'convenience' trait, since it is common
77
# to get hold of the context's type manager to see if some object has a
79
type_manager = Property(Instance(TypeManager))
83
# Fired when an object has been added to the context (either via 'bind' or
84
# 'create_subcontext').
85
object_added = Event(NamingEvent)
87
# Fired when an object has been changed (via 'rebind').
88
object_changed = Event(NamingEvent)
90
# Fired when an object has been removed from the context (either via
91
# 'unbind' or 'destroy_subcontext').
92
object_removed = Event(NamingEvent)
94
# Fired when an object in the context has been renamed (via 'rename').
95
object_renamed = Event(NamingEvent)
97
# Fired when the contents of the context have changed dramatically.
98
context_changed = Event(NamingEvent)
100
#### Protected 'Context' interface #######################################
102
# The bindings in the context.
103
_bindings = Dict(Str, Any)
105
###########################################################################
106
# 'Context' interface.
107
###########################################################################
109
#### Properties ###########################################################
111
def _get_namespace_name(self):
113
Return the name of the context within its own namespace.
115
That is the full-path, through the namespace this context participates
116
in, to get to this context. For example, if the root context of the
117
namespace was called 'Foo', and there was a subcontext of that called
118
'Bar', and we were within that and called 'Baz', then this should
119
return 'Foo/Bar/Baz'.
123
# FIXME: We'd like to raise an exception and force implementors to
124
# decide what to do. However, it appears to be pretty common that
125
# most Context implementations do not override this method -- possibly
126
# because the comments aren't clear on what this is supposed to be?
128
# Anyway, if we raise an exception then it is impossible to use any
129
# evaluations when building a Traits UI for a Context. That is, the
130
# Traits UI can't include items that have a 'visible_when' or
131
# 'enabled_when' evaluation. This is because the Traits evaluation
132
# code calls the 'get()' method on the Context which attempts to
133
# retrieve the current namespace_name value.
134
#raise OperationNotSupportedError()
137
def _get_type_manager(self):
138
""" Returns the type manager in the context's environment.
140
This will return None if no type manager was used to create the initial
145
return self.environment.get(self.TYPE_MANAGER)
147
#### Methods ##############################################################
149
def bind(self, name, obj, make_contexts=False):
150
""" Binds a name to an object.
152
If 'make_contexts' is True then any missing intermediate contexts are
153
created automatically.
158
raise InvalidNameError('empty name')
161
components = self._parse_name(name)
163
# If there is exactly one component in the name then the operation
164
# takes place in this context.
165
if len(components) == 1:
168
# Is the name already bound?
169
if self._is_bound(atom):
170
raise NameAlreadyBoundError(name)
172
# Do the actual bind.
173
self._bind(atom, obj)
175
# Trait event notification.
176
self.object_added = NamingEvent(
177
new_binding=Binding(name=name, obj=obj, context=self)
180
# Otherwise, attempt to continue resolution into the next context.
182
if not self._is_bound(components[0]):
184
self._create_subcontext(components[0])
187
raise NameNotFoundError(components[0])
189
next_context = self._get_next_context(components[0])
190
next_context.bind('/'.join(components[1:]), obj, make_contexts)
194
def rebind(self, name, obj, make_contexts=False):
195
""" Binds an object to a name that may already be bound.
197
If 'make_contexts' is True then any missing intermediate contexts are
198
created automatically.
200
The object may be a different object but may also be the same object
201
that is already bound to the specified name. The name may or may not be
202
already used. Think of this as a safer version of 'bind' since this
203
one will never raise an exception regarding a name being used.
208
raise InvalidNameError('empty name')
211
components = self._parse_name(name)
213
# If there is exactly one component in the name then the operation
214
# takes place in this context.
215
if len(components) == 1:
216
# Do the actual rebind.
217
self._rebind(components[0], obj)
219
# Trait event notification.
220
self.object_changed = NamingEvent(
221
new_binding=Binding(name=name, obj=obj, context=self)
224
# Otherwise, attempt to continue resolution into the next context.
226
if not self._is_bound(components[0]):
228
self._create_subcontext(components[0])
231
raise NameNotFoundError(components[0])
233
next_context = self._get_next_context(components[0])
234
next_context.rebind('/'.join(components[1:]), obj, make_contexts)
238
def unbind(self, name):
239
""" Unbinds a name. """
242
raise InvalidNameError('empty name')
245
components = self._parse_name(name)
247
# If there is exactly one component in the name then the operation
248
# takes place in this context.
249
if len(components) == 1:
252
if not self._is_bound(atom):
253
raise NameNotFoundError(name)
255
# Lookup the object that we are unbinding to use in the event
257
obj = self._lookup(atom)
259
# Do the actual unbind.
262
# Trait event notification.
263
self.object_removed = NamingEvent(
264
old_binding=Binding(name=name, obj=obj, context=self)
267
# Otherwise, attempt to continue resolution into the next context.
269
if not self._is_bound(components[0]):
270
raise NameNotFoundError(components[0])
272
next_context = self._get_next_context(components[0])
273
next_context.unbind('/'.join(components[1:]))
277
def rename(self, old_name, new_name):
278
""" Binds a new name to an object. """
280
if len(old_name) == 0 or len(new_name) == 0:
281
raise InvalidNameError('empty name')
284
old_components = self._parse_name(old_name)
285
new_components = self._parse_name(new_name)
287
# If there is axactly one component in BOTH names then the operation
288
# takes place ENTIRELY in this context.
289
if len(old_components) == 1 and len(new_components) == 1:
290
# Is the old name actually bound?
291
if not self._is_bound(old_name):
292
raise NameNotFoundError(old_name)
294
# Is the new name already bound?
295
if self._is_bound(new_name):
296
raise NameAlreadyBoundError(new_name)
298
# Do the actual rename.
299
self._rename(old_name, new_name)
301
# Lookup the object that we are renaming to use in the event
303
obj = self._lookup(new_name)
305
# Trait event notification.
306
self.object_renamed = NamingEvent(
307
old_binding=Binding(name=old_name, obj=obj, context=self),
308
new_binding=Binding(name=new_name, obj=obj, context=self)
312
# fixme: This really needs to be transactional in case the bind
313
# succeeds but the unbind fails. To be safe should we just not
314
# support cross-context renaming for now?!?!
317
obj = self.lookup(old_name)
320
self.bind(new_name, obj)
322
# Unbind the old one.
323
self.unbind(old_name)
327
def lookup(self, name):
328
""" Resolves a name relative to this context. """
330
# If the name is empty we return the context itself.
332
# fixme: The JNDI spec. says that this should return a COPY of
337
components = self._parse_name(name)
339
# If there is exactly one component in the name then the operation
340
# takes place in this context.
341
if len(components) == 1:
344
if not self._is_bound(atom):
345
raise NameNotFoundError(name)
347
# Do the actual lookup.
348
obj = self._lookup(atom)
350
# Otherwise, attempt to continue resolution into the next context.
352
if not self._is_bound(components[0]):
353
raise NameNotFoundError(components[0])
355
next_context = self._get_next_context(components[0])
356
obj = next_context.lookup('/'.join(components[1:]))
362
def lookup_binding(self, name):
363
""" Looks up the binding for a name relative to this context. """
366
raise InvalidNameError('empty name')
369
components = self._parse_name(name)
371
# If there is exactly one component in the name then the operation
372
# takes place in this context.
373
if len(components) == 1:
376
if not self._is_bound(atom):
377
raise NameNotFoundError(name)
379
# Do the actual lookup.
380
binding = self._lookup_binding(atom)
382
# Otherwise, attempt to continue resolution into the next context.
384
if not self._is_bound(components[0]):
385
raise NameNotFoundError(components[0])
387
next_context = self._get_next_context(components[0])
388
binding = next_context.lookup_binding('/'.join(components[1:]))
393
def lookup_context(self, name):
394
""" Resolves a name relative to this context.
396
The name MUST resolve to a context. This method is useful to return
401
# If the name is empty we return the context itself.
403
# fixme: The JNDI spec. says that this should return a COPY of
408
components = self._parse_name(name)
410
# If there is exactly one component in the name then the operation
411
# takes place in this context.
412
if len(components) == 1:
415
if not self._is_bound(atom):
416
raise NameNotFoundError(name)
418
# Do the actual lookup.
419
obj = self._get_next_context(atom)
421
# Otherwise, attempt to continue resolution into the next context.
423
if not self._is_bound(components[0]):
424
raise NameNotFoundError(components[0])
426
next_context = self._get_next_context(components[0])
427
obj = next_context.lookup('/'.join(components[1:]))
431
def create_subcontext(self, name):
432
""" Creates a sub-context. """
435
raise InvalidNameError('empty name')
438
components = self._parse_name(name)
440
# If there is exactly one component in the name then the operation
441
# takes place in this context.
442
if len(components) == 1:
445
# Is the name already bound?
446
if self._is_bound(atom):
447
raise NameAlreadyBoundError(name)
449
# Do the actual creation of the sub-context.
450
sub = self._create_subcontext(atom)
452
# Trait event notification.
453
self.object_added = NamingEvent(
454
new_binding=Binding(name=name, obj=sub, context=self)
457
# Otherwise, attempt to continue resolution into the next context.
459
if not self._is_bound(components[0]):
460
raise NameNotFoundError(components[0])
462
next_context = self._get_next_context(components[0])
463
sub = next_context.create_subcontext('/'.join(components[1:]))
467
def destroy_subcontext(self, name):
468
""" Destroys a sub-context. """
471
raise InvalidNameError('empty name')
474
components = self._parse_name(name)
476
# If there is exactly one component in the name then the operation
477
# takes place in this context.
478
if len(components) == 1:
481
if not self._is_bound(atom):
482
raise NameNotFoundError(name)
484
obj = self._lookup(atom)
485
if not self._is_context(atom):
486
raise NotContextError(name)
488
# Do the actual destruction of the sub-context.
489
self._destroy_subcontext(atom)
491
# Trait event notification.
492
self.object_removed = NamingEvent(
493
old_binding=Binding(name=name, obj=obj, context=self)
496
# Otherwise, attempt to continue resolution into the next context.
498
if not self._is_bound(components[0]):
499
raise NameNotFoundError(components[0])
501
next_context = self._get_next_context(components[0])
502
next_context.destroy_subcontext('/'.join(components[1:]))
507
def get_unique_name(self, prefix):
508
""" Returns a name that is unique within the context.
510
The name returned will start with the specified prefix.
514
return make_unique_name(prefix, existing=self.list_names(''),
518
def list_names(self, name=''):
519
""" Lists the names bound in a context. """
521
# If the name is empty then the operation takes place in this context.
523
names = self._list_names()
525
# Otherwise, attempt to continue resolution into the next context.
528
components = self._parse_name(name)
530
if not self._is_bound(components[0]):
531
raise NameNotFoundError(components[0])
533
next_context = self._get_next_context(components[0])
534
names = next_context.list_names('/'.join(components[1:]))
538
def list_bindings(self, name=''):
539
""" Lists the bindings in a context. """
541
# If the name is empty then the operation takes place in this context.
543
bindings = self._list_bindings()
545
# Otherwise, attempt to continue resolution into the next context.
548
components = self._parse_name(name)
550
if not self._is_bound(components[0]):
551
raise NameNotFoundError(components[0])
553
next_context = self._get_next_context(components[0])
554
bindings = next_context.list_bindings('/'.join(components[1:]))
559
def is_context(self, name):
560
""" Returns True if the name is bound to a context. """
562
# If the name is empty then it refers to this context.
568
components = self._parse_name(name)
570
# If there is exactly one component in the name then the operation
571
# takes place in this context.
572
if len(components) == 1:
575
if not self._is_bound(atom):
576
raise NameNotFoundError(name)
578
# Do the actual check.
579
is_context = self._is_context(atom)
581
# Otherwise, attempt to continue resolution into the next context.
583
if not self._is_bound(components[0]):
584
raise NameNotFoundError(components[0])
586
next_context = self._get_next_context(components[0])
587
is_context = next_context.is_context('/'.join(components[1:]))
592
def search(self, obj):
593
""" Returns a list of namespace names that are bound to obj. """
595
# don't look for None
599
# Obj is bound to these names relative to this context
602
# path contain the name components down to the current context
605
self._search( obj, names, path, {} )
609
###########################################################################
610
# Protected 'Context' interface.
611
###########################################################################
613
def _parse_name(self, name):
614
""" Parse a name into a list of components.
616
e.g. 'foo/bar/baz' -> ['foo', 'bar', 'baz']
620
return name.split('/')
622
def _is_bound(self, name):
623
""" Is a name bound in this context? """
625
return name in self._bindings
627
def _lookup(self, name):
628
""" Looks up a name in this context. """
630
obj = self._bindings[name]
632
return naming_manager.get_object_instance(obj, name, self)
634
def _lookup_binding(self, name):
635
""" Looks up the binding for a name in this context. """
637
return Binding(name=name, obj=self._lookup(name), context=self)
639
def _bind(self, name, obj):
640
""" Binds a name to an object in this context. """
642
state = naming_manager.get_state_to_bind(obj, name, self)
643
self._bindings[name] = state
647
def _rebind(self, name, obj):
648
""" Rebinds a name to an object in this context. """
650
self._bind(name, obj)
654
def _unbind(self, name):
655
""" Unbinds a name from this context. """
657
del self._bindings[name]
661
def _rename(self, old_name, new_name):
662
""" Renames an object in this context. """
665
self._bindings[new_name] = self._bindings[old_name]
667
# Unbind the old one.
668
del self._bindings[old_name]
672
def _create_subcontext(self, name):
673
""" Creates a sub-context of this context. """
675
sub = self.__class__(environment=self.environment)
676
self._bindings[name] = sub
680
def _destroy_subcontext(self, name):
681
""" Destroys a sub-context of this context. """
683
del self._bindings[name]
687
def _list_bindings(self):
688
""" Lists the bindings in this context. """
691
for name in self._list_names():
693
Binding(name=name, obj=self._lookup(name), context=self)
698
def _list_names(self):
699
""" Lists the names bound in this context. """
701
return self._bindings.keys()
703
def _is_context(self, name):
704
""" Returns True if a name is bound to a context. """
706
return self._get_next_context(name) is not None
708
def _get_next_context(self, name):
709
""" Returns the next context. """
711
obj = self._lookup(name)
713
# If the object is a context then everything is just dandy.
714
if isinstance(obj, Context):
717
# Otherwise, instead of just giving up, see if the context has a type
718
# manager that knows how to adapt the object to make it quack like a
721
next_context = self._get_context_adapter(obj)
723
# If no adapter was found then we cannot continue name resolution.
724
if next_context is None:
725
raise NotContextError(name)
729
def _search( self, obj, names, path, searched):
730
""" Append to names any name bound to obj.
731
Join path and name with '/' to for a complete name from the
735
# Check the bindings recursively.
736
for binding in self.list_bindings():
737
if binding.obj is obj:
738
path.append( binding.name )
739
names.append( '/'.join(path) )
742
if isinstance( binding.obj, Context ) \
743
and not binding.obj in searched:
744
path.append( binding.name )
745
searched[binding.obj] = True
746
binding.obj._search( obj, names, path, searched )
751
###########################################################################
753
###########################################################################
755
def _get_context_adapter(self, obj):
756
""" Returns a context adapter for an object.
758
Returns None if no such adapter is available.
762
if self.type_manager is not None:
763
adapter = self.type_manager.object_as(
764
obj, Context, environment=self.environment, context=self
772
#### EOF ######################################################################