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.
- data/CHANGELOG +7 -0
- data/lib/rgen/metamodel_builder.rb +5 -4
- data/lib/rgen/metamodel_builder.rb.bak +196 -0
- data/lib/rgen/metamodel_builder/builder_extensions.rb +51 -38
- data/lib/rgen/metamodel_builder/builder_extensions.rb.bak +437 -0
- data/lib/rgen/metamodel_builder/builder_runtime.rb +2 -20
- data/lib/rgen/metamodel_builder/builder_runtime.rb.bak +73 -0
- data/lib/rgen/name_helper.rb.bak +37 -0
- data/lib/rgen/template_language.rb +8 -0
- data/lib/rgen/template_language.rb.bak +289 -0
- data/lib/rgen/template_language/directory_template_container.rb +11 -0
- data/lib/rgen/template_language/directory_template_container.rb.bak +69 -0
- data/lib/rgen/template_language/output_handler.rb +3 -2
- data/lib/rgen/template_language/output_handler.rb.bak +88 -0
- data/lib/rgen/template_language/template_container.rb +5 -4
- data/lib/rgen/template_language/template_container.rb.bak +196 -0
- data/lib/rgen/transformer.rb.bak +381 -0
- data/test/environment_test.rb.bak +52 -0
- data/test/metamodel_builder_test.rb +6 -0
- data/test/metamodel_builder_test.rb.bak +443 -0
- data/test/metamodel_roundtrip_test/TestModel_Regenerated.rb +34 -32
- data/test/metamodel_roundtrip_test/houseMetamodel_Regenerated.ecore +58 -58
- data/test/output_handler_test.rb +8 -0
- data/test/output_handler_test.rb.bak +50 -0
- data/test/template_language_test.rb +23 -0
- data/test/template_language_test.rb.bak +72 -0
- data/test/template_language_test/indentStringTestDefaultIndent.out +1 -0
- data/test/template_language_test/indentStringTestTabIndent.out +1 -0
- data/test/template_language_test/templates/indent_string_test.tpl +12 -0
- data/test/template_language_test/templates/null_context_test.tpl +12 -0
- data/test/transformer_test.rb.bak +223 -0
- metadata +65 -48
- 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
|
-
|
187
|
+
arg.each_pair { |k,v| setGeneric(k, v) } if arg.is_a?(Hash)
|
188
188
|
end
|
189
|
-
|
190
|
-
|
191
|
-
|
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
|
-
|
223
|
-
|
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
|
-
|
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
|
298
|
-
_register
|
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
|
-
|
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
|
-
|
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?
|
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
|
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
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
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
|
-
|
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
|
-
|
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
|