Skip to content

Commit 99bbfd3

Browse files
author
Mike Ragalie
committed
Implement JRuby function wrapping support
Whenever a RubyProc is passed into an Observable method, wrap it in a Java class that implements the correct RxJava interfaces. This avoids ambiguous method errors and costly proxy instantiations by JRuby's default method delegation logic.
1 parent af2b35a commit 99bbfd3

File tree

5 files changed

+350
-0
lines changed

5 files changed

+350
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apply plugin: 'osgi'
2+
3+
dependencies {
4+
compile project(':rxjava-core')
5+
compile 'org.jruby:jruby:1.7+'
6+
provided 'junit:junit-dep:4.10'
7+
provided 'org.mockito:mockito-core:1.8.5'
8+
}
9+
10+
jar {
11+
manifest {
12+
name = 'rxjava-jruby'
13+
instruction 'Bundle-Vendor', 'Netflix'
14+
instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava'
15+
instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*'
16+
instruction 'Fragment-Host', 'com.netflix.rxjava.core'
17+
}
18+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/**
2+
* Copyright 2013 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package rx.lang.jruby;
17+
18+
import org.jruby.RubyProc;
19+
import org.jruby.Ruby;
20+
import org.jruby.runtime.ThreadContext;
21+
import org.jruby.runtime.builtin.IRubyObject;
22+
import org.jruby.javasupport.JavaUtil;
23+
24+
import rx.util.functions.Action;
25+
import rx.util.functions.Action0;
26+
import rx.util.functions.Action1;
27+
import rx.util.functions.Action2;
28+
import rx.util.functions.Action3;
29+
30+
/**
31+
* Concrete wrapper that accepts a {@link RubyProc} and produces any needed Rx {@link Action}.
32+
*
33+
* @param <T1>
34+
* @param <T2>
35+
* @param <T3>
36+
* @param <T4>
37+
*/
38+
public class JRubyActionWrapper<T1, T2, T3, T4> implements Action, Action0, Action1<T1>, Action2<T1, T2>, Action3<T1, T2, T3> {
39+
40+
private final RubyProc proc;
41+
private final ThreadContext context;
42+
private final Ruby runtime;
43+
44+
public JRubyActionWrapper(ThreadContext context, RubyProc proc) {
45+
this.proc = proc;
46+
this.context = context;
47+
this.runtime = context.getRuntime();
48+
}
49+
50+
@Override
51+
public void call() {
52+
IRubyObject[] array = new IRubyObject[0];
53+
proc.call(context, array);
54+
}
55+
56+
@Override
57+
public void call(T1 t1) {
58+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1)};
59+
proc.call(context, array);
60+
}
61+
62+
@Override
63+
public void call(T1 t1, T2 t2) {
64+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1),
65+
JavaUtil.convertJavaToRuby(runtime, t2)};
66+
proc.call(context, array);
67+
}
68+
69+
@Override
70+
public void call(T1 t1, T2 t2, T3 t3) {
71+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1),
72+
JavaUtil.convertJavaToRuby(runtime, t2),
73+
JavaUtil.convertJavaToRuby(runtime, t3)};
74+
proc.call(context, array);
75+
}
76+
77+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/**
2+
* Copyright 2013 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package rx.lang.jruby;
17+
18+
import org.jruby.RubyProc;
19+
import org.jruby.Ruby;
20+
import org.jruby.runtime.ThreadContext;
21+
import org.jruby.runtime.builtin.IRubyObject;
22+
import org.jruby.javasupport.JavaUtil;
23+
24+
import rx.util.functions.Func0;
25+
import rx.util.functions.Func1;
26+
import rx.util.functions.Func2;
27+
import rx.util.functions.Func3;
28+
import rx.util.functions.Func4;
29+
import rx.util.functions.Func5;
30+
import rx.util.functions.Func6;
31+
import rx.util.functions.Func7;
32+
import rx.util.functions.Func8;
33+
import rx.util.functions.Func9;
34+
import rx.util.functions.FuncN;
35+
import rx.util.functions.Function;
36+
37+
/**
38+
* Concrete wrapper that accepts a {@link RubyProc} and produces any needed Rx {@link Function}.
39+
*
40+
* @param <T1>
41+
* @param <T2>
42+
* @param <T3>
43+
* @param <T4>
44+
* @param <R>
45+
*/
46+
public class JRubyFunctionWrapper<T1, T2, T3, T4, T5, T6, T7, T8, T9, R> implements
47+
Func0<R>,
48+
Func1<T1, R>,
49+
Func2<T1, T2, R>,
50+
Func3<T1, T2, T3, R>,
51+
Func4<T1, T2, T3, T4, R>,
52+
Func5<T1, T2, T3, T4, T5, R>,
53+
Func6<T1, T2, T3, T4, T5, T6, R>,
54+
Func7<T1, T2, T3, T4, T5, T6, T7, R>,
55+
Func8<T1, T2, T3, T4, T5, T6, T7, T8, R>,
56+
Func9<T1, T2, T3, T4, T5, T6, T7, T8, T9, R>,
57+
FuncN<R> {
58+
59+
private final RubyProc proc;
60+
private final ThreadContext context;
61+
private final Ruby runtime;
62+
63+
public JRubyFunctionWrapper(ThreadContext context, RubyProc proc) {
64+
this.proc = proc;
65+
this.context = context;
66+
this.runtime = context.getRuntime();
67+
}
68+
69+
@Override
70+
public R call() {
71+
IRubyObject[] array = new IRubyObject[0];
72+
return (R) proc.call(context, array);
73+
}
74+
75+
@Override
76+
public R call(T1 t1) {
77+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1)};
78+
return (R) proc.call(context, array);
79+
}
80+
81+
@Override
82+
public R call(T1 t1, T2 t2) {
83+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1),
84+
JavaUtil.convertJavaToRuby(runtime, t2)};
85+
return (R) proc.call(context, array);
86+
}
87+
88+
@Override
89+
public R call(T1 t1, T2 t2, T3 t3) {
90+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1),
91+
JavaUtil.convertJavaToRuby(runtime, t2),
92+
JavaUtil.convertJavaToRuby(runtime, t3)};
93+
return (R) proc.call(context, array);
94+
}
95+
96+
@Override
97+
public R call(T1 t1, T2 t2, T3 t3, T4 t4) {
98+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1),
99+
JavaUtil.convertJavaToRuby(runtime, t2),
100+
JavaUtil.convertJavaToRuby(runtime, t3),
101+
JavaUtil.convertJavaToRuby(runtime, t4)};
102+
return (R) proc.call(context, array);
103+
}
104+
105+
@Override
106+
public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5) {
107+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1),
108+
JavaUtil.convertJavaToRuby(runtime, t2),
109+
JavaUtil.convertJavaToRuby(runtime, t3),
110+
JavaUtil.convertJavaToRuby(runtime, t4),
111+
JavaUtil.convertJavaToRuby(runtime, t5)};
112+
return (R) proc.call(context, array);
113+
}
114+
115+
@Override
116+
public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6) {
117+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1),
118+
JavaUtil.convertJavaToRuby(runtime, t2),
119+
JavaUtil.convertJavaToRuby(runtime, t3),
120+
JavaUtil.convertJavaToRuby(runtime, t4),
121+
JavaUtil.convertJavaToRuby(runtime, t5),
122+
JavaUtil.convertJavaToRuby(runtime, t6)};
123+
return (R) proc.call(context, array);
124+
}
125+
126+
@Override
127+
public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7) {
128+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1),
129+
JavaUtil.convertJavaToRuby(runtime, t2),
130+
JavaUtil.convertJavaToRuby(runtime, t3),
131+
JavaUtil.convertJavaToRuby(runtime, t4),
132+
JavaUtil.convertJavaToRuby(runtime, t5),
133+
JavaUtil.convertJavaToRuby(runtime, t6),
134+
JavaUtil.convertJavaToRuby(runtime, t7)};
135+
return (R) proc.call(context, array);
136+
}
137+
138+
@Override
139+
public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8) {
140+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1),
141+
JavaUtil.convertJavaToRuby(runtime, t2),
142+
JavaUtil.convertJavaToRuby(runtime, t3),
143+
JavaUtil.convertJavaToRuby(runtime, t4),
144+
JavaUtil.convertJavaToRuby(runtime, t5),
145+
JavaUtil.convertJavaToRuby(runtime, t6),
146+
JavaUtil.convertJavaToRuby(runtime, t7),
147+
JavaUtil.convertJavaToRuby(runtime, t8)};
148+
return (R) proc.call(context, array);
149+
}
150+
151+
@Override
152+
public R call(T1 t1, T2 t2, T3 t3, T4 t4, T5 t5, T6 t6, T7 t7, T8 t8, T9 t9) {
153+
IRubyObject[] array = {JavaUtil.convertJavaToRuby(runtime, t1),
154+
JavaUtil.convertJavaToRuby(runtime, t2),
155+
JavaUtil.convertJavaToRuby(runtime, t3),
156+
JavaUtil.convertJavaToRuby(runtime, t4),
157+
JavaUtil.convertJavaToRuby(runtime, t5),
158+
JavaUtil.convertJavaToRuby(runtime, t6),
159+
JavaUtil.convertJavaToRuby(runtime, t7),
160+
JavaUtil.convertJavaToRuby(runtime, t8),
161+
JavaUtil.convertJavaToRuby(runtime, t9)};
162+
return (R) proc.call(context, array);
163+
}
164+
165+
@Override
166+
public R call(Object... args) {
167+
IRubyObject[] array = new IRubyObject[args.length];
168+
for (int i = 0; i < args.length; i++) {
169+
array[i] = JavaUtil.convertJavaToRuby(runtime, args[i]);
170+
}
171+
return (R) proc.call(context, array);
172+
}
173+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
module Rx
2+
module Lang
3+
module Jruby
4+
class Interop
5+
WRAPPERS = {
6+
Java::RxUtilFunctions::Action => Java::RxLangJruby::JRubyActionWrapper
7+
}
8+
9+
WRAPPERS.default = Java::RxLangJruby::JRubyFunctionWrapper
10+
11+
KLASSES = [Java::Rx::Observable, Java::RxObservables::BlockingObservable]
12+
FUNCTION = Java::RxUtilFunctions::Function.java_class
13+
RUNTIME = JRuby.runtime
14+
15+
def self.instance
16+
@instance ||= new
17+
end
18+
19+
def initialize
20+
KLASSES.each do |klass|
21+
function_methods = (klass.java_class.declared_instance_methods + klass.java_class.declared_class_methods).select do |method|
22+
method.public? && method.parameter_types.any? {|type| FUNCTION.assignable_from?(type)}
23+
end
24+
25+
parameter_types = {}
26+
function_methods.each do |method|
27+
parameter_types[[method.name, method.static?]] ||= []
28+
29+
method.parameter_types.each_with_index do |type, idx|
30+
next unless FUNCTION.assignable_from?(type)
31+
32+
constructor = WRAPPERS.find do |java_class, wrapper|
33+
type.ruby_class.ancestors.include?(java_class)
34+
end
35+
36+
constructor = (constructor && constructor.last) || WRAPPERS.default
37+
38+
parameter_types[[method.name, method.static?]][idx] ||= []
39+
parameter_types[[method.name, method.static?]][idx] << constructor
40+
end
41+
end
42+
43+
parameter_types.each_pair do |_, types|
44+
types.map! do |type|
45+
next type.first if type && type.uniq.length == 1
46+
nil
47+
end
48+
end
49+
50+
parameter_types.each_pair do |(method_name, static), types|
51+
next if types.all?(&:nil?)
52+
53+
klass_to_open = static ? klass.singleton_class : klass
54+
55+
klass_to_open.send(:alias_method, "#{method_name}_without_wrapping", method_name)
56+
klass_to_open.send(:define_method, method_name) do |*args, &block|
57+
context = RUNTIME.get_current_context
58+
59+
args = args.each_with_index.map do |arg, idx|
60+
if arg.is_a?(Proc) && types[idx]
61+
types[idx].new(context, arg)
62+
else
63+
arg
64+
end
65+
end
66+
67+
if block && types[args.length]
68+
block = types[args.length].new(context, block)
69+
end
70+
71+
send("#{method_name}_without_wrapping", *(args + [block].compact))
72+
end
73+
end
74+
end
75+
end
76+
end
77+
end
78+
end
79+
end
80+
81+
Rx::Lang::Jruby::Interop.instance

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ rootProject.name='rxjava'
22
include 'rxjava-core', \
33
'language-adaptors:rxjava-groovy', \
44
'language-adaptors:rxjava-clojure', \
5+
'language-adaptors:rxjava-jruby', \
56
'language-adaptors:rxjava-scala', \
67
'language-adaptors:rxjava-scala-java', \
78
'rxjava-contrib:rxjava-swing', \

0 commit comments

Comments
 (0)