rgen 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. data/CHANGELOG +9 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README +73 -0
  4. data/lib/ea/xmi_class_instantiator.rb +45 -0
  5. data/lib/ea/xmi_helper.rb +26 -0
  6. data/lib/ea/xmi_metamodel.rb +19 -0
  7. data/lib/ea/xmi_object_instantiator.rb +42 -0
  8. data/lib/ea/xmi_to_classmodel.rb +78 -0
  9. data/lib/ea/xmi_to_objectmodel.rb +89 -0
  10. data/lib/mmgen/metamodel_generator.rb +19 -0
  11. data/lib/mmgen/mm_ext/uml_classmodel_ext.rb +71 -0
  12. data/lib/mmgen/mmgen.rb +21 -0
  13. data/lib/mmgen/templates/uml_classmodel.tpl +63 -0
  14. data/lib/rgen/array_extensions.rb +23 -0
  15. data/lib/rgen/auto_class_creator.rb +56 -0
  16. data/lib/rgen/environment.rb +57 -0
  17. data/lib/rgen/metamodel_builder.rb +102 -0
  18. data/lib/rgen/metamodel_builder/build_helper.rb +29 -0
  19. data/lib/rgen/metamodel_builder/builder_extensions.rb +191 -0
  20. data/lib/rgen/metamodel_builder/builder_runtime.rb +67 -0
  21. data/lib/rgen/name_helper.rb +18 -0
  22. data/lib/rgen/template_language.rb +169 -0
  23. data/lib/rgen/template_language/directory_template_container.rb +51 -0
  24. data/lib/rgen/template_language/output_handler.rb +84 -0
  25. data/lib/rgen/template_language/template_container.rb +153 -0
  26. data/lib/rgen/template_language/template_helper.rb +26 -0
  27. data/lib/rgen/transformer.rb +316 -0
  28. data/lib/rgen/xml_instantiator/dependency_resolver.rb +23 -0
  29. data/lib/rgen/xml_instantiator/xml_instantiator.rb +78 -0
  30. data/lib/rgen/xml_instantiator/xml_parser.rb +39 -0
  31. data/lib/uml/objectmodel_instantiator.rb +53 -0
  32. data/lib/uml/uml_classmodel.rb +92 -0
  33. data/lib/uml/uml_objectmodel.rb +65 -0
  34. data/test/array_extensions_test.rb +54 -0
  35. data/test/environment_test.rb +47 -0
  36. data/test/metamodel_builder_test.rb +175 -0
  37. data/test/metamodel_generator_test.rb +45 -0
  38. data/test/metamodel_generator_test/TestModel.rb +40 -0
  39. data/test/metamodel_generator_test/expected_result.txt +40 -0
  40. data/test/output_handler_test.rb +40 -0
  41. data/test/rgen_test.rb +13 -0
  42. data/test/template_language_test.rb +46 -0
  43. data/test/template_language_test/expected_result.txt +10 -0
  44. data/test/template_language_test/templates/content/chapter.tpl +5 -0
  45. data/test/template_language_test/templates/index/c/cmod.tpl +1 -0
  46. data/test/template_language_test/templates/index/chapter.tpl +3 -0
  47. data/test/template_language_test/templates/root.tpl +22 -0
  48. data/test/template_language_test/testout.txt +10 -0
  49. data/test/transformer_test.rb +176 -0
  50. data/test/xmi_class_instantiator_test.rb +107 -0
  51. data/test/xmi_instantiator_test/testmodel.eap +0 -0
  52. data/test/xmi_instantiator_test/testmodel.xml +962 -0
  53. data/test/xmi_object_instantiator_test.rb +65 -0
  54. metadata +117 -0
