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