We're very pleased that you want to get in touch with us. Please fill in the form below:



or   Close this form  
Some content

Peter Pilgrim :: Java Champion :: Digital Developer Architect

I design Java EE and Scala software solutions for the blue-chip clients and private sector

Hey all! Thanks for visiting. I provide fringe benefits to interested readers: checkout consultancy, training or mentorship Please make enquiries by email or call +44 (0)7397 067 658.

Due to the Off-Payroll Working plan for the UK government, I am enforcing stricter measures on contracts. All potential public sector GOV.UK contracts engagements must be approved by QDOS and/or SJD Accounting. Please enquire for further information.

Scalafication of an Equity Quote Application Part 3

26 July 2011 Comments off

9 minutes

1990

This is the final part in a series of article about porting an existing Java application to Scala. The application calls the Yahoo! Financial web service and retrieve stock quotes for the user, storing them in a local repository. The original Java project was described in the part one.

In the part two of this series, we looked at the unit test, and in particular the behaviour driven development (BDD) styles in the ScalaTest framework.

We will look at the Scala implementation of this business application. The easiest task is to simply convert the exceptions. Here are the Exceptions.scala file:

 

package uk.co.xenonique.stockquoteapp_scala

class ConnectionException( msg: String, root: Exception ) 
extends RuntimeException( msg, root ) {
   def this( msg: String ) = this( msg, null )
}

class DataRetrievalException( msg: String, root: Exception ) 
extends RuntimeException( msg, root ) {
   def this( msg: String ) = this( msg, null )
}

class UnknownStockSymbolException( msg: String, root: Exception ) 
extends RuntimeException( msg, root ) {
   def this( msg: String ) = this( msg, null )
}

 

We have more than one public class in a single source file. It is sort of allow Scala to look like a dynamic scripting language like Python or Ruby.

Next, we port the quote repository over to Scala. We remove the semi-colons, reverse the declarations of the variable types on the way. We take advantage of Scala support for Option types, we retrieve the stock quote by symbol name. The Scala library has two types of collections: immutable and mutable. By default, immutable collections are those that imported into the compiler as a predefinition. The QuoteRepositoryKeyValueStore imports a mutable Map collection for storing symbol associated with stock quotes. 

Here is the QuoteRepository.scala file:

 

package uk.co.xenonique.stockquoteapp_scala

import scala.collection.mutable.Map

trait QuoteRepository {
  def isEmpty(): Boolean
  def size(): Int
  def clear(): Unit
  def store( quote: StockQuote  ): Unit
  def get( symbol: String ): Option[StockQuote] 
  def contains( symbol: String ): Boolean
  def getSymbols(): List[String]
}

class QuoteRepositoryKeyValueStore extends QuoteRepository {

  private var store: Map[String, StockQuote] = Map()

  def clear(): Unit = { store.clear() }

  def contains(symbol: String): Boolean = store.contains(symbol)

  def get(symbol: String ): Option[StockQuote] = store.get(symbol)

  def isEmpty(): Boolean = store.isEmpty
	
  def size(): Int = store.size

  def store(quote: StockQuote ): Unit = {
    store.put(quote.symbol, quote)
  }

  def getSymbols(): List[String] = {
    // We use a tuple to iterate through the map, which return mutable list
    // then we use "toList" to convert to an immutable list.
    ( for ( (key,value ) <- store ) yield (key)  ).toList
  }

}

 

In above implementation class, we can write, in Scala, methods such as size() as one-liner methods.

We can convert the Java stock quotes to a case class and get the benefits of an implicit equalsTo(), hashCode() and toString() methods; and even get a handy copy() method too. Note: That we override the definition of the toString() class specifically for better formatting and, of course, debugging, but we did not need to do this for a case class.

Here is the StockQuote.scala file:

 

package uk.co.xenonique.stockquoteapp_scala

import java.math.RoundingMode
import java.math.BigDecimal

case class StockQuote( symbol: String, askPrice: BigDecimal, bidPrice: BigDecimal ) {

  // Auxiliary (convenience) constructor
  def this( symbol: String, askPriceStr: String, bidPriceStr: String ) =
    this ( symbol, new BigDecimal(askPriceStr).setScale(2, RoundingMode.DOWN ), new BigDecimal(bidPriceStr).setScale(2, RoundingMode.DOWN ) )

  // Auxiliary (copy) constructor
  def this( ref: StockQuote ) = this ( ref.symbol, ref.askPrice, ref.bidPrice )

  def meanPrice: BigDecimal = bidPrice.add( askPrice.subtract(bidPrice).divide( StockQuote.TWO ).setScale(2, RoundingMode.DOWN ) )

  override def toString(): String = {
    return "StockQuote [ symbol=" + symbol +", askPrice=" + askPrice +
      ", bidPrice=" + bidPrice + ", meanPrice="+meanPrice+"]";
  }
}

object StockQuote {
  def TWO = new BigDecimal("2.0")
}

 

The real hard part of the quote retriever service is get information from the Internet, by calling the web service. Luckily calling the Yahoo! Finance service is a simple REST GET request in comparison to other more complicated SOAP or REST web services.

