rgen 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. data/CHANGELOG +7 -0
  2. data/lib/rgen/metamodel_builder.rb +5 -4
  3. data/lib/rgen/metamodel_builder.rb.bak +196 -0
  4. data/lib/rgen/metamodel_builder/builder_extensions.rb +51 -38
  5. data/lib/rgen/metamodel_builder/builder_extensions.rb.bak +437 -0
  6. data/lib/rgen/metamodel_builder/builder_runtime.rb +2 -20
  7. data/lib/rgen/metamodel_builder/builder_runtime.rb.bak +73 -0
  8. data/lib/rgen/name_helper.rb.bak +37 -0
  9. data/lib/rgen/template_language.rb +8 -0
  10. data/lib/rgen/template_language.rb.bak +289 -0
  11. data/lib/rgen/template_language/directory_template_container.rb +11 -0
  12. data/lib/rgen/template_language/directory_template_container.rb.bak +69 -0
  13. data/lib/rgen/template_language/output_handler.rb +3 -2
  14. data/lib/rgen/template_language/output_handler.rb.bak +88 -0
  15. data/lib/rgen/template_language/template_container.rb +5 -4
  16. data/lib/rgen/template_language/template_container.rb.bak +196 -0
  17. data/lib/rgen/transformer.rb.bak +381 -0
  18. data/test/environment_test.rb.bak +52 -0
  19. data/test/metamodel_builder_test.rb +6 -0
  20. data/test/metamodel_builder_test.rb.bak +443 -0
  21. data/test/metamodel_roundtrip_test/TestModel_Regenerated.rb +34 -32
  22. data/test/metamodel_roundtrip_test/houseMetamodel_Regenerated.ecore +58 -58
  23. data/test/output_handler_test.rb +8 -0
  24. data/test/output_handler_test.rb.bak +50 -0
  25. data/test/template_language_test.rb +23 -0
  26. data/test/template_language_test.rb.bak +72 -0
  27. data/test/template_language_test/indentStringTestDefaultIndent.out +1 -0
  28. data/test/template_language_test/indentStringTestTabIndent.out +1 -0
  29. data/test/template_language_test/templates/indent_string_test.tpl +12 -0
  30. data/test/template_language_test/templates/null_context_test.tpl +12 -0
  31. data/test/transformer_test.rb.bak +223 -0
  32. metadata +65 -48
  33. data/lib/rgen/environment.rb.bak +0 -42
data/CHANGELOG CHANGED
@@ -57,3 +57,10 @@
57
57
  * Performance improvement: find on environment hashes elements by class
58
58
  * Extended Transformer to allow sharing of result maps between several Transformer instances
59
59
  * Bugfix: User defined upper bound values are no longer overwritten by -1 in all "many" metamodel builder methods
60
+
61
+ =0.4.3 (Aug 12th, 2008)
62
+
63
+ * Performance improvement: significant speed up of metamodel reverse registration
64
+ * Bugfix: Use object identity for metamodel to-many add/remove methods
65
+ * Bugfix: If expand's :for expression evaluates to nil an error is generated (silently used current context before)
66
+ * Template language indentation string can be set on DirectoryTemplateContainer and with the "file" command
@@ -184,11 +184,12 @@ module MetamodelBuilder
184
184
  extend RGen::ECore::ECoreInstantiator
185
185
 
186
186
  def initialize(arg=nil)
187
- arg.each_pair { |k,v| setGeneric(k, v) } if arg.is_a?(Hash)
187
+ arg.each_pair { |k,v| setGeneric(k, v) } if arg.is_a?(Hash)
188
188
  end
189
- def self.method_added(m)
190
- raise "Do not add methods to model classes directly, add them to the ClassModule instead"
191
- end
189
+
190
+ def self.method_added(m)
191
+ raise "Do not add methods to model classes directly, add them to the ClassModule instead"
192
+ end
192
193
  end
193
194
 
194
195
  end
