Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 35 additions & 29 deletions lib/rgen/array_extensions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
234 changes: 117 additions & 117 deletions lib/rgen/environment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,126 +4,126 @@ module RGen
#
class Environment

def initialize
@elements = {}
@subClasses = {}
@subClassesUpdated = {}
@deleted = {}
@deletedClasses = {}
end

# Add a model element. Returns the environment so <code><<</code> 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 <code><<</code> 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[@[email protected]].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
end
13 changes: 13 additions & 0 deletions test/array_extensions_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down