In software development there are no silver bullets, but I am always looking out for the next bronze one.

Tuesday, June 9, 2009

A Refactoring in Scala

    One pet project I have going is a little Logo interpreter done using Scala parser combinators with a Swing GUI where code can be edited and executed with results being rendered on a canvas. Being a good little Agile developer, I wrote automated tests for it

    I've had experience on a previous Swing project using the Netbeans Jemmy library for driving the GUI tests in Java. I was curious of what Scala can wrap around such testing libraries like Jemmy. Initially, the test code was more Java style than Scala since I've only used Jemmy with Java previously. I found that interesting refactorings can be applied as the code takes on the more functional style of Scala.

    Experienced Scala developers are probably familiar with these concepts, so the intended audience is developers who are relatively new to Scala and insights into why Scala code is the way it is.

    As I was developing the tests, I needed to programatically find buttons on a toolbar that do such things as "New", "Save", "Load", and "Execute". These buttons don't have text, so I am using the tooltip to distinguish them. A Java-style Scala function to do this would look like this:



def findJButton(container:Container, toolTipText:String) = {
val button = JButtonOperator.findJButton(container,
new ComponentChooser() {
override def checkComponent(component:Component) =
if (component.getClass.isAssignableFrom(classOf[JButton])) {
component.asInstanceOf[JButton].getToolTipText == toolTipText
} else {
false
}
override def getDescription() = "JButton Finder using toolTipText"
}
)
if (button == null) {
fail("cannot find JButton with tooltip = '" + toolTipText + "'")
}
new JButtonOperator(button)
}

Client code:



val newButtonOper = findJButton(frmOper.getContentPane, "Start new Logo program" )
val runButtonOper = findJButton(frmOper.getContentPane, "Run Logo program")

    The "*Operator" classes in Jemmy wraps corresponding Swing components so that can be programatically manipulated in the same way an actual user would. The frmOper object is a Jemmy JFrameOperator that wraps the main frame of the application.

    Now in Java, one can refactor the inner class into a regular class and leverage generics so that it can match any JComponent subtype since that is where getToolTipText is first implemented. In Scala, we will do something like that and make it so that a higher order function can be passed in the do the comparison provided the component is the correct type:



class LoanChooser[A <: java.awt.Component](clazz: Class[A], filter: A => Boolean)
extends ComponentChooser
{
override def checkComponent(component:Component) =
if (component.getClass.isAssignableFrom(clazz)) {
filter(component.asInstanceOf[A])
} else {
false
}
override def getDescription() = "Generic Finder using Loan Pattern"
}

def findJButton(container:Container, filter: JButton => Boolean) = {
val button = JButtonOperator.findJButton(container,
new LoanChooser(classOf[JButton], filter))
if (button == null) {
fail("cannot find JButton with matching criteria")
}
new JButtonOperator(button)
}

Client code:



val newButtonOper = findJButton(frmOper.getContentPane, btn => btn.getToolTipText == "Start new Logo program" )
val runButtonOper = findJButton(frmOper.getContentPane, btn => btn.getToolTipText == "Run Logo program")

    The code above uses the "Loan Pattern" in Scala (http://scala.sygneca.com/patterns/loan). The client code is loaned the the JButton instance so it can be tested to see if it matches the criteria.

    Knowing the Scala implements functions as objects, I know that two identical anonymous function classes are going be created in the form "JButton => Boolean", so I create a common function that can be curried (or partially applied) with the toolTipText as the first parameter:


def toolTipTextEquals(toolTipText:String)(component:JComponent) = component.getToolTipText == toolTipText


val newButtonOper = findJButton(frmOper.getContentPane, toolTipTextEquals("Start new Logo program"))
val runButtonOper = findJButton(frmOper.getContentPane, toolTipTextEquals("Run Logo program"))

    Now is getting pretty obvious what the code is trying to accomplish. There are some repeated code patterns that can be refactored out. At this point, it probably more at matter of taste in that some will want to refactor more and others might see it an an exercise in of "Scala Golf" (http://codegolf.com/). First we can get rid of the repeated "frmOper.getContentPane" by the traditional means of temporary local variable:


val contentPane = frmOper.getContentPane
val newButtonOper = findJButton(contentPane, toolTipTextEquals("Start new Logo program"))
val runButtonOper = findJButton(contentPane, toolTipTextEquals("Run Logo program"))

Or use an implicit conversion:



implicit def jFrameOperator2Container(frmOper:JFrameOperator) = frmOper.getContentPane

val newButtonOper = findJButton(frmOper, toolTipTextEquals("Start new Logo program"))
val runButtonOper = findJButton(frmOper, toolTipTextEquals("Run Logo program"))

    The implicit conversion is invoked since the findJButton function it expecting a Container as the first param, but is provided a JFrameOperator. Since there is an implicit function in scope in the form "JFrameOperator => Container", it is used to perform the conversion implicitly.

    My Logo application currently has four buttons on a toolbar that will need to be tested and I'll need JButtonOperators for all of them:


val newButtonOper = findJButton(frmOper, toolTipTextEquals("Start new Logo program"))
val openButtonOper = findJButton(frmOper, toolTipTextEquals("Open existing .logo file"))
val saveButtonOper = findJButton(frmOper, toolTipTextEquals("Save current logo code to file"))
val runButtonOper = findJButton(frmOper, toolTipTextEquals("Run Logo program"))

Once can utilize Scala's ability to deconstruct on patterns during declarations and do this:


val List(newButtonOper, openButtonOper, saveButtonOper, runButtonOper) =
List("Start new Logo program",
"Open existing .logo file",
"Save current logo code to file",
"Run Logo program").map(toolTipText => findJButton(frmOper, toolTipTextEquals(toolTipText)))

    It is a matter of taste whether four buttons warrant such refactoring, but if the code started to look unwieldy and I didn't want to keep them in some kind of collection, I would definitely consider this approach.


    I felt this recent experience refactoring Scala code was interesting enough to blog about it and hope that readers gain some insight from this and want to use Scala more. I feel that Scala or at least a language with many of its features (like some kind of "Scala--") will play a big role on the JVM platform going forward.


Sidebar: Whither SQUIB

    I haven't done much with the SQUIB project recently. I am seeing too many drawbacks to its approach:


  • Property resolution done at runtime - If the property is misspelled or uses an invalid type the best thing to do is fail fast and throw an exception. It would be nice if such issues could be detected at compile time at least.

  • Lack of IDE support - It would be nice if a framework uses actual methods that an IDE could then provide auto-completion and the like.

  • It's just a little heavyweight: I've found that very useful things can be done in Scala with minimal code.