月曜日, 5月 30, 2016

Gradle - マルチプロジェクト

最近は専らmavenよりもgradleを使っている.近年はmake, ant,maven, sbtと比較的短期間で様々なビルドツールを使わざるを得なかったことに食傷気味ではあったが,gradleをいざ使ってみると,mavenやantの冗長なコンフィグからの脱却や拡張性の高さがすぐに気に入った.

以下はgradleでmavenのようなマルチモジュールのプロジェクトを定義した例.
以下の例では親プロジェクトが"tanu-im",子モジュールとして"desktop-ui"と"core"を想定している.

1. settings.gradleにはプロジェクトの定義を書く

rootProject.name = 'tanu-im'

include 'core'
include 'desktop-ui'

2. ルートプロジェクト直下のbuild.gradle. dependencyManagementを使いたいので,spring-gradleのdependency-managementプラグインを追加.
group 'org.tanuneko.im'
version '1.0-SNAPSHOT'

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'io.spring.gradle:dependency-management-plugin:0.5.6.RELEASE'
    }
}

3. common.gradleとdependencies.gradleの作成 共通の処理をcommon.gradle,プロジェクトの依存ライブラリをdependencies.gradleに定義.

common.gradle
// apply plugin here
apply plugin: 'java'
apply plugin: 'io.spring.dependency-management'
apply from: "${rootProject.projectDir}/gradle/dependencies.gradle"

sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies.gradle

repositories {
    mavenCentral()
}

project.ext {
    // spring
    springVersion = '4.2.6.RELEASE'
    // JSON
    jacksonVersion = '2.7.1'
    // unit test
    junitVersion = '4.11'
    hamcrestVersion = '1.3'
    // util
    commonsLang3Version = '3.4'
    commonsIOVersion = '2.4'
    // logging
    slf4jVersion = '1.7.21'
    logbackVersion = '1.1.5'
}

dependencyManagement {

    dependencies {
        // spring
        dependency "org.springframework:spring-core:${springVersion}"
        dependency "org.springframework:spring-test:${springVersion}"

        // JSON
        dependency "com.fasterxml.jackson.core:jackson-databind:${jacksonVersion}"
        dependency "com.fasterxml.jackson.core:jackson-annotations:${jacksonVersion}"

        // unit test
        dependency "junit:junit:${junitVersion}"
        dependency "org.hamcrest:hamcrest-all:${hamcrestVersion}"

        // util
        dependency "org.apache.commons:commons-lang3:${commonsLang3Version}"
        dependency "commons-io:commons-io:${commonsIOVersion}"

        // logging
        dependency "ch.qos.logback:logback-classic:${logbackVersion}"
        dependency "org.slf4j:slf4j-api:${slf4jVersion}"
    }
}

dependencies {
    compile 'org.slf4j:slf4j-api'
    testCompile 'junit:junit'
    testCompile 'ch.qos.logback:logback-classic'
}

