Module: RGen::MetamodelBuilder

Included in:
Util::Base
Defined in:
lib/rgen/metamodel_builder.rb,
lib/rgen/metamodel_builder/data_types.rb,
lib/rgen/metamodel_builder/mm_multiple.rb,
lib/rgen/metamodel_builder/builder_runtime.rb,
lib/rgen/metamodel_builder/module_extension.rb,
lib/rgen/metamodel_builder/builder_extensions.rb,
lib/rgen/metamodel_builder/intermediate/feature.rb,
lib/rgen/metamodel_builder/constant_order_helper.rb,
lib/rgen/metamodel_builder/intermediate/annotation.rb

Overview

MetamodelBuilder can be used to create a metamodel, i.e. Ruby classes which act as metamodel elements.

To create a new metamodel element, create a Ruby class which inherits from MetamodelBuilder::MMBase

class Person < RGen::MetamodelBuilder::MMBase end

This way a couple of class methods are made available to the new class. These methods can be used to:

  • add attributes to the class

  • add associations with other classes

Here is an example:

class Person < RGen::MetamodelBuilder::MMBase has_attr ‘name’, String has_attr ‘age’, Integer end

class House < RGen::MetamodelBuilder::MMBase has_attr ‘address’ # String is default end

Person.many_to_many ‘homes’, House, ‘inhabitants’

See BuilderExtensions for details about the available class methods.

Attributes

The example above creates two classes ‘Person’ and ‘House’. Person has the attributes ‘name’ and ‘age’, House has the attribute ‘address’. The attributes can be accessed on instances of the classes in the following way:

p = Person.new p.name = “MyName” p.age = 22 p.name # => “MyName” p.age # => 22

Note that the class Person takes care of the type of its attributes. As declared above, a ‘name’ can only be a String, an ‘age’ must be an Integer. So the following would return an exception:

p.name = :myName # => exception: can not put a Symbol where a String is expected

If the type of an attribute should be left undefined, use Object as type.

Associations

As well as attributes show up as instance methods, associations bring their own accessor methods. For the Person-to-House association this would be:

h1 = House.new h1.address = “Street1” h2 = House.new h2.address = “Street2” p.addHomes(h1) p.addHomes(h2) p.removeHomes(h1) p.homes # => [ h2 ]

The Person-to-House association is bidirectional. This means that with the addition of a House to a Person, the Person is also added to the House. Thus:

h1.inhabitants # => [] h2.inhabitants # => [ p ] Note that the association is defined between two specific classes, instances of different classes can not be added. Thus, the following would result in an exception:

p.addHomes(:justASymbol) # => exception: can not put a Symbol where a House is expected

ECore Metamodel description

The class methods described above are used to create a Ruby representation of the metamodel we have in mind in a very simple and easy way. We don’t have to care about all the details of a metamodel at this point (e.g. multiplicities, changeability, etc).

At the same time however, an instance of the ECore metametamodel (i.e. a ECore based description of our metamodel) is provided for all the Ruby classes and modules we create. Since we did not provide the nitty-gritty details of the metamodel, defaults are used to fully complete the ECore metamodel description.

In order to access the ECore metamodel description, just call the ecore method on a Ruby class or module object belonging to your metamodel.

Here is the example continued from above:

Person.ecore.eAttributes.name # => [“name”, “age”] h2pRef = House.ecore.eReferences.first h2pRef.eType # => Person h2pRef.eOpposite.eType # => House h2pRef.lowerBound # => 0 h2pRef.upperBound # => -1 h2pRef.many # => true h2pRef.containment # => false

Note that the use of array_extensions.rb is assumed here to make model navigation convenient.

The following metamodel builder methods are supported, see individual method description for details:

Attributes:

  • BuilderExtensions#has_attr

Unidirectional references:

  • BuilderExtensions#has_one

  • BuilderExtensions#has_many

  • BuilderExtensions#contains_one_uni

  • BuilderExtensions#contains_many_uni

Bidirectional references:

  • BuilderExtensions#one_to_one

  • BuilderExtensions#one_to_many

  • BuilderExtensions#many_to_one

  • BuilderExtensions#many_to_many

  • BuilderExtensions#contains_one

  • BuilderExtensions#contains_many

Every builder command can optionally take a specification of further ECore properties. Additional properties for Attributes and References are (with defaults in brackets):

  • :ordered (true),

  • :unique (true),

  • :changeable (true),

  • :volatile (false),

  • :transient (false),

  • :unsettable (false),

  • :derived (false),

  • :lowerBound (0),

  • :resolveProxies (true) references only,

Using these additional properties, the above example can be refined as follows:

class Person < RGen::MetamodelBuilder::MMBase has_attr ‘name’, String, :lowerBound => 1 has_attr ‘yearOfBirth’, Integer, has_attr ‘age’, Integer, :derived => true def age_derived Time.now.year - yearOfBirth end end

Person.many_to_many ‘homes’, House, ‘inhabitants’, :upperBound => 5

Person.ecore.eReferences.find{|r| r.name == ‘homes’}.upperBound # => 5

This way we state that there must be a name for each person, we introduce a new attribute ‘yearOfBirth’ and make ‘age’ a derived attribute. We also say that a person can have at most 5 houses in our metamodel.

Derived attributes and references

If the attribute ‘derived’ of an attribute or reference is set to true, a method attributeName_derived has to be provided. This method is called whenever the original attribute is accessed. The original attribute can not be written if it is derived.

Defined Under Namespace

Modules: BuilderExtensions, BuilderRuntime, DataTypes, Intermediate, ModuleExtension Classes: MMBase, MMGeneric, MMProxy

Constant Summary collapse

ConstantOrderHelper =

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.

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

Class Method Summary collapse

Class Method Details

.MMMultiple(*superclasses) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/rgen/metamodel_builder/mm_multiple.rb', line 6

def self.MMMultiple(*superclasses)
  c = Class.new(MMBase)
  class << c
    attr_reader :multiple_superclasses
  end
  c.instance_variable_set(:@multiple_superclasses, superclasses)
  superclasses.collect{|sc| sc.ancestors}.flatten.
    reject{|m| m.is_a?(Class)}.each do |arg|
      c.instance_eval do
        include arg
      end
  end
  return c
end