vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
--[[
Name: AceOO-2.0
Revision: $Rev: 7845 $
Author(s): ckknight (ckknight@gmail.com)
           kergoth (kergoth@handhelds.org)
Website: http://www.wowace.com/
Documentation: http://wiki.wowace.com/index.php/AceOO-2.0
SVN: http://svn.wowace.com/root/trunk/Ace2/AceOO-2.0
Description: Library to provide an object-orientation framework.
Dependencies: AceLibrary, Compost-2.0 (optional)
]]

local MAJOR_VERSION = "AceOO-2.0"
local MINOR_VERSION = "$Revision: 7845 $"

-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version
if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end

local Compost = AceLibrary:HasInstance("Compost-2.0") and AceLibrary("Compost-2.0")

local AceOO = {
        error = AceLibrary.error,
        argCheck = AceLibrary.argCheck
}

-- @function    getuid
-- @brief               Obtain a unique string identifier for the object in question.
-- @param t             The object to obtain the uid for.
-- @return              The uid string.
local function pad(cap)
        return string.rep('0', 8 - string.len(cap)) .. cap
end
local function getuid(t)
        local mt = getmetatable(t)
        setmetatable(t, nil)
        local str = tostring(t)
        setmetatable(t, mt)
        local _,_,cap = string.find(str, '[^:]*: 0x(.*)$')
        if cap then return pad(cap) end
        _,_,cap = string.find(str, '[^:]*: (.*)$')
        if cap then return pad(cap) end
end

local function getlibrary(o)
        if type(o) == "table" then
                return o
        elseif type(o) == "string" then
                if not AceLibrary:HasInstance(o) then
                        AceOO:error("Library %q does not exist.", o)
                end
                return AceLibrary(o)
        end
end

-- @function            Factory
-- @brief                       Construct a factory for the creation of objects.
-- @param obj           The object whose init method will be called on the new factory
--                                      object.
-- @param newobj        The object whose init method will be called on the new
--                                      objects that the Factory creates, to initialize them.
-- @param (a1..a20) Arguments which will be passed to obj.init() in addition
--                                      to the Factory object.
-- @return                      The new factory which creates a newobj when its new method is called,
--                                      or when it is called directly (__call metamethod).
local Factory
do
        local function new(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
                                                        a13, a14, a15, a16, a17, a18, a19, a20)
                local t = Compost and Compost:Acquire() or {}
                local uid = getuid(t)
                local l = getlibrary
                obj:init(t, l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7),
                                        l(a8), l(a9), l(a10), l(a11), l(a12), l(a13),
                                        l(a14), l(a15), l(a16), l(a17), l(a18), l(a19),
                                        l(a20))
                t.uid = uid
                return t
        end
        
        local function createnew(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
                                                                        a11, a12, a13, a14, a15, a16, a17, a18,
                                                                        a19, a20)
                local o = self.prototype
                local l = getlibrary
                return new(o, l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7),
                                                l(a8), l(a9), l(a10), l(a11), l(a12), l(a13),
                                                l(a14), l(a15), l(a16), l(a17), l(a18), l(a19),
                                                l(a20))
        end

        function Factory(obj, newobj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
                                                                        a11, a12, a13, a14, a15, a16, a17, a18,
                                                                        a19, a20)
                local t = new(obj, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
                                                        a13, a14, a15, a16, a17, a18, a19, a20)
                t.prototype = newobj
                t.new = createnew
                getmetatable(t).__call = t.new
                return t
        end
end


local function objtostring(self)
        if self.ToString then
                return self:ToString()
        elseif self.GetLibraryVersion then
                return (self:GetLibraryVersion())
        elseif self.super then
                local s = "Sub-" .. tostring(self.super)
                local first = true
                if self.interfaces then
                        for interface in pairs(self.interfaces) do
                                if first then
                                        s = s .. "(" .. tostring(interface)
                                        first = false
                                else
                                        s = s .. ", " .. tostring(interface)
                                end
                        end
                end
                if self.mixins then
                        for mixin in pairs(self.mixins) do
                                if first then
                                        s = s .. tostring(mixin)
                                        first = false
                                else
                                        s = s .. ", " .. tostring(mixin)
                                end
                        end
                end
                if first then
                        if self.uid then
                                return s .. ":" .. self.uid
                        else
                                return s
                        end
                else
                        return s .. ")"
                end
        else
                return self.uid and 'Subclass:' .. self.uid or 'Subclass'
        end
