rgen 0.5.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (125) hide show
  1. data/CHANGELOG +28 -0
  2. data/Rakefile +3 -4
  3. data/lib/ea_support/uml13_ea_metamodel.rb +3 -3
  4. data/lib/ea_support/uml13_ea_to_uml13.rb +33 -2
  5. data/lib/ea_support/uml13_to_uml13_ea.rb +7 -0
  6. data/lib/mmgen/mm_ext/ecore_mmgen_ext.rb +4 -4
  7. data/lib/mmgen/templates/metamodel_generator.tpl +143 -143
  8. data/lib/rgen/ecore/ecore.rb +11 -1
  9. data/lib/rgen/ecore/ecore_interface.rb +47 -0
  10. data/lib/rgen/ecore/ecore_to_ruby.rb +166 -0
  11. data/lib/rgen/ecore/{ecore_transformer.rb → ruby_to_ecore.rb} +11 -11
  12. data/lib/rgen/environment.rb +15 -2
  13. data/lib/rgen/fragment/dump_file_cache.rb +63 -0
  14. data/lib/rgen/fragment/fragmented_model.rb +139 -0
  15. data/lib/rgen/fragment/model_fragment.rb +268 -0
  16. data/lib/rgen/instantiator/abstract_xml_instantiator.rb +44 -72
  17. data/lib/rgen/instantiator/default_xml_instantiator.rb +2 -2
  18. data/lib/rgen/instantiator/ecore_xml_instantiator.rb +16 -1
  19. data/lib/rgen/instantiator/json_instantiator.rb +16 -2
  20. data/lib/rgen/instantiator/nodebased_xml_instantiator.rb +118 -138
  21. data/lib/rgen/instantiator/qualified_name_resolver.rb +5 -1
  22. data/lib/rgen/instantiator/reference_resolver.rb +126 -24
  23. data/lib/rgen/instantiator/xmi11_instantiator.rb +6 -2
  24. data/lib/rgen/metamodel_builder.rb +18 -6
  25. data/lib/rgen/metamodel_builder/builder_extensions.rb +431 -407
  26. data/lib/rgen/metamodel_builder/builder_runtime.rb +8 -8
  27. data/lib/rgen/metamodel_builder/constant_order_helper.rb +4 -4
  28. data/lib/rgen/metamodel_builder/data_types.rb +5 -1
  29. data/lib/rgen/metamodel_builder/intermediate/feature.rb +167 -0
  30. data/lib/rgen/metamodel_builder/module_extension.rb +2 -2
  31. data/lib/rgen/model_builder.rb +10 -5
  32. data/lib/rgen/model_builder/builder_context.rb +17 -1
  33. data/lib/rgen/serializer/opposite_reference_filter.rb +18 -0
  34. data/lib/rgen/serializer/qualified_name_provider.rb +45 -0
  35. data/lib/rgen/template_language/template_container.rb +3 -1
  36. data/lib/rgen/{auto_class_creator.rb → util/auto_class_creator.rb} +6 -1
  37. data/lib/rgen/util/cached_glob.rb +67 -0
  38. data/lib/rgen/util/file_cache_map.rb +104 -0
  39. data/lib/rgen/util/file_change_detector.rb +78 -0
  40. data/lib/rgen/{method_delegation.rb → util/method_delegation.rb} +18 -3
  41. data/lib/rgen/{model_comparator.rb → util/model_comparator.rb} +17 -5
  42. data/lib/rgen/{model_comparator_base.rb → util/model_comparator_base.rb} +6 -1
  43. data/lib/rgen/{model_dumper.rb → util/model_dumper.rb} +6 -1
  44. data/lib/rgen/{name_helper.rb → util/name_helper.rb} +6 -1
  45. data/lib/rgen/util/pattern_matcher.rb +329 -0
  46. data/lib/transformers/uml13_to_ecore.rb +103 -60
  47. data/test/ecore_self_test.rb +43 -42
  48. data/test/json_test.rb +15 -0
  49. data/test/metamodel_builder_test.rb +361 -206
  50. data/test/metamodel_from_ecore_test.rb +45 -0
  51. data/test/metamodel_order_test.rb +10 -4
  52. data/test/metamodel_roundtrip_test.rb +2 -2
  53. data/test/metamodel_roundtrip_test/TestModel_Regenerated.rb +1 -1
  54. data/test/metamodel_roundtrip_test/houseMetamodel_Regenerated.ecore +50 -50
  55. data/test/method_delegation_test.rb +9 -9
  56. data/test/model_builder/ecore_internal.rb +19 -9
  57. data/test/model_builder/serializer_test.rb +1 -1
  58. data/test/reference_resolver_test.rb +79 -12
  59. data/test/rgen_test.rb +2 -0
  60. data/test/template_language_test.rb +7 -0
  61. data/test/template_language_test/templates/callback_indent_test/a.tpl +12 -0
  62. data/test/template_language_test/templates/callback_indent_test/b.tpl +5 -0
  63. data/test/testmodel/ea_testmodel_regenerated.xml +588 -583
  64. data/test/transformer_test.rb +3 -3
  65. data/test/util/file_cache_map_test.rb +91 -0
  66. data/test/util/file_cache_map_test/testdir/fileA +1 -0
  67. data/test/util_test.rb +4 -0
  68. data/test/xml_instantiator_test.rb +139 -135
  69. metadata +49 -104
  70. data/lib/rgen/ecore/ecore_instantiator.rb +0 -31
  71. data/lib/rgen/metamodel_builder/metamodel_description.rb +0 -232
  72. data/redist/xmlscan/ChangeLog +0 -1301
  73. data/redist/xmlscan/README +0 -34
  74. data/redist/xmlscan/THANKS +0 -11
  75. data/redist/xmlscan/doc/changes.html +0 -74
  76. data/redist/xmlscan/doc/changes.rd +0 -80
  77. data/redist/xmlscan/doc/en/conformance.html +0 -136
  78. data/redist/xmlscan/doc/en/conformance.rd +0 -152
  79. data/redist/xmlscan/doc/en/manual.html +0 -356
  80. data/redist/xmlscan/doc/en/manual.rd +0 -402
  81. data/redist/xmlscan/doc/ja/conformance.ja.html +0 -118
  82. data/redist/xmlscan/doc/ja/conformance.ja.rd +0 -134
  83. data/redist/xmlscan/doc/ja/manual.ja.html +0 -325
  84. data/redist/xmlscan/doc/ja/manual.ja.rd +0 -370
  85. data/redist/xmlscan/doc/src/Makefile +0 -41
  86. data/redist/xmlscan/doc/src/conformance.rd.src +0 -256
  87. data/redist/xmlscan/doc/src/langsplit.rb +0 -110
  88. data/redist/xmlscan/doc/src/manual.rd.src +0 -614
  89. data/redist/xmlscan/install.rb +0 -41
  90. data/redist/xmlscan/lib/xmlscan/encoding.rb +0 -311
  91. data/redist/xmlscan/lib/xmlscan/htmlscan.rb +0 -289
  92. data/redist/xmlscan/lib/xmlscan/namespace.rb +0 -352
  93. data/redist/xmlscan/lib/xmlscan/parser.rb +0 -299
  94. data/redist/xmlscan/lib/xmlscan/scanner.rb +0 -1109
  95. data/redist/xmlscan/lib/xmlscan/version.rb +0 -22
  96. data/redist/xmlscan/lib/xmlscan/visitor.rb +0 -158
  97. data/redist/xmlscan/lib/xmlscan/xmlchar.rb +0 -441
  98. data/redist/xmlscan/memo/CONFORMANCE +0 -1249
  99. data/redist/xmlscan/memo/PRODUCTIONS +0 -195
  100. data/redist/xmlscan/memo/contentspec.ry +0 -335
  101. data/redist/xmlscan/samples/chibixml.rb +0 -105
  102. data/redist/xmlscan/samples/getxmlchar.rb +0 -122
  103. data/redist/xmlscan/samples/rexml.rb +0 -159
  104. data/redist/xmlscan/samples/xmlbench.rb +0 -88
  105. data/redist/xmlscan/samples/xmlbench/parser/chibixml.rb +0 -22
  106. data/redist/xmlscan/samples/xmlbench/parser/nqxml.rb +0 -29
  107. data/redist/xmlscan/samples/xmlbench/parser/rexml.rb +0 -62
  108. data/redist/xmlscan/samples/xmlbench/parser/xmlparser.rb +0 -22
  109. data/redist/xmlscan/samples/xmlbench/parser/xmlscan-0.0.10.rb +0 -62
  110. data/redist/xmlscan/samples/xmlbench/parser/xmlscan-chibixml.rb +0 -22
  111. data/redist/xmlscan/samples/xmlbench/parser/xmlscan-rexml.rb +0 -22
  112. data/redist/xmlscan/samples/xmlbench/parser/xmlscan.rb +0 -99
  113. data/redist/xmlscan/samples/xmlbench/xmlbench-lib.rb +0 -116
  114. data/redist/xmlscan/samples/xmlconftest.rb +0 -200
  115. data/redist/xmlscan/test.rb +0 -7
  116. data/redist/xmlscan/tests/deftestcase.rb +0 -73
  117. data/redist/xmlscan/tests/runtest.rb +0 -47
  118. data/redist/xmlscan/tests/testall.rb +0 -14
  119. data/redist/xmlscan/tests/testencoding.rb +0 -438
  120. data/redist/xmlscan/tests/testhtmlscan.rb +0 -752
  121. data/redist/xmlscan/tests/testnamespace.rb +0 -457
  122. data/redist/xmlscan/tests/testparser.rb +0 -591
  123. data/redist/xmlscan/tests/testscanner.rb +0 -1749
  124. data/redist/xmlscan/tests/testxmlchar.rb +0 -143
  125. data/redist/xmlscan/tests/visitor.rb +0 -34
