blob: 7141145e2c3cf5ef4100a861f2911fea5264035c [file] [log] [blame]
# 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