end

-- @table                       Object
-- @brief                       Base of all objects, including Class.
--
-- @method                      init
-- @brief                       Initialize a new object.
-- @param newobject The object to initialize
-- @param class         The class to make newobject inherit from
local Object
do
        Object = {}
        function Object:init(newobject, class)
                local parent = class or self
                if not rawget(newobject, 'uid') then
                        newobject.uid = getuid(newobject)
                end
                local mt = Compost and Compost:AcquireHash(
                        '__index', parent,
                        '__tostring', objtostring
                ) or {
                        __index = parent,
                        __tostring = objtostring,
                }
                setmetatable(newobject, mt)
        end
        Object.uid = getuid(Object)
        setmetatable(Object, { __tostring = function() return 'Object' end })
end

local Interface

local function validateInterface(object, interface)
        if not object.class and object.prototype then
                object = object.prototype
        end
        for k,v in pairs(interface.interface) do
                if tostring(type(object[k])) ~= v then
                        return false
                end
        end
        if interface.superinterfaces then
                for superinterface in pairs(interface.superinterfaces) do
                        if not validateInterface(object, superinterface) then
                                return false
                        end
                end
        end
        if type(object.class) == "table" and rawequal(object.class.prototype, object) then
                if not object.class.interfaces then
                        rawset(object.class, 'interfaces', Compost and Compost:Acquire() or {})
                end
                object.class.interfaces[interface] = true
        elseif type(object.class) == "table" and type(object.class.prototype) == "table" then
                validateInterface(object.class.prototype, interface)
                -- check if class is proper, thus preventing future checks.
        end
        return true
end

-- @function            inherits
-- @brief                       Return whether an Object or Class inherits from a given
--                                      parent.
-- @param object        Object or Class to check
-- @param parent        Parent to test inheritance from
-- @return                      whether an Object or Class inherits from a given
--                                      parent.
local function inherits(object, parent)
        object = getlibrary(object)
        if type(parent) == "string" then
                if not AceLibrary:HasInstance(parent) then
                        return false
                else
                        parent = AceLibrary(parent)
                end
        end
        AceOO:argCheck(parent, 2, "table")
        if type(object) ~= "table" then
                return false
        end
        local current
        if object.class then
                current = object.class
        else
                current = object
        end
        if type(current) ~= "table" then
                return false
        end
        if rawequal(current, parent) then
                return true
        end
        if parent.class then
                while true do
                        if rawequal(current, Object) then
                                break
                        end
                        if current.mixins then
                                for mixin in pairs(current.mixins) do
                                        if rawequal(mixin, parent) then
                                                return true
                                        end
                                end
                        end
                        if current.interfaces then
                                for interface in pairs(current.interfaces) do
                                        if rawequal(interface, parent) then
                                                return true
                                        end
                                end
                        end
                        current = current.super
                        if type(current) ~= "table" then
                                break
                        end
                end
                
                local isInterface = false
                local curr = parent.class
                while true do
                        if rawequal(curr, Object) then
                                break
                        elseif rawequal(curr, Interface) then
                                isInterface = true
                                break
                        end
                        curr = curr.super
                        if type(curr) ~= "table" then
                                break
                        end
                end
                return isInterface and validateInterface(object, parent)
        else
                while true do
                        if rawequal(current, parent) then
                                return true
                        elseif rawequal(current, Object) then
                                return false
                        end
                        current = current.super
                        if type(current) ~= "table" then
                                return false
                        end
                end
        end
end