4. 子モジュールの作成.ルートプロジェクト直下に"core", "desktop-ui"のディレクトリを作ったのち,mavenスタンダードアーキタイプの構成を作る(e.g. src/[main|test]/[java|resources].
5. build.gradleを子モジュールのルートに作成.ここには子モジュール独自の依存関係や処理を定義.

日曜日, 3月 06, 2016

Java - Retrofit RESTクライアント

Javaにはいまいち使い勝手の良いRESTクライアントは無かった。。と思うが,最近話題になっているRetrofit2は非常に使い勝手が良いように思える.
以下の例は統合テストにおいてlocalhost:9099/beanmgrに対してGETのリクエストを投げる例.

サービスインターフェース定義
package org.tanuneko;

import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;

/**
 * Created by neko32 on 2016/03/06.
 */
public interface WebInterfaceIF {

    @GET("tanubean/{id}")
    public Call<TanuBean> getNeko(@Path("id") int id);

    @POST("tanubean")
    public Call<TanuResponse> createNeko(@Body TanuBean bean);

}

統合テストからサービスインタフェース経由でGETリクエストを出す
    @Test
    public void testTanuAppWithRetroFit() throws IOException {

        Retrofit retro = new Retrofit.Builder()
                .baseUrl("http://localhost:9099/beanmgr/")
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        WebInterfaceIF service = retro.create(WebInterfaceIF.class);
        Call<TanuBean> tanu = service.getNeko(1);
        TanuBean t = tanu.execute().body();
        System.out.println("Retrived - id:" + t.getId() + ",name:" + t.getName());
        assertThat(t.getId(), is(1));
        assertThat(t.getName(), is("Tanuchan"));
    }


日曜日, 2月 07, 2016

Scala - 初めてのAkka - アクター

最近の流行りはどうやらreactive programming..らしく.. というか別に新しいものじゃない..old wine in a new bottleの典型例ではないか...
憤る気分を抑えながらもakkaを試してみた.
以下の例は単純なアクターをつくった例.私が読んだ本でakkaが紹介されているが,そのサンプルはakkaの非同期的性質を無視した代物であり,ランダムに失敗する.というわけで,以下の例は汚く危ない例ではあるがフェイルはしないようフラグを付けたもの.フラグを外すと,メッセージが到着する前にActorSystemが閉じることもある為,ランダムにdead letter のエラーが出るだろう.
import akka.actor.{Actor, ActorSystem, Props}
import akka.event.Logging

/**
  * Created by neko32 on 2016/02/07.
  */

object HelloActor {
  var flag = false
}

class HelloActor(processId:Int, processName:String) extends Actor {

  val log = Logging(context.system, this)
  def receive = {
    case "hello" => { log.info(s"[HELLO@${processName}:${processId}]"); println("Konnitiwa!");HelloActor.flag = true }
    case _ => { log.info(s"[UNKNOWN@${processName}:${processId}]"); println("..."); }
  }
}

object Runner extends App {

  val sys = ActorSystem("HelloSystem")
  val helloActor = sys.actorOf(Props(new HelloActor(1, "Tanuki")), name = "helloActor")
  helloActor ! "muimui!"
  helloActor ! "RUirui!"
  helloActor ! "hello"
  while(!HelloActor.flag) {
    Thread.sleep(1000)
  }
  sys.shutdown

}

月曜日, 1月 18, 2016

Spark - StatsCounter

StatsCounterを使えば簡単に統計用の基本的な値を計算できる.

import org.apache.spark.{SparkContext, SparkConf}

/**
  * Created by neko32 on 2016/01/18.
  */
object Stats {

  def main(args:Array[String]) = {
    val conf = new SparkConf().setMaster("local").setAppName("stats")
    val sc = new SparkContext(conf)
    val rdd = sc.textFile("/user/neko32/spark/study7/nums.txt")
    val stats = rdd.flatMap(l => l.split(":")(1).split(" ").map(_.toInt)).stats
    println(s"mean:${stats.mean}, max:${stats.max}, min:${stats.min}, var:${stats.variance}")
  }
}

Spark - パーティション単位でのマップ

PairRDDのpartitionBy()を使ってパーティション毎の要素の合計を計算をする例.
入力ファイルは以下のようなキーバリューの対とする.また,バリューはスペース区切りの整数の列とする.

lineA:73 44 58 62
lineAB:88 21 20
lineABC:50 21 99 82 25 62

import org.apache.spark.{Partitioner, SparkConf, SparkContext}

/**
  * Created by neko32 on 2016/01/17.
  */
object CalcPart {

  def main(args:Array[String]) = {
    val conf = new SparkConf().setMaster("local").setAppName("Takoran")
    val sc = new SparkContext(conf)
    val data =  sc.textFile("/user/neko32/spark/study7/nums.txt")
          .map(l => {
            val splitted = l.split(":")
            (splitted(0), splitted(1).split(" "))
          })
    data.partitionBy(new OddEvenPartitioner(2)).persist

    data.foreach(elem => {
      val buf = new StringBuilder
      buf ++= "key[" + elem._1 + "]:"
      elem._2.foreach(n => buf ++= (n + " "))
      println(buf.toString)
    })

    val x = data.mapPartitions(iter => {
        val nums = iter.map(x => (x._1, x._2.foldLeft(0)(_ + _.toInt)))
        nums
      }
    )

    x foreach println
    for(x <- x collect) {
      println(s"${x._1}:${x._2}")
    }
  }
}

class OddEvenPartitioner(nums:Int) extends Partitioner {
  override def numPartitions: Int = nums

  override def getPartition(key: Any): Int = {
    val n = key.asInstanceOf[String].length
    n % 2
  }

  override def equals(others: Any) = others match {
    case o:OddEvenPartitioner => o.numPartitions == this.numPartitions
    case _ => false
  }
}

金曜日, 1月 15, 2016

Scala - JSONの作成

以下の例はPlay frameworkのJSONライブラリを使ってJSONを作った例.
Scalaの型から作られたJSONをJson.parse()を使ってまたJSON化もしている.

import play.api.libs.json._
/**
  * Created by neko32 on 2016/01/15.
  */
object JsonSample {

  def main(args:Array[String]) = {
    val mine = Json.obj("name" -> "tanuchan", "age" -> 5, "Nekoz" -> Json.arr("Chatora", "Mikeneko", "Snowball"))
    println(mine.toString)
  }

}

SBTには以下を追加.
libraryDependencies ++= Seq("com.typesafe.play" %% "play-json" % "2.4.6")

水曜日, 1月 13, 2016

Spark - JDBC経由でデータを取得

以下の例はSparkJDBCを使ってRDB(ここではMySQL. MySQLは使いやすくていいね!)からデータを取得しJDBCRDDを構築する.

import java.sql.{ResultSet, DriverManager}

import org.apache.spark.rdd.JdbcRDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * Created by neko32 on 2016/01/13.
  */
object MySQLSpark {

  def getConn() = {
   Class.forName("com.mysql.jdbc.Driver").newInstance
    DriverManager.getConnection("jdbc:mysql://localhost/neko32?user=takoneko888&password=XXXX")
}
  def main(args:Array[String]):Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("MySql")
    val sc = new SparkContext(conf)

    val data = new JdbcRDD(sc, getConn, "select * from test where id >= ? and id <= ?",
                            upperBound = 2,
                            lowerBound = 1,
                            numPartitions = 1,
                            mapRow = (r:ResultSet) => (r.getInt("id"), r.getString("name")))
    println(data.collect.toList)

  }
}