@@ -0,0 +1,196 @@
1
+ # RGen Framework
2
+ # (c) Martin Thiede, 2006
3
+
4
+ require 'rgen/metamodel_builder/builder_runtime'
5
+ require 'rgen/metamodel_builder/builder_extensions'
6
+ require 'rgen/metamodel_builder/module_extension'
7
+ require 'rgen/metamodel_builder/data_types'
8
+ require 'rgen/metamodel_builder/mm_multiple'
9
+ require 'rgen/ecore/ecore_instantiator'
10
+
11
+ module RGen
12
+
13
+ # MetamodelBuilder can be used to create a metamodel, i.e. Ruby classes which
14
+ # act as metamodel elements.
15
+ #
16
+ # To create a new metamodel element, create a Ruby class which inherits from
17
+ # MetamodelBuilder::MMBase
18
+ #
19
+ # class Person < RGen::MetamodelBuilder::MMBase
20
+ # end
21
+ #
22
+ # This way a couple of class methods are made available to the new class.
23
+ # These methods can be used to:
24
+ # * add attributes to the class
25
+ # * add associations with other classes
26
+ #
27
+ # Here is an example:
28
+ #
29
+ # class Person < RGen::MetamodelBuilder::MMBase
30
+ # has_attr 'name', String
31
+ # has_attr 'age', Integer
32
+ # end
33
+ #
34
+ # class House < RGen::MetamodelBuilder::MMBase
35
+ # has_attr 'address' # String is default
36
+ # end
37
+ #
38
+ # Person.many_to_many 'homes', House, 'inhabitants'
39
+ #
40
+ # See BuilderExtensions for details about the available class methods.
41
+ #
42
+ # =Attributes
43
+ #
44
+ # The example above creates two classes 'Person' and 'House'. Person has the attributes
45
+ # 'name' and 'age', House has the attribute 'address'. The attributes can be
46
+ # accessed on instances of the classes in the following way:
47
+ #
48
+ # p = Person.new
49
+ # p.name = "MyName"
50
+ # p.age = 22
51
+ # p.name # => "MyName"
52
+ # p.age # => 22
53
+ #
54
+ # Note that the class Person takes care of the type of its attributes. As
55
+ # declared above, a 'name' can only be a String, an 'age' must be an Integer.
56
+ # So the following would return an exception:
57
+ #
58
+ # p.name = :myName # => exception: can not put a Symbol where a String is expected
59
+ #
60
+ # If the type of an attribute should be left undefined, use Object as type.
61
+ #
62
+ # =Associations
63
+ #
64
+ # As well as attributes show up as instance methods, associations bring their own
65
+ # accessor methods. For the Person-to-House association this would be:
66
+ #
67
+ # h1 = House.new
68
+ # h1.address = "Street1"
69
+ # h2 = House.new
70
+ # h2.address = "Street2"
71
+ # p.addHomes(h1)
72
+ # p.addHomes(h2)
73
+ # p.removeHomes(h1)
74
+ # p.homes # => [ h2 ]
75
+ #
76
+ # The Person-to-House association is _bidirectional_. This means that with the
77
+ # addition of a House to a Person, the Person is also added to the House. Thus:
78
+ #
79
+ # h1.inhabitants # => []
80
+ # h2.inhabitants # => [ p ]
81
+ #
82
+ # Note that the association is defined between two specific classes, instances of
83
+ # different classes can not be added. Thus, the following would result in an
84
+ # exception:
85
+ #
86
+ # p.addHomes(:justASymbol) # => exception: can not put a Symbol where a House is expected
87
+ #
88
+ # =ECore Metamodel description
89
+ #
90
+ # The class methods described above are used to create a Ruby representation of the metamodel
91
+ # we have in mind in a very simple and easy way. We don't have to care about all the details
92
+ # of a metamodel at this point (e.g. multiplicities, changeability, etc).
93
+ #
94
+ # At the same time however, an instance of the ECore metametamodel (i.e. a ECore based
95
+ # description of our metamodel) is provided for all the Ruby classes and modules we create.
96
+ # Since we did not provide the nitty-gritty details of the metamodel, defaults are used to
97
+ # fully complete the ECore metamodel description.
98
+ #
99
+ # In order to access the ECore metamodel description, just call the +ecore+ method on a
100
+ # Ruby class or module object belonging to your metamodel.
101
+ #
102
+ # Here is the example continued from above:
103
+ #
104
+ # Person.ecore.eAttributes.name # => ["name", "age"]
105
+ # h2pRef = House.ecore.eReferences.first
106
+ # h2pRef.eType # => Person
107
+ # h2pRef.eOpposite.eType # => House
108
+ # h2pRef.lowerBound # => 0
109
+ # h2pRef.upperBound # => -1
110
+ # h2pRef.many # => true
111
+ # h2pRef.containment # => false
112
+ #
113
+ # Note that the use of array_extensions.rb is assumed here to make model navigation convenient.
114
+ #
115
+ # The following metamodel builder methods are supported, see individual method description
116
+ # for details:
117
+ #
118
+ # Attributes:
119
+ # * BuilderExtensions#has_attr
120
+ #
121
+ # Unidirectional references:
122
+ # * BuilderExtensions#has_one
123
+ # * BuilderExtensions#has_many
124
+ # * BuilderExtensions#contains_one_uni
125
+ # * BuilderExtensions#contains_many_uni
126
+ #
127
+ # Bidirectional references:
128
+ # * BuilderExtensions#one_to_one
129
+ # * BuilderExtensions#one_to_many
130
+ # * BuilderExtensions#many_to_one
131
+ # * BuilderExtensions#many_to_many
132
+ # * BuilderExtensions#contains_one
133
+ # * BuilderExtensions#contains_many
134
+ #
135
+ # Every builder command can optionally take a specification of further ECore properties.
136
+ # Additional properties for Attributes and References are (with defaults in brackets):
137
+ # * :ordered (true),
138
+ # * :unique (true),
139
+ # * :changeable (true),
140
+ # * :volatile (false),
141
+ # * :transient (false),
142
+ # * :unsettable (false),
143
+ # * :derived (false),
144
+ # * :lowerBound (0),
145
+ # * :resolveProxies (true) <i>references only</i>,
146
+ #
147
+ # Using these additional properties, the above example can be refined as follows:
148
+ #
149
+ # class Person < RGen::MetamodelBuilder::MMBase
150
+ # has_attr 'name', String, :lowerBound => 1
151
+ # has_attr 'yearOfBirth', Integer,
152
+ # has_attr 'age', Integer, :derived => true
153
+ # def age_derived
154
+ # Time.now.year - yearOfBirth
155
+ # end
156
+ # end
157
+ #
158
+ # Person.many_to_many 'homes', House, 'inhabitants', :upperBound => 5
159
+ #
160
+ # Person.ecore.eReferences.find{|r| r.name == 'homes'}.upperBound # => 5
161
+ #
162
+ # This way we state that there must be a name for each person, we introduce a new attribute
163
+ # 'yearOfBirth' and make 'age' a derived attribute. We also say that a person can
164
+ # have at most 5 houses in our metamodel.
165
+ #
166
+ # ==Derived attributes and references
167
+ #
168
+ # If the attribute 'derived' of an attribute or reference is set to true, a method +attributeName_derived+
169
+ # has to be provided. This method is called whenever the original attribute is accessed. The
170
+ # original attribute can not be written if it is derived.
171
+ #
172
+ #
173
+ module MetamodelBuilder
174
+
175
+ # Use this class as a start for new metamodel elements (i.e. Ruby classes)
176
+ # by inheriting for it.
177
+ #
178
+ # See MetamodelBuilder for an example.
179
+ class MMBase
180
+ include BuilderRuntime
181
+ include DataTypes
182
+ extend BuilderExtensions
183
+ extend ModuleExtension
184
+ extend RGen::ECore::ECoreInstantiator
185
+
186
+ def initialize(arg=nil)
187
+ arg.each_pair { |k,v| setGeneric(k, v) } if arg.is_a?(Hash)
188
+ end
189
+ def self.method_added(m)
190
+ raise "Do not add methods to model classes directly, add them to the ClassModule instead"
191
+ end
192
+ end
193
+
194
+ end
195
+
196
+ end
@@ -219,18 +219,10 @@ module BuilderExtensions
219
219
  @metamodel_description ||= []