-- @table                       Class
-- @brief                       An object factory which sets up inheritence and supports
--                                      'mixins'.
--
-- @metamethod          Class call
-- @brief                       Call ClassFactory:new() to create a new class.
--
-- @method                      Class new
-- @brief                       Construct a new object.
-- @param (a1..a20) Arguments to pass to the object init function.
-- @return                      The new object.
--
-- @method                      Class init
-- @brief                       Initialize a new class.
-- @param parent        Superclass.
-- @param (a1..a20) Mixins.
--
-- @method                      Class ToString
-- @return                      A string representing the object, in this case 'Class'.
local initStatus
local Class
local Mixin
local autoEmbed = false
local function traverseInterfaces(bit, total)
        if bit.superinterfaces then
                for interface in pairs(bit.superinterfaces) do
                        if not total[interface] then
                                total[interface] = true
                                traverseInterfaces(interface, total)
                        end
                end
        end
end
do
        Class = Factory(Object, setmetatable({}, {__index = Object}), Object)
        Class.super = Object
        
        local function protostring(t)
                return '<' .. tostring(t.class) .. ' prototype>'
        end
        local function classobjectstring(t)
                if t.ToString then
                        return t:ToString()
                elseif t.GetLibraryVersion then
                        return (t:GetLibraryVersion())
                else
                        return '<' .. tostring(t.class) .. ' instance>'
                end
        end
        local function classobjectequal(self, other)
                if type(self) == "table" and self.Equals then
                        return self:Equals(other)
                elseif type(other) == "table" and other.Equals then
                        return other:Equals(self)
                elseif type(self) == "table" and self.CompareTo then
                        return self:CompareTo(other) == 0
                elseif type(other) == "table" and other.CompareTo then
                        return other:CompareTo(self) == 0
                else
                        return rawequal(self, other)
                end
        end
        local function classobjectlessthan(self, other)
                if type(self) == "table" and self.IsLessThan then
                        return self:IsLessThan(other)
                elseif type(other) == "table" and other.IsLessThanOrEqualTo then
                        return not other:IsLessThanOrEqualTo(self)
                elseif type(self) == "table" and self.CompareTo then
                        return self:CompareTo(other) < 0
                elseif type(other) == "table" and other.CompareTo then
                        return other:CompareTo(self) > 0
                elseif type(other) == "table" and other.IsLessThan and other.Equals then
                        return other:Equals(self) or other:IsLessThan(self)
                else
                        AceOO:error("cannot compare two objects")
                end
        end
        local function classobjectlessthanequal(self, other)
                if type(self) == "table" and self.IsLessThanOrEqualTo then
                        return self:IsLessThanOrEqualTo(other)
                elseif type(other) == "table" and other.IsLessThan then
                        return not other:IsLessThan(self)
                elseif type(self) == "table" and self.CompareTo then
                        return self:CompareTo(other) <= 0
                elseif type(other) == "table" and other.CompareTo then
                        return other:CompareTo(self) >= 0
                elseif type(self) == "table" and self.IsLessThan and self.Equals then
                        return self:Equals(other) or self:IsLessThan(other)
                else
                        AceOO:error("cannot compare two incompatible objects")
                end
        end
        local function classobjectadd(self, other)
                if type(self) == "table" and self.Add then
                        return self:Add(other)
                else
                        AceOO:error("cannot add two incompatible objects")
                end
        end
        local function classobjectsub(self, other)
                if type(self) == "table" and self.Subtract then
                        return self:Subtract(other)
                else
                        AceOO:error("cannot subtract two incompatible objects")
                end
        end
        local function classobjectunm(self, other)
                if type(self) == "table" and self.UnaryNegation then
                        return self:UnaryNegation(other)
                else
                        AceOO:error("attempt to negate an incompatible object")
                end
        end
        local function classobjectmul(self, other)
                if type(self) == "table" and self.Multiply then
                        return self:Multiply(other)
                else
                        AceOO:error("cannot multiply two incompatible objects")
                end
        end
        local function classobjectdiv(self, other)
                if type(self) == "table" and self.Divide then
                        return self:Divide(other)
                else
                        AceOO:error("cannot divide two incompatible objects")
                end
        end
        local function classobjectpow(self, other)
                if type(self) == "table" and self.Exponent then
                        return self:Exponent(other)
                else
                        AceOO:error("cannot exponentiate two incompatible objects")
                end
        end
        local function classobjectconcat(self, other)
                if type(self) == "table" and self.Concatenate then
                        return self:Concatenate(other)
                else
                        AceOO:error("cannot concatenate two incompatible objects")
                end
        end
        function class_new(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
                                                a13, a14, a15, a16, a17, a18, a19, a20)
                if self.virtual then
                        AceOO:error("Cannot instantiate a virtual class.")
                end
                
                local o = self.prototype
                local newobj = Compost and Compost:Acquire() or {}
                if o.class and o.class.instancemeta then
                        setmetatable(newobj, o.class.instancemeta)
                else
                        Object:init(newobj, o)
                end
                
                if self.interfaces and not self.interfacesVerified then
                        -- Verify the interfaces
                        
                        for interface in pairs(self.interfaces) do
                                for field,kind in pairs(interface.interface) do
                                        if tostring(type(newobj[field])) ~= kind then
                                                AceOO:error("Class did not satisfy all interfaces. %q is required to be a %s. It is a %s", field, kind, tostring(type(newobj[field])))
                                        end
                                end
                        end
                        self.interfacesVerified = true
                end
                local tmp = initStatus
                initStatus = newobj
                newobj:init(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
                                                a13, a14, a15, a16, a17, a18, a19, a20)
                if initStatus then
                        initStatus = tmp
                        AceOO:error("Initialization not completed, be sure to call the superclass's init method.")
                        return
                end
                initStatus = tmp
                return newobj
        end
        local classmeta = {
                __tostring = objtostring,
                __call = function(self, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
                                                a13, a14, a15, a16, a17, a18, a19, a20)
                        return self:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12,
                                                a13, a14, a15, a16, a17, a18, a19, a20)
                end,
        }
        function Class:init(newclass, parent, a1, a2, a3, a4, a5, a6, a7, a8, a9,
                                                                                        a10, a11, a12, a13, a14, a15, a16,
                                                                                        a17, a18, a19, a20)
                parent = parent or self
                
                local total
                
                if parent.class then
                        total = {
                                parent, a1, a2, a3, a4, a5, a6, a7, a8, a9,
                                                a10, a11, a12, a13, a14, a15, a16,
                                                a17, a18, a19, a20
                        }
                        parent = self
                else
                        total = {
                                a1, a2, a3, a4, a5, a6, a7, a8, a9,
                                a10, a11, a12, a13, a14, a15, a16,
                                a17, a18, a19, a20
                        }
                end
                if not inherits(parent, Class) then
                        AceOO:error("Classes must inherit from a proper class")
                end
                if parent.sealed then
                        AceOO:error("Cannot inherit from a sealed class")
                end
                for i,v in ipairs(total) do
                        if inherits(v, Mixin) and v.class then
                                if not newclass.mixins then
                                        newclass.mixins = Compost and Compost:Acquire() or {}
                                end
                                if newclass.mixins[v] then
                                        AceOO:error("Cannot explicitly inherit from the same mixin twice")
                                end
                                newclass.mixins[v] = true
                        elseif inherits(v, Interface) and v.class then
                                if not newclass.interfaces then
                                        newclass.interfaces = Compost and Compost:Acquire() or {}
                                end
                                if newclass.interfaces[v] then
                                        AceOO:error("Cannot explicitly inherit from the same interface twice")
                                end
                                newclass.interfaces[v] = true
                        else
                                AceOO:error("Classes can only inherit from one or zero classes and any number of mixins or interfaces")
                        end
                end
                if parent.interfaces then
                        if newclass.interfaces then
                                for interface in pairs(parent.interfaces) do
                                        newclass.interfaces[interface] = true
                                end
                        else
                                newclass.interfaces = parent.interfaces
                        end
                end
                for k in pairs(total) do
                        total[k] = nil
                end
                table.setn(total, 0)
                
                newclass.super = parent
                
                newclass.prototype = setmetatable(total, Compost and Compost:AcquireHash(
                        '__index',  parent.prototype,
                        '__tostring', protostring
                ) or {
                        __index = parent.prototype,
                        __tostring = protostring,
                })
                total = nil
                
                newclass.instancemeta = {
                        __index = newclass.prototype,
                        __tostring = classobjectstring,
                        __eq = classobjectequal,
                        __lt = classobjectlessthan,
                        __le = classobjectlessthanequal,
                        __add = classobjectadd,
                        __sub = classobjectsub,
                        __unm = classobjectunm,
                        __mul = classobjectmul,
                        __div = classobjectdiv,
                        __pow = classobjectpow,
                        __concat = classobjectconcat,
                }
                
                setmetatable(newclass, classmeta)
                
                newclass.new = class_new
                
                if newclass.mixins then
                        -- Fold in the mixins
                        local err, msg
                        for mixin in pairs(newclass.mixins) do
                                local ret
                                autoEmbed = true
                                ret, msg = pcall(mixin.embed, mixin, newclass.prototype)
                                autoEmbed = false
                                if not ret then
                                        err = true
                                        break
                                end
                        end
        
                        if err then
                                local pt = newclass.prototype
                                for k,v in pairs(pt) do
                                        pt[k] = nil
                                end
        
                                -- method conflict
                                AceOO:error(msg)
                        end
                end
                
                newclass.prototype.class = newclass
                
                if newclass.interfaces then
                        for interface in pairs(newclass.interfaces) do
                                traverseInterfaces(interface, newclass.interfaces)
                        end
                end
                if newclass.mixins then
                        for mixin in pairs(newclass.mixins) do
                                if mixin.interfaces then
                                        if not newclass.interfaces then
                                                newclass.interfaces = Compost and Compost:Acquire() or {}
                                        end
                                        for interface in pairs(mixin.interfaces) do
                                                newclass.interfaces[interface] = true
                                        end
                                end
                        end
                end
        end
        function Class:ToString()
                if type(self.GetLibraryVersion) == "function" then
                        return (self:GetLibraryVersion())
                else
                        return "Class"
                end
        end
        
        local tmp
        function Class.prototype:init()
                if rawequal(self, initStatus) then
                        initStatus = nil
                else
                        AceOO:error("Improper self passed to init. You must do MyClass.super.prototype.init(self, ...)", 2)
                end
                self.uid = getuid(self)
                local current = self.class
                while true do
                        if current == Class then
                                break
                        end
                        if current.mixins then
                                for mixin in pairs(current.mixins) do
                                        if type(mixin.OnInstanceInit) == "function" then
                                                mixin:OnInstanceInit(self)
                                        end
                                end
                        end
                        current = current.super
                end
        end