@@ -0,0 +1,26 @@
1
+ # RGen Framework
2
+ # (c) Martin Thiede, 2006
3
+
4
+ module RGen
5
+
6
+ module TemplateLanguage
7
+
8
+ module TemplateHelper
9
+
10
+ private
11
+
12
+ def _splitArgsAndOptions(all)
13
+ if all[-1] and all[-1].is_a? Hash
14
+ args = all[0..-2] || []
15
+ options = all[-1]
16
+ else
17
+ args = all
18
+ options = {}
19
+ end
20
+ return args, options
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,316 @@
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
+ # Here is an example:
17
+ #
18
+ # class MyTransformer < RGen::Transformer
19
+ #
20
+ # transform InputClass, :to => OutputClass do
21
+ # { :name => name, :otherClass => trans(otherClass) }
22
+ # end
23
+ #
24
+ # transform OtherInputClass, :to => OtherOutputClass do
25
+ # { :name => name }
26
+ # end
27
+ # end
28
+ #
29
+ # In this example a transformation rule is specified for model elements of class InputClass
30
+ # as well as for elements of class OtherInputClass. The former is to be transformed into
31
+ # an instance of OutputClass, the latter into an instance of OtherOutputClass.
32
+ # Note that the Ruby class objects are used to specifiy the classes.
33
+ #
34
+ # =Transforming Attributes
35
+ #
36
+ # Besides the target class of a transformation, the attributes of the result object are
37
+ # specified in the above example. This is done by providing a Ruby block with the call of
38
+ # +transform+. Within this block arbitrary Ruby code may be placed, however the block
39
+ # must return a hash. This hash object specifies the attribute assignment of the
40
+ # result object using key/value pairs: The key must be a Symbol specifying the attribute
41
+ # which is to be assigned by name, the value is the value that will be assigned.
42
+ #
43
+ # For convenience, the transformation block will be evaluated in the context of the
44
+ # source model element which is currently being converted. This way it is possible to just
45
+ # write <code>:name => name</code> in the example in order to assign the name of the source
46
+ # object to the name attribute of the target object.
47
+ #
48
+ # =Transforming References
49
+ #
50
+ # When attributes of elements are references to other elements, those referenced
51
+ # elements have to be transformed as well. As shown above, this can be done by calling
52
+ # the Transformer#trans method. This method initiates a transformation of the element
53
+ # or array of elements passed as parameter according to transformation rules specified
54
+ # using +transform+. In fact the +trans+ method is the only way to start the transformation
55
+ # at all.
56
+ #
57
+ # For convenience and performance reasons, the result of +trans+ is cached with respect
58
+ # to the parameter object. I.e. calling trans on the same source object a second time will
59
+ # return the same result object _without_ a second evaluation of the corresponding
60
+ # transformation rules.
61
+ #
62
+ # This way the +trans+ method can be used to lookup the target element for some source
63
+ # element without the need to locally store a reference to the target element. In addition
64
+ # this can be useful if it is not clear if certain element has already been transformed
65
+ # when it is required within some other transformation block. See example below.
66
+ #
67
+ # Special care has been taken to allow the transformation of elements which reference
68
+ # each other cyclically. The key issue here is that the target element of some transformation
69
+ # is created _before_ the transformation's block is evaluated, i.e before the elements
70
+ # attributes are set. Otherwise a call to +trans+ within the transformation's block
71
+ # could lead to a +trans+ of the element itself.
72
+ #
73
+ # Here is an example:
74
+ #
75
+ # transform ModelAIn, :to => ModelAOut do
76
+ # { :name => name, :modelB => trans(modelB) }
77
+ # end
78
+ #
79
+ # transform ModelBIn, :to => ModelBOut do
80
+ # { :name => name, :modelA => trans(modelA) }
81
+ # end
82
+ #
83
+ # Note that in this case it does not matter if the transformation is initiated by calling
84
+ # +trans+ with a ModelAIn element or ModelBIn element due to the caching feature described
85
+ # above.
86
+ #
87
+ # =Transformer Methods
88
+ #
89
+ # When code in transformer blocks becomes more complex it might be useful to refactor
90
+ # it into smaller methods. If regular Ruby methods within the Transformer subclass are
91
+ # used for this purpose, it is necessary to know the source element being transformed.
92
+ # This could be achieved by explicitly passing the +@current_object+ as parameter of the
93
+ # method (see Transformer#trans).
94
+ #
95
+ # A more convenient way however is to define a special kind of method using the
96
+ # Transformer.method class method. Those methods are evaluated within the context of the
97
+ # current source element being transformed just the same as transformer blocks are.
98
+ #
99
+ # Here is an example:
100
+ #
101
+ # transform ModelIn, :to => ModelOut do
102
+ # { :number => doubleNumber }
103
+ # end
104
+ #
105
+ # method :doubleNumber do
106
+ # number * 2;
107
+ # end
108
+ #
109
+ # In this example the transformation assigns the 'number' attribute of the source element
110
+ # multiplied by 2 to the target element. The multiplication is done in a dedicated method
111
+ # called 'doubleNumber'. Note that the 'number' attribute of the source element is
112
+ # accessed without an explicit reference to the source element as the method's body
113
+ # evaluates in the source element's context.
114
+ #
115
+ # =Conditional Transformations
116
+ #
117
+ # Using the transformations as described above, all elements of the same class are
118
+ # transformed the same way. Conditional transformations allow to transform elements of
119
+ # the same class into elements of different target classes as well as applying different
120
+ # transformations on the attributes.
121
+ #
122
+ # Conditional transformations are defined by specifying multiple transformer blocks for
123
+ # the same source class and providing a condition with each block. Since it is important
124
+ # to create the target object before evaluation of the transformation block (see above),
125
+ # the conditions must also be evaluated separately _before_ the transformer block.
126
+ #
127
+ # Conditions are specified using transformer methods as described above. If the return
128
+ # value is true, the corresponding block is used for transformation. If more than one
129
+ # conditions are true, only the first transformer block will be evaluated.
130
+ #
131
+ # Here is an example:
132
+ #
133
+ # transform ModelIn, :to => ModelOut, :if => :largeNumber do
134
+ # { :number => number * 2}
135
+ # end
136
+ #
137
+ # transform ModelIn, :to => ModelOut, :if => :smallNumber do
138
+ # { :number => number / 2 }
139
+ # end
140
+ #
141
+ # method :largeNumber do
142
+ # number > 1000
143
+ # end
144
+ #
145
+ # method :smallNumber do
146
+ # number < 500
147
+ # end
148
+ #
149
+ # In this case the transformation of an element of class ModelIn depends on the value
150
+ # of the element's 'number' attribute. If the value is greater than 1000, the first rule
151
+ # as taken and the number is doubled. If the value is smaller than 500, the second rule
152
+ # is taken and the number is divided by two.
153
+ #
154
+ # Note that it is up to the user to avoid cycles within the conditions. A cycle could
155
+ # occure if the condition are based on transformation target elements, i.e. if +trans+
156
+ # is used within the condition to lookup or transform other elements.
157
+ #
158
+ class Transformer
159
+
160
+ TransformationDescription = Struct.new(:block, :target) # :nodoc:
161
+
162
+ @@methods = {}
163
+ @@transformer_blocks = {}
164
+
165
+ def self._transformer_blocks # :nodoc:
166
+ @@transformer_blocks[self] ||= {}
167
+ end
168
+
169
+ def self._methods # :nodoc:
170
+ @@methods[self] ||= {}
171
+ end
172
+
173
+ # This class method is used to specify a transformation rule.
174
+ #
175
+ # The first argument specifies the class of elements for which this rule applies.
176
+ # The second argument must be a hash including the target class
177
+ # (as value of key ':to') and an optional condition (as value of key ':if').
178
+ #
179
+ # The target class is specified by passing the actual Ruby class object.
180
+ # The condition is either the name of a transformer method (see Transfomer.method) as
181
+ # a symbol or a proc object. In either case the block is evaluated at transformation
182
+ # time and its result value determines if the rule applies.
183
+ #
184
+ def self.transform(from, desc=nil, &block)
185
+ to = (desc && desc.is_a?(Hash) && desc[:to])
186
+ condition = (desc && desc.is_a?(Hash) && desc[:if])
187
+ raise StandardError.new("No transformation target specified.") unless to
188
+ block_desc = TransformationDescription.new(block, to)
189
+ if condition
190
+ _transformer_blocks[from] ||= {}
191
+ raise StandardError.new("Multiple (non-conditional) transformations for class #{from.name}.") unless _transformer_blocks[from].is_a?(Hash)
192
+ _transformer_blocks[from][condition] = block_desc
193
+ else
194
+ raise StandardError.new("Multiple (non-conditional) transformations for class #{from.name}.") unless _transformer_blocks[from].nil?
195
+ _transformer_blocks[from] = block_desc
196
+ end
197
+ end
198
+
199
+ # Define a transformer method for the current transformer class.
200
+ # In contrast to regular Ruby methods, a method defined this way executes in the
201
+ # context of the object currently being transformed.
202
+ #
203
+ def self.method(name, &block)
204
+ _methods[name.to_s] = block
205
+ end
206
+
207
+
208
+ # Creates a new transformer with the specified input and output Environment.
209
+ #
210
+ def initialize(env_in, env_out)
211
+ @env_in = env_in
212
+ @env_out = env_out
213
+ @transformer_results = {}
214
+ end
215
+
216
+
217
+ # Transforms a given model element according to the rules specified by means of
218
+ # the Transformer.transform class method.
219
+ #
220
+ # The transformation result element is created in the output environment and returned.
221
+ # In addition, the result is cached, i.e. a second invocation with the same parameter
222
+ # object will return the same result object without any further evaluation of the
223
+ # transformation rules. Nil will be transformed into nil.
224
+ #
225
+ # The transformation input can be given as:
226
+ # * a single object
227
+ # * an array each element of which is transformed in turn
228
+ # * a hash used as input to Environment#find with the result being transformed
229
+ #
230
+ def trans(obj)
231
+ return nil if obj.nil?
232
+ return @transformer_results[obj] if @transformer_results[obj]
233
+ obj = @env_in.find(obj) if obj.is_a?(Hash)
234
+ return obj.collect{|o| trans(o)}.compact if obj.is_a? Enumerable
235
+ raise StandardError.new("No transformer for class #{obj.class.name}") unless self.class._transformer_blocks[obj.class]
236
+ block_desc = _evaluateCondition(obj)
237
+ return nil unless block_desc
238
+ @transformer_results[obj] = _instantiateTargetClass(obj, block_desc.target)
239
+ old_object, @current_object = @current_object, obj
240
+ block_result = instance_eval(&block_desc.block)
241
+ raise StandardError.new("Transformer must return a hash") unless block_result.is_a? Hash
242
+ @current_object = old_object
243
+ _attributesFromHash(@transformer_results[obj], block_result)
244
+ end
245
+
246
+ # Each call which is not handled by the transformer object is passed to the object
247
+ # currently being transformed.
248
+ # If that object also does not respond to the call, it is treated as a transformer
249
+ # method call (see Transformer.method).
250
+ #
251
+ def method_missing(m) #:nodoc:
252
+ if @current_object.respond_to?(m)
253
+ @current_object.send(m)
254
+ else
255
+ _invokeMethod(m)
256
+ end
257
+ end
258
+
259
+ private
260
+
261
+ # returns the first TransformationDescription for which condition is true :nodoc:
262
+ def _evaluateCondition(obj)
263
+ tb = self.class._transformer_blocks[obj.class]
264
+ block_description = nil
265
+ if tb.is_a?(TransformationDescription)
266
+ # non-conditional
267
+ block_description = tb
268
+ else
269
+ old_object, @current_object = @current_object, obj
270
+ tb.each_pair {|condition, block|
271
+ if condition.is_a?(Proc)
272
+ result = instance_eval(&condition)
273
+ elsif condition.is_a?(Symbol)
274
+ result = _invokeMethod(condition)
275
+ else
276
+ result = condition
277
+ end
278
+ if result
279
+ block_description = block
280
+ break
281
+ end
282
+ }
283
+ @current_object = old_object
284
+ end
285
+ block_description
286
+ end
287
+
288
+ def _instantiateTargetClass(obj, target_desc) # :nodoc:
289
+ old_object, @current_object = @current_object, obj
290
+ if target_desc.is_a?(Proc)
291
+ target_class = instance_eval(&target_desc)
292
+ elsif target_desc.is_a?(Symbol)
293
+ target_class = _invokeMethod(target_desc)
294
+ else
295
+ target_class = target_desc
296
+ end
297
+ @current_object = old_object
298
+ @env_out.new target_class
299
+ end
300
+
301
+ def _invokeMethod(m) # :nodoc:
302
+ raise StandardError.new("Method not found: #{m}") unless self.class._methods[m.to_s]
303
+ instance_eval(&self.class._methods[m.to_s])
304
+ end
305
+
306
+ def _attributesFromHash(obj, hash) # :nodoc:
307
+ hash.delete(:class)
308
+ hash.each_pair{|k,v|
309
+ obj.send("#{k}=", v)
310
+ }
311
+ obj
312
+ end
313
+
314
+ end
315
+
316
+ end
@@ -0,0 +1,23 @@
1
+ # RGen Framework
2
+ # (c) Martin Thiede, 2006
3
+
4
+ require 'rgen/name_helper'
5
+
6
+ module RGen
7
+
8
+ module DependencyResolver
9
+ include NameHelper
10
+
11
+ def resolveById(idAttribute,refAttribute)
12
+ lookup = {}
13
+ @elements.each { |e|
14
+ lookup[e.send("get"+firstToUpper(idAttribute))] = e if e.respond_to?("get"+firstToUpper(idAttribute))
15
+ }
16
+ @elements.each { |e|
17
+ target = lookup[e.send("get"+firstToUpper(refAttribute))] if e.respond_to?("get"+firstToUpper(refAttribute))
18
+ assocObjectsOneToMany(target, className(e)+"_"+refAttribute, e, refAttribute+"_"+className(target)) if target
19
+ }
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,78 @@
1
+ # RGen Framework
2
+ # (c) Martin Thiede, 2006
3
+
4
+ require 'rgen/name_helper'
5
+ require 'rgen/xml_instantiator/xml_parser'
6
+ require 'rgen/xml_instantiator/dependency_resolver'
7
+
8
+ module RGen
9
+
10
+ class XMLInstantiator
11
+ include NameHelper
12
+ include DependencyResolver
13
+
14
+ def initialize(mod, createMM=false, &config)
15
+ @mod = mod
16
+ @createMM = createMM
17
+ @configBlock = config
18
+ end
19
+
20
+ def instantiate(env, str)
21
+ @elements = []
22
+ XMLParser.new(self).parse(str)
23
+ @configBlock.call(self) if @configBlock
24
+ @elements.each {|e| env << e}
25
+ end
26
+
27
+ def newObject(name)
28
+ # capitalize first letter only
29
+ className = firstToUpper(normalizeName(name))
30
+ begin
31
+ cls = @mod.const_get(className)
32
+ obj = cls.new
33
+ @elements << obj
34
+ return obj
35
+ rescue NameError
36
+ if @createMM
37
+ @mod.module_eval("class #{className} < RGen::MetamodelBuilder::MMBase; end")
38
+ retry
39
+ else
40
+ raise
41
+ end
42
+ end
43
+ end
44
+
45
+ def setAttribute(obj, name, val)
46
+ m = normalizeName(name)
47
+ begin
48
+ obj.send("#{m}=", val)
49
+ rescue NoMethodError
50
+ if @createMM
51
+ obj.class.has_one(m)
52
+ retry
53
+ else
54
+ raise
55
+ end
56
+ end
57
+ end
58
+
59
+ def assocObjectsParentToChild(parent, child)
60
+ assocObjectsOneToMany(parent,className(child),child,"_p_"+className(parent))
61
+ end
62
+
63
+ def assocObjectsOneToMany(objOne, roleOne, objMany, roleMany)
64
+ begin
65
+ objOne.addGeneric(roleOne, objMany)
66
+ rescue NoMethodError => nme
67
+ if @createMM
68
+ objOne.class.one_to_many(roleOne, objMany.class, roleMany)
69
+ retry
70
+ else
71
+ raise
72
+ end
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ end
@@ -0,0 +1,39 @@
1
+ # RGen Framework
2
+ # (c) Martin Thiede, 2006
3
+
4
+ require 'rexml/parsers/sax2parser'
5
+ require 'rexml/sax2listener'
6
+
7
+ module RGen
8
+
9
+ class XMLParser
10
+ include REXML::SAX2Listener
11
+ include NameHelper
12
+
13
+ def initialize(instantiator)
14
+ @stack = []
15
+ @inst = instantiator
16
+ end
17
+
18
+ def parse(src)
19
+ parser = REXML::Parsers::SAX2Parser.new(src)
20
+ parser.listen(self)
21
+ parser.parse
22
+ end
23
+
24
+ def start_element(uri, localname, qname, attributes)
25
+ currentObject = @inst.newObject(qname)
26
+ @inst.assocObjectsParentToChild(@stack[-1],currentObject) if @stack.size > 0
27
+ attributes.each_pair { |a,v|
28
+ @inst.setAttribute(currentObject, a, v)
29
+ }
30
+ @stack.push currentObject
31
+ end
32
+
33
+ def end_element(uri, localname, qname)
34
+ @stack.pop
35
+ end
36
+
37
+ end
38
+
39
+ end