1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
|
module RGen
module MetamodelBuilder
# The purpose of the ConstantOrderHelper is to capture the definition order of RGen metamodel builder
# classes, modules and enums. The problem is that Ruby doesn't seem to track the order of
# constants being created in a module. However the order is important because it defines the order
# of eClassifiers and eSubpackages in a EPackage.
#
# It would be helpful here if Ruby provided a +const_added+ callback, but this is not the case up to now.
#
# The idea for capturing is that all events of creating a RGen class, module or enum are reported to the
# ConstantOrderHelper singleton.
# For classes and modules it tries to add their names to the parent's +_constantOrder+ array.
# The parent module is derived from the class's or module's name. However, the new name is only added
# if the respective parent module has a new constant (which is not yet in +_constantOrder+) which
# points to the new class or module.
# For enums it is a bit more complicated, because at the time the enum is created, the parent
# module does not yet contain the constant to which the enum is assigned. Therefor, the enum is remembered
# and it is tried to be stored on the next event (class, module or enum) within the module which was
# created last (which was last extended with ModuleExtension). If it can not be found in that module,
# all parent modules of the last module are searched. This way it should also be correctly entered in
# case it was defined outside of the last created module.
# Note that an enum is not stored to the constant order array unless another event occurs. That's why
# it is possible that one enum is missing at the enum. This needs to be taken care of by the ECore transformer.
#
# This way of capturing should be sufficient for the regular use cases of the RGen metamodel builder language.
# However, it is possible to write code which messes this up, see unit tests for details.
# In the worst case, the new classes, modules or enums will just not be found in a parent module and thus be ignored.
#
ConstantOrderHelper = Class.new do
def initialize
@currentModule = nil
@pendingEnum = nil
end
def classCreated(c)
handlePendingEnum
cont = containerModule(c)
name = (c.name || "").split("::").last
return unless cont.respond_to?(:_constantOrder) && !cont._constantOrder.include?(name)
cont._constantOrder << name
end
def moduleCreated(m)
handlePendingEnum
cont = containerModule(m)
name = (m.name || "").split("::").last
return unless cont.respond_to?(:_constantOrder) && !cont._constantOrder.include?(name)
cont._constantOrder << name
@currentModule = m
end
def enumCreated(e)
handlePendingEnum
@pendingEnum = e
end
private
def containerModule(m)
containerName = (m.name || "").split("::")[0..-2].join("::")
containerName.empty? ? nil : eval(containerName, TOPLEVEL_BINDING)
end
def handlePendingEnum
return unless @pendingEnum
m = @currentModule
while m
if m.respond_to?(:_constantOrder)
newConstants = m.constants - m._constantOrder
const = newConstants.find{|c| m.const_get(c).object_id == @pendingEnum.object_id}
if const
m._constantOrder << const.to_s
break
end
end
m = containerModule(m)
end
@pendingEnum = nil
end
end.new
end
end
|