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 |