RemoteAdapterGenerator

This is a simple project that I did in JetBrains MPS. Since I very much like working with MPS, I’ve written this blog post to document it. I hope that it may find it useful if you’re starting with MPS.

I have tried to include as many illustrations as possible.

Motivation

While developing 2D algorithms, I found the debugging to be a real pain. It’s easier to see a curve on a display than to make sense from a (lengthy) set of coordinates.

Requirements

Functional requirements

A set of methods, constructed from the primitive commands that the display offers, eg. “drawPath”, “clearDisplay”.

Quality requirements

To be able to draw curves while in the debugger. The display needs to be updated even if the application under test is stopped.

Communication only needs to be one way: From the application under test to the display.

The number of calls to the draw method will be small. Therefore performance is not an issue.

Solution

Create a stand-alone process with a JSVG canvas. This will guarantee that the display will be updated even if the application under test is stopped.

Use HTTP to connect the application under test to the display. This is simple to write and simple to test (e.g. via a web browser). The connection can be established “on demand”.

Solution design

Client and server and their components

The client issues remote commands, sends them via HTTP to the server, where they are translated into draw commands. The remote commands and the corresponding command handlers would be defined using a DSL.

If you open the following URL in your browser

http://localhost:8000/draw?message%3DHello%20World%21%26path%3DM%20100%2C100%20L%20100%2C200%20200%2C200%20200%2C%20100%20z%26style%3Dfill%3Anone%3Bstroke%3A%23000000

the server will display this image:

The server displaying an SVG path

To generate the query, I’ve used a simple online tool to URL-encode the parameters.

The development process in detail

Step 1: Hand-writing a prototype solution

Hand-writing a prototype solution

A prototype client and server were written by hand, using an IDE.

Step 2: Creating the concepts in MPS

Creating the concepts in MPS

Step 3: Transferring the JAR files to MPS

Transferring the JAR files to MPS

The code generated by MPS would refer to classes and methods from the hand-written solution. Therefore JAR files, containing this part of the prototype application need to be imported into MPS.

Step 4: Creating templates in MPS

Creating templates in MPS

Now that the classes and methods are available in MPS, the templates are created. The hand-written code is used as a basis for this step.

Step 5: Creating the model

Creating the model

The first lines of the model: Defining (global) constants and a first command
Defining a command that references a global constant

In the definition of this command a global constant, styleBlue, is referenced.

Defining a command with a local constant.

In the definition of this command a local constant, styleGreen, is defined and referenced.

Step 6: Generating the source code

Generating the source code from the model

Client Business Logic (generated)

The following code is generated for the three client commands shown above. The code uses an abstraction layer QueryGenerator to specify the URI, add parameters and execute the query.

Server Business Logic (generated)

The first block defines the global constants and registers the Request Handler classes.

Server code: Defining global constants and adding the RequestHandler classes
Server code: A RequestHandler that references a global constant.
Server code: RequestHandlers that defines a local constant.

Implementation details

Structure aspect

Simplified class hierarchy
Class diagram for StringValue, etc.

I have introduced the Interface StringValueTraits. The scope of StringParameters makes them unique, if they are unique for the ClientMethod. StringConstants, on the other hand, can clash between different instances of CommandListVersion. Therefore the version of the CommandListVersion is appended to the declarationName.

References and reference constraints

I want to define (client) methods with (string) parameters. In these method definitions I want to call draw methods and provide these method calls with values. A value may be

  • a parameter of the method that I am declaring
  • a constant, that I defined within the method declaration, or
  • a global constant.

Here I found the calculator example from the MPS Advanced Online Course rather helpful.

The following diagram shows the possible reference targets from a StringValueReference within a ServerCommand.

In the first attempt, I implemented the scope handler in StringValueReference.

This handler looks for its ancestor of type ClientCommand and collects all StringConstant instances from commandConstants and all StringParameter instances from commandParameters. It then looks for its ancestor of type CommandListVersion and collects all StringConstant instances.

Collecting all references in the ScopeProvider of ServerCommand

In the next attempt, I went for inherited scope and implemented getScope() in ClientCommand and in CommandListVersion.

ScopeProvider for ClientCommand
ScopeProvider for CommandListVersion

The chain of ScopeProviders looks like this:

Using InheritedScope and ScopeProviders for ClientCommand and CommandListVersion

Editor aspect

As the language is very straightforward, so I will not show the editors here.

Checking Rules

I use Checking Rules to validate the model on the fly:

  • Does the name follow the rules for a valid identifier?
  • Are the names unique?
  • Are version numbers unique?
  • Are all parameters and constants referenced?
Are all parameters of the ClientCommand clientCommand referenced?

Generator aspect

The generator consists of nested loops. A SWITCH macro is used to generate the code for the difference ServerCommands.

Generator template for the server side code (excerpt).
The template switch for the different commands.

Within the property / reference macros behavior methods are used, to keep the templates free from application logic.

Using a behavior method within a property macro.

The method commandHandlerClassname() will only generate a version identifier if there are more than one CommandListVersion.

Lessons Learned

Isolate the generator from the implementation

  • The client and server application are based on the HTTP protocol.
  • To get the Proof of Concept up and running as fast as possible, I imported the full JDK into MPS, since the generated code relied on java.net classes. But: Importing the JDK “pollutes” the model.
  • For version 2 I created an abstraction layer that consists mostly of interfaces and that contains no dependencies on the JDK. By using the factory pattern, dependencies on the handwritten code for client and server were eliminated.

Downloads

The projects are available on GitHub, if you would like to take a deep dive.

The MPS project RemoteAdapterGenerator

The Maven project remote canvas

The Maven project contains three parts:

  1. svghttpd: A swing application with an SVG canvas and an attached HTTP server
  2. svgclient: A client library to issue drawing commands to the swing application via HTTP
  3. svgdemo: A demo application that shows how to use the client library

To build and run the swing application:

  • mvn clean compile assembly:single
  • java -jar target\svghttpd-1.0-jar-with-dependencies.jar

This project also creates the two interface JAR files that are used in RemoteAdapterGenerator:

  • svgclient-1.0-interface.jar
  • svghttpd-1.0-interface.jar

References

Here are resources that I found helpful:

Gabriele Tomassetti: How to Add JARs to a Jetbrains MPS Project – Strumenta (tomassetti.me)