@@ -0,0 +1,67 @@
1
+ module RGen
2
+
3
+ module Util
4
+
5
+ # WARNING: the mechanism of taking timestamps of directories in order to find out if the
6
+ # content has changed doesn't work reliably across all kinds of filesystems
7
+ #
8
+ class CachedGlob
9
+
10
+ def initialize(dir_glob, file_glob)
11
+ @dir_glob = dir_glob
12
+ @file_glob = file_glob
13
+ @root_dirs = []
14
+ @dirs = {}
15
+ @files = {}
16
+ @timestamps = {}
17
+ end
18
+
19
+ # returns all files contained in directories matched by +dir_glob+ which match +file_glob+.
20
+ # +file_glob+ must be relative to +dir_glob+.
21
+ # dir_glob "*/a" with file_glob "**/*.txt" is basically equivalent with Dir.glob("*/a/**/*.txt")
22
+ # the idea is that the file glob will only be re-eavluated when the content of one of the
23
+ # directories matched by dir_glob has changed.
24
+ # this will only be faster than a normal Dir.glob if the number of dirs matched by dir_glob is
25
+ # relatively large and changes in files affect only a few of them at a time.
26
+ def glob
27
+ root_dirs = Dir.glob(@dir_glob)
28
+ (@root_dirs - root_dirs).each do |d|
29
+ remove_root_dir(d)
30
+ end
31
+ (@root_dirs & root_dirs).each do |d|
32
+ update_root_dir(d) if dir_changed?(d)
33
+ end
34
+ (root_dirs - @root_dirs).each do |d|
35
+ update_root_dir(d)
36
+ end
37
+ @root_dirs = root_dirs
38
+ @root_dirs.sort.collect{|d| @files[d]}.flatten
39
+ end
40
+
41
+ private
42
+
43
+ def dir_changed?(dir)
44
+ @dirs[dir].any?{|d| File.mtime(d) != @timestamps[dir][d]}
45
+ end
46
+
47
+ def update_root_dir(dir)
48
+ @dirs[dir] = Dir.glob(dir+"/**/")
49
+ @files[dir] = Dir.glob(dir+"/"+@file_glob)
50
+ @timestamps[dir] = {}
51
+ @dirs[dir].each do |d|
52
+ @timestamps[dir][d] = File.mtime(d)
53
+ end
54
+ end
55
+
56
+ def remove_root_dir(dir)
57
+ @dirs.delete(dir)
58
+ @files.delete(dir)
59
+ @timestamps.delete(dir)
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+
@@ -0,0 +1,104 @@
1
+ require 'digest'
2
+ require 'fileutils'
3
+
4
+ module RGen
5
+
6
+ module Util
7
+
8
+ # Implements a cache for storing and loading data associated with arbitrary files.
9
+ # The data is stored in cache files within a subfolder of the folder where
10
+ # the associated files exists.
11
+ # The cache files are protected with a checksum and loaded data will be
12
+ # invalid in case either the associated file are the cache file has changed.
13
+ #
14
+ class FileCacheMap
15
+ # optional program version info to be associated with the cache files
16
+ # if the program version changes, cached data will also be invalid
17
+ attr_accessor :version_info
18
+
19
+ # +cache_dir+ is the name of the subfolder where cache files are created
20
+ # +postfix+ is an extension appended to the original file name for
21
+ # creating the name of the cache file
22
+ def initialize(cache_dir, postfix)
23
+ @postfix = postfix
24
+ @cache_dir = cache_dir
25
+ end
26
+
27
+ # load data associated with file +key_path+
28
+ # returns :invalid in case either the associated file or the cache file has changed
29
+ def load_data(key_path)
30
+ cf = cache_file(key_path)
31
+ return :invalid unless File.exist?(cf)
32
+ result = nil
33
+ File.open(cf, "rb") do |f|
34
+ header = f.read(41)
35
+ return :invalid unless header
36
+ checksum = header[0..39]
37
+ data = f.read
38
+ if calc_sha1(data) == checksum
39
+ if calc_sha1_keydata(key_path) == data[0..39]
40
+ result = data[41..-1]
41
+ else
42
+ result = :invalid
43
+ end
44
+ else
45
+ result = :invalid
46
+ end
47
+ end
48
+ result
49
+ end
50
+
51
+ # store data +value_data+ associated with file +key_path+
52
+ def store_data(key_path, value_data)
53
+ data = calc_sha1_keydata(key_path) + "\n" + value_data
54
+ data = calc_sha1(data) + "\n" + data
55
+ cf = cache_file(key_path)
56
+ FileUtils.mkdir(File.dirname(cf)) rescue Errno::EEXIST
57
+ File.open(cf, "wb") do |f|
58
+ f.write(data)
59
+ end
60
+ end
61
+
62
+ # remove cache files which are not associated with any file in +key_paths+
63
+ # will only remove files within +root_path+
64
+ def clean_unused(root_path, key_paths)
65
+ raise "key paths must be within root path" unless key_paths.all?{|p| p.index(root_path) == 0}
66
+ used_files = key_paths.collect{|p| cache_file(p)}
67
+ files = Dir[root_path+"/**/"+@cache_dir+"/*"+@postfix]
68
+ files.each do |f|
69
+ FileUtils.rm(f) unless used_files.include?(f)
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def cache_file(path)
76
+ File.dirname(path) + "/"+@cache_dir+"/" + File.basename(path) + @postfix
77
+ end
78
+
79
+ def calc_sha1(data)
80
+ sha1 = Digest::SHA1.new
81
+ sha1.update(data)
82
+ sha1.hexdigest
83
+ end
84
+
85
+ def keyData(path)
86
+ File.read(path)+@version_info.to_s
87
+ end
88
+
89
+ # this method is much faster than calling +keyData+ and putting the result in +calc_sha1+
90
+ # reason is probably that there are not so many big strings being created
91
+ def calc_sha1_keydata(path)
92
+ sha1 = Digest::SHA1.new
93
+ sha1.file(path)
94
+ sha1.update(@version_info.to_s)
95
+ sha1.hexdigest
96
+ end
97
+
98
+ end
99
+
100
+ end
101
+
102
+ end
103
+
104
+
@@ -0,0 +1,78 @@
1
+ require 'digest'
2
+
3
+ module RGen
4
+
5
+ module Util
6
+
7
+ # The FileChangeDetector detects changes in a set of files.
8
+ # Changes are detected between successive calls to check_files with a give set of files.
9
+ # Changes include files being added, removed or having changed their content.
10
+ #
11
+ class FileChangeDetector
12
+
13
+ FileInfo = Struct.new(:timestamp, :digest)
14
+
15
+ # Create a FileChangeDetector, options include:
16
+ #
17
+ # :file_added
18
+ # a proc which is called when a file is added, receives the filename
19
+ #
20
+ # :file_removed
21
+ # a proc which is called when a file is removed, receives the filename
22
+ #
23
+ # :file_changed
24
+ # a proc which is called when a file is changed, receives the filename
25
+ #
26
+ def initialize(options={})
27
+ @file_added = options[:file_added]
28
+ @file_removed = options[:file_removed]
29
+ @file_changed = options[:file_changed]
30
+ @file_info = {}
31
+ end
32
+
33
+ # Checks if any of the files has changed compared to the last call of check_files.
34
+ # When called for the first time on a new object, all files will be reported as being added.
35
+ #
36
+ def check_files(files)
37
+ files_before = @file_info.keys
38
+ used_files = {}
39
+ files.each do |file|
40
+ used_files[file] = true
41
+ if @file_info[file]
42
+ if @file_info[file].timestamp != File.mtime(file)
43
+ @file_info[file].timestamp = File.mtime(file)
44
+ digest = calc_digest(file)
45
+ if @file_info[file].digest != digest
46
+ @file_info[file].digest = digest
47
+ @file_changed && @file_changed.call(file)
48
+ end
49
+ end
50
+ else
51
+ @file_info[file] = FileInfo.new
52
+ @file_info[file].timestamp = File.mtime(file)
53
+ @file_info[file].digest = calc_digest(file)
54
+ @file_added && @file_added.call(file)
55
+ end
56
+ end
57
+ files_before.each do |file|
58
+ if !used_files[file]
59
+ @file_info.delete(file)
60
+ @file_removed && @file_removed.call(file)
61
+ end
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def calc_digest(file)
68
+ sha1 = Digest::SHA1.new
69
+ sha1.file(file)
70
+ sha1.hexdigest
71
+ end
72
+
73
+ end
74
+
75
+ end
76
+
77
+ end
78
+
@@ -1,4 +1,6 @@
1
1
  module RGen