Here is the QuoteRetriever.scala file:

 

package uk.co.xenonique.stockquoteapp_scala

import java.lang.NumberFormatException
import java.io._
import java.net._

trait QuoteRetriever {
  def getStockQuote( symbol: String ): StockQuote
}

object QuoteRetrieverYahooImpl {
   val TOKEN_SYMBOL="@SYMBOL@"
   val DEFAULT_URL_FRAGMENT="http://finance.yahoo.com/d/quotes.csv?s="+TOKEN_SYMBOL+"&f=sb2b3"

}

class QuoteRetrieverYahooImpl( var urlFragment: String = QuoteRetrieverYahooImpl.DEFAULT_URL_FRAGMENT ) 
extends QuoteRetriever {
  
  def getStockQuote( symbol: String ): StockQuote = {

    val queryUrl: String = urlFragment.replace(QuoteRetrieverYahooImpl .TOKEN_SYMBOL, symbol)

    try {
      val url: URL = new URL( queryUrl )
      val connection: HttpURLConnection  = url.openConnection().asInstanceOf[HttpURLConnection]
      connection.setRequestMethod("GET")
      connection.connect()

      val is: InputStream = connection.getInputStream()
      val reader: BufferedReader = new BufferedReader( new InputStreamReader(is) )

      try {
        val line = reader.readLine()
        if ( line != null ) {
          val tokens = line.split(",").toList
          if ( tokens.length < 3 ) {
            throw new DataRetrievalException(
              "Data service at URL=["+queryUrl+"] does not supply enough fields (3 != "+tokens.length+")")
          }
          var stockSymbol = tokens(0)
          if ( stockSymbol.length() > 0 &&
              stockSymbol.charAt(0) == '"' && stockSymbol.charAt(stockSymbol.length()-1) == '"') {
            stockSymbol = stockSymbol.substring(1, stockSymbol.length()-1 )
          }
          val askPrice = tokens(1)
          if ( "N/A".equals(askPrice)) {
            throw new UnknownStockSymbolException(
              "Unknown stock quote symbol ["+stockSymbol+"] price not available")
          }
          val bidPrice = tokens(2)
          if ( "N/A".equals(bidPrice)) {
            throw new UnknownStockSymbolException(
              "Unknown stock quote symbol ["+stockSymbol+"] price not available")
          }

          try {
            return new StockQuote(stockSymbol, askPrice, bidPrice)
          }
          catch {
            case e:NumberFormatException =>
              throw new DataRetrievalException(
                "Unable to read numerical data from quote service URL=["+queryUrl+"]", e)
          }
        }
        else {
          throw new DataRetrievalException(
            "Data unavailable from quote service URL=["+queryUrl+"]")
        }
      }
      finally  {
        if ( reader != null) {
          try {
            reader.close()
          }
          catch  {
            case e: IOException => null
          }
        }
        if ( is != null) {
          try {
            is.close()
          } 
          catch {
            case e: IOException => null
          }
        }
      }
    }
    catch {
      case e: MalformedURLException  =>
        throw new ConnectionException("unable to connect to the remote quote service URL=["+queryUrl+"]", e)

      case e: IOException =>
        throw new ConnectionException("I/O failure reading service URL=["+queryUrl+"]", e)
    }
  }
}

 

Effectively, we could have left the QuoteRetriever as a simple Java interface, because we gained nothing. If however we wanted to define a default implementation in a Scala trait we could do so, where it would be not possible with the current version of Java 6 / 7.

We make use of the companion object QuoteRetrieverYahooImpl to define Scala constants. Essentially this would be same as declaring a Java variable as public final static String.

Inside the QuoteRetrieverYahooImpl class, let us look at the implementation method getStockQuote(). We chucked away Java standard library StringTokenizer and replaced it with the nicer Scala equivalent. We split a String into Array[String] of fragments using the split method, and then convert the array into a list collection by calling toList. Since Scala 2.8, these methods toList(), toSet() and toArray() are ubiquitous across the Scala collections.

There is probably a more object functional way of building the temporary variables using functions and closures, however for intermediate Java developer this is easier to follow. The rest of the code is just boiler plate to tackle the exception handling of the Java I/O library. See the last part of this article for an improvement.

Last, but not least is the main application class in the StockQuoteApp.scala file.

 

package uk.co.xenonique.stockquoteapp_scala

import java.lang.System

class StockQuoteApp( retriever: QuoteRetriever, repo: QuoteRepository ) {
  

  def this() = this( new QuoteRetrieverYahooImpl(), new QuoteRepositoryKeyValueStore() )

