rgen 0.2.0

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