Java to JRuby translation of Akka's first tutorial to compute Pi using Madhava-Leibniz series. (http://akka.io/docs/akka/1.1.2/intro/getting-started-first-java.html)
1. pi.rb Page 1 of 3
1: #! /usr/bin/env jruby
2:
3: # First things first.
4: # Load Java integration support
5: require "java"
6:
7: # Add ’lib’ in the same directory as this file to the load path
8: $: << File.join(File.dirname(__FILE__), ’lib’)
9:
10: # Load Java libraries
11: require ’scala-library’
12: require ’akka/akka-actor-1.1.2’
13:
14: # Here, we import Java classes so that we don’t have to prefix them
15: # with Java::.
16: java_import ’akka.actor.Actors’
17: java_import ’akka.actor.ActorRef’
18: java_import ’akka.actor.UntypedActor’
19: java_import ’akka.actor.UntypedActorFactory’
20: java_import ’akka.routing.CyclicIterator’
21: java_import ’akka.routing.InfiniteIterator’
22: java_import ’akka.routing.Routing’
23: java_import ’akka.routing.UntypedLoadBalancer’
24: # Java’s built-in classes don’t need to be quoted (for a String)
25: java_import java.util.concurrent.CountDownLatch
26:
27: # Convenience method for creating an Actor
28: def actorOf(&code)
29: Actors.actorOf(Class.new do
30: include UntypedActorFactory
31: define_method(:create) do |*args|
32: code[*args]
33: end
34: end.new)
35: end
36:
37: class Calculate; end
38: # Struct.new(...) creates an instance having the instance variables
39: # passed
40: class Work < Struct.new(:start, :nrOfElements); end
41: class Result < Struct.new(:value); end
42:
43: class Worker < UntypedActor
44: # needed by actorOf
45: def self.create(*args)
46: new *args
47: end
48:
49: # define the work
50: def calculatePiFor(start, nrOfElements)
51: # Here, we are using the identity
52: # Pi = 4 * sum_{k=0}^{infty} (-1)^k/(2 k + 1)
53: # We divide the work into chunks of nrOfElements
54: # Enumerable#inject may be a little foreign for Java programmers, but
55: # it can be thought of as a shorthand for looping and re-assigning
56: # the value of the block inside to the memo varialbe ("acc" in this case)
57: # "M...N" means a Range starting at M, ending *1 before* N.
58: ((start * nrOfElements)...((start + 1) * nrOfElements)).inject(0) do |acc,i|
59: acc + 4.0 * (1 - (i.modulo 2) * 2) / (2 * i + 1)
60: end
61: end
62:
63: # message handler
64: def onReceive(message)
65: # examining the class of an object is not very Ruby-esque.
2. pi.rb Page 2 of 3
66: # in generatl, Rubyists prefer duck typing
67: if message.kind_of? Work
68: work = message
69:
70: # perform the work
71: result = calculatePiFor(work.start, work.nrOfElements)
72:
73: # reply with the result
74: context.replyUnsafe(Result.new(result))
75:
76: else
77: raise IllegalArgumentException.new "Unknown message [#{message + b}]"
78: end
79: end
80: end
81:
82: class PiRouter < UntypedLoadBalancer
83: attr_reader :seq
84:
85: def initialize(workers)
86: super() # make sure the underlying Java proxy is properly initialized
87: @seq = CyclicIterator.new(workers)
88: end
89: end
90:
91: class Master < UntypedActor
92: def initialize(nrOfWorkers, nrOfMessages, nrOfElements, latch)
93: super()
94: @nrOfMessages, @nrOfElements, @latch = nrOfMessages, nrOfElements, latch
95: @nrOfResults, @pi = 0, 0.0
96:
97: # create the workers
98: workers = java.util.ArrayList.new
99: nrOfWorkers.times { workers << Actors.actorOf(Worker).start }
100:
101: # wrap them with a load-balancing router
102: @router = actorOf { PiRouter.new(workers) }.start
103: end
104:
105: # message handler
106: def onReceive(message)
107: if message.kind_of? Calculate
108: # schedule work
109: @nrOfMessages.times do |start|
110: @router.sendOneWay(Work.new(start, @nrOfElements), context)
111: end
112:
113: # send a PoisonPill to all workers telling them to shut down themselves
114: @router.sendOneWay(Routing::Broadcast.new(Actors.poisonPill))
115:
116: # send a PoisonPill to the router, telling him to shut himself down
117: @router.sendOneWay Actors.poisonPill
118: elsif message.kind_of? Result # handle result from the worker
119: @pi += message.value
120: @nrOfResults += 1
121: context.stop if @nrOfResults == @nrOfMessages
122: else
123: raise IllegalArgumentException.new "Unknown message [#{message}]"
124: end
125: end
126:
127: def preStart
128: @start = java.lang.System.currentTimeMillis
129: end
130:
3. pi.rb Page 3 of 3
131: def postStop
132: # tell the world that the calculation is complete
133: puts format("ntPi estimate: tt%sntCalculation time: t%s millis",
134: @pi, (java.lang.System.currentTimeMillis - @start))
135: @latch.countDown
136: end
137: end
138:
139:
140: class Pi
141: def self.calculate(nrOfWorkers, nrOfElements, nrOfMessages)
142: # this latch is only plumbing to know when the calculation is completed
143: latch = CountDownLatch.new(1)
144:
145: # create the master
146: master = Actors.actorOf do
147: Master.new(nrOfWorkers.to_i, nrOfMessages.to_i, nrOfElements.to_i, latch)
148: end.start
149: master.sendOneWay(Calculate.new) # start the calculation
150: latch.await # wait for master to shut down
151: end
152: end
153:
154: # Ready to do the real work
155: if (ARGV.length < 3)
156: exit "Usage: $0 num_workers num_elements num_messages"
157: end
158:
159: Pi.calculate(*ARGV[0..2])