2
+
3
+ module Util
2
4
 
3
5
  module MethodDelegation
4
6
 
@@ -46,7 +48,7 @@ class << self
46
48
  end
47
49
 
48
50
  def createDelegatingMethod(object, method)
49
- if object.methods.include?(method.to_s)
51
+ if hasMethod(object, method)
50
52
  object.instance_eval <<-END
51
53
  class << self
52
54
  alias #{aliasMethodName(method)} #{method}
@@ -71,7 +73,7 @@ class << self
71
73
  end
72
74
 
73
75
  def removeDelegatingMethod(object, method)
74
- if object.methods.include?(aliasMethodName(method))
76
+ if hasMethod(object, aliasMethodName(method))
75
77
  # there is an aliased original, restore it
76
78
  object.instance_eval <<-END
77
79
  class << self
@@ -89,6 +91,16 @@ class << self
89
91
  end
90
92
  end
91
93
 
94
+ def hasMethod(object, method)
95
+ # in Ruby 1.9, #methods returns symbols
96
+ if object.methods.first.is_a?(Symbol)
97
+ method = method.to_sym
98
+ else
99
+ method = method.to_s
100
+ end
101
+ object.methods.include?(method)
102
+ end
103
+
92
104
  def aliasMethodName(method)
93
105
  "#{method}_delegate_original"
