So… you want to use the TeeTime framework to implement your own P&F architectures, eh? If you are reading this, you may expect some kind of step-by-step tutorial or such… and yes, this is the right place to look for it!

As a simple introduction, we will implement an architecture which counts all files in a given directory. In order to work with TeeTime you need to make sure it is added to your project. Further assistance on doing so can be found in the download section.

Separate that Logic

The Pipe&Filter architecture is well known for its property to divide work into disjoint steps. As - for example - a car manufacturer which divides the assembly of a new car into different steps to increase efficiency and throughput, we will also follow this guideline in our work with TeeTime. So the first step is to analyze our program and divide it into different steps.

The steps of our “File Counter” can be summarised to:

At first, we look recursively for all nested directories, read each directory and pass all containing files one by one to the next processing step.
Next, we count all files by incrementing a counter upon each sent file.
The last stage will save or print the resulting number.

Our mission is to implement those four stages as separate filter and connect them.

Implement them Stages

This section will explain the implementation of the stages we need for our “File Counter”. If you want to dig deeper into the implementation mechanisms of the TeeTime framework, we recommend to take a look at this tutorial.

Our first stage will be the DirReader. It visits all directories and sends all containing files to the next stage.
This is a perfect task to start working with TeeTime: implement this logic into a single stage. You may extend from teetime.framework.AbstractProducerStage and use its field outputPort to send elements to the next stage. Make sure you use terminate() after the stage is done with all its work, so that the framework knows the job is done.
A suitable solution can be found here.

The next task is to implement the FileCounter. It receives elements of the type File and simply increments a counter upon each incoming element. teetime.framework.AbstractConsumerStage suits best this requirements. The counter only contains the correct result if the architecture is about to terminate. So make sure you override the method onTerminating() to send the final result.
One more hint: you may use createInputPort() or createOutputPort() to create additional ports.
Of course, there is also a sample solution for this part.

After all the hard work, we will finish everything by implementing the ResultPrinter. It receives elements of the type Integer and prints them to the console. As this is a simple consumer, teetime.framework.AbstractConsumerStage should also be used here.
This few lines of code may look like this.

Connect them Stages

What splendid stages we have, which await to be connected. Analogous to integrated circuits which are connected through their ports, we only need to connect the ports of the stages with pipes. For this purpose we can use the class teetime.framework.Configuration. We create instances of our three stages and connect them correctly. Please remember that TeeTime only supports uni-directed communication.
A sample solution can look like this.

TeeTime Configuration Builder

With the TeeTime configuration builder you can simply connect your stages in a short, type-safe, and fluent way. Typically, you create your configurations by instantiating all stages and connecting them via lines of connectPorts(...) calls.

public class MyConfiguration extends Configuration {

	public MyConfiguration(final List<String> strings) {
		final InitialElementProducer<String> producer = new InitialElementProducer<>(strings);
		final ToLowerCase toLowerCase = new ToLowerCase();
		final WordcharacterFilter wordcharacterFilter = new WordcharacterFilter();
		final WordCounter wordCounter = new WordCounter();
		final Printer<CountingMap<String>> printer = new Printer<>();

		this.connectPorts(producer.getOutputPort(), toLowerCase.getInputPort());
		this.connectPorts(toLowerCase.getOutputPort(), wordcharacterFilter.getInputPort());
		this.connectPorts(wordcharacterFilter.getOutputPort(), wordCounter.getInputPort());
		this.connectPorts(wordCounter.getOutputPort(), printer.getInputPort());
	}

}

With the TeeTime configuration builder you can simply connect the stages by calling from(...), to(...), and end(...).

public MyConfiguration(final List<String> strings) {
	final InitialElementProducer<String> producer = new InitialElementProducer<>(strings);
	final ToLowerCase toLowerCase = new ToLowerCase();
	final WordcharacterFilter wordcharacterFilter = new WordcharacterFilter();
	final WordCounter wordCounter = new WordCounter();
	final Printer<CountingMap<String>> printer = new Printer<>();

	this.from(producer).to(toLowerCase).to(wordcharacterFilter).to(wordCounter).end(printer);
}

In addition to shorter and more expressive code, the configuration builder forces you to use every stage port exactly once. Furthermore, you do not have to declare all your stages as local variables if you do not need access to them.

public MyConfiguration(final List<String> strings) {
	this.from(new InitialElementProducer<>(strings))
		.to(new ToLowerCase())
		.to(new WordcharacterFilter())
		.to(new WordCounter())
		.end(new Printer<>());
}

If you do not need an own class for your configuration, you can create configurations straightforward by using the ConfigurationBuilder.from(...) method as shown in the next example.

final Configuration myConfiguration = ConfigurationBuilder
				.from(new InitialElementProducer<>(strings))
				.to(new ToLowerCase())
				.to(new WordcharacterFilter())
				.to(new WordCounter())
				.end(new Printer<>());

You may want to use stages that do implement the ITransformation interface, for example, because they have multiple output ports like the InstanceOfFilter. For this case you an alternative form of the to(..) method, in which you can specify the stage´s input and output port you want use. This is done via a lambda function as shown below.

final Configuration myConfiguration = ConfigurationBuilder
				.from(new MyProducerStage())
				.to(new MyFilterStage(), s -> s.getMyInputPort(), s -> s.getMyOutputPort())
				.end(new MyConsumerStage());

Execute that Configuration

After all the hard work you may be way to excited to go to bed yet. To reap the fruits of your success you still need to execute the architecture.
As the caption of this section already spoils, we only need to instantiate the class teetime.framework.Execution and pass the desired Configuration to its constructor. Eventually, call executeBlocking() (or executeNonBlocking()) and take a glance at your finished work. Following example is extracted from the sample solution PrintResultConfig.java of the section above:

public static void main(final String... args) {
	PrintResultConfig config = new PrintResultConfig(new File("."));

	Execution<PrintResultConfig> execution = new Execution<PrintResultConfig>(config);
	execution.executeBlocking();
	// or: future = execution.executeNonBlocking();
}

If everything is implemented correctly you should see the number of files in your console. If it shows up: Congrats!
Feel free to experiment with this configuration. For instance, you could save a file with the result in it instead of simply printing it. Introducing a stage which filters the files for a given name pattern would also be a nice exercise. There are endless of great things you can make with TeeTime… discover the explorer within you.

Note: As of version 2.1, you are also able to run configurations from command line. To execute a configuration run the command java -cp ... teetime.framework.Execution your.own.Configuration where the classpath is set to the directory of the configuration and also the directory where TeeTime is located. In order to enable this feature you need also to add all dependencies to the classpath or you can simply make use of the provided teetime-…-jar-with-dependencies.jar file. This feature only allows to execute configurations without any arguments in their constructor. You can also run multiple configurations by adding them to the end of the command.

Back to top

Reflow Maven skin by Andrius Velykis.