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