220
220
  end
221
221
 
222
- def inherited(c)
223
- c._class_module
222
+ def inherited(c)
223
+ c.send(:include, c.const_set(:ClassModule, Module.new))
224
224
  end
225
225
 
226
- def _class_module # :nodoc:
227
- unless const_defined?(:ClassModule)
228
- const_set(:ClassModule, Module.new)
229
- include const_get(:ClassModule)
230
- end
231
- const_get(:ClassModule)
232
- end
233
-
234
226
  protected
235
227
 
236
228
  # Central builder method
@@ -282,7 +274,7 @@ module BuilderExtensions
282
274
  alias get<%= firstToUpper(name) %> <%= name %>
283
275
 
284
276
  CODE
285
- _class_module.module_eval(@@one_read_builder.result(binding))
277
+ self::ClassModule.module_eval(@@one_read_builder.result(binding))
286
278
  end
287
279
 
288
280
  if props.value(:changeable)
@@ -294,14 +286,22 @@ module BuilderExtensions
294
286
  oldval = @<%= name %>
295
287
  @<%= name %> = val
296
288
  <% if other_role && other_kind %>
297
- _unregister(self,oldval,"<%= other_role %>","<%= other_kind %>")
298
- _register(self,val,"<%= other_role %>","<%= other_kind %>")
289
+ oldval._unregister<%= firstToUpper(other_role) %>(self) unless oldval.nil?
290
+ val._register<%= firstToUpper(other_role) %>(self) unless val.nil?
299
291
  <% end %>
300
292
  end
301
293
  alias set<%= firstToUpper(name) %> <%= name %>=
302
-
294
+
295
+ def _register<%= firstToUpper(name) %>(val)
296
+ @<%= name %> = val
297
+ end
298
+
299
+ def _unregister<%= firstToUpper(name) %>(val)
300
+ @<%= name %> = nil
301
+ end
302
+
303
303
  CODE
