rgen 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. data/CHANGELOG +6 -0
  2. data/lib/mmgen/metamodel_generator.rb +2 -1
  3. data/lib/mmgen/mm_ext/{ecore_ext.rb → ecore_mmgen_ext.rb} +3 -7
  4. data/lib/mmgen/templates/metamodel_generator.tpl +10 -8
  5. data/lib/rgen/ecore/ecore_ext.rb +69 -0
  6. data/lib/rgen/ecore/ecore_transformer.rb +1 -1
  7. data/lib/rgen/metamodel_builder/builder_extensions.rb +10 -2
  8. data/lib/rgen/serializer/xmi11_serializer.rb +101 -0
  9. data/lib/rgen/serializer/xmi20_serializer.rb +7 -7
  10. data/lib/rgen/serializer/xml_serializer.rb +47 -14
  11. data/test/ea_serializer_test.rb +29 -0
  12. data/test/ea_serializer_test/ea_testmodel_regenerated.xml +821 -0
  13. data/test/metamodel_roundtrip_test.rb +1 -2
  14. data/test/metamodel_roundtrip_test/TestModel.rb +1 -0
  15. data/test/metamodel_roundtrip_test/TestModel_Regenerated.rb +34 -35
  16. data/test/metamodel_roundtrip_test/houseMetamodel_Regenerated.ecore +128 -236
  17. data/test/rgen_test.rb +1 -0
  18. metadata +93 -95
  19. data/lib/rgen/metamodel_builder.rb.bak +0 -196
  20. data/lib/rgen/metamodel_builder/builder_extensions.rb.bak +0 -437
  21. data/lib/rgen/metamodel_builder/builder_runtime.rb.bak +0 -73
  22. data/lib/rgen/name_helper.rb.bak +0 -37
  23. data/lib/rgen/template_language.rb.bak +0 -289
  24. data/lib/rgen/template_language/directory_template_container.rb.bak +0 -69
  25. data/lib/rgen/template_language/output_handler.rb.bak +0 -88
  26. data/lib/rgen/template_language/template_container.rb.bak +0 -196
  27. data/lib/rgen/transformer.rb.bak +0 -381
  28. data/test/environment_test.rb.bak +0 -52
  29. data/test/metamodel_builder_test.rb.bak +0 -443
  30. data/test/output_handler_test.rb.bak +0 -50
  31. data/test/template_language_test.rb.bak +0 -72
  32. data/test/transformer_test.rb.bak +0 -223