end


-- @object      ClassFactory
-- @brief       A factory for creating classes. Rarely used directly.
local ClassFactory = Factory(Object, Class, Object)

function Class:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11,
                                        a12, a13, a14, a15, a16, a17, a18, a19, a20)
        return ClassFactory:new(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11,
                                                        a12, a13, a14, a15, a16, a17, a18, a19, a20)
end
getmetatable(Class).__call = Class.new

-- @class       Mixin
-- @brief       A class to create mixin objects, which contain methods that get
-- "mixed in" to class prototypes.
--
-- @object      Mixin prototype
-- @brief       The prototype that mixin objects inherit their methods from.
--
-- @method      Mixin prototype embed
-- @brief       Mix in the methods of our object which are listed in our interface
--               to the supplied target table.
--
-- @method      Mixin prototype init
-- @brief       Initialize the mixin object.
-- @param       newobj   The new object we're initializing.
-- @param       interface       The interface we implement (the list of methods our
--                                      prototype provides which should be mixed into the target
--                                      table by embed).
do
        Mixin = Class()
        function Mixin:ToString()
                if self.GetLibraryVersion then
                        return (self:GetLibraryVersion())
                else
                        return 'Mixin'
                end
        end
        local function _Embed(state, field, target)
                field = next(state.export, field)
                if field == nil then
                        return
                end

                if rawget(target, field) or (target[field] and target[field] ~= state[field]) then
                        AceOO:error("Method conflict in attempt to mixin. Field %q", field)
                end

                target[field] = state[field]

                local ret,msg = pcall(_Embed, state, field, target)
                if not ret then
                        -- Mix in the next method according to the defined interface.   If that
                        -- fails due to a conflict, re-raise to back out the previous mixed
                        -- methods.

                        target[field] = nil
                        AceOO:error(msg)
                end
        end
        function Mixin.prototype:embed(target)
                local mt = getmetatable(target)
                setmetatable(target, nil)
                local err, msg = pcall(_Embed, self, nil, target)
                if not err then
                        setmetatable(target, mt)
                        AceOO:error(msg)
                        return
                end
                if type(self.embedList) == "table" then
                        self.embedList[target] = true
                end
                if type(target.class) ~= "table" then
                        target[self] = true
                end
                if not autoEmbed and type(self.OnManualEmbed) == "function" then
                        self:OnManualEmbed(target)
                end
                setmetatable(target, mt)
        end
        
        function Mixin.prototype:activate(oldLib, oldDeactivate)
                if oldLib and oldLib.embedList then
                        for target in pairs(oldLib.embedList) do
                                local mt = getmetatable(target)
                                setmetatable(target, nil)
                                for field in pairs(oldLib.export) do
                                        target[field] = nil
                                end
                                setmetatable(target, mt)
                        end
                        self.embedList = oldLib.embedList
                        for target in pairs(self.embedList) do
                                self:embed(target)
                        end
                else
                        self.embedList = setmetatable(Compost and Compost:Acquire() or {}, Compost and Compost:AcquireHash('__mode', 'k') or {__mode="k"})
                end
        end
        
        function Mixin.prototype:init(export, a1, a2, a3, a4, a5, a6, a7, a8, a9,
                                                                                        a10, a11, a12, a13, a14, a15, a16,
                                                                                        a17, a18, a19, a20)
                AceOO:argCheck(export, 2, "table")
                for k,v in pairs(export) do
                        if type(k) ~= "number" then
                                AceOO:error("All keys to argument #2 must be numbers.")
                        elseif type(v) ~= "string" then
                                AceOO:error("All values to argument #2 must be strings.")
                        end
                end
                local num = table.getn(export)
                for i = 1, num do
                        local v = export[i]
                        export[i] = nil
                        export[v] = true
                end
                table.setn(export, 0)
                local interfaces
                if a1 then
                        local l = getlibrary
                        interfaces = Compost and Compost:Acquire(l(a1), l(a2), l(a3), l(a4),
                                l(a5), l(a6), l(a7), l(a8), l(a9), l(a10), l(a11), l(a12),
                                l(a13), l(a14), l(a15), l(a16), l(a17), l(a18), l(a19), l(a20))
                                or { l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), l(a8),
                                l(a9), l(a10), l(a11), l(a12), l(a13), l(a14), l(a15), l(a16),
                                l(a17), l(a18), l(a19), l(a20) }
                        for _,v in ipairs(interfaces) do
                                if not v.class or not inherits(v, Interface) then
                                        AceOO:error("Mixins can inherit only from interfaces")
                                end
                        end
                        local num = table.getn(interfaces)
                        for i = 1, num do
                                local v = interfaces[i]
                                interfaces[i] = nil
                                interfaces[v] = true
                        end
                        table.setn(interfaces, 0)
                        for interface in pairs(interfaces) do
                                traverseInterfaces(interface, interfaces)
                        end
                        for interface in pairs(interfaces) do
                                for field,kind in pairs(interface.interface) do
                                        if kind ~= "nil" then
                                                local good = false
                                                for bit in pairs(export) do
                                                        if bit == field then
                                                                good = true
                                                                break
                                                        end
                                                end
                                                if not good then
                                                        AceOO:error("Mixin does not fully accommodate field %q", field)
                                                end
                                        end
                                end
                        end
                end
                self.super = Mixin.prototype
                Mixin.super.prototype.init(self)
                self.export = export
                self.interfaces = interfaces
        end