304
- _class_module.module_eval(@@one_write_builder.result(binding))
304
+ self::ClassModule.module_eval(@@one_write_builder.result(binding))
305
305
 
306
306
  end
307
307
  end
@@ -324,7 +324,7 @@ module BuilderExtensions
324
324
  alias get<%= firstToUpper(name) %> <%= name %>
325
325
 
326
326
  CODE
327
- _class_module.module_eval(@@many_read_builder.result(binding))
327
+ self::ClassModule.module_eval(@@many_read_builder.result(binding))
328
328
  end
329
329
 
330
330
  if props.value(:changeable)
@@ -332,41 +332,54 @@ module BuilderExtensions
332
332
 
333
333
  def add<%= firstToUpper(name) %>(val)
334
334
  @<%= name %> = [] unless @<%= name %>
335
- return if val.nil? or @<%= name %>.include?(val)
335
+ return if val.nil? || @<%= name %>.any?{|e| e.object_id == val.object_id}
336
336
  <%= type_check_code("val", props) %>
337
337
  @<%= name %>.push val
338
338
  <% if other_role && other_kind %>
339
- _register(self, val, "<%= other_role %>", "<%= other_kind %>")
339
+ val._register<%= firstToUpper(other_role) %>(self)
340
340
  <% end %>
341
341
  end
342
342
 
343
343
  def remove<%= firstToUpper(name) %>(val)
344
344
  @<%= name %> = [] unless @<%= name %>
345
- return unless @<%= name %>.include?(val)
346
- @<%= name %>.delete val
347
- <% if other_role && other_kind %>
348
- _unregister(self, val, "<%= other_role %>", "<%= other_kind %>")
349
- <% end %>
350
- end
351
-
352
- def <%= name %>=(val)
353
- return if val.nil?
354
- raise _assignmentTypeError(self, val, Array) unless val.is_a? Array
355
- getGeneric(:<%= name %>).each {|e|
356
- remove<%= firstToUpper(name) %>(e)
357
- }
358
- val.each {|v|
359
- add<%= firstToUpper(name) %>(v)
360
- }
345
+ @<%= name %>.each_with_index do |e,i|
346
+ if e.object_id == val.object_id
347
+ @<%= name %>.delete_at(i)
348
+ <% if other_role && other_kind %>
349
+ val._unregister<%= firstToUpper(other_role) %>(self)
350
+ <% end %>
351
+ return
352
+ end
353
+ end
361
354
  end
355
+
356
+ def <%= name %>=(val)
357
+ return if val.nil?
358
+ raise _assignmentTypeError(self, val, Array) unless val.is_a? Array
359
+ get<%= firstToUpper(name) %>.each {|e|
360
+ remove<%= firstToUpper(name) %>(e)
361
+ }
362
+ val.each {|v|
363
+ add<%= firstToUpper(name) %>(v)
364
+ }
365
+ end
362
366
  alias set<%= firstToUpper(name) %> <%= name %>=
363
-
367
+
368
+ def _register<%= firstToUpper(name) %>(val)
369
+ @<%= name %> = [] unless @<%= name %>
370
+ @<%= name %>.push val
371
+ end
372
+
373
+ def _unregister<%= firstToUpper(name) %>(val)
374
+ @<%= name %>.delete val
375
+ end
376
+
364
377
  CODE
365
- _class_module.module_eval(@@many_write_builder.result(binding))
378
+ self::ClassModule.module_eval(@@many_write_builder.result(binding))
366
379
  end
367
380
 
368
381
  end
369
-
382
+
370
383
  private
371
384
 
372
385
  def build_derived_method(name, props, kind)
@@ -392,7 +405,7 @@ module BuilderExtensions
392
405
  #TODO final_method :<%= name %>
393
406
 
394
407
  CODE
395
- _class_module.module_eval(@@derived_builder.result(binding))
408
+ self::ClassModule.module_eval(@@derived_builder.result(binding))
396
409
  end
397
410
 
398
411
  def type_check_code(varname, props)
