rgen 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
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