Table of Contents
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.
A simple Java API is delivered with the lamp which allows controlling the lamp programmatically.
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.
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.
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);
}
}
Once we have tested the Alarm role we can fuse it
together with the real lamp using a binder
composite named AlarmContext.
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.
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.