File: metamodel_builder.rb

package info (click to toggle)
ruby-rgen 0.10.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,428 kB
  • sloc: ruby: 11,344; xml: 1,368; yacc: 72; makefile: 10
file content (224 lines) | stat: -rw-r--r-- 7,765 bytes parent folder | download | duplicates (11)
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