@@ -1,196 +0,0 @@
1
- # RGen Framework
2
- # (c) Martin Thiede, 2006
3
-
4
- require 'erb'
5
- require 'fileutils'
6
- require 'rgen/template_language/output_handler'
7
- require 'rgen/template_language/template_helper'
8
-
9
- module RGen
10
-
11
- module TemplateLanguage
12
-
13
- class TemplateContainer
14
- include TemplateHelper
15
-
16
- def initialize(metamodels, output_path, parent, filename)
17
- @templates = {}
18
- @parent = parent
19
- @filename = filename
20
- @indent = 0
21
- @output_path = output_path
22
- raise StandardError.new("Can not set metamodel, dup class first") if self.class == TemplateContainer
23
- @@metamodels = metamodels
24
- @@metamodels = [ @@metamodels ] unless @@metamodels.is_a?(Array)
25
- end
26
-
27
- def load
28
- #print "Loading templates in #{@filename} ...\n"
29
- File.open(@filename) { |f|
30
- begin
31
- ERB.new(f.read,nil,nil,'@output').result(binding)
32
- rescue Exception => e
33
- processAndRaise(e)
34
- end
35
- }
36
- end
37
-
38
- # if this container can handle the call, the expansion result is returned
39
- # otherwise expand is called on the appropriate container and the result is added to @output
40
- def expand(template, *all_args)
41
- args, params = _splitArgsAndOptions(all_args)
42
- if params[:foreach].is_a? Enumerable
43
- _expand_foreach(template, args, params)
44
- else
45
- _expand(template, args, params)
46
- end
47
- end
48
-
49
- def this
50
- @context
51
- end
52
-
53
- def method_missing(name, *args)
54
- @context.send(name, *args)
55
- end
56
-
57
- def self.const_missing(name)
58
- super unless @@metamodels
59
- @@metamodels.each do |mm|
60
- return mm.const_get(name) rescue NameError
61
- end
62
- super
63
- end
64
-
65
- private
66
-
67
- def nonl
68
- @output.ignoreNextNL
69
- end
70
-
71
- def nows
72
- @output.ignoreNextWS
73
- end
74
-
75
- def nl
76
- _direct_concat("\n")
77
- end
78
-
79
- def ws
80
- _direct_concat(" ")
81
- end
82
-
83
- def iinc
84
- @indent += 1
85
- @output.indent = @indent
86
- end
87
-
88
- def idec
89
- @indent -= 1 if @indent > 0
90
- @output.indent = @indent
91
- end
92
-
93
- def define(template, params={}, &block)
94
- @templates[template] ||= {}
95
- cls = params[:for] || Object
96
- @templates[template][cls] = block
97
- end
98
-
99
- def file(name)
100
- old_output, @output = @output, OutputHandler.new(@indent)
101
- begin
102
- yield
103
- rescue Exception => e
104
- processAndRaise(e)
105
- end
106
- path = ""
107
- path += @output_path+"/" if @output_path
108
- dirname = File.dirname(path+name)
109
- FileUtils.makedirs(dirname) unless File.exist?(dirname)
110
- File.open(path+name,"w") { |f| f.write(@output) }
111
- @output = old_output
112
- end
113
-
114
- # private private
115
-
116
- def _expand_foreach(template, args, params)
117
- sep = params[:separator]
118
- params[:foreach].each_with_index {|e,i|
119
- single_params = params.dup
120
- single_params[:for] = e
121
- _direct_concat(sep.to_s) if sep && i > 0
122
- _expand(template, args, single_params)
123
- }
124
- end
125
-
126
- LOCAL_TEMPLATE_REGEX = /^:*(\w+)$/
127
-
128
- def _expand(template, args, params)
129
- context = params[:for]
130
- @indent = params[:indent] || @indent
131
- # if this is the first call to expand within this container, @output is nil
132
- noIndentNextLine = params[:noIndentNextLine]
133
- noIndentNextLine = (@output.to_s.size > 0 && @output.to_s[-1] != "\n"[0]) if noIndentNextLine.nil?
134
- old_context, @context = @context, context if context
135
- local_output = nil
136
- if template =~ LOCAL_TEMPLATE_REGEX
137
- tplname = $1
138
- throw "Template not found: #{$1}" unless @templates[tplname]
139
- old_output, @output = @output, OutputHandler.new(@indent)
140
- @output.noIndentNextLine if noIndentNextLine
141
- _call_template(tplname, @context, args)
142
- local_output, @output = @output, old_output
143
- else
144
- local_output = @parent.expand(template, *(args.dup << {:for => @context, :indent => @indent, :noIndentNextLine => noIndentNextLine}))
145
- end
146
- _direct_concat(local_output)
147
- @context = old_context if old_context
148
- local_output
149
- end
150
-
151
- def processAndRaise(e, tpl=nil)
152
- bt = e.backtrace.dup
153
- e.backtrace.each_with_index do |t,i|
154
- if t =~ /\(erb\):(\d+):/
155
- bt[i] = "#{@filename}:#{$1}"
156
- bt[i] += ":in '#{tpl}'" if tpl
157
- break
158
- end
159
- end
160
- raise e, e.to_s, bt
161
- end
162
-
163
- def _call_template(tpl, context, args)
164
- found = false
165
- @templates[tpl].each_pair { |key, value|
166
- if context.is_a?(key)
167
- proc = @templates[tpl][key]
168
- arity = proc.arity
169
- arity = 0 if arity == -1 # if no args are given
170
- raise StandardError.new("Wrong number of arguments calling template #{tpl}: #{args.size} for #{arity} "+
171
- "(Beware: Hashes as last arguments are taken as options and are ignored)") \
172
- if arity != args.size
173
- begin
174
- proc.call(*args)
175
- rescue Exception => e
176
- processAndRaise(e, tpl)
177
- end
178
- found = true
179
- end
180
- }
181
- raise StandardError.new("Template class not matching: #{tpl} for #{context.class.name}") unless found
182
- end
183
-
184
- def _direct_concat(s)
185
- if @output.is_a? OutputHandler
186
- @output.direct_concat(s)
187
- else
188
- @output << s
189
- end
190
- end
191
-
192
- end
193
-
194
- end
195
-
196
- end
@@ -1,381 +0,0 @@
1
- module RGen
2
-
3
- # The Transformer class can be used to specify model transformations.
4
- #
5
- # Model transformations take place between a <i>source model</i> (located in the <i>source
6
- # environment</i> being an instance of the <i>source metamodel</i>) and a <i>target model</i> (located
7
- # in the <i>target environment</i> being an instance of the <i>target metamodel</i>).
8
- # Normally a "model" consists of several model elements associated with each other.
9
- #
10
- # =Transformation Rules
11
- #
12
- # The transformation is specified within a subclass of Transformer.
13
- # Within the subclass, the Transformer.transform class method can be used to specify transformation
14
- # blocks for specific metamodel classes of the source metamodel.
15
- #
16
- # If there is no transformation rule for the current object's class, a rule for the superclass
17
- # is used instead. If there's no rule for the superclass, the class hierarchy is searched
18
- # this way until Object.
19
- #
20
- # Here is an example:
21
- #
22
- # class MyTransformer < RGen::Transformer
23
- #
24
- # transform InputClass, :to => OutputClass do
25
- # { :name => name, :otherClass => trans(otherClass) }
26
- # end
27
- #
28
- # transform OtherInputClass, :to => OtherOutputClass do
29
- # { :name => name }
30
- # end
31
- # end
32
- #
33
- # In this example a transformation rule is specified for model elements of class InputClass
34
- # as well as for elements of class OtherInputClass. The former is to be transformed into
35
- # an instance of OutputClass, the latter into an instance of OtherOutputClass.
36
- # Note that the Ruby class objects are used to specifiy the classes.
37
- #
38
- # =Transforming Attributes
39
- #
40
- # Besides the target class of a transformation, the attributes of the result object are
41
- # specified in the above example. This is done by providing a Ruby block with the call of
42
- # +transform+. Within this block arbitrary Ruby code may be placed, however the block
43
- # must return a hash. This hash object specifies the attribute assignment of the
44
- # result object using key/value pairs: The key must be a Symbol specifying the attribute
45
- # which is to be assigned by name, the value is the value that will be assigned.
46
- #
47
- # For convenience, the transformation block will be evaluated in the context of the
48
- # source model element which is currently being converted. This way it is possible to just
49
- # write <code>:name => name</code> in the example in order to assign the name of the source
50
- # object to the name attribute of the target object.
51
- #
52
- # =Transforming References
53
- #
54
- # When attributes of elements are references to other elements, those referenced
55
- # elements have to be transformed as well. As shown above, this can be done by calling
56
- # the Transformer#trans method. This method initiates a transformation of the element
57
- # or array of elements passed as parameter according to transformation rules specified
58
- # using +transform+. In fact the +trans+ method is the only way to start the transformation
59
- # at all.
60
- #
61
- # For convenience and performance reasons, the result of +trans+ is cached with respect
62
- # to the parameter object. I.e. calling trans on the same source object a second time will
63
- # return the same result object _without_ a second evaluation of the corresponding
64
- # transformation rules.
65
- #
66
- # This way the +trans+ method can be used to lookup the target element for some source
67
- # element without the need to locally store a reference to the target element. In addition
68
- # this can be useful if it is not clear if certain element has already been transformed
69
- # when it is required within some other transformation block. See example below.
70
- #
71
- # Special care has been taken to allow the transformation of elements which reference
72
- # each other cyclically. The key issue here is that the target element of some transformation
73
- # is created _before_ the transformation's block is evaluated, i.e before the elements
74
- # attributes are set. Otherwise a call to +trans+ within the transformation's block
75
- # could lead to a +trans+ of the element itself.
76
- #
77
- # Here is an example:
78
- #
79
- # transform ModelAIn, :to => ModelAOut do
80
- # { :name => name, :modelB => trans(modelB) }
81
- # end
82
- #
83
- # transform ModelBIn, :to => ModelBOut do
84
- # { :name => name, :modelA => trans(modelA) }
85
- # end
86
- #
87
- # Note that in this case it does not matter if the transformation is initiated by calling
88
- # +trans+ with a ModelAIn element or ModelBIn element due to the caching feature described
89
- # above.
90
- #
91
- # =Transformer Methods
92
- #
93
- # When code in transformer blocks becomes more complex it might be useful to refactor
94
- # it into smaller methods. If regular Ruby methods within the Transformer subclass are
95
- # used for this purpose, it is necessary to know the source element being transformed.
96
- # This could be achieved by explicitly passing the +@current_object+ as parameter of the
97
- # method (see Transformer#trans).
98
- #
99
- # A more convenient way however is to define a special kind of method using the
100
- # Transformer.method class method. Those methods are evaluated within the context of the
101
- # current source element being transformed just the same as transformer blocks are.
102
- #
103
- # Here is an example:
104
- #
105
- # transform ModelIn, :to => ModelOut do
106
- # { :number => doubleNumber }
107
- # end
108
- #
109
- # method :doubleNumber do
110
- # number * 2;
111
- # end
112
- #
113
- # In this example the transformation assigns the 'number' attribute of the source element
114
- # multiplied by 2 to the target element. The multiplication is done in a dedicated method
115
- # called 'doubleNumber'. Note that the 'number' attribute of the source element is
116
- # accessed without an explicit reference to the source element as the method's body
117
- # evaluates in the source element's context.
118
- #
119
- # =Conditional Transformations
120
- #
121
- # Using the transformations as described above, all elements of the same class are
122
- # transformed the same way. Conditional transformations allow to transform elements of
123
- # the same class into elements of different target classes as well as applying different
124
- # transformations on the attributes.
125
- #
126
- # Conditional transformations are defined by specifying multiple transformer blocks for
127
- # the same source class and providing a condition with each block. Since it is important
128
- # to create the target object before evaluation of the transformation block (see above),
129
- # the conditions must also be evaluated separately _before_ the transformer block.
130
- #
131
- # Conditions are specified using transformer methods as described above. If the return
132
- # value is true, the corresponding block is used for transformation. If more than one
133
- # conditions are true, only the first transformer block will be evaluated.
134
- #
135
- # If there is no rule with a condition evaluating to true, rules for superclasses will
136
- # be checked as described above.
137
- #
138
- # Here is an example:
139
- #
140
- # transform ModelIn, :to => ModelOut, :if => :largeNumber do
141
- # { :number => number * 2}
142
- # end
143
- #
144
- # transform ModelIn, :to => ModelOut, :if => :smallNumber do
145
- # { :number => number / 2 }
146
- # end
147
- #
148
- # method :largeNumber do
149
- # number > 1000
150
- # end
151
- #
152
- # method :smallNumber do
153
- # number < 500
154
- # end
155
- #
156
- # In this case the transformation of an element of class ModelIn depends on the value
157
- # of the element's 'number' attribute. If the value is greater than 1000, the first rule
158
- # as taken and the number is doubled. If the value is smaller than 500, the second rule
159
- # is taken and the number is divided by two.
160
- #
161
- # Note that it is up to the user to avoid cycles within the conditions. A cycle could
162
- # occure if the condition are based on transformation target elements, i.e. if +trans+
163
- # is used within the condition to lookup or transform other elements.
164
- #
165
- class Transformer
166
-
167
- TransformationDescription = Struct.new(:block, :target) # :nodoc:
168
-
169
- @@methods = {}
170
- @@transformer_blocks = {}
171
-
172
- def self._transformer_blocks # :nodoc:
173
- @@transformer_blocks[self] ||= {}
174
- end
175
-
176
- def self._methods # :nodoc:
177
- @@methods[self] ||= {}
178
- end
179
-
180
- # This class method is used to specify a transformation rule.
181
- #
182
- # The first argument specifies the class of elements for which this rule applies.
183
- # The second argument must be a hash including the target class
184
- # (as value of key ':to') and an optional condition (as value of key ':if').
185
- #
186
- # The target class is specified by passing the actual Ruby class object.
187
- # The condition is either the name of a transformer method (see Transfomer.method) as
188
- # a symbol or a proc object. In either case the block is evaluated at transformation
189
- # time and its result value determines if the rule applies.
190
- #
191
- def self.transform(from, desc=nil, &block)
192
- to = (desc && desc.is_a?(Hash) && desc[:to])
193
- condition = (desc && desc.is_a?(Hash) && desc[:if])
194
- raise StandardError.new("No transformation target specified.") unless to
195
- block_desc = TransformationDescription.new(block, to)
196
- if condition
197
- _transformer_blocks[from] ||= {}
198
- raise StandardError.new("Multiple (non-conditional) transformations for class #{from.name}.") unless _transformer_blocks[from].is_a?(Hash)
199
- _transformer_blocks[from][condition] = block_desc
200
- else
201
- raise StandardError.new("Multiple (non-conditional) transformations for class #{from.name}.") unless _transformer_blocks[from].nil?
202
- _transformer_blocks[from] = block_desc
203
- end
204
- end
205
-
206
- # This class method specifies that all objects of class +from+ are to be copied
207
- # into an object of class +to+. If +to+ is omitted, +from+ is used as target class.
208
- # During copy, all attributes and references of the target object
209
- # are set to their transformed counterparts of the source object.
210
- #
211
- def self.copy(from, to=nil)
212
- transform(from, :to => to || from) do
213
- Hash[*@current_object.class.ecore.eAllStructuralFeatures.inject([]) {|l,a|
214
- l + [a.name.to_sym, trans(@current_object.send(a.name))]
215
- }]
216
- end
217
- end
218
-
219
- # Define a transformer method for the current transformer class.
220
- # In contrast to regular Ruby methods, a method defined this way executes in the
221
- # context of the object currently being transformed.
222
- #
223
- def self.method(name, &block)
224
- _methods[name.to_s] = block
225
- end
226
-
227
-
228
- # Creates a new transformer
229
- # Optionally an input and output Environment can be specified.
230
- #
231
- def initialize(env_in=nil, env_out=nil)
232
- @env_in = env_in
233
- @env_out = env_out
234
- @transformer_results = {}
235
- @transformer_jobs = []
236
- end
237
-
238
-
239
- # Transforms a given model element according to the rules specified by means of
240
- # the Transformer.transform class method.
241
- #
242
- # The transformation result element is created in the output environment and returned.
243
- # In addition, the result is cached, i.e. a second invocation with the same parameter
244
- # object will return the same result object without any further evaluation of the
245
- # transformation rules. Nil will be transformed into nil. Ruby "singleton" objects
246
- # +true+, +false+, numerics and symbols will be returned without any change. Ruby strings
247
- # will be duplicated with the result being cached.
248
- #
249
- # The transformation input can be given as:
250
- # * a single object
251
- # * an array each element of which is transformed in turn
252
- # * a hash used as input to Environment#find with the result being transformed
253
- #
254
- def trans(obj)
255
- if obj.is_a?(Hash)
256
- raise StandardError.new("No input environment available to find model element.") unless @env_in
257
- obj = @env_in.find(obj)
258
- end
259
- return nil if obj.nil?
260
- return obj if obj.is_a?(TrueClass) or obj.is_a?(FalseClass) or obj.is_a?(Numeric) or obj.is_a?(Symbol)
261
- return @transformer_results[obj] if @transformer_results[obj]
262
- return @transformer_results[obj] = obj.dup if obj.is_a?(String)
263
- return obj.collect{|o| trans(o)}.compact if obj.is_a? Array
264
- raise StandardError.new("No transformer for class #{obj.class.name}") unless _transformerBlock(obj.class)
265
- block_desc = _evaluateCondition(obj)
266
- return nil unless block_desc
267
- @transformer_results[obj] = _instantiateTargetClass(obj, block_desc.target)
268
- # we will transform the properties later
269
- @transformer_jobs << TransformerJob.new(self, obj, block_desc)
270
- # if there have been jobs in the queue before, don't process them in this call
271
- # this way calls to trans are not nested; a recursive implementation
272
- # may cause a "Stack level too deep" exception for large models
273
- return @transformer_results[obj] if @transformer_jobs.size > 1
274
- # otherwise this is the first call of trans, process all jobs here
275
- # more jobs will be added during job execution
276
- while @transformer_jobs.size > 0
277
- @transformer_jobs.first.execute
278
- @transformer_jobs.shift
279
- end
280
- @transformer_results[obj]
281
- end
282
-
283
- def _transformProperties(obj, block_desc) #:nodoc:
284
- old_object, @current_object = @current_object, obj
285
- block_result = instance_eval(&block_desc.block)
286
- raise StandardError.new("Transformer must return a hash") unless block_result.is_a? Hash
287
- @current_object = old_object
288
- _attributesFromHash(@transformer_results[obj], block_result)
289
- end
290
-
291
- class TransformerJob #:nodoc:
292
- def initialize(transformer, obj, block_desc)
293
- @transformer, @obj, @block_desc = transformer, obj, block_desc
294
- end
295
- def execute
296
- @transformer._transformProperties(@obj, @block_desc)
297
- end
298
- end
299
-
300
- # Each call which is not handled by the transformer object is passed to the object
301
- # currently being transformed.
302
- # If that object also does not respond to the call, it is treated as a transformer
303
- # method call (see Transformer.method).
304
- #
305
- def method_missing(m, *args) #:nodoc:
306
- if @current_object.respond_to?(m)
307
- @current_object.send(m, *args)
308
- else
309
- _invokeMethod(m, *args)
310
- end
311
- end
312
-
313
- private
314
-
315
- # returns _transformer_blocks content for clazz or one of its superclasses
316
- def _transformerBlock(clazz) # :nodoc:
317
- block = self.class._transformer_blocks[clazz]
318
- block = _transformerBlock(clazz.superclass) if block.nil? && clazz != Object
319
- block
320
- end
321
-
322
- # returns the first TransformationDescription for clazz or one of its superclasses
323
- # for which condition is true
324
- def _evaluateCondition(obj, clazz=obj.class) # :nodoc:
325
- tb = self.class._transformer_blocks[clazz]
326
- block_description = nil
327
- if tb.is_a?(TransformationDescription)
328
- # non-conditional
329
- block_description = tb
330
- elsif tb
331
- old_object, @current_object = @current_object, obj
332
- tb.each_pair {|condition, block|
333
- if condition.is_a?(Proc)
334
- result = instance_eval(&condition)
335
- elsif condition.is_a?(Symbol)
336
- result = _invokeMethod(condition)
337
- else
338
- result = condition
339
- end
340
- if result
341
- block_description = block
342
- break
343
- end
344
- }
345
- @current_object = old_object
346
- end
347
- block_description = _evaluateCondition(obj, clazz.superclass) if block_description.nil? && clazz != Object
348
- block_description
349
- end
350
-
351
- def _instantiateTargetClass(obj, target_desc) # :nodoc:
352
- old_object, @current_object = @current_object, obj
353
- if target_desc.is_a?(Proc)
354
- target_class = instance_eval(&target_desc)
355
- elsif target_desc.is_a?(Symbol)
356
- target_class = _invokeMethod(target_desc)
357
- else
358
- target_class = target_desc
359
- end
360
- @current_object = old_object
361
- result = target_class.new
362
- @env_out << result if @env_out
363
- result
364
- end
365
-
366
- def _invokeMethod(m) # :nodoc:
367
- raise StandardError.new("Method not found: #{m}") unless self.class._methods[m.to_s]
368
- instance_eval(&self.class._methods[m.to_s])
369
- end
370
-
371
- def _attributesFromHash(obj, hash) # :nodoc:
372
- hash.delete(:class)
373
- hash.each_pair{|k,v|
374
- obj.send("#{k}=", v)
375
- }
376
- obj
377
- end
378
-
379
- end
380
-
381
- end