end

-- @class Interface
-- @brief A class to create interfaces, which contain contracts that classes
--                      which inherit from this must comply with.
--
-- @object Interface prototype
-- @brief       The prototype that interface objects must adhere to.
--
-- @method Interface prototype init
-- @brief       Initialize the mixin object.
-- @param       interface       The interface we contract (the hash of fields forced).
-- @param       (a1..a20)       Superinterfaces
do
        Interface = Class()
        function Interface:ToString()
                if self.GetLibraryVersion then
                        return (self:GetLibraryVersion())
                else
                        return 'Instance'
                end
        end
        function Interface.prototype:init(interface, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20)
                Interface.super.prototype.init(self)
                AceOO:argCheck(interface, 2, "table")
                for k,v in pairs(interface) do
                        if type(k) ~= "string" then
                                AceOO:error("All keys to argument #2 must be numbers.")
                        elseif type(v) ~= "string" then
                                AceOO:error("All values to argument #2 must be strings.")
                        elseif v ~= "nil" and v ~= "string" and v ~= "number" and v ~= "table" and v ~= "function" then
                                AceOO:error('All values to argument #2 must either be "nil", "string", "number", "table", or "function".')
                        end
                end
                local l = getlibrary
                a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20 = l(a1), l(a2), l(a3), l(a4), l(a5), l(a6), l(a7), l(a8), l(a9), l(a10), l(a11), l(a12), l(a13), l(a14), l(a15), l(a16), l(a17), l(a18), l(a19), l(a20)
                if a1 then
                        self.superinterfaces = Compost and Compost:Acquire(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20) or {a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20}
                        for k,v in ipairs(self.superinterfaces) do
                                if not inherits(v, Interface) or not v.class then
                                        AceOO:error('Cannot provide a non-Interface to inherit from')
                                end
                        end
                        local num = table.getn(self.superinterfaces)
                        for i = 1, num do
                                local v = self.superinterfaces[i]
                                self.superinterfaces[i] = nil
                                self.superinterfaces[v] = true
                        end
                        table.setn(self.superinterfaces, 0)
                end
                self.interface = interface
        end
