diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f46c00 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +pkg +doc +*.gem +build diff --git a/Rakefile b/Rakefile index a00aa4d..08e2b7e 100644 --- a/Rakefile +++ b/Rakefile @@ -12,12 +12,12 @@ desc "Compile the extension" task :compile => "pkg/classes" do |t| ant.javac :srcdir => "src", :destdir => t.prerequisites.first, :source => "1.5", :target => "1.5", :debug => true, - :classpath => "${java.class.path}:${sun.boot.class.path}:lib/clojure-1.2.0.jar" + :classpath => "${java.class.path}:${sun.boot.class.path}:javalib/clojure-1.2.0.jar" end desc "Build the jar" task :jar => :compile do - ant.jar :basedir => "pkg/classes", :destfile => "javalib/cloby_ext.jar", :includes => "**/*.class" + ant.jar :basedir => "pkg/classes", :destfile => "lib/cloby_ext.jar", :includes => "**/*.class" end task :package => :jar diff --git a/examples/agents.rb b/examples/agents.rb new file mode 100644 index 0000000..f522446 --- /dev/null +++ b/examples/agents.rb @@ -0,0 +1,13 @@ +require 'cloby' + +ag = Agent.new 42 + +puts ag.deref # => 42 + +ag.dispatch 19 do |a,b| + a-b +end + +puts ag.deref # => 23 + +Agent.shutdown # IMPORTANT! program will not exit if agents are not shutdown \ No newline at end of file diff --git a/examples/atoms.rb b/examples/atoms.rb new file mode 100644 index 0000000..30ec1c1 --- /dev/null +++ b/examples/atoms.rb @@ -0,0 +1,18 @@ +require 'cloby' + +atom = Atom.new 5 + +atom.swap{|a| a + 6} +puts atom.deref # => 11 + +puts atom.compare_and_set(42, 11) # => false +puts atom.deref # => 11 + +puts atom.compare_and_set(11, 42) # => true +puts atom.deref # => 42 + +puts atom.swap(1,2,3) {|a,b,c,d| a+b+c+d} # => 48 + +atom.reset 13 + +puts atom.deref # => 13 \ No newline at end of file diff --git a/lib/cloby_ext.jar b/lib/cloby_ext.jar index 0141f4a..cc6a223 100644 Binary files a/lib/cloby_ext.jar and b/lib/cloby_ext.jar differ diff --git a/src/org/jruby/clojure/ClojureAgent.java b/src/org/jruby/clojure/ClojureAgent.java new file mode 100644 index 0000000..b7afc20 --- /dev/null +++ b/src/org/jruby/clojure/ClojureAgent.java @@ -0,0 +1,108 @@ +package org.jruby.clojure; + + +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyModule; +import org.jruby.RubyObject; +import org.jruby.anno.JRubyMethod; +import org.jruby.anno.JRubyClass; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import clojure.lang.Agent; +import clojure.lang.ArraySeq; +import org.jruby.javasupport.JavaUtil; +import org.jruby.runtime.Block; +import org.jruby.RubyBoolean; +import static org.jruby.runtime.Visibility.*; + +@JRubyClass(name = "Agent") +public class ClojureAgent extends RubyObject { + + public static RubyClass createAgentClass(Ruby runtime) { + RubyClass agentc = runtime.defineClass("Agent", runtime.getObject(), AGENT_ALLOCATOR); + agentc.setReifiedClass(ClojureAgent.class); + agentc.kindOf = new RubyModule.KindOf() { + @Override + public boolean isKindOf(IRubyObject obj, RubyModule type) { + return obj instanceof ClojureAgent; + } + }; + agentc.defineAnnotatedMethods(ClojureAgent.class); + return agentc; + } + + private static ObjectAllocator AGENT_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(Ruby runtime, RubyClass klass) { + return new ClojureAgent(runtime, klass); + } + }; + + Ruby ruby; + Agent agent; + + ClojureAgent(Ruby runtime, RubyClass klass) { + super(runtime, klass); + ruby = null; + agent = null; + } + + @JRubyMethod(name = "shutdown", meta = true) + public static IRubyObject shutdown(ThreadContext context, IRubyObject recv) { + Agent.shutdown(); + return context.getRuntime().getNil(); + } + + @JRubyMethod(visibility = PRIVATE) + public IRubyObject initialize(final ThreadContext context, final IRubyObject state, Block block) { + ruby = context.getRuntime(); + try { + agent = new Agent(state); + this.setErrorHandler(context, block); + } catch (Exception e) { + throw ruby.newConcurrencyError(e.getLocalizedMessage()); + } + return this; + } + + @JRubyMethod(name = "dispatch", rest = true) + public IRubyObject dispatch(final ThreadContext context, final IRubyObject[] args, final Block block) { + agent.dispatch( new ClojureFunction(context, block), ArraySeq.create((Object[])args), true); + return ruby.getNil(); + } + + @JRubyMethod + public Object deref(final ThreadContext context) { + try { + return agent.deref(); + } catch (Exception e) { + throw context.getRuntime().newConcurrencyError(e.getLocalizedMessage()); + } + } + + @JRubyMethod(name = "get_error") + public Object getError(final ThreadContext context) { + return agent.getError(); + } + + @JRubyMethod + public Object restart(final ThreadContext context, final IRubyObject newState) { + return restart(context, newState, context.getRuntime().getTrue()); + } + + @JRubyMethod + public Object restart(final ThreadContext context, final IRubyObject newState, final IRubyObject clearActions) { + try { + return agent.restart(newState, clearActions.isTrue()); + } catch (Exception e) { + throw context.getRuntime().newConcurrencyError(e.getLocalizedMessage()); + } + } + + @JRubyMethod(name = "set_error_handler") + public IRubyObject setErrorHandler(final ThreadContext context, final Block block) { + agent.setErrorHandler(new ClojureFunction(context, block)); + return context.getRuntime().getNil(); + } +} \ No newline at end of file diff --git a/src/org/jruby/clojure/ClojureAtom.java b/src/org/jruby/clojure/ClojureAtom.java new file mode 100644 index 0000000..4cc02f0 --- /dev/null +++ b/src/org/jruby/clojure/ClojureAtom.java @@ -0,0 +1,115 @@ +package org.jruby.clojure; + +import org.jruby.Ruby; +import org.jruby.RubyClass; +import org.jruby.RubyModule; +import org.jruby.RubyObject; +import org.jruby.anno.JRubyMethod; +import org.jruby.anno.JRubyClass; +import org.jruby.runtime.ObjectAllocator; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; +import org.jruby.runtime.Block; +import clojure.lang.Atom; +import clojure.lang.ArraySeq; +import static org.jruby.runtime.Visibility.*; + +@JRubyClass(name = "Atom") +public class ClojureAtom extends RubyObject { + + public static RubyClass createAtomClass(final Ruby runtime) { + RubyClass atomc = runtime.defineClass("Atom", runtime.getObject(), ATOM_ALLOCATOR); + atomc.setReifiedClass(ClojureAtom.class); + atomc.kindOf = new RubyModule.KindOf() { + @Override + public boolean isKindOf(IRubyObject obj, RubyModule type) { + return obj instanceof ClojureAtom; + } + }; + atomc.defineAnnotatedMethods(ClojureAtom.class); + return atomc; + } + + Atom atom; + + private static ObjectAllocator ATOM_ALLOCATOR = new ObjectAllocator() { + public IRubyObject allocate(final Ruby runtime, final RubyClass klass) { + return new ClojureAtom(runtime, klass); + } + }; + + public ClojureAtom(final Ruby runtime, final RubyClass klass) { + super(runtime, klass); + atom = null; + } + + @JRubyMethod(visibility = PRIVATE) + public IRubyObject initialize(final ThreadContext context, final IRubyObject state) { + try { + atom = new Atom(state); + } catch (Exception e) { + throw context.getRuntime().newConcurrencyError(e.getLocalizedMessage()); + } + return this; + } + + @JRubyMethod + public Object swap(final ThreadContext context, final Block block) { + try { + return atom.swap(new ClojureFunction(context, block)); + } catch (Exception e) { + throw context.getRuntime().newConcurrencyError(e.getLocalizedMessage()); + } + } + + @JRubyMethod + public Object swap(final ThreadContext context, final IRubyObject arg0, final Block block) { + try { + return atom.swap(new ClojureFunction(context, block), arg0); + } catch (Exception e) { + throw context.getRuntime().newConcurrencyError(e.getLocalizedMessage()); + } + } + + @JRubyMethod + public Object swap(final ThreadContext context, final IRubyObject arg0, final IRubyObject arg1, final Block block) { + try { + return atom.swap(new ClojureFunction(context, block), arg0, arg1); + } catch (Exception e) { + throw context.getRuntime().newConcurrencyError(e.getLocalizedMessage()); + } + } + + @JRubyMethod(rest = true) + public Object swap(final ThreadContext context, final IRubyObject[] args, final Block block) { + ArraySeq seq = ArraySeq.create((Object[])args); + Object arg0 = seq.get(0); + Object arg1 = seq.get(1); + + try { + return atom.swap(new ClojureFunction(context, block), arg0, arg1, seq.next().next()); + } catch (Exception e) { + throw context.getRuntime().newConcurrencyError(e.getLocalizedMessage()); + } + } + + @JRubyMethod(name = "compare_and_set") + public IRubyObject compareAndSet(ThreadContext context, IRubyObject oldv, IRubyObject newv){ + return atom.compareAndSet(oldv, newv) ? context.getRuntime().getTrue() : context.getRuntime().getFalse(); + } + + @JRubyMethod + public Object reset(final ThreadContext context, final IRubyObject newVal) { + return atom.reset(newVal); + } + + @JRubyMethod + public Object deref(final ThreadContext context) { + try { + return atom.deref(); + } catch (Exception e) { + throw context.getRuntime().newConcurrencyError(e.getLocalizedMessage()); + } + } + +} \ No newline at end of file diff --git a/src/org/jruby/clojure/ClojureFunction.java b/src/org/jruby/clojure/ClojureFunction.java new file mode 100644 index 0000000..4df265f --- /dev/null +++ b/src/org/jruby/clojure/ClojureFunction.java @@ -0,0 +1,240 @@ +package org.jruby.clojure; + +import org.jruby.Ruby; +import org.jruby.runtime.Block; +import org.jruby.runtime.ThreadContext; +import clojure.lang.IFn; +import clojure.lang.ISeq; +import clojure.lang.RT; +import org.jruby.javasupport.JavaUtil; + +public class ClojureFunction implements IFn { + + final Block block; + final Ruby ruby; + + public ClojureFunction(final ThreadContext context, final Block block) { + this.block = block; + this.ruby = context.getRuntime(); + } + + @Override + public Object call() throws Exception { + return invoke(); + } + + public void run() { + try { + invoke(); + } catch(Exception e) { + throw new RuntimeException(e); + } + } + + public Object invoke() throws Exception { + return block.call(ruby.getCurrentContext()); + } + + public Object invoke(Object arg1) throws Exception { + return block.call(ruby.getCurrentContext(), + JavaUtil.convertJavaToRuby(ruby,arg1) + ); + } + + public Object invoke(Object arg1, Object arg2) throws Exception { + return block.call(ruby.getCurrentContext(), + JavaUtil.convertJavaToRuby(ruby,arg1), + JavaUtil.convertJavaToRuby(ruby,arg2) + ); + } + + public Object invoke(Object arg1, Object arg2, Object arg3) throws Exception { + return block.call(ruby.getCurrentContext(), + JavaUtil.convertJavaToRuby(ruby,arg1), + JavaUtil.convertJavaToRuby(ruby,arg2), + JavaUtil.convertJavaToRuby(ruby,arg3) + ); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7) + throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10, Object arg11) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10, arg11 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10, Object arg11, Object arg12) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10, arg11, arg12 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10, Object arg11, Object arg12, Object arg13) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10, arg11, arg12, arg13 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10, Object arg11, Object arg12, Object arg13, Object arg14) + throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10, arg11, arg12, arg13, arg14 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10, Object arg11, Object arg12, Object arg13, Object arg14, + Object arg15) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10, arg11, arg12, arg13, arg14, + arg15 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10, Object arg11, Object arg12, Object arg13, Object arg14, + Object arg15, Object arg16) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10, arg11, arg12, arg13, arg14, + arg15, arg16 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10, Object arg11, Object arg12, Object arg13, Object arg14, + Object arg15, Object arg16, Object arg17) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10, arg11, arg12, arg13, arg14, + arg15, arg16, arg17 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10, Object arg11, Object arg12, Object arg13, Object arg14, + Object arg15, Object arg16, Object arg17, Object arg18) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10, arg11, arg12, arg13, arg14, + arg15, arg16, arg17, arg18 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10, Object arg11, Object arg12, Object arg13, Object arg14, + Object arg15, Object arg16, Object arg17, Object arg18, Object arg19) throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10, arg11, arg12, arg13, arg14, + arg15, arg16, arg17, arg18, arg19 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10, Object arg11, Object arg12, Object arg13, Object arg14, + Object arg15, Object arg16, Object arg17, Object arg18, Object arg19, Object arg20) + throws Exception { + Object[] args = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10, arg11, arg12, arg13, arg14, + arg15, arg16, arg17, arg18, arg19, arg20 + }; + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, args)); + } + + public Object invoke(Object arg1, Object arg2, Object arg3, Object arg4, Object arg5, Object arg6, Object arg7, + Object arg8, Object arg9, Object arg10, Object arg11, Object arg12, Object arg13, Object arg14, + Object arg15, Object arg16, Object arg17, Object arg18, Object arg19, Object arg20, + Object... args) throws Exception { + Object[] args1 = { + arg1, arg2, arg3, arg4, arg5, arg6, arg7, + arg8, arg9, arg10, arg11, arg12, arg13, arg14, + arg15, arg16, arg17, arg18, arg19, arg20 + }; + + Object[] conArgs = new Object[args1.length+args.length]; + System.arraycopy(args1, 0, conArgs, 0, args1.length); + System.arraycopy(args, 0, conArgs, 0, args.length); + + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby,conArgs)); + } + + @Override + public Object applyTo(final ISeq arglist) throws Exception{ + return block.call(ruby.getCurrentContext(), JavaUtil.convertJavaArrayToRuby(ruby, RT.seqToArray(arglist))); + } +} \ No newline at end of file diff --git a/src/org/jruby/clojure/ClojureLibrary.java b/src/org/jruby/clojure/ClojureLibrary.java index 26e48f1..54a3a36 100644 --- a/src/org/jruby/clojure/ClojureLibrary.java +++ b/src/org/jruby/clojure/ClojureLibrary.java @@ -23,6 +23,8 @@ public void load(Ruby runtime, boolean wrap) throws IOException { runtime.defineClassUnder("Object", runtime.getObject(), new ClojureObjectAllocator(), cljModule); runtime.getKernel().defineAnnotatedMethods(ClojureDosync.class); + ClojureAgent.createAgentClass(runtime); + ClojureAtom.createAtomClass(runtime); } public static class ClojureObjectAllocator implements ObjectAllocator {