火曜日, 1月 12, 2016

Spark - CSVファイルの書き込み

以下の例はRDDの内容(Neko case class)をCSVファイルとしてHDFSに書き込む例.

package tanuneko

import java.io.StringWriter

import com.opencsv.CSVWriter
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.fs.{Path, FileSystem}
import org.apache.spark.{SparkConf, SparkContext}

import scala.collection.JavaConverters._

/**
  * Created by neko32 on 2016/01/11.
  */
object CSVWrite {

  case class Neko(name:String, age:Int)

  def main(args:Array[String]):Unit = {
    val conf = new SparkConf().setMaster("local").setAppName("WriteJson")
    val sc = new SparkContext(conf)
    val hadoopConf = new Configuration
    val fs = FileSystem.get(hadoopConf)
    val outFile = "/user/neko32/spark/study4/csvwrite"

    val nekoz = sc.parallelize(List(Neko("Tora", 8), Neko("Mikeyo", 4)))
    val stred = nekoz.map(n => List(n.age.toString, n.name).toArray)
    val nekoz2 = stred.mapPartitions { n =>
      val writer = new StringWriter
      val csvWriter = new CSVWriter(writer)
      csvWriter.writeAll(n.toList.asJava)
      Iterator(writer.toString)
    }.cache
    nekoz2 foreach println
    if(fs.exists(new Path(outFile))) {
      fs.delete(new Path(outFile), true)
    }
    nekoz2 saveAsTextFile(outFile)
  }

}

日曜日, 1月 10, 2016

Spark - ファイル名をキー,内容をバリューとしてファイルを読み込み

SparkContextのwholeTextFiles()を使うと,ファイル名をキー,内容をバリューのRDDを作ることが出来る.以下の例では,HDFS上に空白区切りで数字が羅列されている複数のファイルを一括で読み込んでそれぞれの算術平均を求めている.

val in = sc.wholeTextFiles("/user/neko32/spark/study3")
val rez = in mapValues {
    case d =>
    val nums = d.trim.split(" ").map(_.toInt)
    nums.sum / nums.size
}
rez foreach println

上記のコードをファイルに保存した後spark-shellから以下のコマンドでロード.

:load ./avgcal.scala

木曜日, 1月 07, 2016

Spark メモ - CSVからkey - non-key ペア変換

HDFS上のCSV風テキストを最終的に主キー - 非キー別タプルに変換する例.


// read input file separated by comma
// suppose this input file consists of 4 cols (tradeid, version, instrument and trader name)
val myrdd = sc.textFile("/user/neko32/spark/study1/contract.txt")

// transform string to list
val lines = myrdd.map(x => x.split(",").toList)

