xref: /aosp_15_r20/development/tools/axl/singletonmixin.py (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker"""
2*90c8c64dSAndroid Build Coastguard WorkerA Python Singleton mixin class that makes use of some of the ideas
3*90c8c64dSAndroid Build Coastguard Workerfound at http://c2.com/cgi/wiki?PythonSingleton. Just inherit
4*90c8c64dSAndroid Build Coastguard Workerfrom it and you have a singleton. No code is required in
5*90c8c64dSAndroid Build Coastguard Workersubclasses to create singleton behavior -- inheritance from
6*90c8c64dSAndroid Build Coastguard WorkerSingleton is all that is needed.
7*90c8c64dSAndroid Build Coastguard Worker
8*90c8c64dSAndroid Build Coastguard WorkerAssume S is a class that inherits from Singleton. Useful behaviors
9*90c8c64dSAndroid Build Coastguard Workerare:
10*90c8c64dSAndroid Build Coastguard Worker
11*90c8c64dSAndroid Build Coastguard Worker1) Getting the singleton:
12*90c8c64dSAndroid Build Coastguard Worker
13*90c8c64dSAndroid Build Coastguard Worker    S.getInstance()
14*90c8c64dSAndroid Build Coastguard Worker
15*90c8c64dSAndroid Build Coastguard Workerreturns the instance of S. If none exists, it is created.
16*90c8c64dSAndroid Build Coastguard Worker
17*90c8c64dSAndroid Build Coastguard Worker2) The usual idiom to construct an instance by calling the class, i.e.
18*90c8c64dSAndroid Build Coastguard Worker
19*90c8c64dSAndroid Build Coastguard Worker    S()
20*90c8c64dSAndroid Build Coastguard Worker
21*90c8c64dSAndroid Build Coastguard Workeris disabled for the sake of clarity. If it were allowed, a programmer
22*90c8c64dSAndroid Build Coastguard Workerwho didn't happen  notice the inheritance from Singleton might think he
23*90c8c64dSAndroid Build Coastguard Workerwas creating a new instance. So it is felt that it is better to
24*90c8c64dSAndroid Build Coastguard Workermake that clearer by requiring the call of a class method that is defined in
25*90c8c64dSAndroid Build Coastguard WorkerSingleton. An attempt to instantiate via S() will restult in an SingletonException
26*90c8c64dSAndroid Build Coastguard Workerbeing raised.
27*90c8c64dSAndroid Build Coastguard Worker
28*90c8c64dSAndroid Build Coastguard Worker3) If S.__init__(.) requires parameters, include them in the
29*90c8c64dSAndroid Build Coastguard Workerfirst call to S.getInstance(.). If subsequent calls have parameters,
30*90c8c64dSAndroid Build Coastguard Workera SingletonException is raised.
31*90c8c64dSAndroid Build Coastguard Worker
32*90c8c64dSAndroid Build Coastguard Worker4) As an implementation detail, classes that inherit
33*90c8c64dSAndroid Build Coastguard Workerfrom Singleton may not have their own __new__
34*90c8c64dSAndroid Build Coastguard Workermethods. To make sure this requirement is followed,
35*90c8c64dSAndroid Build Coastguard Workeran exception is raised if a Singleton subclass includ
36*90c8c64dSAndroid Build Coastguard Workeres __new__. This happens at subclass instantiation
37*90c8c64dSAndroid Build Coastguard Workertime (by means of the MetaSingleton metaclass.
38*90c8c64dSAndroid Build Coastguard Worker
39*90c8c64dSAndroid Build Coastguard WorkerBy Gary Robinson, [email protected]. No rights reserved --
40*90c8c64dSAndroid Build Coastguard Workerplaced in the public domain -- which is only reasonable considering
41*90c8c64dSAndroid Build Coastguard Workerhow much it owes to other people's version which are in the
42*90c8c64dSAndroid Build Coastguard Workerpublic domain. The idea of using a metaclass came from
43*90c8c64dSAndroid Build Coastguard Workera  comment on Gary's blog (see
44*90c8c64dSAndroid Build Coastguard Workerhttp://www.garyrobinson.net/2004/03/python_singleto.html#comments).
45*90c8c64dSAndroid Build Coastguard WorkerNot guaranteed to be fit for any particular purpose.
46*90c8c64dSAndroid Build Coastguard Worker"""
47*90c8c64dSAndroid Build Coastguard Worker
48*90c8c64dSAndroid Build Coastguard Workerclass SingletonException(Exception):
49*90c8c64dSAndroid Build Coastguard Worker    pass
50*90c8c64dSAndroid Build Coastguard Worker
51*90c8c64dSAndroid Build Coastguard Workerclass MetaSingleton(type):
52*90c8c64dSAndroid Build Coastguard Worker    def __new__(metaclass, strName, tupBases, dict):
53*90c8c64dSAndroid Build Coastguard Worker        if '__new__' in dict:
54*90c8c64dSAndroid Build Coastguard Worker            raise SingletonException, 'Can not override __new__ in a Singleton'
55*90c8c64dSAndroid Build Coastguard Worker        return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict)
56*90c8c64dSAndroid Build Coastguard Worker
57*90c8c64dSAndroid Build Coastguard Worker    def __call__(cls, *lstArgs, **dictArgs):
58*90c8c64dSAndroid Build Coastguard Worker        raise SingletonException, 'Singletons may only be instantiated through getInstance()'
59*90c8c64dSAndroid Build Coastguard Worker
60*90c8c64dSAndroid Build Coastguard Workerclass Singleton(object):
61*90c8c64dSAndroid Build Coastguard Worker    __metaclass__ = MetaSingleton
62*90c8c64dSAndroid Build Coastguard Worker
63*90c8c64dSAndroid Build Coastguard Worker    def getInstance(cls, *lstArgs):
64*90c8c64dSAndroid Build Coastguard Worker        """
65*90c8c64dSAndroid Build Coastguard Worker        Call this to instantiate an instance or retrieve the existing instance.
66*90c8c64dSAndroid Build Coastguard Worker        If the singleton requires args to be instantiated, include them the first
67*90c8c64dSAndroid Build Coastguard Worker        time you call getInstance.
68*90c8c64dSAndroid Build Coastguard Worker        """
69*90c8c64dSAndroid Build Coastguard Worker        if cls._isInstantiated():
70*90c8c64dSAndroid Build Coastguard Worker            if len(lstArgs) != 0:
71*90c8c64dSAndroid Build Coastguard Worker                raise SingletonException, 'If no supplied args, singleton must already be instantiated, or __init__ must require no args'
72*90c8c64dSAndroid Build Coastguard Worker        else:
73*90c8c64dSAndroid Build Coastguard Worker            if len(lstArgs) != cls._getConstructionArgCountNotCountingSelf():
74*90c8c64dSAndroid Build Coastguard Worker                raise SingletonException, 'If the singleton requires __init__ args, supply them on first instantiation'
75*90c8c64dSAndroid Build Coastguard Worker            instance = cls.__new__(cls)
76*90c8c64dSAndroid Build Coastguard Worker            instance.__init__(*lstArgs)
77*90c8c64dSAndroid Build Coastguard Worker            cls.cInstance = instance
78*90c8c64dSAndroid Build Coastguard Worker        return cls.cInstance
79*90c8c64dSAndroid Build Coastguard Worker    getInstance = classmethod(getInstance)
80*90c8c64dSAndroid Build Coastguard Worker
81*90c8c64dSAndroid Build Coastguard Worker    def _isInstantiated(cls):
82*90c8c64dSAndroid Build Coastguard Worker        return hasattr(cls, 'cInstance')
83*90c8c64dSAndroid Build Coastguard Worker    _isInstantiated = classmethod(_isInstantiated)
84*90c8c64dSAndroid Build Coastguard Worker
85*90c8c64dSAndroid Build Coastguard Worker    def _getConstructionArgCountNotCountingSelf(cls):
86*90c8c64dSAndroid Build Coastguard Worker        return cls.__init__.im_func.func_code.co_argcount - 1
87*90c8c64dSAndroid Build Coastguard Worker    _getConstructionArgCountNotCountingSelf = classmethod(_getConstructionArgCountNotCountingSelf)
88*90c8c64dSAndroid Build Coastguard Worker
89*90c8c64dSAndroid Build Coastguard Worker    def _forgetClassInstanceReferenceForTesting(cls):
90*90c8c64dSAndroid Build Coastguard Worker        """
91*90c8c64dSAndroid Build Coastguard Worker        This is designed for convenience in testing -- sometimes you
92*90c8c64dSAndroid Build Coastguard Worker        want to get rid of a singleton during test code to see what
93*90c8c64dSAndroid Build Coastguard Worker        happens when you call getInstance() under a new situation.
94*90c8c64dSAndroid Build Coastguard Worker
95*90c8c64dSAndroid Build Coastguard Worker        To really delete the object, all external references to it
96*90c8c64dSAndroid Build Coastguard Worker        also need to be deleted.
97*90c8c64dSAndroid Build Coastguard Worker        """
98*90c8c64dSAndroid Build Coastguard Worker        try:
99*90c8c64dSAndroid Build Coastguard Worker            delattr(cls,'cInstance')
100*90c8c64dSAndroid Build Coastguard Worker        except AttributeError:
101*90c8c64dSAndroid Build Coastguard Worker            # run up the chain of base classes until we find the one that has the instance
102*90c8c64dSAndroid Build Coastguard Worker            # and then delete it there
103*90c8c64dSAndroid Build Coastguard Worker            for baseClass in cls.__bases__:
104*90c8c64dSAndroid Build Coastguard Worker                if issubclass(baseClass, Singleton):
105*90c8c64dSAndroid Build Coastguard Worker                    baseClass._forgetClassInstanceReferenceForTesting()
106*90c8c64dSAndroid Build Coastguard Worker    _forgetClassInstanceReferenceForTesting = classmethod(_forgetClassInstanceReferenceForTesting)
107*90c8c64dSAndroid Build Coastguard Worker
108*90c8c64dSAndroid Build Coastguard Worker
109*90c8c64dSAndroid Build Coastguard Workerif __name__ == '__main__':
110*90c8c64dSAndroid Build Coastguard Worker    import unittest
111*90c8c64dSAndroid Build Coastguard Worker
112*90c8c64dSAndroid Build Coastguard Worker    class PublicInterfaceTest(unittest.TestCase):
113*90c8c64dSAndroid Build Coastguard Worker        def testReturnsSameObject(self):
114*90c8c64dSAndroid Build Coastguard Worker            """
115*90c8c64dSAndroid Build Coastguard Worker            Demonstrates normal use -- just call getInstance and it returns a singleton instance
116*90c8c64dSAndroid Build Coastguard Worker            """
117*90c8c64dSAndroid Build Coastguard Worker
118*90c8c64dSAndroid Build Coastguard Worker            class A(Singleton):
119*90c8c64dSAndroid Build Coastguard Worker                def __init__(self):
120*90c8c64dSAndroid Build Coastguard Worker                    super(A, self).__init__()
121*90c8c64dSAndroid Build Coastguard Worker
122*90c8c64dSAndroid Build Coastguard Worker            a1 = A.getInstance()
123*90c8c64dSAndroid Build Coastguard Worker            a2 = A.getInstance()
124*90c8c64dSAndroid Build Coastguard Worker            self.assertEquals(id(a1), id(a2))
125*90c8c64dSAndroid Build Coastguard Worker
126*90c8c64dSAndroid Build Coastguard Worker        def testInstantiateWithMultiArgConstructor(self):
127*90c8c64dSAndroid Build Coastguard Worker            """
128*90c8c64dSAndroid Build Coastguard Worker            If the singleton needs args to construct, include them in the first
129*90c8c64dSAndroid Build Coastguard Worker            call to get instances.
130*90c8c64dSAndroid Build Coastguard Worker            """
131*90c8c64dSAndroid Build Coastguard Worker
132*90c8c64dSAndroid Build Coastguard Worker            class B(Singleton):
133*90c8c64dSAndroid Build Coastguard Worker
134*90c8c64dSAndroid Build Coastguard Worker                def __init__(self, arg1, arg2):
135*90c8c64dSAndroid Build Coastguard Worker                    super(B, self).__init__()
136*90c8c64dSAndroid Build Coastguard Worker                    self.arg1 = arg1
137*90c8c64dSAndroid Build Coastguard Worker                    self.arg2 = arg2
138*90c8c64dSAndroid Build Coastguard Worker
139*90c8c64dSAndroid Build Coastguard Worker            b1 = B.getInstance('arg1 value', 'arg2 value')
140*90c8c64dSAndroid Build Coastguard Worker            b2 = B.getInstance()
141*90c8c64dSAndroid Build Coastguard Worker            self.assertEquals(b1.arg1, 'arg1 value')
142*90c8c64dSAndroid Build Coastguard Worker            self.assertEquals(b1.arg2, 'arg2 value')
143*90c8c64dSAndroid Build Coastguard Worker            self.assertEquals(id(b1), id(b2))
144*90c8c64dSAndroid Build Coastguard Worker
145*90c8c64dSAndroid Build Coastguard Worker
146*90c8c64dSAndroid Build Coastguard Worker        def testTryToInstantiateWithoutNeededArgs(self):
147*90c8c64dSAndroid Build Coastguard Worker
148*90c8c64dSAndroid Build Coastguard Worker            class B(Singleton):
149*90c8c64dSAndroid Build Coastguard Worker
150*90c8c64dSAndroid Build Coastguard Worker                def __init__(self, arg1, arg2):
151*90c8c64dSAndroid Build Coastguard Worker                    super(B, self).__init__()
152*90c8c64dSAndroid Build Coastguard Worker                    self.arg1 = arg1
153*90c8c64dSAndroid Build Coastguard Worker                    self.arg2 = arg2
154*90c8c64dSAndroid Build Coastguard Worker
155*90c8c64dSAndroid Build Coastguard Worker            self.assertRaises(SingletonException, B.getInstance)
156*90c8c64dSAndroid Build Coastguard Worker
157*90c8c64dSAndroid Build Coastguard Worker        def testTryToInstantiateWithoutGetInstance(self):
158*90c8c64dSAndroid Build Coastguard Worker            """
159*90c8c64dSAndroid Build Coastguard Worker            Demonstrates that singletons can ONLY be instantiated through
160*90c8c64dSAndroid Build Coastguard Worker            getInstance, as long as they call Singleton.__init__ during construction.
161*90c8c64dSAndroid Build Coastguard Worker
162*90c8c64dSAndroid Build Coastguard Worker            If this check is not required, you don't need to call Singleton.__init__().
163*90c8c64dSAndroid Build Coastguard Worker            """
164*90c8c64dSAndroid Build Coastguard Worker
165*90c8c64dSAndroid Build Coastguard Worker            class A(Singleton):
166*90c8c64dSAndroid Build Coastguard Worker                def __init__(self):
167*90c8c64dSAndroid Build Coastguard Worker                    super(A, self).__init__()
168*90c8c64dSAndroid Build Coastguard Worker
169*90c8c64dSAndroid Build Coastguard Worker            self.assertRaises(SingletonException, A)
170*90c8c64dSAndroid Build Coastguard Worker
171*90c8c64dSAndroid Build Coastguard Worker        def testDontAllowNew(self):
172*90c8c64dSAndroid Build Coastguard Worker
173*90c8c64dSAndroid Build Coastguard Worker            def instantiatedAnIllegalClass():
174*90c8c64dSAndroid Build Coastguard Worker                class A(Singleton):
175*90c8c64dSAndroid Build Coastguard Worker                    def __init__(self):
176*90c8c64dSAndroid Build Coastguard Worker                        super(A, self).__init__()
177*90c8c64dSAndroid Build Coastguard Worker
178*90c8c64dSAndroid Build Coastguard Worker                    def __new__(metaclass, strName, tupBases, dict):
179*90c8c64dSAndroid Build Coastguard Worker                        return super(MetaSingleton,metaclass).__new__(metaclass, strName, tupBases, dict)
180*90c8c64dSAndroid Build Coastguard Worker
181*90c8c64dSAndroid Build Coastguard Worker            self.assertRaises(SingletonException, instantiatedAnIllegalClass)
182*90c8c64dSAndroid Build Coastguard Worker
183*90c8c64dSAndroid Build Coastguard Worker
184*90c8c64dSAndroid Build Coastguard Worker        def testDontAllowArgsAfterConstruction(self):
185*90c8c64dSAndroid Build Coastguard Worker            class B(Singleton):
186*90c8c64dSAndroid Build Coastguard Worker
187*90c8c64dSAndroid Build Coastguard Worker                def __init__(self, arg1, arg2):
188*90c8c64dSAndroid Build Coastguard Worker                    super(B, self).__init__()
189*90c8c64dSAndroid Build Coastguard Worker                    self.arg1 = arg1
190*90c8c64dSAndroid Build Coastguard Worker                    self.arg2 = arg2
191*90c8c64dSAndroid Build Coastguard Worker
192*90c8c64dSAndroid Build Coastguard Worker            b1 = B.getInstance('arg1 value', 'arg2 value')
193*90c8c64dSAndroid Build Coastguard Worker            self.assertRaises(SingletonException, B, 'arg1 value', 'arg2 value')
194*90c8c64dSAndroid Build Coastguard Worker
195*90c8c64dSAndroid Build Coastguard Worker        def test_forgetClassInstanceReferenceForTesting(self):
196*90c8c64dSAndroid Build Coastguard Worker            class A(Singleton):
197*90c8c64dSAndroid Build Coastguard Worker                def __init__(self):
198*90c8c64dSAndroid Build Coastguard Worker                    super(A, self).__init__()
199*90c8c64dSAndroid Build Coastguard Worker            class B(A):
200*90c8c64dSAndroid Build Coastguard Worker                def __init__(self):
201*90c8c64dSAndroid Build Coastguard Worker                    super(B, self).__init__()
202*90c8c64dSAndroid Build Coastguard Worker
203*90c8c64dSAndroid Build Coastguard Worker            # check that changing the class after forgetting the instance produces
204*90c8c64dSAndroid Build Coastguard Worker            # an instance of the new class
205*90c8c64dSAndroid Build Coastguard Worker            a = A.getInstance()
206*90c8c64dSAndroid Build Coastguard Worker            assert a.__class__.__name__ == 'A'
207*90c8c64dSAndroid Build Coastguard Worker            A._forgetClassInstanceReferenceForTesting()
208*90c8c64dSAndroid Build Coastguard Worker            b = B.getInstance()
209*90c8c64dSAndroid Build Coastguard Worker            assert b.__class__.__name__ == 'B'
210*90c8c64dSAndroid Build Coastguard Worker
211*90c8c64dSAndroid Build Coastguard Worker            # check that invoking the 'forget' on a subclass still deletes the instance
212*90c8c64dSAndroid Build Coastguard Worker            B._forgetClassInstanceReferenceForTesting()
213*90c8c64dSAndroid Build Coastguard Worker            a = A.getInstance()
214*90c8c64dSAndroid Build Coastguard Worker            B._forgetClassInstanceReferenceForTesting()
215*90c8c64dSAndroid Build Coastguard Worker            b = B.getInstance()
216*90c8c64dSAndroid Build Coastguard Worker            assert b.__class__.__name__ == 'B'
217*90c8c64dSAndroid Build Coastguard Worker
218*90c8c64dSAndroid Build Coastguard Worker    unittest.main()
219*90c8c64dSAndroid Build Coastguard Worker
220*90c8c64dSAndroid Build Coastguard Worker
221