2 minutes Chaplin tutorial

Zbyněk Šlajchrt



Table of Contents

Introduction
Alarm's logic
Testing the Alarm's logic
Fusing the alarm and the lamp
Running the alarm

Introduction

Programming in Chaplin is really easy and the resulting code is very concise and readable as you will see in this tutorial. Let's say I've got a small reading-lamp which can be connected to a computer via the standard USB cable.

Figure 1. USB lamp

USB lamp

A simple Java API is delivered with the lamp which allows controlling the lamp programmatically.

Figure 2. Lamp API

Lamp API

The API is extremely simple as it only allows switching the lamp on and off. I started thinking about other uses of this device. Among other toys the lamp could be used as an alarm or a morse-code transmiter, for instance. We can think of these two uses as two other roles of the lamp; besides the role of the tool which makes reading easier. The goal of this introductory paragraph is to point out the difference between the device and its roles. So let's start developing the simpler use case - the alarm.

Alarm's logic

The basic moment in the role-based development is encapsulating logic of every role into a standalone class.

Example 1. Alarm role

public abstract class Alarm {

    /**
     * The pause in ms between two signals.
     */
    @FromContext
    long pause;

    /**
     * The number of signals.
     */
    @FromContext
    int repeatCount;

    /**
     * The switch-on message
     */
    @FromContext
    abstract void switchOn();

    /**
     * The switch-off message
     */
    @FromContext
    abstract void switchOff();

    /**
     * The alarm's logic.
     */
    public void runAlarm() throws Exception {
        for (int i = 0; i < repeatCount; i++) {
            switchOn();
            sleep();
            switchOff();
        }
    }

    protected void sleep() throws InterruptedException {
        Thread.sleep(pause);
    }

}


The @FromContext annotations marks all members which must be provided by the context in which the Alarm's instance will be running. It means that this role expects that the the pause and repeatCount configuration parameters as well as the logic of the switchOn and switchOff methods will be provided at the runtime.

Testing the Alarm's logic

A nice Chaplin's feature is that it lets you develop components which are agnostic of other components. It allows us to write unit tests easily.

Example 2. Test subclass of Alarm

public class TestAlarm extends Alarm {

    int onCounter;
    int offCounter;

    public TestAlarm(int repCnt) {
        repeatCount = repCnt;
    }

    void switchOn() {
        onCounter++;
    }

    void switchOff() {
        offCounter++;
    }
}


This test subclass of the Alarm role just maintains the numbers of how many times the switchOn and switchOff methods were called. The unit test class tests if the behavior of the alarm corresponds to the configuration.

Example 3. Unit test class for the Alarm

public class AlarmTest extends TestCase {

    public void testBeepCount() throws Exception {
        TestAlarm alarm = new TestAlarm(10);
        alarm.runAlarm();
        assertEquals(10, alarm.offCounter);
        assertEquals(10, alarm.onCounter);
    }

}


Fusing the alarm and the lamp

Once we have tested the Alarm role we can fuse it together with the real lamp using a binder composite named AlarmContext.

Figure 3. Idea of fusing the components

Idea of fusing the components


Example 4. Fusing both the alarm role and the lamp

@Binder
public class AlarmContext {
    // The Lamp component
    final Lamp lamp = LampFactory.getLamp();

    // The Alarm role component
    final Alarm alarm = $();

    // configuration
    long pause;
    int repeatCount;

    // initialization
    public AlarmContext(long pause, int repeatCount) {
        this.pause = pause;
        this.repeatCount = repeatCount;
    }

    // execution
    public void execute() throws Exception {
        alarm.runAlarm();
    }
}


The binder composite is a class annotated with the @Binder annotation and which serves as a container for components. The components are held by final fields. The AlarmContext class is a binder composite which consists of two components: the lamp and the alarm role. The Alarm instance cannot be instantiated directly as it is an abstract class. So the magic dollar method $ is used here instead. This method is statically imported into the class:

import static org.iqual.chaplin.DynaCastUtils.$;

Furthermore, the composite contains the configuration for the Alarm role which is passed via the constructor's arguments.

Running the alarm

Now, we are ready to run the alarm.

Example 5. Running the AlarmContext

public class AlarmProgram {

    public static void main(String[] args) throws Exception {
        long pause = Long.parseLong(args[0])
        int repeatCount = Integer.parseInt(args[1]);

        AlarmContext alarmCtx = new AlarmContext(pause, repeatCount);
        alarmCtx.execute();
    }
}

The Java VM must be properly configured before you launch a Chaplin based program. One of the modes in which the Chaplin may work is the JVM Agent mode. This mode is activated by configuring the Java VM parameters as follows:

-javaagent:chaplin.jar=org.iqual.chaplin.tutor

This example assumes the chaplin.jar is located in the working directory and the package containing the example classes is chaplin.examples.