// transform line list to tuple
val myIn = lines.map(x => (x(0), x(1), x(2), x(3)))

// transform line as tupleN to key-val pairs
val byKey = myIn.map{case (c,v,i,n) => (c,v) -> (i,n)}

// reduce by key
val reducedByKey = byKey.reduceByKey{ case (x,y) => (x._1 + y._1, x._2 + y._2) }

日曜日, 1月 03, 2016

scala - 外部コマンドの実行

Scalaの外部コマンド実行はJavaのProcessBuilderほぼ同じ.
/**
  * Created by neko32 on 2016/01/03.
  */
object RunCommand {
  def main(args:Array[String]):Unit = {
    import sys.process._
    // execute external command
    val result = "javac -version".!
    println(result)
    // return stdout
    val result2 = "javac -version".!!
    println(result2)
    // via Process or Seq command can be triggered
    val result3 = Seq("javac", "-version").!!
    println(result3)
    val result4 = Process("javac -version").!!
    println(result4)
    // dump stderr
    val result5 = "javac -takorin".lineStream_!.mkString
    println(result5)
    // redirect output to a file
    val result6 = ("javac -version" #> new File("C:\\tmp\\javav")).!
    println(result6)
    // this step may not work on Windows
    assume(new File("C:\\tmp\\javav").exists() &&
           Source.fromFile("C:\\tmp\\javav").mkString.length() > 0, "file should exists!")
  }
}

しかしながらscalaはパイプを用意に実現できたりとより強力である.
scala> ("ls /cygdrive/c/tmp/" #| "grep -i rachmaninov").!!.trim
res1: String =
Rachmaninov_piano3.jpg
Rachmaninov2.jpg

scala - CSVファイルの読み込み

CSVファイルを読み込んで多重配列に保存するコードのメモ..

import resource._

import scala.io.Source

object CSVReadRunner extends CSVRead {
  def main(args:Array[String]):Unit = {
    val vals = read("C:\\tmp\\tankoneko.csv")
    println("done.")
  }
}

/**
  * Created by neko32 on 2016/01/03.
  */
trait CSVRead {
  def read(path:String):Array[Array[String]] = {
    val arr = Array.ofDim[String](4,2)
    for {
      out <- managed(Source.fromFile(path))
    } {
      for{ (line,i) <- out.getLines.zipWithIndex} {
        println(s"($i)@ILOOP")
        for{ (elem,j) <- line.split(",").map(_.trim).zipWithIndex} {
          println(s"($i,$j)@JLOOP")
          arr(i)(j) = elem
        }
      }
    }
    arr
  }
}

scala - try with resource相当

Scalaにおいてtry-with-resourceのようなARMを使うには,私の知っている限りscala-ARMを使う必要がある.
以下の例はmanagedを使ってwriterをmanagedを使って自動で閉じ,scala.io.Sourceで読みだした後,自前のcloseAfterのローンパターンで閉じている.(Alvin Alexander氏のScala Cookbookを参考とさせていただいた)(
Source.from([file])が返すBufferedSourceはcloseメソッドを持っているのでcloseAfterの型要件を満たしている.
import java.io.{BufferedWriter, File, FileWriter}

import resource.managed

import scala.io.Source

/**
  * Created by neko32 on 2016/01/03.
  */
object FileIO {
  def main(args:Array[String]):Unit = {
    val path = "C:\\tmp\\testneko.txt"
    val file = new File(path)
    if(file.exists) file.delete

    for {
      out <- data-blogger-escaped-bufferedwriter="" data-blogger-escaped-closeafter="" data-blogger-escaped-file="" data-blogger-escaped-filewriter="" data-blogger-escaped-managed="" data-blogger-escaped-n="" data-blogger-escaped-new="" data-blogger-escaped-nmachiko="" data-blogger-escaped-nmikeyo="" data-blogger-escaped-npowder="" data-blogger-escaped-orachan="" data-blogger-escaped-ource.fromfile="" data-blogger-escaped-out.flush="" data-blogger-escaped-out.write="" data-blogger-escaped-path="" data-blogger-escaped-src="">
        val texts = src.getLines()
        for((m,n) <- data-blogger-escaped-a="" data-blogger-escaped-b="" data-blogger-escaped-close="" data-blogger-escaped-closeafter="" data-blogger-escaped-closelater="" data-blogger-escaped-def="" data-blogger-escaped-f:="" data-blogger-escaped-m="" data-blogger-escaped-n="" data-blogger-escaped-nit="" data-blogger-escaped-object="" data-blogger-escaped-println="" data-blogger-escaped-resource:="" data-blogger-escaped-s="" data-blogger-escaped-texts.zipwithindex=""> B):B = {
    try {
      f(resource)
    } finally {
      resource.close()
    }
  }
}

scala-ARMを使うには以下の行をbuild.sbtに追加.
libraryDependencies ++= Seq("org.specs2" %% "specs2-core" % "3.6.5" % "test")

Scala - マップのキー・バリュー交換

scalaでは,mapのキー・バリュースワップを容易に実現出来る.


    val myMap = Map("Tora" -> "Chatora", "Mikeyo" -> "Mike", "Powder" -> "Mike")
    val reversed = for((x,y) <- myMap) yield(y,x)
    println(reversed)

木曜日, 12月 31, 2015

Scala - Sequenceマッチ

Scalaのmatch文は極めて強力で,そのうちの一つのリスト要素へのパターンマッチも便利なものの一つだろう.以下の例はMyCatへのコンストラクタマッチとリスト要素へのシーケンスパターンマッチの組み合わせの例.もしパターンマッチ内でジェネリック型も含めた型パターンマッチ等をしていて,かつJavaからそのscalaコードから作られたクラスを利用する場合は,Javaの型消去の特性に気を付けるように.


object Matcher {

  def main(args:Array[String]):Unit = {
    val nekoz = genCats()
    nekoz.foreach{ x =>
      x match {
        case MyCat(a,b) if a > 5 => println("Adult neko.")
        case _ => println("..")
      }
    }
    nekoz match {
      case l @ List(MyCat(8, _), _*) => println(s"okay, starting with 8 year old cat - $l")
      case _ => println("..")
   }

  }


  def genCats() = {
    MyCat(8, "Tora") :: MyCat(4, "Mikeyo") :: List()
  }

}

case class MyCat(age:Int, name:String)


水曜日, 12月 30, 2015

Scala - ワードカウント

SparkではreduceByKeyで簡単にワードカウントができるが,scalaではfoldLeftで実装出来る.

    val x = """kiji,saru,saru#saru,inu,kiji,saru#inu,kiji,kiji#kiji#inu,inu#saru"""
    val v = x.split("#").flatMap(_.split(",")).map(s => (s,1))
    val z = v.foldLeft(Map[String,Int]())((accm, elem) => {
      if(accm.contains(elem._1))
        accm.updated(elem._1, elem._2 + accm(elem._1))
      else
        accm + elem
    })

火曜日, 12月 29, 2015

scala - ディレクトリのファイル操作

妻のパソコンの,とあるフォルダにある画像ファイルのタイムスタンプが何故かおかしくなってしまった.ファイルの数が膨大ということもあり,scalaでさっとコードを書いて修正した.

import java.io.File
import java.time.{LocalDateTime, ZoneId}
import java.util.Date

import org.apache.commons.io.FileUtils

/**
  * Created by neko32 on 2015/12/28.
  */
class FileDateChanger(preProcessDir:String, postProcessDir:String) {

  implicit def dateToLDate(d:Date):LocalDateTime = {
    d.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime()
  }

  def process(): Unit = {
    clean()
    val files = new File(preProcessDir).listFiles().filter(f => dateToLDate(new Date(f.lastModified())).getYear() == 2007)
    val totalNum = files.size
    var at = 0
    println(s"Total number of files - ${totalNum}")
    files.foreach { f =>
      var dat = new Date(f.lastModified())
      var f2 = new File(postProcessDir + "\\" + f.getName())
      println(s"processing .. ${f.getName()}[${new Date(f.lastModified())}]")
      FileUtils.copyFile(f, f2)
      f2.setLastModified(dat.plusYears(8).atZone(ZoneId.systemDefault()).toInstant().toEpochMilli)
      println(s"to ${new Date(f2.lastModified())}")
      if(at % 10 == 0) println(s"processed ${at}")
      at += 1
    }
    println(s"done. # of processed files [${at}]")
  }

  private def clean(): Unit = {
    println(s"cleaning ${postProcessDir}")
    new File(postProcessDir).listFiles().foreach(_.delete())
    println("cleaning done.")
  }
}

object Runner {
  def main(args:Array[String]) = {
    val pre = "C:\\tmp\\pre_process"
    val post = "C:\\tmp\\post_process"
    new FileDateChanger(pre, post).process()
  }
}

日曜日, 12月 27, 2015

Scala - 乱数生成プロバイダを用いるstream


    val r = new Random(System.currentTimeMillis())
    def rand(max:Int):Stream[Int] = Stream.cons(r.nextInt(max), rand(max))
    rand(100) take 10 foreach println
    // assign index starting from 1. zipWithIndex()'s index starts from 0
    rand(50) take 5 zip(Stream from 1) foreach println

金曜日, 12月 25, 2015

Scala - Enumeration

ScalaでEnumerationを継承してenumを定義する例.

object Weekdays extends Enumeration {
  type WEEKDAY = Value
  val MON, TUE, WED = Value
}

object MyMain {

  def isMonday(w:Weekdays.Value) = {
    w == Weekdays.MON
  }

  def main(args:Array[String]) = {
    val w = Weekdays.MON
    val w2 = Weekdays.WED
    println(isMonday(w))
    println(isMonday(w2))
  }
}

木曜日, 12月 24, 2015

Scala - akka アクタ

Akka actorのサンプルメモ..

import akka.actor._

/**
  * Created by neko32 on 2015/12/23.
  */

sealed abstract trait Message
case class Request(a:Int, b:Int, op:Operator) extends Message
case class Response(a:Int, b:Int, op:Operator, result:Int) extends Message
case class ExecStart(cmd:String) extends Message
case class ExecEnd(a:Any) extends Message

sealed abstract class Operator(a:Int, b:Int) {
  def calc():Int
}
case class Plus(a:Int, b:Int) extends Operator(a,b) {
  override def calc() = a + b
  override def toString() = " + "
}
case class Minus(a:Int, b:Int) extends Operator(a,b) {
  override def calc() = a - b
  override def toString() = " - "
}
case class Multiply(a:Int, b:Int) extends Operator(a,b) {
  override def calc() = a * b
  override def toString() = " * "
}
case class Divide(a:Int, b:Int) extends Operator(a,b) {
  require(b != 0, "zero divide not allowed")
  override def calc() = a / b
  override def toString() = " / "
}

class CalcWorker extends Actor {
  override def receive = {
    case Request(a,b,op) if op.isInstanceOf[Plus] => println("sending.."); sender ! Response(a, b, op, a + b)
    case Request(a,b,op) if op.isInstanceOf[Minus] => sender ! Response(a, b, op, a - b)
    case Request(a,b,op) if op.isInstanceOf[Multiply] => sender ! Response(a, b, op, a * b)
    case Request(a,b,op) if op.isInstanceOf[Divide] => sender ! Response(a, b, op, a / b)
    case ExecEnd => context.stop(self)
  }
}

trait Requester extends Actor with ActorLogging {
  override def receive = {
    case Response(a, b, op, rez) =>
      log.info(s"got result from calc agent .. ${a}${op}${b} = ${rez}")
    case ExecStart(cmd) => run(cmd)
    case ExecEnd => context.stop(self)
    case _ => log.info("....")
  }
  def run(cmd:String)
}

class CalcRequester(calcAgent:ActorRef) extends Requester {

  def toOp(vals:Array[String]):Operator = vals(2) match {
    case s if s == "plus" => Plus(vals(0).toInt, vals(1).toInt)
    case s if s == "minus" => Minus(vals(0).toInt, vals(1).toInt)
    case s if s == "mult" => Multiply(vals(0).toInt, vals(1).toInt)
    case s if s == "div" => Divide(vals(0).toInt, vals(1).toInt)
  }

  override def run(cmd:String) = {
      // parse a,b
      assume(cmd.count(_ == ',') == 2)
      val vals = cmd.toLowerCase().split(",")
      println("parsed input - " + vals(0) + "," + vals(1) + "," + vals(2))
      val res = calcAgent ! Request(vals(0).toInt, vals(1).toInt, toOp(vals))
  }
}

object MyApp {
  def main(args:Array[String]) = {
    val sys = ActorSystem("CalcSys")
    val calcAgent = sys.actorOf(Props[CalcWorker], "CalcAgent")
    val calcReq = sys.actorOf(Props(new CalcRequester(calcAgent)), "CalcReq")
    calcReq ! ExecStart("3,8,plus")
    Thread.sleep(3000)
    calcReq ! ExecEnd
    calcAgent ! ExecEnd
    val f = sys.terminate()
  }
}