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