Welcome, guest | Sign In | My Account | Store | Cart
# 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 <lukius at gmail dot com>
#
# 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 <lukius at gmail dot com>'
__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))
###############################################################################

History