end

-- @function Classpool
-- @brief       Obtain a read only class from our pool of classes, indexed by the
--               superclass and mixins.
-- @param       sc               The superclass of the class we want.
-- @param       (m1..m20)       Mixins of the class we want's objects.
-- @return A read only class from the class pool.
local Classpool
do
        local pool = setmetatable({}, {__mode = 'v'})
        local function newindex(k, v)
                AceOO:error('Attempt to modify a read-only class.')
        end
        local function protonewindex(k, v)
                AceOO:error('Attempt to modify a read-only class prototype.')
        end
        local function ts(bit)
                if type(bit) ~= "table" then
                        return tostring(bit)
                elseif getmetatable(bit) and bit.__tostring then
                        return tostring(bit)
                elseif type(bit.GetLibraryVersion) == "function" then
                        return bit:GetLibraryVersion()
                else
                        return tostring(bit)
                end
        end
        local t
        local function getcomplexuid(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9,
                m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20)
                if not t then t = {} end
                if sc then if sc.uid then table.insert(t, sc.uid) else AceOO:error("%s is not an appropriate class/mixin", ts(sc)) end
                if m1 then if m1.uid then table.insert(t, m1.uid) else AceOO:error("%s is not an appropriate mixin", ts(m1)) end
                if m2 then if m2.uid then table.insert(t, m2.uid) else AceOO:error("%s is not an appropriate mixin", ts(m2)) end
                if m3 then if m3.uid then table.insert(t, m3.uid) else AceOO:error("%s is not an appropriate mixin", ts(m3)) end
                if m4 then if m4.uid then table.insert(t, m4.uid) else AceOO:error("%s is not an appropriate mixin", ts(m4)) end
                if m5 then if m5.uid then table.insert(t, m5.uid) else AceOO:error("%s is not an appropriate mixin", ts(m5)) end
                if m6 then if m6.uid then table.insert(t, m6.uid) else AceOO:error("%s is not an appropriate mixin", ts(m6)) end
                if m7 then if m7.uid then table.insert(t, m7.uid) else AceOO:error("%s is not an appropriate mixin", ts(m7)) end
                if m8 then if m8.uid then table.insert(t, m8.uid) else AceOO:error("%s is not an appropriate mixin", ts(m8)) end
                if m9 then if m9.uid then table.insert(t, m9.uid) else AceOO:error("%s is not an appropriate mixin", ts(m9)) end
                if m10 then if m10.uid then table.insert(t, m10.uid) else AceOO:error("%s is not an appropriate mixin", ts(m10)) end
                if m11 then if m11.uid then table.insert(t, m11.uid) else AceOO:error("%s is not an appropriate mixin", ts(m11)) end
                if m12 then if m12.uid then table.insert(t, m12.uid) else AceOO:error("%s is not an appropriate mixin", ts(m12)) end
                if m13 then if m13.uid then table.insert(t, m13.uid) else AceOO:error("%s is not an appropriate mixin", ts(m13)) end
                if m14 then if m14.uid then table.insert(t, m14.uid) else AceOO:error("%s is not an appropriate mixin", ts(m14)) end
                if m15 then if m15.uid then table.insert(t, m15.uid) else AceOO:error("%s is not an appropriate mixin", ts(m15)) end
                if m16 then if m16.uid then table.insert(t, m16.uid) else AceOO:error("%s is not an appropriate mixin", ts(m16)) end
                if m17 then if m17.uid then table.insert(t, m17.uid) else AceOO:error("%s is not an appropriate mixin", ts(m17)) end
                if m18 then if m18.uid then table.insert(t, m18.uid) else AceOO:error("%s is not an appropriate mixin", ts(m18)) end
                if m19 then if m19.uid then table.insert(t, m19.uid) else AceOO:error("%s is not an appropriate mixin", ts(m19)) end
                if m20 then if m20.uid then table.insert(t, m20.uid) else AceOO:error("%s is not an appropriate mixin", ts(m20)) end
                end end end end end end end end end end end end end end end end end end end end end
                table.sort(t)
                local uid = table.concat(t, '')
                for k in pairs(t) do t[k] = nil end
                table.setn(t, 0)
                return uid
        end
        local classmeta
        function Classpool(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9,
                m10, m11, m12, m13, m14, m15, m16,
                m17, m18, m19, m20)
                local l = getlibrary
                sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20 = l(sc), l(m1), l(m2), l(m3), l(m4), l(m5), l(m6), l(m7), l(m8), l(m9), l(m10), l(m11), l(m12), l(m13), l(m14), l(m15), l(m16), l(m17), l(m18), l(m19), l(m20)
                if sc and sc.class then
                        sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20 = Class, sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19
                end
                sc = sc or Class
                local key = getcomplexuid(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15, m16, m17, m18, m19, m20)
                if not pool[key] then
                        local class = Class(sc, m1, m2, m3, m4, m5, m6, m7, m8, m9,
                        m10, m11, m12, m13, m14, m15, m16, m17,
                        m18, m19, m20)
                        if not classmeta then
                                classmeta = {}
                                local mt = getmetatable(class)
                                for k,v in pairs(mt) do
                                        classmeta[k] = v
                                end
                                classmeta.__newindex = newindex
                        end
                        -- Prevent the user from adding methods to this class.
                        -- NOTE: I'm not preventing modifications of existing class members,
                        -- but it's likely that only a truly malicious user will be doing so.
                        class.sealed = true
                        setmetatable(class, classmeta)
                        getmetatable(class.prototype).__newindex = protonewindex
                        pool[key] = class
                end
                return pool[key]
        end
end

AceOO.Factory = Factory
AceOO.Object = Object
AceOO.Class = Class
AceOO.Mixin = Mixin
AceOO.Interface = Interface
AceOO.Classpool = Classpool
AceOO.inherits = inherits

-- Library handling bits

local function activate(self, oldLib, oldDeactivate)
        AceOO = self
        Factory = self.Factory
        Object = self.Object
        Class = self.Class
        ClassFactory.prototype = Class
        Mixin = self.Mixin
        Interface = self.Interface
        Classpool = self.Classpool
        if oldDeactivate then
                oldDeactivate(oldLib)
        end
end

local function external(self, major, instance)
        if major == "Compost-2.0" then
                Compost = instance
        end
end

AceLibrary:Register(AceOO, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)
AceOO = AceLibrary(MAJOR_VERSION)