1##############################################################################
2#
3# Copyright (c) 2003-2009 Zope Foundation and Contributors.
4# All Rights Reserved.
5#
6# This software is subject to the provisions of the Zope Public License,
7# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11# FOR A PARTICULAR PURPOSE.
12#
13##############################################################################
14"""Classes to support implenting IContained
15"""
16__docformat__ = 'restructuredtext'
17
18import zope.component
19import zope.component.interfaces
20import zope.interface
21from zope.location.interfaces import ILocationInfo
22from zope.location.interfaces import ILocation, IRoot
23from zope.location.location import Location
24
25
26class LocationPhysicallyLocatable(object):
27    """Provide location information for location objects
28
29    >>> from zope.interface.verify import verifyObject
30    >>> info = LocationPhysicallyLocatable(Location())
31    >>> verifyObject(ILocationInfo, info)
32    True
33
34    """
35
36    zope.component.adapts(ILocation)
37    zope.interface.implements(ILocationInfo)
38
39    def __init__(self, context):
40        self.context = context
41
42    def getRoot(self):
43        """Get the root location for a location.
44
45        See ILocationInfo
46
47        The root location is a location that contains the given
48        location and that implements IContainmentRoot.
49
50        >>> root = Location()
51        >>> zope.interface.directlyProvides(root, IRoot)
52        >>> LocationPhysicallyLocatable(root).getRoot() is root
53        True
54
55        >>> o1 = Location(); o1.__parent__ = root
56        >>> LocationPhysicallyLocatable(o1).getRoot() is root
57        True
58
59        >>> o2 = Location(); o2.__parent__ = o1
60        >>> LocationPhysicallyLocatable(o2).getRoot() is root
61        True
62
63        We'll get a TypeError if we try to get the location fo a
64        rootless object:
65
66        >>> o1.__parent__ = None
67        >>> LocationPhysicallyLocatable(o1).getRoot()
68        Traceback (most recent call last):
69        ...
70        TypeError: Not enough context to determine location root
71        >>> LocationPhysicallyLocatable(o2).getRoot()
72        Traceback (most recent call last):
73        ...
74        TypeError: Not enough context to determine location root
75
76        If we screw up and create a location cycle, it will be caught:
77
78        >>> o1.__parent__ = o2
79        >>> LocationPhysicallyLocatable(o1).getRoot()
80        Traceback (most recent call last):
81        ...
82        TypeError: Maximum location depth exceeded, """ \
83                """probably due to a a location cycle.
84        """
85        context = self.context
86        max = 9999
87        while context is not None:
88            if IRoot.providedBy(context):
89                return context
90            context = context.__parent__
91            max -= 1
92            if max < 1:
93                raise TypeError("Maximum location depth exceeded, "
94                                "probably due to a a location cycle.")
95
96        raise TypeError("Not enough context to determine location root")
97
98    def getPath(self):
99        """Get the path of a location.
100
101        See ILocationInfo
102
103        This is an "absolute path", rooted at a root object.
104
105        >>> root = Location()
106        >>> zope.interface.directlyProvides(root, IRoot)
107        >>> LocationPhysicallyLocatable(root).getPath()
108        u'/'
109
110        >>> o1 = Location(); o1.__parent__ = root; o1.__name__ = 'o1'
111        >>> LocationPhysicallyLocatable(o1).getPath()
112        u'/o1'
113
114        >>> o2 = Location(); o2.__parent__ = o1; o2.__name__ = u'o2'
115        >>> LocationPhysicallyLocatable(o2).getPath()
116        u'/o1/o2'
117
118        It is an error to get the path of a rootless location:
119
120        >>> o1.__parent__ = None
121        >>> LocationPhysicallyLocatable(o1).getPath()
122        Traceback (most recent call last):
123        ...
124        TypeError: Not enough context to determine location root
125
126        >>> LocationPhysicallyLocatable(o2).getPath()
127        Traceback (most recent call last):
128        ...
129        TypeError: Not enough context to determine location root
130
131        If we screw up and create a location cycle, it will be caught:
132
133        >>> o1.__parent__ = o2
134        >>> LocationPhysicallyLocatable(o1).getPath()
135        Traceback (most recent call last):
136        ...
137        TypeError: Maximum location depth exceeded, """ \
138                """probably due to a a location cycle.
139
140        """
141
142        path = []
143        context = self.context
144        max = 9999
145        while context is not None:
146            if IRoot.providedBy(context):
147                if path:
148                    path.append('')
149                    path.reverse()
150                    return u'/'.join(path)
151                else:
152                    return u'/'
153            path.append(context.__name__)
154            context = context.__parent__
155            max -= 1
156            if max < 1:
157                raise TypeError("Maximum location depth exceeded, "
158                                "probably due to a a location cycle.")
159
160        raise TypeError("Not enough context to determine location root")
161
162    def getParent(self):
163        """Returns the container the object was traversed via.
164
165        Returns None if the object is a containment root.
166        Raises TypeError if the object doesn't have enough context to get the
167        parent.
168
169        >>> root = Location()
170        >>> zope.interface.directlyProvides(root, IRoot)
171        >>> o1 = Location()
172        >>> o2 = Location()
173
174        >>> LocationPhysicallyLocatable(o2).getParent() # doctest: +ELLIPSIS
175        Traceback (most recent call last):
176        TypeError: ('Not enough context information to get parent', <zope.location.location.Location object at 0x...>)
177
178        >>> o1.__parent__ = root
179        >>> LocationPhysicallyLocatable(o1).getParent() == root
180        True
181
182        >>> o2.__parent__ = o1
183        >>> LocationPhysicallyLocatable(o2).getParent() == o1
184        True
185
186        """
187        parent = getattr(self.context, '__parent__', None)
188        if parent is not None:
189            return parent
190
191        raise TypeError('Not enough context information to get parent',
192                        self.context)
193
194    def getParents(self):
195        """Returns a list starting with the object's parent followed by
196        each of its parents.
197
198        Raises a TypeError if the object is not connected to a containment
199        root.
200
201        >>> root = Location()
202        >>> zope.interface.directlyProvides(root, IRoot)
203        >>> o1 = Location()
204        >>> o2 = Location()
205        >>> o1.__parent__ = root
206        >>> o2.__parent__ = o1
207        >>> LocationPhysicallyLocatable(o2).getParents() == [o1, root]
208        True
209
210        If the last parent is not an IRoot object, TypeError will be
211        raised as statet before.
212
213        >>> zope.interface.noLongerProvides(root, IRoot)
214        >>> LocationPhysicallyLocatable(o2).getParents()
215        Traceback (most recent call last):
216        ...
217        TypeError: Not enough context information to get all parents
218
219        """
220        # XXX Merge this implementation with getPath. This was refactored
221        # from zope.traversing.
222        parents = []
223        w = self.context
224        while 1:
225            w = w.__parent__
226            if w is None:
227                break
228            parents.append(w)
229
230        if parents and IRoot.providedBy(parents[-1]):
231            return parents
232
233        raise TypeError("Not enough context information to get all parents")
234
235    def getName(self):
236        """Get a location name
237
238        See ILocationInfo
239
240        >>> o1 = Location(); o1.__name__ = u'o1'
241        >>> LocationPhysicallyLocatable(o1).getName()
242        u'o1'
243        """
244        return self.context.__name__
245
246    def getNearestSite(self):
247        """return the nearest site, see ILocationInfo
248
249        >>> o1 = Location()
250        >>> o1.__name__ = 'o1'
251        >>> LocationPhysicallyLocatable(o1).getNearestSite()
252        Traceback (most recent call last):
253        ...
254        TypeError: Not enough context information to get all parents
255
256        >>> root = Location()
257        >>> zope.interface.directlyProvides(root, IRoot)
258        >>> o1 = Location()
259        >>> o1.__name__ = 'o1'
260        >>> o1.__parent__ = root
261        >>> LocationPhysicallyLocatable(o1).getNearestSite() is root
262        True
263
264        >>> zope.interface.directlyProvides(
265        ...     o1, zope.component.interfaces.ISite)
266        >>> LocationPhysicallyLocatable(o1).getNearestSite() is o1
267        True
268
269        >>> o2 = Location()
270        >>> o2.__parent__ = o1
271        >>> LocationPhysicallyLocatable(o2).getNearestSite() is o1
272        True
273
274        """
275        if zope.component.interfaces.ISite.providedBy(self.context):
276            return self.context
277        for parent in self.getParents():
278            if zope.component.interfaces.ISite.providedBy(parent):
279                return parent
280        return self.getRoot()
281
282class RootPhysicallyLocatable(object):
283    """Provide location information for the root object
284
285    >>> from zope.interface.verify import verifyObject
286    >>> info = RootPhysicallyLocatable(None)
287    >>> verifyObject(ILocationInfo, info)
288    True
289
290    This adapter is very simple, because there's no places to search
291    for parents and nearest sites, so we are only working with context
292    object, knowing that its the root object already.
293
294    """
295
296    zope.component.adapts(IRoot)
297    zope.interface.implements(ILocationInfo)
298
299    def __init__(self, context):
300        self.context = context
301
302    def getRoot(self):
303        """See ILocationInfo
304
305        No need to search for root when our context is already root :)
306
307        >>> o1 = object()
308        >>> RootPhysicallyLocatable(o1).getRoot() is o1
309        True
310
311        """
312        return self.context
313
314    def getPath(self):
315        """See ILocationInfo
316
317        Root object is at the top of the tree, so always return ``/``.
318
319        >>> o1 = object()
320        >>> RootPhysicallyLocatable(o1).getPath()
321        u'/'
322
323        """
324        return u'/'
325
326    def getName(self):
327        """See ILocationInfo
328
329        Always return empty unicode string for the root object
330
331        >>> o1 = object()
332        >>> RootPhysicallyLocatable(o1).getName()
333        u''
334
335        """
336        return u''
337
338    def getParent(self):
339        """Returns the container the object was traversed via.
340
341        Returns None if the object is a containment root.
342        Raises TypeError if the object doesn't have enough context to get the
343        parent.
344
345        >>> o1 = object()
346        >>> RootPhysicallyLocatable(o1).getParent()
347
348        """
349        return None
350
351    def getParents(self):
352        """See ILocationInfo
353
354        There's no parents for the root object, return empty list.
355
356        >>> o1 = object()
357        >>> RootPhysicallyLocatable(o1).getParents()
358        []
359
360        """
361        return []
362
363    def getNearestSite(self):
364        """See ILocationInfo
365
366        Return object itself as the nearest site, because there's no
367        other place to look for. It's also usual that the root is the
368        site as well.
369
370
371        >>> o1 = object()
372        >>> RootPhysicallyLocatable(o1).getNearestSite() is o1
373        True
374
375        """
376        return self.context
377