| # Copyright 2015 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Abstract ObjectFactory class used for injection of external dependencies.""" |
| |
| from __future__ import print_function |
| |
| import functools |
| |
| |
| class ObjectFactoryIllegalOperation(Exception): |
| """Raised when attemping an illegal ObjectFactory operation.""" |
| |
| _NO_SINGLETON_INSTANCE = object() |
| |
| class ObjectFactory(object): |
| """Abstract object factory, used for injection of external dependencies. |
| |
| A call to Setup(...) is necessary before a call to GetInstance(). |
| """ |
| |
| _object_name = '' |
| _is_setup = False |
| _setup_type = None |
| _setup_instance = None |
| _types = {} |
| |
| def __init__(self, object_name, setup_types, allowed_transitions=None): |
| """ObjectFactory constructor. |
| |
| Args: |
| object_name: Human readable name for the type of object that this factory |
| generates. |
| setup_types: A (set up type name -> generator function) dictionary, which |
| teaches ObjectFactory how to construct instances after setup |
| has been called. For set up types where a singleton instance |
| is specified at setup(...) time, generator function should be |
| None. |
| allowed_transitions: Optional function, where |
| allowed_transitions(from_type, to_type) specifies |
| whether transition from |from_type| to |to_type| is |
| allowed. |
| |
| If unspecified, no transitions are allowed. |
| """ |
| |
| self._object_name = object_name |
| self._types = setup_types |
| self._allowed_transitions = allowed_transitions |
| |
| def Setup(self, setup_type, singleton_instance=_NO_SINGLETON_INSTANCE): |
| # Prevent set up to unknown types. |
| if setup_type not in self._types: |
| raise ObjectFactoryIllegalOperation( |
| 'Unknown %s setup_type %s' % (self._object_name, setup_type)) |
| |
| # Prevent illegal setup transitions. |
| if self._is_setup: |
| if self._allowed_transitions: |
| if not self._allowed_transitions(self._setup_type, setup_type): |
| raise ObjectFactoryIllegalOperation( |
| 'Illegal set up transition from %s to %s.' % (self._setup_type, |
| setup_type)) |
| else: |
| raise ObjectFactoryIllegalOperation( |
| '%s already set up.' % self._object_name) |
| |
| # Allow singleton_instance if and only if factory method for this type is |
| # None. |
| instance_supplied = (singleton_instance != _NO_SINGLETON_INSTANCE) |
| factory_is_none = (self._types[setup_type] is None) |
| if instance_supplied != factory_is_none: |
| raise ObjectFactoryIllegalOperation( |
| 'singleton_instance should be supplied if and only if setup_type has ' |
| 'a factory that is None.') |
| |
| self._setup_type = setup_type |
| self._setup_instance = singleton_instance |
| self._is_setup = True |
| |
| @property |
| def is_setup(self): |
| """Returns True iff a call to get_instance is expected to succeed.""" |
| return self._is_setup |
| |
| @property |
| def setup_type(self): |
| """Returns the setup_type.""" |
| return self._setup_type |
| |
| def GetInstance(self): |
| """Returns an object instance iff setup has been called. |
| |
| Raises: |
| ObjectFactoryIllegalOperation: if setup has not yet been called. |
| """ |
| if not self.is_setup: |
| raise ObjectFactoryIllegalOperation( |
| '%s is not set up.' % self._object_name) |
| if self._setup_instance != _NO_SINGLETON_INSTANCE: |
| return self._setup_instance |
| return self._types[self.setup_type]() |
| |
| def _clear_setup(self): |
| """Clear setup, for testing purposes only.""" |
| self._setup_type = None |
| self._is_setup = False |
| self._setup_instance = _NO_SINGLETON_INSTANCE |
| |
| |
| def CachedFunctionCall(function): |
| """Wraps a parameterless |function| in a cache.""" |
| cached_value = [] |
| |
| @functools.wraps(function) |
| def wrapper(): |
| if not cached_value: |
| cached_value.append(function()) |
| return cached_value[0] |
| |
| return wrapper |