# concrecte_class_finder.py
# An object-oriented alternative to complex if-then-else or switches
# Tested under Python 2.7 and 2.6.6 only
#
# Copyright (C) 2011 by Lucio Santi
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
__author__ = 'Lucio Santi '
__version__ = '1.0'
__all__ = ['ConcreteClassFinder',
'SubclassNotFoundException',
'ClassNotFoundException',
'MultipleSubclassesFoundException',
'MultipleClassesFoundException']
###############################################################################
class ConcreteClassFinder(object):
"""An object that searches for a suitable class for handling a single
object or a collection of them. This search can be performed over the leaf
subclasses of a base class as well as over an iterable object containing
classes (e.g., a list).
Each of the classes inspected should provide a user-defined class method
that indicates whether the respective class can correctly handle the
desired object/s. By default, this method name is 'can_handle'/
'can_handle_objects'.
Actions can be specified for exceptional cases where no classes or multiple
classes are found.
"""
@classmethod
def __default_testing_method(cls):
return 'can_handle'
@classmethod
def __default_testing_method_with_args(cls):
return 'can_handle_objects'
@classmethod
def __class_handles(cls, klass, method_name, args):
method = getattr(klass, method_name)
try:
return method(args)
except Exception:
return False
@classmethod
def find_subclass(cls, base_class, object, if_none = None, if_many = None, method = None):
if( method is None ):
method = cls.__default_testing_method()
return cls.__pre_find_subclass(base_class, method, object, if_none, if_many)
@classmethod
def find_subclass_with_args(cls, base_class, args, if_none = None, if_many = None, method = None):
if( method is None ):
method = cls.__default_testing_method_with_args()
return cls.__pre_find_subclass(base_class, method, args, if_none, if_many)
@classmethod
def __pre_find_subclass(cls, base_class, method, args, if_none = None, if_many = None):
def default_action_if_none():
raise SubclassNotFoundException(base_class, method, args)
def default_action_if_many(candidates):
raise MultipleSubclassesFoundException(base_class, method, args, candidates)
if( if_none is None ):
if_none = default_action_if_none
if( if_many is None ):
if_many = default_action_if_many
candidates = [klass for klass in LeafSubclassRetriever(base_class).value() if hasattr(klass, method)]
return cls.__find_class(candidates, method, args, if_none, if_many)
@classmethod
def find_class(cls, classes, object, if_none = None, if_many = None, method = None):
if( method is None ):
method = cls.__default_testing_method()
return cls.__pre_find_class(classes, method, object, if_none, if_many)
@classmethod
def find_class_with_args(cls, classes, args, if_none = None, if_many = None, method = None):
if( method is None ):
method = cls.__default_testing_method_with_args()
return cls.__pre_find_class(classes, method, args, if_none, if_many)
@classmethod
def __pre_find_class(cls, classes, method, args, if_none = None, if_many = None):
def default_action_if_none():
raise ClassNotFoundException(classes, method, args)
def default_action_if_many(candidates):
raise MultipleClassesFoundException(classes, method, args, candidates)
if( if_none is None ):
if_none = default_action_if_none
if( if_many is None ):
if_many = default_action_if_many
return cls.__find_class(classes, method, args, if_none, if_many)
@classmethod
def __find_class(cls, classes, method, args, action_if_none, action_if_many):
suitable_classes = filter(lambda klass: cls.__class_handles(klass, method, args), classes)
if( len(suitable_classes) < 1 ): return action_if_none()
if( len(suitable_classes) > 1 ): return action_if_many(suitable_classes)
return suitable_classes[0]
###############################################################################
###############################################################################
class LeafSubclassRetriever(object):
def __init__(self, base_class):
self.base_class = base_class
def value(self):
direct_subclasses = self.base_class.__subclasses__()
leaf_subclasses = list()
for klass in direct_subclasses:
if( len(klass.__subclasses__()) > 0 ):
leaf_subclasses += LeafSubclassRetriever(klass).value()
else:
leaf_subclasses.append(klass)
return leaf_subclasses
###############################################################################
###############################################################################
class ClassFindingException(Exception):
def __init__(self, method, args, candidates = None):
self.method = method
self.arguments = args
self.candidates = candidates
def __str__(self):
raise NotImplementedError('subclass responsibility')
class SubclassNotFoundException(ClassFindingException):
def __init__(self, base_class, method, args):
super(SubclassNotFoundException, self).__init__(method, args)
self.base_class = base_class
def __str__(self):
return 'base class %s has no subclass satisfying %s(%s)' \
% (self.base_class.__name__, str(self.method), str(self.arguments))
class ClassNotFoundException(ClassFindingException):
def __init__(self, classes, method, args):
super(ClassNotFoundException, self).__init__(method, args)
self.classes = classes
def __str__(self):
return '%s does not contain any class satisfying %s(%s)' \
% (str(self.classes), str(self.method), str(self.arguments))
class MultipleSubclassesFoundException(ClassFindingException):
def __init__(self, base_class, method, args, candidates):
super(MultipleSubclassesFoundException, self).__init__(method, args, candidates)
self.base_class = base_class
def __str__(self):
return 'base class %s has multiple subclasses satisfying %s(%s): %s' \
% (self.base_class.__name__, str(self.method), str(self.arguments), str(self.candidates))
class MultipleClassesFoundException(ClassFindingException):
def __init__(self, classes, method, args, candidates):
super(MultipleSubclassesFoundException, self).__init__(method, args, candidates)
self.classes = classes
def __str__(self):
return '%s contains multiple classes satisfying %s(%s): %s' \
% (str(self.classes), str(self.method), str(self.arguments), str(self.candidates))
###############################################################################