diff --git a/lib/rgen/array_extensions.rb b/lib/rgen/array_extensions.rb
index d1d7bed..9d44f68 100644
--- a/lib/rgen/array_extensions.rb
+++ b/lib/rgen/array_extensions.rb
@@ -8,38 +8,44 @@ class Array
def >>(method)
compact.inject([]) { |r,e| r | ( (o=e.send(method)).is_a?(Array) ? o : [o] ) }
end
-
- def method_missing(m, *args)
- # This extensions has the side effect that it allows to call any method on any
- # empty array with an empty array as the result. This behavior is required for
- # navigating models.
- #
- # This is a problem for Hash[] called with an (empty) array of tupels.
- # It will call to_hash expecting a Hash as the result. When it gets an array instead,
- # it fails with an exception. Make sure it gets a NoMethodException as without this
- # extension and it will catch that and return an empty hash as expected.
- #
- # Similar problems exist for other Ruby built-in methods which are expected to fail.
- #
- return super unless (size == 0 &&
- m != :to_hash && m != :to_str) ||
- compact.any?{|e| e.is_a? RGen::MetamodelBuilder::MMBase}
- # use an array to build the result to achiev similar ordering
- result = []
- inResult = {}
- compact.each do |e|
- if e.is_a? RGen::MetamodelBuilder::MMBase
- ((o=e.send(m)).is_a?(Array) ? o : [o] ).each do |v|
- next if inResult[v.object_id]
- inResult[v.object_id] = true
- result << v
+ unless self.public_instance_methods.include?(:method_missing)
+ def _th(m)
+ # use an array to build the result to achieve similar ordering
+ result = []
+ inResult = {}
+ self.each do |e|
+ next if e.nil?
+ if e.is_a? RGen::MetamodelBuilder::MMBase
+ ((o=e.send(m)).is_a?(Array) ? o : [o] ).each do |v|
+ next if v.nil? || inResult[v.object_id]
+ inResult[v.object_id] = true
+ result << v
+ end
+ else
+ raise StandardError.new("Trying to call a method on an array element not a RGen MMBase")
end
- else
- raise StandardError.new("Trying to call a method on an array element not a RGen MMBase")
end
+ result
end
- result.compact
- end
+ def method_missing(m, *args)
+
+ # This extensions has the side effect that it allows to call any method on any
+ # empty array with an empty array as the result. This behavior is required for
+ # navigating models.
+ #
+ # This is a problem for Hash[] called with an (empty) array of tupels.
+ # It will call to_hash expecting a Hash as the result. When it gets an array instead,
+ # it fails with an exception. Make sure it gets a NoMethodException as without this
+ # extension and it will catch that and return an empty hash as expected.
+ #
+ # Similar problems exist for other Ruby built-in methods which are expected to fail.
+ #
+ return super unless (size == 0 &&
+ m != :to_hash && m != :to_str) ||
+ self.any?{|e| e.is_a? RGen::MetamodelBuilder::MMBase}
+ self._th(m)
+ end
+ end
end
diff --git a/lib/rgen/environment.rb b/lib/rgen/environment.rb
index cf88bd4..ac3064a 100644
--- a/lib/rgen/environment.rb
+++ b/lib/rgen/environment.rb
@@ -4,126 +4,126 @@ module RGen
#
class Environment
- def initialize
- @elements = {}
- @subClasses = {}
- @subClassesUpdated = {}
- @deleted = {}
- @deletedClasses = {}
- end
-
- # Add a model element. Returns the environment so << can be chained.
- #
- def <<(el)
- clazz = el.class
- @elements[clazz] ||= []
- @elements[clazz] << el
- updateSubClasses(clazz)
- self
- end
+ def initialize
+ @elements = []
+ @last_add=0
+ @elements_by_class = Hash::new{|h,k| h[k] = [] }
+ @subClasses = Hash::new{|h,k| h[k] = [] }
+ @subClassesUpdated = {}
+ end
+
+ # Add a model element. Returns the environment so << can be chained.
+ #
+ def <<(el)
+ return self if el.nil?
+ @elements.push(el)
+ self
+ end
+
+ # Removes model element from environment.
+ def delete(el)
+ sync_caches
+ @elements.delete(el)
+ @elements_by_class[el.class].delete(el)
+ @last_add = @elements.size
+ end
+
+ # Iterates each element
+ #
+ def each(&b)
+ @elements.each(&b)
+ end
- # Removes model element from environment.
- def delete(el)
- @deleted[el] = true
- @deletedClasses[el.class] = true
- end
-
- # Iterates each element
- #
- def each(&b)
- removeDeleted
- @elements.values.flatten.each(&b)
- end
-
- # Return the elements of the environment as an array
- #
- def elements
- removeDeleted
- @elements.values.flatten
- end
-
- # This method can be used to instantiate a class and automatically put it into
- # the environment. The new instance is returned.
- #
- def new(clazz, *args)
- obj = clazz.new(*args)
- self << obj
- obj
- end
-
- # Finds and returns model elements in the environment.
- #
- # The search description argument must be a hash specifying attribute/value pairs.
- # Only model elements are returned which respond to the specified attribute methods
- # and return the specified values as result of these attribute methods.
- #
- # As a special hash key :class can be used to look for model elements of a specific
- # class. In this case an array of possible classes can optionally be given.
- #
- def find(desc)
- removeDeleted
- result = []
- classes = desc[:class] if desc[:class] and desc[:class].is_a?(Array)
- classes = [ desc[:class] ] if !classes and desc[:class]
- if classes
- hashKeys = classesWithSubClasses(classes)
- else
- hashKeys = @elements.keys
- end
- hashKeys.each do |clazz|
- next unless @elements[clazz]
- @elements[clazz].each do |e|
- failed = false
- desc.each_pair { |k,v|
- failed = true if k != :class and ( !e.respond_to?(k) or e.send(k) != v )
- }
- result << e unless failed
- end
- end
- result
- end
-
- private
+ # Return the elements of the environment as an array
+ #
+ def elements
+ @elements
+ end
+
+ # This method can be used to instantiate a class and automatically put it into
+ # the environment. The new instance is returned.
+ #
+ def new(clazz, *args)
+ obj = clazz.new(*args)
+ self << obj
+ obj
+ end
- def removeDeleted
- @deletedClasses.keys.each do |c|
- @elements[c].reject!{|e| @deleted[e]}
+ # Finds and returns model elements in the environment.
+ #
+ # The search description argument must be a hash specifying attribute/value pairs.
+ # Only model elements are returned which respond to the specified attribute methods
+ # and return the specified values as result of these attribute methods.
+ #
+ # As a special hash key :class can be used to look for model elements of a specific
+ # class. In this case an array of possible classes can optionally be given.
+ #
+ def find(desc)
+ sync_caches
+ result = []
+ classes = desc[:class] if desc[:class] and desc[:class].is_a?(Array)
+ classes = [ desc[:class] ] if !classes and desc[:class]
+ if classes
+ hashKeys = classesWithSubClasses(classes)
+ else
+ hashKeys = @elements_by_class.keys
+ end
+ hashKeys.each do |clazz|
+ next unless @elements_by_class[clazz]
+ @elements_by_class[clazz].each do |e|
+ failed = false
+ desc.each_pair { |k,v|
+ failed = true if k != :class and ( !e.respond_to?(k) or e.send(k) != v )
+ }
+ result << e unless failed
+ end
end
- @deletedClasses.clear
- @deleted.clear
+ result
end
-
- def updateSubClasses(clazz)
- return if @subClassesUpdated[clazz]
- if clazz.respond_to?( :ecore )
- superClasses = clazz.ecore.eAllSuperTypes.collect{|c| c.instanceClass}
- else
- superClasses = superclasses(clazz)
- end
- superClasses.each do |c|
- next if c == Object
- @subClasses[c] ||= []
- @subClasses[c] << clazz
- end
- @subClassesUpdated[clazz] = true
- end
-
- def classesWithSubClasses(classes)
- result = classes
- classes.each do |c|
- result += @subClasses[c] if @subClasses[c]
- end
- result.uniq
- end
-
- def superclasses(clazz)
- if clazz == Object
- []
- else
- superclasses(clazz.superclass) << clazz.superclass
- end
- end
-
+
+ private
+
+ unless self.public_instance_methods.include?(:sync_caches)
+ def sync_caches
+ return if @last_add == @elements.size
+ @elements[@last_add..@elements.size].each do |el|
+ @elements_by_class[el.class].push(el)
+ updateSubClasses(el.class)
+ end
+ @last_add = @elements.size
+ end
+ end
+
+ def updateSubClasses(clazz)
+ return if @subClassesUpdated[clazz]
+ if clazz.respond_to?( :ecore )
+ superClasses = clazz.ecore.eAllSuperTypes.collect{|c| c.instanceClass}
+ else
+ superClasses = superclasses(clazz)
+ end
+ superClasses.each do |c|
+ next if c == Object
+ @subClasses[c] << clazz
+ end
+ @subClassesUpdated[clazz] = true
+ end
+
+ def classesWithSubClasses(classes)
+ result = classes
+ classes.each do |c|
+ result += @subClasses[c] if @subClasses[c]
+ end
+ result.uniq
+ end
+
+ def superclasses(clazz)
+ if clazz == Object
+ []
+ else
+ superclasses(clazz.superclass) << clazz.superclass
+ end
+ end
+
end
-end
\ No newline at end of file
+end
diff --git a/test/array_extensions_test.rb b/test/array_extensions_test.rb
index 62c1f91..49107d5 100644
--- a/test/array_extensions_test.rb
+++ b/test/array_extensions_test.rb
@@ -51,6 +51,19 @@ def test_with_mmbase
end
end
+ def test_through_with_mmbase
+ e1 = MMBaseClass.new
+ e1.name = "MyName"
+ e1.age = 33
+ e2 = MMBaseClass.new
+ e2.name = "YourName"
+ e2.age = 22
+ a = [e1, e2]
+ assert_equal ["MyName", "YourName"], a._th(:name)
+ assert_equal [33, 22], a._th(:age)
+ end
+
+
def test_hash_square
assert_equal({}, Hash[[]])
end