  def doInteractive(): Unit = {

    println("=======================================================")
    println("   Welcome Peter Pilgrim's Stock Quote Application")
    println("=======================================================")

    var quit = false
    while ( !quit) {
      println("Type in a stock symbol or `:repo' to list the repository or `:quit' to quit")
      println("or `:clear' to clear the repository")
      var line = readLine("$ ")
      if ( line != "" ) {
        line = line.trim()
        if ( line.startsWith(":r") || line.startsWith(":p")) {
          val symbols  = repo.getSymbols()
          printf("%10s  %9s %9s %9s\n", "SYMBOL", "MEAN", "ASK", "BID")
          printf("%10s  %9s %9s %9s\n", "--------", "------", "-----", "-----")
          for ( symbol <- symbols ) {
            val quote = repo.get(symbol).get
            printf("%10s: %9.2f %9.2f %9.2f\n", quote.symbol, quote.meanPrice, quote.askPrice, quote.bidPrice )
          }
        }
        else if ( line.startsWith(":c")) {
          repo.clear()
          println("Repository cleared.")
        }
        else if ( line.startsWith(":q")) {
          quit = true
        }
        else {
          if ( line.length() > 0 ) {
            try {
              val quote = findAndUpdateStockQuote(line)
              printf("%s: %7.2f\n", quote.symbol, quote.meanPrice)
            }
            catch {
              case e:ConnectionException =>
                System.err.println("CONNECTION ERROR : "+e.getMessage())

              case e: UnknownStockSymbolException =>
                System.err.println("STOCK SYMBOL NOT FOUND : "+e.getMessage())
            }
          }
        }
      }
    }

    // Prints name and age to the console
    println("Goodbye.")
  }


  def  findAndUpdateStockQuote(symbol: String): StockQuote = {
    val quote = retriever.getStockQuote(symbol)
    repo.store(quote)
    quote
  }

}

object StockQuoteApp {
  val CONNECTION_PROPERTIES_FILENAME: String = "connection.properties"

  def main( args: Array[String] ): Unit = {
    println("Hello world. This is the Scala application!")
    new StockQuoteApp().doInteractive
  }
}

 

It should be fairly self-explanatory that this program is interactive one. It reads a symbol at the command line and invokes the quote retriever to find the stock quote for the symbol.

There is one other improvement to QuoteRetrieverYahooImpl we could make use of. Scala supports function objects; functions calling other functions and returning a different (or the same input) function. Scala methods on class or object types can have more than one parameter list in order to support currying of parameters. Scala language has the feature of partial functions that enable this facility.

Here is a class called TryResource, which closes an InputStream resource.

 

package uk.co.xenonique.stockquoteapp_scala

import java.io.InputStream
import java.io.FileInputStream
import java.io.InputStreamReader
import java.io.BufferedReader
import java.io.IOException
import java.lang.System

// Needs to be generic
class TryResource[T <: InputStream]( inputStream: T ) {

  private val is: T = inputStream

  def execute( body: ( InputStream => Unit ) ): Unit = {
    try {
      body(is)
    }
    finally {
      if ( is != null ) {
        try {
          is.close()
        }
        catch {
          case e:IOException => System.err.println(e)
        }
      }
    }
  }
}

object TryResource {

  def tryResource[T <: InputStream]( inputStream: T )( body: ( InputStream => Unit ) ) {
    val construct = new TryResource( inputStream)
    construct.execute( body )
  }

  def demo1() {
    val construct = new TryResource( new FileInputStream("pom.xml") )
    construct.execute( (is: InputStream ) => {
      val reader = new BufferedReader( new InputStreamReader(is))
      var count = 1
      var line: String = null
      do {
          line = reader.readLine()
          if ( line != null ) {
            printf("demo1 %5d %s\n", count, line)
            count = count + 1
          }
      }
      while ( line != null  )
    } )
    println("==== Done it ====")
  }

  def demo2() {
    tryResource ( new FileInputStream("pom.xml") ) ( ( is: InputStream ) =>  {
      val reader = new BufferedReader( new InputStreamReader(is))
      var count = 1
      var line: String = null
      do {
          line = reader.readLine()
          if ( line != null ) {
            printf("demo2 %5d %s\n", count, line)
            count = count + 1
          }
      }
      while ( line != null  )
    } )
    println("==== Done it ====")
  }

  def main( args: Array[String]): Unit = {
    demo2()
  }
}

 

The method tryResource takes two parameter lists. The first parameter list is a argument list of one, the input stream resource. The second parameter list is also a argument of one, except it is a so-called called-by-name type, by which the function call is delayed, evaluated after inside the method. In other words, we call tryResource()() by passing a block of code that operates on the InputStream and the library function will automatically close the resource for us, regardless of whether block of code completes with normal or abnormal termination. Java 7 now has a similar acquire-release mechanism as a language feature, whereas in Scala, we implemented this a library feature. Scala is scalable language after all.

It would be straight forward now to use tryResource in the QuoteRetrieverYahooImpl directly. This is an exercise for you, the reader. Hint: You should have a good read about Scala’s structured types!

Good Luck!

No Comments

No comments yet.

RSS feed for comments on this post.

Sorry, the comment form is closed at this time.

Contents of this blog entry are under copyright © 2017 by Peter Pilgrim and associates. For enquiries after republishing, please contact us for permission. All requests for syndicated content will be ignored /dev/null, consider yourself warned!

I help to design, create and build JVM components and services that are behind popular e-commerce websites.

My Blurb

Please get in touch , directly, to establish hire availability, contract & consulting opportunities.

Speaking at Your Conference

Contact by invitation

What Peter Does

Contact