94
106
  end
@@ -96,4 +108,7 @@ end
96
108
 
97
109
  end
98
110
 
99
- end
111
+ end
112
+
113
+ end
114
+
@@ -2,6 +2,8 @@ require 'rgen/ecore/ecore'
2
2
 
3
3
  module RGen
4
4
 
5
+ module Util
6
+
5
7
  module ModelComparator
6
8
 
7
9
  # This method compares to models regarding equality
@@ -27,10 +29,18 @@ def _modelEqual_internal(a, b, featureIgnoreList, path)
27
29
  puts "#{path.inspect}\n Array size differs: #{a.size} vs. #{b.size}"
28
30
  return false
29
31
  end
30
- as = a
31
- as = a.sort if a.all?{|o| o.respond_to?('<=>')}
32
- bs = b
33
- bs = b.sort if b.all?{|o| o.respond_to?('<=>')}
32
+ begin
33
+ # in Ruby 1.9 every object has the <=> operator but the default one returns
34
+ # nil and thus sorting won't work (ArgumentError)
35
+ as = a.sort
36
+ rescue ArgumentError, NoMethodError
37
+ as = a
38
+ end
39
+ begin
40
+ bs = b.sort
41
+ rescue ArgumentError, NoMethodError
42
+ bs = b
43
+ end
34
44
  a.each_index do |i|
