Description
As suggested by @eregon in this StackOverflow question https://stackoverflow.com/questions/76567012/extending-monkey-patching-java-classes-in-truffle-ruby, I am opening an issue here.
We are currently checking the feasibility of porting a large Java/JRuby project to Truffle/GraalVM and we've hit a major problem with so-called monkey patching of Java classes in Ruby, which JRuby allows but Truffle does not. @eregon suggested that if the Java classes implement the "correct" interfaces then the polyglot api will convert additional ruby methods (eg [] or []=) to Java calls automatically.
Unfortunately this is not going to work for us, as I imagine it possibly won't for other large JRuby projects looking to port. The problem is as follows: over the course of 10 years and thousands of lines of code, the ruby programmers generally didn't interface with the Java programmers to add domain specific methods to their Java classes. They just implemented them themselves by monkey patching the Java classes directly in JRuby. This was both an agile and a "clean" approach, because the Java class was kept generic and free from project-specific extensions.
The result is that for some Java classes we have ruby files with additional method definitions running to several hundred lines. There is no way we can justify refactoring the entire ruby code base to rework all of the references to these methods.
Finally, and just to emphasize that the approach taken by polyglot is elegant in theory but unfeasible in practice I will post the polyglot HashTrait for [] and its analog in our monkey patch file:
Polyglot:
def [](key)
Truffle::Interop.read_hash_value_or_default(self, key, nil)
end
Our legacy code:
def [] (*keys)
if keys.size == 2 && keys[0].instance_of?(Array)
self.java_send :fgetObj, [java.lang.String[], java.lang.Object], keys[0].to_s.to_java(:string), keys[1]
elsif keys.size > 1
self.java_send :fgetObj, [java.lang.String[]], keys.to_java(:string)
elsif keys[0].instance_of? Array
self.java_send :fgetObj, [java.lang.String[]], keys[0].to_s.to_java(:string)
else
self.java_send :getObj, [java.lang.String], keys[0].to_s.to_java(:string) rescue nil
end
end
We can see no way round this at the moment and so will continue to use JRuby for our ruby environment and will use polyglot for our Python implementation, which has much less legacy code.