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 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
|
# RGen Framework
# (c) Martin Thiede, 2006
require 'rgen/metamodel_builder/constant_order_helper'
require 'rgen/metamodel_builder/builder_runtime'
require 'rgen/metamodel_builder/builder_extensions'
require 'rgen/metamodel_builder/module_extension'
require 'rgen/metamodel_builder/data_types'
require 'rgen/metamodel_builder/mm_multiple'
require 'rgen/ecore/ecore_interface'
module RGen
# 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) <i>references only</i>,
#
# 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.
#
#
module MetamodelBuilder
# Use this class as a start for new metamodel elements (i.e. Ruby classes)
# by inheriting for it.
#
# See MetamodelBuilder for an example.
class MMBase
include BuilderRuntime
include DataTypes
extend BuilderExtensions
extend ModuleExtension
extend RGen::ECore::ECoreInterface
def initialize(arg=nil)
raise StandardError.new("Class #{self.class} is abstract") if self.class._abstract_class
arg.each_pair { |k,v| setGeneric(k, v) } if arg.is_a?(Hash)
end
# Object#inspect causes problems on most models
def inspect
self.class.name
end
def self.method_added(m)
raise "Do not add methods to model classes directly, add them to the ClassModule instead"
end
end
# Instances of MMGeneric can be used as values of any attribute are reference
class MMGeneric
# empty implementation so we don't have to check if a value is a MMGeneriv before setting the container
def _set_container(container, containing_feature_name)
end
end
# MMProxy objects can be used instead of real target elements in case references should be resolved later on
class MMProxy < MMGeneric
# The +targetIdentifer+ is an object identifying the element the proxy represents
attr_accessor :targetIdentifier
# +data+ is optional additional information to be associated with the proxy
attr_accessor :data
def initialize(ident=nil, data=nil)
@targetIdentifier = ident
@data = data
end
end
end
end
|