35
45
  return false unless _modelEqual_internal(as[i], bs[i], featureIgnoreList, path+[i])
36
46
  end
@@ -53,4 +63,6 @@ end
53
63
  end
54
64
 
55
65
  end
56
-
66
+
67
+ end
68
+
@@ -1,6 +1,8 @@
1
1
  require 'andand'
2
2
 
3
3
  module RGen
4
+
5
+ module Util
4
6
 
5
7
  class ModelComparatorBase
6
8
 
@@ -134,4 +136,7 @@ class ModelComparatorBase
134
136
 
135
137
  end
136
138
 
137
- end
139
+ end
140
+
141
+ end
142
+
@@ -1,5 +1,7 @@
1
1
  module RGen
2
2
 
3
+ module Util
4
+
3
5
  module ModelDumper
4
6
 
5
7
  def dump(obj=nil)
@@ -21,4 +23,7 @@ module ModelDumper
21
23
 
22
24
  end
23
25
 
24
- end
26
+ end
27
+
28
+ end
29
+
@@ -3,6 +3,8 @@
3
3
 
4
4
  module RGen
5
5
 
6
+ module Util
7
+
6
8
  module NameHelper
7
9
 
8
10
  def normalize(name)
@@ -34,4 +36,7 @@ module NameHelper
34
36
  end
