1# Copyright 2019 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5from .composition_parts import DebugInfo
6from .composition_parts import WithIdentifier
7
8
9class Proxy(object):
10    """
11    Proxies attribute access on this object to the target object.
12    """
13
14    def __init__(self,
15                 target_object=None,
16                 target_attrs=None,
17                 target_attrs_with_priority=None):
18        """
19        Creates a new proxy to |target_object|.
20
21        Args:
22            target_object: The object to which attribute access is proxied.
23                This can be set later by set_target_object.
24            target_attrs: None or list of attribute names to be proxied.  If
25                None, all the attribute access is proxied.
26            target_attrs_with_priority: None or list of attribute names to be
27                unconditionally proxied with priority over attributes defined on
28                |self|.  If None, no attribute has priority over own attributes.
29        """
30        if target_attrs is not None:
31            assert isinstance(target_attrs, (list, set, tuple))
32            assert all(isinstance(attr, str) for attr in target_attrs)
33        self._target_object = target_object
34        self._target_attrs = target_attrs
35        self._target_attrs_with_priority = target_attrs_with_priority
36
37    def __getattr__(self, attribute):
38        try:
39            target_object = object.__getattribute__(self, '_target_object')
40            target_attrs = object.__getattribute__(self, '_target_attrs')
41        except AttributeError:
42            # When unpickling, __init__ does not get called.  _target_object is
43            # not defined yet during unpickling.  Then, just fallback to the
44            # default access.
45            return object.__getattribute__(self, attribute)
46        assert target_object is not None
47        if target_attrs is None or attribute in target_attrs:
48            return getattr(target_object, attribute)
49        raise AttributeError
50
51    def __getattribute__(self, attribute):
52        try:
53            target_object = object.__getattribute__(self, '_target_object')
54            target_attrs = object.__getattribute__(
55                self, '_target_attrs_with_priority')
56        except AttributeError:
57            # When unpickling, __init__ does not get called.  _target_object is
58            # not defined yet during unpickling.  Then, just fallback to the
59            # default access.
60            return object.__getattribute__(self, attribute)
61        # It's okay to access own attributes, such as 'identifier', even when
62        # the target object is not yet resolved.
63        if target_object is None:
64            return object.__getattribute__(self, attribute)
65        if target_attrs is not None and attribute in target_attrs:
66            return getattr(target_object, attribute)
67        return object.__getattribute__(self, attribute)
68
69    @staticmethod
70    def get_all_attributes(target_class):
71        """
72        Returns all the attributes of |target_class| including its ancestors'
73        attributes.  Protected attributes (starting with an underscore,
74        including two underscores) are excluded.
75        """
76
77        def collect_attrs_recursively(target_class):
78            attrs_sets = [set(vars(target_class).keys())]
79            for base_class in target_class.__bases__:
80                attrs_sets.append(collect_attrs_recursively(base_class))
81            return set.union(*attrs_sets)
82
83        assert isinstance(target_class, type)
84        return sorted([
85            attr for attr in collect_attrs_recursively(target_class)
86            if not attr.startswith('_')
87        ])
88
89    def make_copy(self, memo):
90        return self
91
92    def set_target_object(self, target_object):
93        assert self._target_object is None
94        assert isinstance(target_object, object)
95        self._target_object = target_object
96
97    @property
98    def target_object(self):
99        assert self._target_object is not None
100        return self._target_object
101
102
103_REF_BY_ID_PASS_KEY = object()
104
105
106class RefById(Proxy, WithIdentifier):
107    """
108    Represents a reference to an object specified with the given identifier,
109    which reference will be resolved later.
110
111    This reference is also a proxy to the object for convenience so that you
112    can treat this reference as if the object itself.
113    """
114
115    def __init__(self,
116                 identifier,
117                 debug_info=None,
118                 target_attrs=None,
119                 target_attrs_with_priority=None,
120                 pass_key=None):
121        assert debug_info is None or isinstance(debug_info, DebugInfo)
122        assert pass_key is _REF_BY_ID_PASS_KEY
123
124        Proxy.__init__(
125            self,
126            target_attrs=target_attrs,
127            target_attrs_with_priority=target_attrs_with_priority)
128        WithIdentifier.__init__(self, identifier)
129        self._ref_own_debug_info = debug_info
130
131    @property
132    def ref_own_debug_info(self):
133        """This reference's own DebugInfo."""
134        return self._ref_own_debug_info
135
136
137class RefByIdFactory(object):
138    """
139    Creates a group of references that are later resolvable.
140
141    All the references created by this factory are grouped per factory, and you
142    can apply a function to all the references.  This allows you to resolve all
143    the references at very end of the compilation phases.
144    """
145
146    def __init__(self, target_attrs=None, target_attrs_with_priority=None):
147        self._references = []
148        # |_is_frozen| is initially False and you can create new references.
149        # The first invocation of |for_each| freezes the factory and you can no
150        # longer create a new reference
151        self._is_frozen = False
152        self._target_attrs = target_attrs
153        self._target_attrs_with_priority = target_attrs_with_priority
154
155    def create(self, identifier, debug_info=None):
156        """
157        Creates a new instance of RefById.
158
159            Args:
160                identifier: An identifier to be resolved later.
161                debug_info: Where the reference is created, which is useful
162                    especially when the reference is unresolvable.
163        """
164        assert not self._is_frozen
165        ref = RefById(
166            identifier,
167            debug_info=debug_info,
168            target_attrs=self._target_attrs,
169            target_attrs_with_priority=self._target_attrs_with_priority,
170            pass_key=_REF_BY_ID_PASS_KEY)
171        self._references.append(ref)
172        return ref
173
174    def init_subclass_instance(self, instance, identifier, debug_info=None):
175        """
176        Initializes an instance of a subclass of RefById.
177        """
178        assert type(instance) is not RefById
179        assert isinstance(instance, RefById)
180        assert not self._is_frozen
181        RefById.__init__(
182            instance,
183            identifier,
184            debug_info=debug_info,
185            target_attrs=self._target_attrs,
186            target_attrs_with_priority=self._target_attrs_with_priority,
187            pass_key=_REF_BY_ID_PASS_KEY)
188        self._references.append(instance)
189
190    def for_each(self, callback):
191        """
192        Applies |callback| to all the references created by this factory.
193
194        You can no longer create a new reference.
195
196        Args:
197            callback: A callable that takes a reference as only the argument.
198                Return value is not used.
199        """
200        assert callable(callback)
201        self._is_frozen = True
202        for ref in self._references:
203            callback(ref)
204