@@ -0,0 +1,437 @@
1
+ # RGen Framework
2
+ # (c) Martin Thiede, 2006
3
+
4
+ require 'erb'
5
+ require 'rgen/metamodel_builder/metamodel_description.rb'
6
+
7
+ module RGen
8
+
9
+ module MetamodelBuilder
10
+
11
+ # This module provides methods which can be used to setup a metamodel element.
12
+ # The module is used to +extend+ MetamodelBuilder::MMBase, i.e. add the module's
13
+ # methods as class methods.
14
+ #
15
+ # MetamodelBuilder::MMBase should be used as a start for new metamodel elements.
16
+ # See MetamodelBuilder for an example.
17
+ #
18
+ module BuilderExtensions
19
+ include NameHelper
20
+
21
+ class FeatureBlockEvaluator
22
+ def self.eval(block, props1, props2=nil)
23
+ return unless block
24
+ e = self.new(props1, props2)
25
+ e.instance_eval(&block)
26
+ end
27
+ def initialize(props1, props2)
28
+ @props1, @props2 = props1, props2
29
+ end
30
+ def annotation(hash)
31
+ @props1.annotations << Intermediate::Annotation.new(hash)
32
+ end
33
+ def opposite_annotation(hash)
34
+ raise "No opposite available" unless @props2
35
+ @props2.annotations << Intermediate::Annotation.new(hash)
36
+ end
37
+ end
38
+
39
+ def has_attr(role, target_class=nil, raw_props={}, &block)
40
+ props = AttributeDescription.new(target_class, _ownProps(raw_props).merge({
41
+ :name=>role, :upperBound=>1}))
42
+ raise "No opposite available" unless _oppositeProps(raw_props).empty?
43
+ FeatureBlockEvaluator.eval(block, props)
44
+ _build_internal(props)
45
+ end
46
+
47
+ # Add a single attribute or unidirectional association.
48
+ # 'role' specifies the name which is used to access the attribute.
49
+ # 'target_class' specifies the type of objects which can be held by this attribute.
50
+ # If no target class is given, String will be default.
51
+ #
52
+ # This class method adds the following instance methods, where 'role' is to be
53
+ # replaced by the given role name:
54
+ # class#role # getter
55
+ # class#role=(value) # setter
56
+ #
57
+ def has_one(role, target_class=nil, raw_props={}, &block)
58
+ props = ReferenceDescription.new(target_class, _ownProps(raw_props).merge({
59
+ :name=>role, :upperBound=>1, :containment=>false}))
60
+ raise "No opposite available" unless _oppositeProps(raw_props).empty?
61
+ FeatureBlockEvaluator.eval(block, props)
62
+ _build_internal(props)
63
+ end
64
+
65
+ # Add an unidirectional _many_ association.
66
+ # 'role' specifies the name which is used to access the attribute.
67
+ # 'target_class' is optional and can be used to fix the type of objects which
68
+ # can be referenced by this association.
69
+ #
70
+ # This class method adds the following instance methods, where 'role' is to be
71
+ # replaced by the given role name:
72
+ # class#addRole(value)
73
+ # class#removeRole(value)
74
+ # class#role # getter, returns an array
75
+ # Note that the first letter of the role name is turned into an uppercase
76
+ # for the add and remove methods.
77
+ #
78
+ def has_many(role, target_class=nil, raw_props={}, &block)
79
+ props = ReferenceDescription.new(target_class, _setManyUpperBound(_ownProps(raw_props).merge({
80
+ :name=>role, :containment=>false})))
81
+ raise "No opposite available" unless _oppositeProps(raw_props).empty?
82
+ FeatureBlockEvaluator.eval(block, props)
83
+ _build_internal(props)
84
+ end
85
+
86
+ def contains_one_uni(role, target_class=nil, raw_props={}, &block)
87
+ props = ReferenceDescription.new(target_class, _ownProps(raw_props).merge({
88
+ :name=>role, :upperBound=>1, :containment=>true}))
89
+ raise "No opposite available" unless _oppositeProps(raw_props).empty?
90
+ FeatureBlockEvaluator.eval(block, props)
91
+ _build_internal(props)
92
+ end
93
+
94
+ def contains_many_uni(role, target_class=nil, raw_props={}, &block)
95
+ props = ReferenceDescription.new(target_class, _setManyUpperBound(_ownProps(raw_props).merge({
96
+ :name=>role, :containment=>true})))
97
+ raise "No opposite available" unless _oppositeProps(raw_props).empty?
98
+ FeatureBlockEvaluator.eval(block, props)
99
+ _build_internal(props)
100
+ end
101
+
102
+ # Add a bidirectional one-to-many association between two classes.
103
+ # The class this method is called on is refered to as _own_class_ in
104
+ # the following.
105
+ #
106
+ # Instances of own_class can use 'own_role' to access _many_ associated instances
107
+ # of type 'target_class'. Instances of 'target_class' can use 'target_role' to
108
+ # access _one_ associated instance of own_class.
109
+ #
110
+ # This class method adds the following instance methods where 'ownRole' and
111
+ # 'targetRole' are to be replaced by the given role names:
112
+ # own_class#addOwnRole(value)
113
+ # own_class#removeOwnRole(value)
114
+ # own_class#ownRole
115
+ # target_class#targetRole
116
+ # target_class#targetRole=(value)
117
+ # Note that the first letter of the role name is turned into an uppercase
118
+ # for the add and remove methods.
119
+ #
120
+ # When an element is added/set on either side, this element also receives the element
121
+ # is is added to as a new element.
122
+ #
123
+ def one_to_many(target_role, target_class, own_role, raw_props={}, &block)
124
+ props1 = ReferenceDescription.new(target_class, _setManyUpperBound(_ownProps(raw_props).merge({
125
+ :name=>target_role, :containment=>false})))
126
+ props2 = ReferenceDescription.new(self, _oppositeProps(raw_props).merge({
127
+ :name=>own_role, :upperBound=>1, :containment=>false}))
128
+ FeatureBlockEvaluator.eval(block, props1, props2)
129
+ _build_internal(props1, props2)
130
+ end
131
+
132
+ def contains_many(target_role, target_class, own_role, raw_props={}, &block)
133
+ props1 = ReferenceDescription.new(target_class, _setManyUpperBound(_ownProps(raw_props).merge({
134
+ :name=>target_role, :containment=>true})))
135
+ props2 = ReferenceDescription.new(self, _oppositeProps(raw_props).merge({
136
+ :name=>own_role, :upperBound=>1, :containment=>false}))
137
+ FeatureBlockEvaluator.eval(block, props1, props2)
138
+ _build_internal(props1, props2)
139
+ end
140
+
141
+ # This is the inverse of one_to_many provided for convenience.
142
+ def many_to_one(target_role, target_class, own_role, raw_props={}, &block)
143
+ props1 = ReferenceDescription.new(target_class, _ownProps(raw_props).merge({
144
+ :name=>target_role, :upperBound=>1, :containment=>false}))
145
+ props2 = ReferenceDescription.new(self, _setManyUpperBound(_oppositeProps(raw_props).merge({
146
+ :name=>own_role, :containment=>false})))
147
+ FeatureBlockEvaluator.eval(block, props1, props2)
148
+ _build_internal(props1, props2)
149
+ end
150
+
151
+ # Add a bidirectional many-to-many association between two classes.
152
+ # The class this method is called on is refered to as _own_class_ in
153
+ # the following.
154
+ #
155
+ # Instances of own_class can use 'own_role' to access _many_ associated instances
156
+ # of type 'target_class'. Instances of 'target_class' can use 'target_role' to
157
+ # access _many_ associated instances of own_class.
158
+ #
159
+ # This class method adds the following instance methods where 'ownRole' and
160
+ # 'targetRole' are to be replaced by the given role names:
161
+ # own_class#addOwnRole(value)
162
+ # own_class#removeOwnRole(value)
163
+ # own_class#ownRole
164
+ # target_class#addTargetRole
165
+ # target_class#removeTargetRole=(value)
166
+ # target_class#targetRole
167
+ # Note that the first letter of the role name is turned into an uppercase
168
+ # for the add and remove methods.
169
+ #
170
+ # When an element is added on either side, this element also receives the element
171
+ # is is added to as a new element.
172
+ #
173
+ def many_to_many(target_role, target_class, own_role, raw_props={}, &block)
174
+ props1 = ReferenceDescription.new(target_class, _setManyUpperBound(_ownProps(raw_props).merge({
175
+ :name=>target_role, :containment=>false})))
176
+ props2 = ReferenceDescription.new(self, _setManyUpperBound(_oppositeProps(raw_props).merge({
177
+ :name=>own_role, :containment=>false})))
178
+ FeatureBlockEvaluator.eval(block, props1, props2)
179
+ _build_internal(props1, props2)
180
+ end
181
+
182
+ # Add a bidirectional one-to-one association between two classes.
183
+ # The class this method is called on is refered to as _own_class_ in
184
+ # the following.
185
+ #
186
+ # Instances of own_class can use 'own_role' to access _one_ associated instance
187
+ # of type 'target_class'. Instances of 'target_class' can use 'target_role' to
188
+ # access _one_ associated instance of own_class.
189
+ #
190
+ # This class method adds the following instance methods where 'ownRole' and
191
+ # 'targetRole' are to be replaced by the given role names:
192
+ # own_class#ownRole
193
+ # own_class#ownRole=(value)
194
+ # target_class#targetRole
195
+ # target_class#targetRole=(value)
196
+ #
197
+ # When an element is set on either side, this element also receives the element
198
+ # is is added to as the new element.
199
+ #
200
+ def one_to_one(target_role, target_class, own_role, raw_props={}, &block)
201
+ props1 = ReferenceDescription.new(target_class, _ownProps(raw_props).merge({
202
+ :name=>target_role, :upperBound=>1, :containment=>false}))
203
+ props2 = ReferenceDescription.new(self, _oppositeProps(raw_props).merge({
204
+ :name=>own_role, :upperBound=>1, :containment=>false}))
205
+ FeatureBlockEvaluator.eval(block, props1, props2)
206
+ _build_internal(props1, props2)
207
+ end
208
+
209
+ def contains_one(target_role, target_class, own_role, raw_props={}, &block)
210
+ props1 = ReferenceDescription.new(target_class, _ownProps(raw_props).merge({
211
+ :name=>target_role, :upperBound=>1, :containment=>true}))
212
+ props2 = ReferenceDescription.new(self, _oppositeProps(raw_props).merge({
213
+ :name=>own_role, :upperBound=>1, :containment=>false}))
214
+ FeatureBlockEvaluator.eval(block, props1, props2)
215
+ _build_internal(props1, props2)
216
+ end
217
+
218
+ def _metamodel_description # :nodoc:
219
+ @metamodel_description ||= []
220
+ end
221
+
222
+ def inherited(c)
223
+ c._class_module
224
+ end
225
+
226
+ def _class_module # :nodoc:
227
+ unless const_defined?(:ClassModule)
228
+ const_set(:ClassModule, Module.new)
229
+ include const_get(:ClassModule)
230
+ end
231
+ const_get(:ClassModule)
232
+ end
233
+
234
+ protected
235
+
236
+ # Central builder method
237
+ #
238
+ def _build_internal(props1, props2=nil)
239
+ _metamodel_description << props1
240
+ if props1.is_a?(ReferenceDescription) && props1.many?
241
+ _build_many_methods(props1, props2)
242
+ else
243
+ _build_one_methods(props1, props2)
244
+ end
245
+ if props2
246
+ # this is a bidirectional reference
247
+ props1.opposite, props2.opposite = props2, props1
248
+ other_class = props1.impl_type
249
+ other_class._metamodel_description << props2
250
+ raise "Internal error: second description must be a ReferenceDescription" \
251
+ unless props2.is_a?(ReferenceDescription)
252
+ if props2.many?
253
+ other_class._build_many_methods(props2, props1)
254
+ else
255
+ other_class._build_one_methods(props2, props1)
256
+ end
257
+ end
258
+ end
259
+
260
+ # To-One association methods
261
+ #
262
+ def _build_one_methods(props, other_props=nil)
263
+ name = props.value(:name)
264
+ other_role = other_props && other_props.value(:name)
265
+ other_kind = other_props && ( other_props.many? ? :many : :one )
266
+
267
+ if props.value(:derived)
268
+ build_derived_method(name, props, :one)
269
+ else
270
+ @@one_read_builder ||= ERB.new <<-CODE
271
+
272
+ def <%= name %>
273
+ <% if props.is_a?(AttributeDescription) && props.value(:defaultValueLiteral) %>
274
+ <% defVal = props.value(:defaultValueLiteral) %>
275
+ <% defVal = '"'+defVal+'"' if props.impl_type == String %>
276
+ <% defVal = ':'+defVal if props.impl_type.is_a?(DataTypes::Enum) && props.impl_type != DataTypes::Boolean %>
277
+ @<%= name %>.nil? ? <%= defVal %> : @<%= name %>
278
+ <% else %>
279
+ @<%= name %>
280
+ <% end %>
281
+ end
282
+ alias get<%= firstToUpper(name) %> <%= name %>
283
+
284
+ CODE
285
+ _class_module.module_eval(@@one_read_builder.result(binding))
286
+ end
287
+
288
+ if props.value(:changeable)
289
+ @@one_write_builder ||= ERB.new <<-CODE
290
+
291
+ def <%= name %>=(val)
292
+ return if val == @<%= name %>
293
+ <%= type_check_code("val", props) %>
294
+ oldval = @<%= name %>
295
+ @<%= name %> = val
296
+ <% if other_role && other_kind %>
297
+ _unregister(self,oldval,"<%= other_role %>","<%= other_kind %>")
298
+ _register(self,val,"<%= other_role %>","<%= other_kind %>")
299
+ <% end %>
300
+ end
301
+ alias set<%= firstToUpper(name) %> <%= name %>=
302
+
303
+ CODE
304
+ _class_module.module_eval(@@one_write_builder.result(binding))
305
+
306
+ end
307
+ end
308
+
309
+ # To-Many association methods
310
+ #
311
+ def _build_many_methods(props, other_props=nil)
312
+ name = props.value(:name)
313
+ other_role = other_props && other_props.value(:name)
314
+ other_kind = other_props && ( other_props.many? ? :many : :one )
315
+
316
+ if props.value(:derived)
317
+ build_derived_method(name, props, :many)
318
+ else
319
+ @@many_read_builder ||= ERB.new <<-CODE
320
+
321
+ def <%= name %>
322
+ ( @<%= name %> ? @<%= name %>.dup : [] )
323
+ end
324
+ alias get<%= firstToUpper(name) %> <%= name %>
325
+
326
+ CODE
327
+ _class_module.module_eval(@@many_read_builder.result(binding))
328
+ end
329
+
330
+ if props.value(:changeable)
331
+ @@many_write_builder ||= ERB.new <<-CODE
332
+
333
+ def add<%= firstToUpper(name) %>(val)
334
+ @<%= name %> = [] unless @<%= name %>
335
+ return if val.nil? or @<%= name %>.include?(val)
336
+ <%= type_check_code("val", props) %>
337
+ @<%= name %>.push val
338
+ <% if other_role && other_kind %>
339
+ _register(self, val, "<%= other_role %>", "<%= other_kind %>")
340
+ <% end %>
341
+ end
342
+
343
+ def remove<%= firstToUpper(name) %>(val)
344
+ @<%= name %> = [] unless @<%= name %>
345
+ return unless @<%= name %>.include?(val)
346
+ @<%= name %>.delete val
347
+ <% if other_role && other_kind %>
348
+ _unregister(self, val, "<%= other_role %>", "<%= other_kind %>")
349
+ <% end %>
350
+ end
351
+
352
+ def <%= name %>=(val)
353
+ return if val.nil?
354
+ raise _assignmentTypeError(self, val, Array) unless val.is_a? Array
355
+ getGeneric(:<%= name %>).each {|e|
356
+ remove<%= firstToUpper(name) %>(e)
357
+ }
358
+ val.each {|v|
359
+ add<%= firstToUpper(name) %>(v)
360
+ }
361
+ end
362
+ alias set<%= firstToUpper(name) %> <%= name %>=
363
+
364
+ CODE
365
+ _class_module.module_eval(@@many_write_builder.result(binding))
366
+ end
367
+
368
+ end
369
+
370
+ private
371
+
372
+ def build_derived_method(name, props, kind)
373
+ raise "Implement method #{name}_derived instead of method #{name}" \
374
+ if (public_instance_methods+protected_instance_methods+private_instance_methods).include?(name)
375
+ @@derived_builder ||= ERB.new <<-CODE
376
+
377
+ def <%= name %>
378
+ raise "Derived feature requires public implementation of method <%= name %>_derived" \
379
+ unless respond_to?(:<%= name+"_derived" %>)
380
+ val = <%= name %>_derived
381
+ <% if kind == :many %>
382
+ raise _assignmentTypeError(self,val,Array) unless val && val.is_a?(Array)
383
+ val.each do |v|
384
+ <%= type_check_code("v", props) %>
385
+ end
386
+ <% else %>
387
+ <%= type_check_code("val", props) %>
388
+ <% end %>
389
+ val
390
+ end
391
+ alias get<%= firstToUpper(name) %> <%= name %>
392
+ #TODO final_method :<%= name %>
393
+
394
+ CODE
395
+ _class_module.module_eval(@@derived_builder.result(binding))
396
+ end
397
+
398
+ def type_check_code(varname, props)
399
+ code = ""
400
+ if props.impl_type.is_a?(Class)
401
+ code << "unless #{varname}.nil? or #{varname}.is_a? #{props.impl_type}\n"
402
+ expected = props.impl_type.to_s
403
+ elsif props.impl_type.is_a?(RGen::MetamodelBuilder::DataTypes::Enum)
404
+ code << "unless #{varname}.nil? or [#{props.impl_type.literals_as_strings.join(',')}].include?(#{varname})\n"
405
+ expected = "["+props.impl_type.literals_as_strings.join(',')+"]"
406
+ else
407
+ raise StandardError.new("Unkown type "+props.impl_type.to_s)
408
+ end
409
+ code << "raise _assignmentTypeError(self,#{varname},\"#{expected}\")\n"
410
+ code << "end"
411
+ code
412
+ end
413
+
414
+ def _ownProps(props)
415
+ Hash[*(props.select{|k,v| !(k.to_s =~ /^opposite_/)}.flatten)]
416
+ end
417
+
418
+ def _oppositeProps(props)
419
+ r = {}
420
+ props.each_pair do |k,v|
421
+ if k.to_s =~ /^opposite_(.*)$/
422
+ r[$1.to_sym] = v
423
+ end
424
+ end
425
+ r
426
+ end
427
+
428
+ def _setManyUpperBound(props)
429
+ props[:upperBound] = -1 unless props[:upperBound].is_a?(Integer) && props[:upperBound] > 1
430
+ props
431
+ end
432
+
433
+ end
434
+
435
+ end
436
+
437
+ end