35
37
  end
36
38
 
37
- end
39
+ end
40
+
41
+ end
42
+
@@ -0,0 +1,329 @@
1
+ module RGen
2
+
3
+ module Util
4
+
5
+ # A PatternMatcher can be used to find, insert and remove patterns on a given model.
6
+ #
7
+ # A pattern is specified by means of a block passed to the add_pattern method.
8
+ # The block must take an Environment as first parameter and at least one model element
9
+ # as connection point as further parameter. The pattern matches if it can be found
10
+ # in a given environment and connected to the given connection point elements.
11
+ #
12
+ class PatternMatcher
13
+
14
+ Match = Struct.new(:root, :elements, :bound_values)
15
+ attr_accessor :debug
16
+
17
+ def initialize
18
+ @patterns = {}
19
+ @insert_mode = false
20
+ @debug = false
21
+ end
22
+
23
+ def add_pattern(name, &block)
24
+ raise "a pattern needs at least 2 block parameters: " +
25
+ "an RGen environment and a model element as connection point" \
26
+ unless block.arity >= 2
27
+ @patterns[name] = block
28
+ end
29
+
30
+ def find_pattern(env, name, *connection_points)
31
+ match = find_pattern_internal(env, name, *connection_points)
32
+ end
33
+
34
+ def insert_pattern(env, name, *connection_points)
35
+ @insert_mode = true
36
+ root = evaluate_pattern(name, env, connection_points)
37
+ @insert_mode = false
38
+ root
39
+ end
40
+
41
+ def remove_pattern(env, name, *connection_points)
42
+ match = find_pattern_internal(env, name, *connection_points)
43
+ if match
44
+ match.elements.each do |e|
45
+ disconnect_element(e)
46
+ env.delete(e)
47
+ end
48
+ match
49
+ else
50
+ nil
51
+ end
52
+ end
53
+
54
+ def lazy(&block)
55
+ if @insert_mode
56
+ block.call
57
+ else
58
+ Lazy.new(&block)
59
+ end
60
+ end
61
+
62
+ class Lazy < RGen::MetamodelBuilder::MMGeneric
63
+ def initialize(&block)
64
+ @block = block
65
+ end
66
+ def _eval
67
+ @block.call
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ class Proxy < RGen::MetamodelBuilder::MMProxy
74
+ attr_reader :_target
75
+ def initialize(target)
76
+ @_target = target
77
+ end
78
+ def method_missing(m, *args)
79
+ result = @_target.send(m, *args)
80
+ if result.is_a?(Array)
81
+ result.collect do |e|
82
+ if e.is_a?(RGen::MetamodelBuilder::MMBase)
83
+ Proxy.new(e)
84
+ else
85
+ e
86
+ end
87
+ end
88
+ else
89
+ if result.is_a?(RGen::MetamodelBuilder::MMBase)
90
+ Proxy.new(result)
91
+ else
92
+ result
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ class Bindable < RGen::MetamodelBuilder::MMGeneric
99
+ # by being an Enumerable, Bindables can be used for many-features as well
100
+ include Enumerable
101
+ def initialize
102
+ @bound = false
103
+ @value = nil
104
+ @many = false
105
+ end
106
+ def _bound?
107
+ @bound
108
+ end
109
+ def _many?
110
+ @many
111
+ end
112
+ def _bind(value)
113
+ @value = value
114
+ @bound = true
115
+ end
116
+ def _value
117
+ @value
118
+ end
119
+ def to_s
120
+ @value.to_s
121
+ end
122
+ # pretend this is an enumerable which contains itself, so the bindable can be
123
+ # inserted into many-features, when this is done the bindable is marked as a many-bindable
124
+ def each
125
+ @many = true
126
+ yield(self)
127
+ end
128
+ def method_missing(m, *args)
129
+ raise "bindable not bound" unless _bound?
130
+ @value.send(m, *args)
131
+ end
132
+ end
133
+
134
+ TempEnv = RGen::Environment.new
135
+ class << TempEnv
136
+ def <<(el); end
137
+ end
138
+
139
+ def find_pattern_internal(env, name, *connection_points)
140
+ proxied_args = connection_points.collect{|a|
141
+ a.is_a?(RGen::MetamodelBuilder::MMBase) ? Proxy.new(a) : a }
142
+ bindables = create_bindables(name, connection_points)
143
+ pattern_root = evaluate_pattern(name, TempEnv, proxied_args+bindables)
144
+ candidates = candidates_via_connection_points(pattern_root, connection_points)
145
+ candidates ||= env.find(:class => pattern_root.class)
146
+ candidates.each do |e|
147
+ # create new bindables for every try, otherwise they can could be bound to old values
148
+ bindables = create_bindables(name, connection_points)
149
+ pattern_root = evaluate_pattern(name, TempEnv, proxied_args+bindables)
150
+ matched = match(pattern_root, e)
151
+ return Match.new(e, matched, bindables.collect{|b| b._value}) if matched
152
+ end
153
+ nil
154
+ end
155
+
156
+ def create_bindables(pattern_name, connection_points)
157
+ (1..(num_pattern_variables(pattern_name) - connection_points.size)).collect{|i| Bindable.new}
158
+ end
159
+
160
+ def candidates_via_connection_points(pattern_root, connection_points)
161
+ @candidates_via_connection_points_refs ||= {}
162
+ refs = (@candidates_via_connection_points_refs[pattern_root.class] ||=
163
+ pattern_root.class.ecore.eAllReferences.reject{|r| r.derived || r.many || !r.eOpposite})
164
+ candidates = nil
165
+ refs.each do |r|
166
+ t = pattern_root.getGeneric(r.name)
167
+ cp = t.is_a?(Proxy) && connection_points.find{|cp| cp.object_id == t._target.object_id}
168
+ if cp
169
+ elements = cp.getGenericAsArray(r.eOpposite.name)
170
+ candidates = elements if candidates.nil? || elements.size < candidates.size
171
+ end
172
+ end
173
+ candidates
174
+ end
175
+
176
+ def match(pat_element, test_element)
177
+ visited = {}
178
+ check_later = []
179
+ return false unless match_internal(pat_element, test_element, visited, check_later)
180
+ while cl = check_later.shift
181
+ pv, tv = cl.lazy._eval, cl.value
182
+ if cl.feature.is_a?(RGen::ECore::EAttribute)
183
+ unless pv == tv
184
+ match_failed(cl.feature, "wrong attribute value (lazy): #{pv} vs. #{tv}")
185
+ return false
186
+ end
187
+ else
188
+ if pv.is_a?(Proxy)
189
+ unless pv._target.object_id == tv.object_id
190
+ match_failed(f, "wrong target object")
191
+ return false
192
+ end
193
+ else
194
+ unless (pv.nil? && tv.nil?) || (!pv.nil? && !tv.nil? && match_internal(pv, tv, visited, check_later))
195
+ return false
196
+ end
197
+ end
198
+ end
199
+ end
200
+ visited.keys
201
+ end
202
+
203
+ CheckLater = Struct.new(:feature, :lazy, :value)
204
+ def match_internal(pat_element, test_element, visited, check_later)
205
+ return true if visited[test_element]
206
+ visited[test_element] = true
207
+ unless pat_element.class == test_element.class
208
+ match_failed(nil, "wrong class: #{pat_element.class} vs #{test_element.class}")
209
+ return false
210
+ end
211
+ all_structural_features(pat_element).each do |f|
212
+ pat_values = pat_element.getGeneric(f.name)
213
+ # nil values must be kept to support size check with Bindables
214
+ pat_values = [ pat_values ] unless pat_values.is_a?(Array)
215
+ test_values = test_element.getGeneric(f.name)
216
+ test_values = [ test_values] unless test_values.is_a?(Array)
217
+ if pat_values.size == 1 && pat_values.first.is_a?(Bindable) && pat_values.first._many?
218
+ unless match_many_bindable(pat_values.first, test_values)
219
+ return false
220
+ end
221
+ else
222
+ unless pat_values.size == test_values.size
223
+ match_failed(f, "wrong size #{pat_values.size} vs. #{test_values.size}")
224
+ return false
225
+ end
226
+ pat_values.each_with_index do |pv,i|
227
+ tv = test_values[i]
228
+ if pv.is_a?(Lazy)
229
+ check_later << CheckLater.new(f, pv, tv)
230
+ elsif pv.is_a?(Bindable)
231
+ if pv._bound?
232
+ unless pv._value == tv
233
+ match_failed(f, "value does not match bound value #{pv._value.class}:#{pv._value.object_id} vs. #{tv.class}:#{tv.object_id}")
234
+ return false
235
+ end
236
+ else
237
+ pv._bind(tv)
238
+ end
239
+ else
240
+ if f.is_a?(RGen::ECore::EAttribute)
241
+ unless pv == tv
242
+ match_failed(f, "wrong attribute value")
243
+ return false
244
+ end
245
+ else
246
+ if pv.is_a?(Proxy)
247
+ unless pv._target.object_id == tv.object_id
248
+ match_failed(f, "wrong target object")
249
+ return false
250
+ end
251
+ else
252
+ unless both_nil_or_match(pv, tv, visited, check_later)
253
+ return false
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+ true
262
+ end
263
+
264
+ def match_many_bindable(bindable, test_values)
265
+ if bindable._bound?
266
+ bindable._value.each_with_index do |pv,i|
267
+ tv = test_values[i]
268
+ if f.is_a?(RGen::ECore::EAttribute)
269
+ unless pv == tv
270
+ match_failed(f, "wrong attribute value")
271
+ return false
272
+ end
273
+ else
274
+ unless both_nil_or_match(pv, tv, visited, check_later)
275
+ return false
276
+ end
277
+ end
278
+ end
279
+ else
280
+ bindable._bind(test_values.dup)
281
+ end
282
+ true
283
+ end
284
+
285
+ def both_nil_or_match(pv, tv, visited, check_later)
286
+ (pv.nil? && tv.nil?) || (!pv.nil? && !tv.nil? && match_internal(pv, tv, visited, check_later))
287
+ end
288
+
289
+ def match_failed(f, msg)
290
+ puts "match failed #{f&&f.eContainingClass.name}##{f&&f.name}: #{msg}" if @debug
291
+ end
292
+
293
+ def num_pattern_variables(name)
294
+ prok = @patterns[name]
295
+ prok.arity - 1
296
+ end
297
+
298
+ def evaluate_pattern(name, env, connection_points)
299
+ prok = @patterns[name]
300
+ raise "unknown pattern #{name}" unless prok
301
+ raise "wrong number of arguments, expected #{prok.arity-1} connection points)" \
302
+ unless connection_points.size == prok.arity-1
303
+ prok.call(env, *connection_points)
304
+ end
305
+
306
+ def disconnect_element(element)
307
+ return if element.nil?
308
+ all_structural_features(element).each do |f|
309
+ if f.many
310
+ element.setGeneric(f.name, [])
311
+ else
312
+ element.setGeneric(f.name, nil)
313
+ end
314
+ end
315
+ end
316
+
317
+ def all_structural_features(element)
318
+ @all_structural_features ||= {}
319
+ return @all_structural_features[element.class] if @all_structural_features[element.class]
320
+ @all_structural_features[element.class] =
321
+ element.class.ecore.eAllStructuralFeatures.reject{|f| f.derived}
322
+ end
323
+
324
+ end
325
+
326
+ end
327
+
328
+ end
329
+