• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1local middleclass = {
2  _VERSION     = 'middleclass v4.0.0',
3  _DESCRIPTION = 'Object Orientation for Lua',
4  _URL         = 'https://github.com/kikito/middleclass',
5  _LICENSE     = [[
6    MIT LICENSE
7
8    Copyright (c) 2011 Enrique García Cota
9
10    Permission is hereby granted, free of charge, to any person obtaining a
11    copy of this software and associated documentation files (the
12    "Software"), to deal in the Software without restriction, including
13    without limitation the rights to use, copy, modify, merge, publish,
14    distribute, sublicense, and/or sell copies of the Software, and to
15    permit persons to whom the Software is furnished to do so, subject to
16    the following conditions:
17
18    The above copyright notice and this permission notice shall be included
19    in all copies or substantial portions of the Software.
20
21    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28  ]]
29}
30
31local function _createIndexWrapper(aClass, f)
32  if f == nil then
33    return aClass.__instanceDict
34  else
35    return function(self, name)
36      local value = aClass.__instanceDict[name]
37
38      if value ~= nil then
39        return value
40      elseif type(f) == "function" then
41        return (f(self, name))
42      else
43        return f[name]
44      end
45    end
46  end
47end
48
49local function _propagateInstanceMethod(aClass, name, f)
50  f = name == "__index" and _createIndexWrapper(aClass, f) or f
51  aClass.__instanceDict[name] = f
52
53  for subclass in pairs(aClass.subclasses) do
54    if rawget(subclass.__declaredMethods, name) == nil then
55      _propagateInstanceMethod(subclass, name, f)
56    end
57  end
58end
59
60local function _declareInstanceMethod(aClass, name, f)
61  aClass.__declaredMethods[name] = f
62
63  if f == nil and aClass.super then
64    f = aClass.super.__instanceDict[name]
65  end
66
67  _propagateInstanceMethod(aClass, name, f)
68end
69
70local function _tostring(self) return "class " .. self.name end
71local function _call(self, ...) return self:new(...) end
72
73local function _createClass(name, super)
74  local dict = {}
75  dict.__index = dict
76
77  local aClass = { name = name, super = super, static = {},
78                   __instanceDict = dict, __declaredMethods = {},
79                   subclasses = setmetatable({}, {__mode='k'})  }
80
81  if super then
82    setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) or super.static[k] end })
83  else
84    setmetatable(aClass.static, { __index = function(_,k) return rawget(dict,k) end })
85  end
86
87  setmetatable(aClass, { __index = aClass.static, __tostring = _tostring,
88                         __call = _call, __newindex = _declareInstanceMethod })
89
90  return aClass
91end
92
93local function _includeMixin(aClass, mixin)
94  assert(type(mixin) == 'table', "mixin must be a table")
95
96  for name,method in pairs(mixin) do
97    if name ~= "included" and name ~= "static" then aClass[name] = method end
98  end
99
100  for name,method in pairs(mixin.static or {}) do
101    aClass.static[name] = method
102  end
103
104  if type(mixin.included)=="function" then mixin:included(aClass) end
105  return aClass
106end
107
108local DefaultMixin = {
109  __tostring   = function(self) return "instance of " .. tostring(self.class) end,
110
111  initialize   = function(self, ...) end,
112
113  isInstanceOf = function(self, aClass)
114    return type(self)       == 'table' and
115           type(self.class) == 'table' and
116           type(aClass)     == 'table' and
117           ( aClass == self.class or
118             type(aClass.isSubclassOf) == 'function' and
119             self.class:isSubclassOf(aClass) )
120  end,
121
122  static = {
123    allocate = function(self)
124      assert(type(self) == 'table', "Make sure that you are using 'Class:allocate' instead of 'Class.allocate'")
125      return setmetatable({ class = self }, self.__instanceDict)
126    end,
127
128    new = function(self, ...)
129      assert(type(self) == 'table', "Make sure that you are using 'Class:new' instead of 'Class.new'")
130      local instance = self:allocate()
131      instance:initialize(...)
132      return instance
133    end,
134
135    subclass = function(self, name)
136      assert(type(self) == 'table', "Make sure that you are using 'Class:subclass' instead of 'Class.subclass'")
137      assert(type(name) == "string", "You must provide a name(string) for your class")
138
139      local subclass = _createClass(name, self)
140
141      for methodName, f in pairs(self.__instanceDict) do
142        _propagateInstanceMethod(subclass, methodName, f)
143      end
144      subclass.initialize = function(instance, ...) return self.initialize(instance, ...) end
145
146      self.subclasses[subclass] = true
147      self:subclassed(subclass)
148
149      return subclass
150    end,
151
152    subclassed = function(self, other) end,
153
154    isSubclassOf = function(self, other)
155      return type(other)      == 'table' and
156             type(self)       == 'table' and
157             type(self.super) == 'table' and
158             ( self.super == other or
159               type(self.super.isSubclassOf) == 'function' and
160               self.super:isSubclassOf(other) )
161    end,
162
163    include = function(self, ...)
164      assert(type(self) == 'table', "Make sure you that you are using 'Class:include' instead of 'Class.include'")
165      for _,mixin in ipairs({...}) do _includeMixin(self, mixin) end
166      return self
167    end
168  }
169}
170
171function middleclass.class(name, super)
172  assert(type(name) == 'string', "A name (string) is needed for the new class")
173  return super and super:subclass(name) or _includeMixin(_createClass(name), DefaultMixin)
174end
175
176setmetatable(middleclass, { __call = function(_, ...) return middleclass.class(...) end })
177
178return middleclass
179