WebSocket Chat example with the SimpleFramework API

26 January 2017

As mentioned in the previous post, the SimpleFramework API and server is a minimal alternative in the world of Java application servers like Glassfish, Tomcat. Hosted on GitHub under an Apache v2.0 license, they offer an embeddable HTTP, Servlet and WebSocket capable stack and API. They claim their performance is better than other Java based web servers.

The Dependency Injection Framework Trap

Whether they live up to their name, and is truly easy and simple to use is somewhat debatable, though. They favour Spring for dependency injection, which means part of the code is specified in XML, and is “magically” coupled during runtime. There might be benefits to that, but the downside is that it becomes difficult to read and debug. In fact, their demo application will not start for whatever reason, and all that is offered in terms of output is an InvalidArgumentException without further details deep down from the Spring code.

Don’t get me wrong, the Google Guice framework is not much better. It might specify its module coupling in Java code instead of XML, however, when it comes to following the code and debugging, it is just as obscure. Implementations tend to be hidden behind layers of interfaces. As for the promise of easily re-wireable code, they both fall short. When it comes down to it, a refactoring will typically involve changes in both class content and class relationships, so a dependency injection framework is more likely to get in the way than help.

Finally, when it comes to testability, dependency injection is of course crucial. Being able to inject mocks or other testing objects keeps unit tests small. However, a wiring framework is not needed to describe the class relationships. In fact, my personal experience is that Guice based code is more cumbersome to use in test related code. It takes considerable effort to track down which classes to include in a unit test, and which to leave out, and then to create a test module of it all. At least Spring leaves the class constructors intact and simple.

Making it work

So, with that rant out of the way, what does it take to make the Simple Framework WebSocket Chat Demo work? My solution was to simply shortcut the 114 line XML Spring configuration, and write the same class relations in Java. The following 20 lines achieves the same as the XML.

Furthermore, it’s worth noting that the SimpleFramework is structured in multiple Maven projects, with dependencies between each other, as seen below the code snippet. It highlights another potential problem with the project. A lot of the server code required to make the Chat demo run is in another demo package. It suggest quite a lot of custom code has to be written to use the server API.

  public static void main(String[] args) throws Exception {
    new LogConfigurer(new File("etc/log4j.xml")).configure();

    Map<String, String> content = new HashMap<>();
    content.put(".*", "text/html");
    ContentTypeResolver typeResolver = new ContentTypeResolver(content);
    FileManager manager = new FileManager(new File("data/chat"));
    FileResolver fileResolver = new FileResolver(manager, "index.html", "index.html");
    Resource fileSystemResource = new FileSystemResource(fileResolver, typeResolver);
    Resource chatRoomLogin = new ChatRoomLogin(fileSystemResource);
    Service chatRoom = new ChatRoom();
    StringResource failureResource = new StringResource("An error occured serving a resource!",
        "text/plain; charset=UTF-8", "UTF-8", Status.INTERNAL_SERVER_ERROR);
    Resource notFoundResource = new StringResource("Resource could not be found!!", "text/plain; charset=UTF-8",
        "UTF-8", Status.NOT_FOUND);
    Map<String, Resource> resources = new HashMap<>();
    resources.put("/login.html", chatRoomLogin);
    ResourceEngine resourceEngine = new RegularExpressionEngine(resources, notFoundResource);
    ResourceContainer resourceContainer = new ResourceContainer(resourceEngine, failureResource);
    TraceAnalyzer analyzer = new LogAnalyzer();
    Container webContainer = new WebContainer(resourceContainer, "Chat/1.0");
    Router webSocketRouter = new DirectRouter(chatRoom);
    Container webSocketContainer = new RouterContainer(webContainer, webSocketRouter, 2);
    WebServer server = new WebServer(webSocketContainer, analyzer, PORT);

    server.start();
  }

   

The GitHub Maven packages and dependencies at the time of writing:

simple (core code)
simple-test (unit tests) -> “simple”

simple-demo (common demo code) -> “simple”
simple-demo-chat -> “simple”, “simple-demo”
simple-demo-graph -> “simple”, “simple-demo”
simple-demo-rest -> “simple”, “simple-demo”, “simple-demo-chat”

It is not entirely clear whether the last demo example, “simple-demo-rest”, depends on the “simple-chat” project as the XML suggests, or whether the duplicate ChatRoom class was intended to be used. It might be that the XML was copied, but not updated - highlighting the previous point about hard to maintain and read dependency injection code.

Focusing only on the “simple-demo-chat” example then, this would be what it could look like in the Eclipse Package Explorer, with each directory created as a separate Java project.

Eclipse setup

Finally, here’s the full main-file hack to make the chat example work. Notice the required helper classes from demo packages.

SfChatDemoMain.txt
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.http;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

import org.simpleframework.demo.http.WebContainer;
import org.simpleframework.demo.http.WebServer;
import org.simpleframework.demo.http.resource.ContentTypeResolver;
import org.simpleframework.demo.http.resource.FileSystemResource;
import org.simpleframework.demo.http.resource.RegularExpressionEngine;
import org.simpleframework.demo.http.resource.Resource;
import org.simpleframework.demo.http.resource.ResourceContainer;
import org.simpleframework.demo.http.resource.ResourceEngine;
import org.simpleframework.demo.http.resource.StringResource;
import org.simpleframework.demo.io.FileManager;
import org.simpleframework.demo.io.FileResolver;
import org.simpleframework.demo.log4j.LogConfigurer;
import org.simpleframework.demo.trace.LogAnalyzer;
import org.simpleframework.http.Status;
import org.simpleframework.http.core.Container;
import org.simpleframework.http.socket.service.DirectRouter;
import org.simpleframework.http.socket.service.Router;
import org.simpleframework.http.socket.service.RouterContainer;
import org.simpleframework.http.socket.service.Service;
import org.simpleframework.transport.trace.TraceAnalyzer;

public class SfChatDemoMain {

  private static final int PORT = 6060;

  public static void main(String[] args) throws Exception {
    new LogConfigurer(new File("etc/log4j.xml")).configure();

    Map<String, String> content = new HashMap<>();
    content.put(".*", "text/html");
    ContentTypeResolver typeResolver = new ContentTypeResolver(content);
    FileManager manager = new FileManager(new File("data/chat"));
    FileResolver fileResolver = new FileResolver(manager, "index.html", "index.html");
    Resource fileSystemResource = new FileSystemResource(fileResolver, typeResolver);
    Resource chatRoomLogin = new ChatRoomLogin(fileSystemResource);
    Service chatRoom = new ChatRoom();
    StringResource failureResource = new StringResource("An error occured serving a resource!",
        "text/plain; charset=UTF-8", "UTF-8", Status.INTERNAL_SERVER_ERROR);
    Resource notFoundResource = new StringResource("Resource could not be found!!", "text/plain; charset=UTF-8",
        "UTF-8", Status.NOT_FOUND);
    Map<String, Resource> resources = new HashMap<>();
    resources.put("/login.html", chatRoomLogin);
    ResourceEngine resourceEngine = new RegularExpressionEngine(resources, notFoundResource);
    ResourceContainer resourceContainer = new ResourceContainer(resourceEngine, failureResource);
    TraceAnalyzer analyzer = new LogAnalyzer();
    Container webContainer = new WebContainer(resourceContainer, "Chat/1.0");
    Router webSocketRouter = new DirectRouter(chatRoom);
    Container webSocketContainer = new RouterContainer(webContainer, webSocketRouter, 2);
    WebServer server = new WebServer(webSocketContainer, analyzer, PORT);

    server.start();
  }
}

A failed attempt at a WebSocket server

25 January 2017

Following the post about the small HTTP server using the the com.sun.net.httpserver API, I thought I’d try to make it work with WebSockets. I’ll save the suspense; it wont work. And I’m not the first to have failed at it, but it’s always more fun to search for the solution afterwards..

The problem is that the httpserver package is not designed for the persistent bi-directional connection used by the WebSocket protocol. The only part which will work is the handshake, which transmits a HTTP like request and response. However, as soon as that response is sent, the connection is closed, and thus the WebSocket object in the browser client will close.

There is a WebSocket API in Java EE, however it will require an application server like Glassfish or Tomcat to run. Another option is the SimpleFramework server, which also includes a WebSocket implementation. More about that in a later post.

The only part of the code worth mentioning here is the handshake response from the server. It sets the correct headers and encodes the server accept key according to the specification. Connecting to this server will therefore result in a valid websocket object on the client, as long as handle() method on the server does not return.

  public void handle(HttpExchange ex) throws IOException {
    Headers reqHeaders = ex.getRequestHeaders();
    reqHeaders.entrySet().forEach(System.out::println);

    String serverResponseKey = getServerResponseKey(reqHeaders.get("Sec-websocket-key").get(0));

    Headers resHeaders = ex.getResponseHeaders();
    resHeaders.add("Sec-WebSocket-Accept", serverResponseKey);
    resHeaders.add("Upgrade", "websocket");
    resHeaders.add("Connection", "Upgrade");

    ex.sendResponseHeaders(101, -1);

    // When time is up, the connection will be closed, and the browser object
    // will get a call to onClose() on the websocket object.
    sleep(5000);
  }

  private String getServerResponseKey(String clientKey) {
    try {
      MessageDigest digest = MessageDigest.getInstance("SHA-1");
      digest.update(clientKey.getBytes());
      digest.update(WEB_SOCKET_GUID.getBytes());
      byte[] sha1 = digest.digest();

      return Base64.getEncoder().encodeToString(sha1);

    } catch (NoSuchAlgorithmException e) {
      e.printStackTrace();
    }

    return null;
  }

Hello World with JavaFx

22 January 2017

JavaFx is a comprehensive platform and set of APIs to develop desktop, web and even mobile applications. Although intended to replace the Swing GUI API, it can get significantly more complex, with FXML for XML based object specification; CSS styling; and special IDE tool chains.

Since JDK 8, it is supposedly included in the main install, however, on Debian I had to install it separately:

apt-get install openjfx openjfx-source

The Oracle documentation and tutorial is detailed and well written, so I will not go into further detail here. The following is a simple Hello World app.

JavaFxHelloWorld.java
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.ui;

import javafx.application.Application;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

@SuppressWarnings("restriction")
public class JavaFxHelloWorld extends Application {

  public static void main(String[] args) {
    launch(args);
  }

  @Override
  public void start(Stage primaryStage) {
    primaryStage.setTitle("My App");

    Label label = new Label();

    Button button = new Button();
    button.setText("Say 'Hello World'");
    button.setOnAction((e) -> {
      label.setText("Hi!");
    });

    VBox root = new VBox();
    ObservableList<Node> children = root.getChildren();
    children.add(button);
    children.add(label);

    primaryStage.setScene(new Scene(root));
    primaryStage.show();
  }
}

Terminal and ncurses Java libraries

22 January 2017

There’s a few Java libraries for creating terminal and ncurses like applications. They abstract away the ANSI escape codes and low level control like tput. I’ve briefly looked at three of them: Lanterna, Charva and JCurses. They all come with basic terminal control, and some widgets and window managing to create terminal based GUI applications.

JCurses seems to be the oldest, and possibly unmaintained. It requires ncurses to be installed, and also a native library. It is available through the Maven Central repository, however, make sure to set up the path for access to the libjcurses.so (in the tar-ball from SourceForge, or within the .jar from Maven). Documentation is sparse, but it seems it’s possible to interface with the terminal with little code.

Charva is also ncurses based and requires a native lib. One of it’s selling points is that it can act as a drop-in replacement for java.awt UIs. It seems unlikely that that will work well for all but the simplest UIs, however, at least it offers an easy transition path.

Finally, Lanterna seems to be the best maintained, and clearly documented project of the three. It is unique in that is handles all terminal details itself, and does not require external libraries. It is compatible with Linux, Windows, Cygwin, OS X terminals. Furthermore, if launched from an X or similar GUI based environment instead of a terminal, it will create its own GUI window into which it will render the terminal. Neat.

The drawback with Lanterna is its rather verbose and involved API. For “serious” apps, maybe that’s OK, but for simple positioning of the cursor, it can get a bit too much. The follow renders a text label with a border:

  public void testWindow() throws IOException, InterruptedException {
    Terminal terminal = new DefaultTerminalFactory().createTerminal();
    Screen screen = new TerminalScreen(terminal);
    screen.startScreen();

    Label label = new Label("test");

    BasicWindow window = new BasicWindow();
    window.setComponent(label);

    MultiWindowTextGUI gui = new MultiWindowTextGUI(screen, new DefaultWindowManager(),
        new EmptySpace(TextColor.ANSI.BLACK));
    gui.setTheme(LanternaThemes.getRegisteredTheme("businessmachine"));
    gui.addWindowAndWait(window);
  }

Lanterna is available through Maven Central, so the following gradle.build lines will do.

repositories {
  mavenCentral()
}

dependencies {
  compile 'com.googlecode.lanterna:lanterna:3.0.0-beta3'
}

Here’s the full test example, including a main-method to easily run it stand-alone from the terminal.

LanternaCliTest.java
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.ui;

import java.io.IOException;

import org.junit.Test;

import com.googlecode.lanterna.TextColor;
import com.googlecode.lanterna.bundle.LanternaThemes;
import com.googlecode.lanterna.gui2.BasicWindow;
import com.googlecode.lanterna.gui2.DefaultWindowManager;
import com.googlecode.lanterna.gui2.EmptySpace;
import com.googlecode.lanterna.gui2.Label;
import com.googlecode.lanterna.gui2.MultiWindowTextGUI;
import com.googlecode.lanterna.screen.Screen;
import com.googlecode.lanterna.screen.TerminalScreen;
import com.googlecode.lanterna.terminal.DefaultTerminalFactory;
import com.googlecode.lanterna.terminal.Terminal;

public class LanternaCliTest {

  public static void main(String[] args) throws Exception {
    new LanternaCliTest().testWindow();
  }

  @Test
  public void testWindow() throws IOException, InterruptedException {
    Terminal terminal = new DefaultTerminalFactory().createTerminal();
    Screen screen = new TerminalScreen(terminal);
    screen.startScreen();

    Label label = new Label("test");

    BasicWindow window = new BasicWindow();
    window.setComponent(label);

    MultiWindowTextGUI gui = new MultiWindowTextGUI(screen, new DefaultWindowManager(),
        new EmptySpace(TextColor.ANSI.BLACK));
    gui.setTheme(LanternaThemes.getRegisteredTheme("businessmachine"));
    gui.addWindowAndWait(window);
  }
}

A simple HTTP server

20 January 2017

The unofficial com.sun packages which is still part of the main JDK, include a few convenience classes for running a HTTP server. However, since these are not part of the official API, there’s typically a warning in the IDEs not to use them. The annotation @SuppressWarnings(“restriction”) can also be used to ignore this warning. Besides that, they will work fine.

Here’s what it takes to start the server on a given port, and serve static files from a specific directory tree:

  void start() throws IOException {
    server = HttpServer.create(new InetSocketAddress(PORT), 0);

    server.createContext("/static", new StaticFileHandler(BASEDIR));

    server.start();
  }

The second argument to the create() method, which is currently 0, is the number of queued incoming queries. Setting a higher number here will allow the server to handle more parallel incoming requests, however potentially at the expense of overall throughput.

The start() is non-blocking, so a typically server application would have to go into an internal loop.

As for the handler, it listens to requests where the path of the URI starts with the specified string, in this case “/static”. It can then use the request URI to map this to hard-coded files, or files with the same name as in this example:

  public void handle(HttpExchange ex) throws IOException {
    URI uri = ex.getRequestURI();
    String name = new File(uri.getPath()).getName();
    File path = new File(baseDir, name);

    Headers h = ex.getResponseHeaders();
    // Could be more clever about the content type based on the filename here.
    h.add("Content-Type", "text/html");

    OutputStream out = ex.getResponseBody();

    if (path.exists()) {
      ex.sendResponseHeaders(200, path.length());
      out.write(Files.readAllBytes(path.toPath()));
    } else {
      System.err.println("File not found: " + path.getAbsolutePath());

      ex.sendResponseHeaders(404, 0);
      out.write("404 File not found.".getBytes());
    }

    out.close();
  }

Finally, to test the running server, here’s two unit tests. The first downloads a file which exists, and prints its lines. The second requests a file which does not exist. Notice that the 404 return code passed from sendResponseHeaders() in the handler, is enough to make the openStream() call throw a FileNotFoundException on the client side.

  @Test
  public void testDownload() throws IOException {
    server.start();

    URL url = new URL("http://localhost:9999/static/test.txt");
    BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream()));
    in.lines().forEach(System.out::println);
    in.close();
  }

  @Test(expected = FileNotFoundException.class)
  public void testFilenNotFound() throws IOException {
    server.start();

    URL url = new URL("http://localhost:9999/static/not_found");
    url.openStream();
  }

Here’s the main class:

SimpleHttpServer.java
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.http;

import java.io.IOException;
import java.net.InetSocketAddress;

import com.sun.net.httpserver.HttpServer;

@SuppressWarnings("restriction")
public class SimpleHttpServer {

  private static final String BASEDIR = "com/rememberjava/http";

  private static final int PORT = 9999;

  private HttpServer server;

  public static void main(String[] args) throws Exception {
    SimpleHttpServer server = new SimpleHttpServer();
    server.start();
  }

  void start() throws IOException {
    server = HttpServer.create(new InetSocketAddress(PORT), 0);

    server.createContext("/static", new StaticFileHandler(BASEDIR));

    server.start();
  }

  public void stop() {
    server.stop(0);
  }
}

And here’s the Handler for static files:

StaticFileHandler.java
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.http;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.Files;

import com.sun.net.httpserver.Headers;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;

@SuppressWarnings("restriction")
public class StaticFileHandler implements HttpHandler {

  private final String baseDir;

  public StaticFileHandler(String baseDir) {
    this.baseDir = baseDir;
  }

  @Override
  public void handle(HttpExchange ex) throws IOException {
    URI uri = ex.getRequestURI();
    String name = new File(uri.getPath()).getName();
    File path = new File(baseDir, name);

    Headers h = ex.getResponseHeaders();
    // Could be more clever about the content type based on the filename here.
    h.add("Content-Type", "text/html");

    OutputStream out = ex.getResponseBody();

    if (path.exists()) {
      ex.sendResponseHeaders(200, path.length());
      out.write(Files.readAllBytes(path.toPath()));
    } else {
      System.err.println("File not found: " + path.getAbsolutePath());

      ex.sendResponseHeaders(404, 0);
      out.write("404 File not found.".getBytes());
    }

    out.close();
  }

}

Send and Receive money with bitcoinj

15 January 2017

The bitcoinj library is easy to use for Bitcoin wallet and transaction functions for both native Java and Android applications. Although there are certain features missing, it seems mature enough to be included in a Bitcoin walled app or service.

Sometimes the source code leaves a bit to be desired in structure and readability: anonymous inner classes and other deep nesting blocks sometimes makes it difficult to follow; inheritance is often used where composition would have been be better; the Collections classes could have been used over arrays in many places. All of this might come back to haunt the developers later, for now they seem to be plowing on.

At least the basics are straight forward. The following code will read a test walled from disk, or create a new one if it does not already exist. The TestNet3 block chain and network is used. Since the bitcoinj library relies heavily on the Google Guava (com.google.common) classes, there are frequent artifacts of the threading and callback handling showing up. In this example, we want the code to block and wait, therefore the extra await-functions are required.

  public void testSync() {
    params = TestNet3Params.get();
    kit = new WalletAppKit(params, new File("/dev/shm"), "test");

    kit.startAsync();
    kit.awaitRunning();

    kit.stopAsync();
    kit.awaitTerminated();
  }

The nice thing about the test block chain is that it is a real public live block chain, with miners and a block chain explorer, but with no value in the coins. In fact, you can get free coins to test with from faucet.xeno-genesis.com or tpfaucet.appspot.com. (The latter has been timing out over the last days).

To get some free test coins, run the following code, wait for the prompt which shows the next receiving address, and head over to faucet.xeno-genesis.com to ask them to send some money there. It should show up as received within a few seconds. Your wallet now contains some coins.

  public void testReceive() {
    startSync();

    wallet.addCoinsReceivedEventListener(this::coinsReceived);

    Address toAdr = wallet.currentReceiveKey().toAddress(params);
    System.out.println("Waiting to receive coins on: " + toAdr);

    while (!receivedCoins) {
      sleep(100);
    }
  }

  private void coinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
    Coin value = tx.getValueSentToMe(wallet);
    System.out.println("Received tx for " + value.toFriendlyString() + ": " + tx);
    receivedCoins = true;
  }

Since the test network is a real network with real miners, it’s good etiquette to return your test coins to the pool for others to use once you’re done with them. The following code takes care of that, returning them to the TP Faucet default return address “n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi”. You can return all your coins, or just a fraction if you want to experiment more. This will also wait a few seconds for the callback confirmation.

  public void testSend() throws InsufficientMoneyException {
    startSync();

    // Adjust how many coins to send. E.g. the minimum; or everything.
    Coin sendValue = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
    // Coin sendValue = wallet.getBalance().minus(Transaction.DEFAULT_TX_FEE);
    
    Address sendToAdr = Address.fromBase58(params, TPFAUCET_RETURN_ADR);
    SendRequest request = SendRequest.to(sendToAdr, sendValue);

    SendResult result = wallet.sendCoins(request);

    result.broadcastComplete.addListener(() -> {
      println("Coins were sent. Transaction hash: " + result.tx.getHashAsString());
      sentCoins = true;
    }, MoreExecutors.sameThreadExecutor());

    while (!sentCoins) {
      sleep(100);
    }
  }

Finally, it’s worth noting that bitcoinj is a “live” library, in development and with the latest update available through Gradle. To make this work, there’s a few settings and dependencies to take care of. The logging framework used by bitcoinj is SL4J, and an actual implementation library (e.g. “sl4j-simple”) is also need. It can be downloaded, or included as a Gradle build dependency as seen below.

Then, your source code directory structure might not match the default Gradle “main”, “test” structure. My current structure keeps all source code under the directory “src”, so I have specified that.

Gradle integrates OK with Eclipse, but be careful with the “refresh” option. It tends to insist on changing the classpath setting of the project, so the packages disappear. It’s a good idea to keep the .classpath setting file under version control.

apply plugin: 'java'

sourceSets {
  main {
    java {
      srcDirs = ['src']
    }
  }
}

repositories {
  jcenter()
}

dependencies {
  compile 'org.slf4j:slf4j-api:1.7.21'
  compile 'org.slf4j:slf4j-simple:1.7.21'
  compile 'org.bitcoinj:bitcoinj-core:0.14.3'

  testCompile 'junit:junit:4.12'
}

The following listing shows all the tests. It demonstrates similar functionality as seen in the ForwardingService class in the main bitcoinj getting started guide. Hopefully, the code is a bit easier to read and run this way.

BitcoinjApiTest.java
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.bitcoin;

import java.io.File;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.params.TestNet3Params;
import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.Wallet.SendResult;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.impl.StaticLoggerBinder;

import com.google.common.util.concurrent.MoreExecutors;

/**
 * Multiple large file and network I/O tests for the Testnet3 block chain.
 * Comment in and out @Test annotations as need.
 * 
 * For more info:<br>
 * https://en.bitcoin.it/wiki/Testnet<br>
 * 
 * For test coins: <br>
 * http://faucet.xeno-genesis.com <br>
 * http://tpfaucet.appspot.com <br>
 * 
 * For test block explorer: <br>
 * https://testnet.blockexplorer.com
 *
 */
public class BitcoinjApiTest {

  /**
   * http://tpfaucet.appspot.com
   */
  private static final String TPFAUCET_RETURN_ADR = "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi";

  private static final File WALLET_DIR = new File("/tmp");

  private static final String WALLET_PREFIX = "rjtest";

  private NetworkParameters params;

  private WalletAppKit kit;

  private Wallet wallet;

  private volatile boolean receivedCoins;

  private volatile boolean sentCoins;

  /**
   * For all tests, connect to the TestNet3 network.
   */
  @Before
  public void setup() {
    params = TestNet3Params.get();
    kit = new WalletAppKit(params, WALLET_DIR, WALLET_PREFIX);
  }

  /**
   * Connect, sync and wait till done.
   */
  private void startSync() {
    kit.startAsync();
    kit.awaitRunning();
    sleep(1000);

    wallet = kit.wallet();
  }

  /**
   * Disconnect and wait till done.
   */
  @After
  public void teardown() {
    kit.stopAsync();
    kit.awaitTerminated();
  }

  /**
   * Even though it is possible to run without the logger implementation classes
   * in place, it is very confusing and misleading.
   * 
   * The bitcoinj logs output connection and transaction information. Without
   * them, it's difficult to know what is happening, and the application might
   * seem "stuck".
   */
  @Test
  public void checkLogger() {
    StaticLoggerBinder.getSingleton();
  }

  /**
   * Connects to the TestNet3 network and disconnects. If there is no walled
   * stored at the specified location, a new one is created and relevant blocks
   * are downloaded. This can take 1 to 2 minutes. If information is stale, it
   * can also take a minute to update.
   * 
   * The walled used in this test is separate from the other tests.
   */
  @Test
  public void testSync() {
    params = TestNet3Params.get();
    kit = new WalletAppKit(params, new File("/dev/shm"), "test");

    kit.startAsync();
    kit.awaitRunning();

    kit.stopAsync();
    kit.awaitTerminated();
  }

  /**
   * Connects and prints address information about the test wallet (from the
   * TestNet3 network).
   */
  @Test
  public void printAddresses() {
    startSync();

    System.out.println("Receive Addresses:");
    wallet.getIssuedReceiveAddresses().stream().forEach(this::println);

    System.out.println("Watched Addresses:");
    wallet.getWatchedAddresses().stream().forEach(this::println);

    System.out.println("Current change address:");
    System.out.println(wallet.currentChangeAddress());
  }

  /**
   * Connects and prints wallet information (from the TestNet3 network).
   */
  @Test
  public void printWalletInfo() {
    startSync();

    Coin balance = wallet.getBalance();
    println("Balance satoshis: " + balance.toString());
    println("Balance friendly: " + balance.toFriendlyString());
    println("Version: " + wallet.getVersion());
  }

  /**
   * Waits for coins to be received on the prompted address. This test blocks
   * until the coinsReceived callback is called.
   * 
   * (This test is commented out to avoid blocking the other tests).
   */
  // @Test
  public void testReceive() {
    startSync();

    wallet.addCoinsReceivedEventListener(this::coinsReceived);

    Address toAdr = wallet.currentReceiveKey().toAddress(params);
    System.out.println("Waiting to receive coins on: " + toAdr);

    while (!receivedCoins) {
      sleep(100);
    }
  }

  /**
   * Callback called when the coins have been received.
   */
  private void coinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
    Coin value = tx.getValueSentToMe(wallet);
    System.out.println("Received tx for " + value.toFriendlyString() + ": " + tx);
    receivedCoins = true;
  }

  /**
   * Sends test coins back to the TPFAUCET test network address. This test
   * blocks until the broadcastComplete callback is called.
   * 
   * (This test is commented out to avoid blocking the other test and
   * inadvertently sending away our money.
   */
  // @Test
  public void testSend() throws InsufficientMoneyException {
    startSync();

    // Adjust how many coins to send. E.g. the minimum; or everything.
    Coin sendValue = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
    // Coin sendValue = wallet.getBalance().minus(Transaction.DEFAULT_TX_FEE);
    
    Address sendToAdr = Address.fromBase58(params, TPFAUCET_RETURN_ADR);
    SendRequest request = SendRequest.to(sendToAdr, sendValue);

    SendResult result = wallet.sendCoins(request);

    result.broadcastComplete.addListener(() -> {
      println("Coins were sent. Transaction hash: " + result.tx.getHashAsString());
      sentCoins = true;
    }, MoreExecutors.sameThreadExecutor());

    while (!sentCoins) {
      sleep(100);
    }
  }

  private void sleep(long millis) {
    try {
      Thread.sleep(millis);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  private void println(Object str) {
    System.out.println(str);
  }
}

MIDI basics

13 January 2017

Working with MIDI in Java is easy. The standard API have classes covering MIDI file I/O, device I/O, sequencing and sound synthesis. This comprhensive tutorial covers most aspects. It also helps to know something about the MIDI format. Juan Bello’s slide deck is an excellent introduction. midi.org is also a good source, including their full message reference table.

To play a single C note through the default included “Gervill” soft synthezier, the following snippet will do. As commented in the code, there’s come discrepancy on pitch or octave labelling, but the 60th note is still on the save octave as A at 440 Hz.

  public void testPlayNote() throws InvalidMidiDataException, MidiUnavailableException {
    // 60 = middle C, C4 (or C3 or C5)
    // https://en.wikipedia.org/wiki/Scientific_pitch_notation#Similar_systems
    int channel = 0;
    int note = 60;
    int velocity = 127; // velocity (i.e. volume); 127 = high
    ShortMessage msg = new ShortMessage();
    msg.setMessage(ShortMessage.NOTE_ON, channel, note, velocity);

    long timeStamp = -1;
    MidiDevice synthesizer = getSynthesizer("Gervill");
    synthesizer.open();
    Receiver receiver = synthesizer.getReceiver();
    receiver.send(msg, timeStamp);
    receiver.close();
  }

This gives a brief overview of the MIDI devices on the system, both software based and hardware devices. Depending on drivers and OS, the various devices might show up under different names and types.

  public void testGetMidiDeviceInfo() throws MidiUnavailableException {
    Info[] infos = MidiSystem.getMidiDeviceInfo();
    for (Info info : infos) {
      MidiDevice device = MidiSystem.getMidiDevice(info);
      boolean isSequencer = device instanceof Sequencer;
      boolean isSynthesizer = device instanceof Synthesizer;

      System.out.println(
          "Info: " + device + ", '" + 
              info.getName() + "',  '" + 
              info.getVendor() + "', '" + 
              info.getVersion() + "', '" + 
              info.getDescription() + "', " + 
              "sequencer=" + isSequencer + 
              ", synthesizer=" + isSynthesizer);
    }
  }

Finally, the following methods demonstream MIDI file I/O.

  public void testWriteFile() throws InvalidMidiDataException, IOException {
    int volume = 0x70;
    int tick = 0;

    Sequence sequence = new Sequence(Sequence.PPQ, 24);
    Track track = sequence.createTrack();

    MetaMessage meta = new MetaMessage();
    String trackName = "First track";
    meta.setMessage(0x03, trackName.getBytes(), trackName.length());
    track.add(new MidiEvent(meta, tick));

    int notes[] = IntStream.range(60, 73).toArray();

    for (int note : notes) {
      ShortMessage on = new ShortMessage(ShortMessage.NOTE_ON, note, volume);
      track.add(new MidiEvent(on, tick));
      tick += 4;

      ShortMessage off = new ShortMessage(ShortMessage.NOTE_OFF, note, 0);
      track.add(new MidiEvent(off, tick));
      tick += 4;
    }

    MidiSystem.write(sequence, 1, MIDI_FILE);
  }

  public void testReadFile() throws InvalidMidiDataException, IOException {
    Sequence sequence = MidiSystem.getSequence(MIDI_FILE);
    
    System.out.println("tracks: " + sequence.getTracks().length);
    System.out.println("patches: " + sequence.getPatchList().length);
    
    for (Track track : sequence.getTracks()) {
      int size = track.size();
      System.out.println("track size: "+size);
      
      IntStream.range(0, size).boxed()
        .map(track::get)
        .map(RawMessage::new)
        .map(AbstractMessage::getNoteOctave)
        .map(str -> str + " ")
        .forEach(System.out::print);
      System.out.println();
    }
  }

  public void testMidiFileFormat() throws InvalidMidiDataException, IOException {
    MidiFileFormat format = MidiSystem.getMidiFileFormat(MIDI_FILE);

    System.out.println("bytes: " + format.getByteLength());
    System.out.println("length ms: " + format.getMicrosecondLength());
    System.out.println("divisionType: " + format.getDivisionType());
    System.out.println("resolution: " + format.getResolution());
    System.out.println("type: " + format.getType());
    System.out.println("properties: " + format.properties().size());

    format.properties().entrySet().stream()
      .map(e -> e.getValue() + "=" + e.getKey())
      .forEach(System.out::println);
  }

The full test case can be seen below. There’s a few more helper classes and details in the same package here. Then there’s some special implementation and details for the Roland TB-03 and TR-8 devices here.

MidiApiTest.java
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.midi;

import static com.rememberjava.midi.MidiUtils.filteredDeviceStream;
import static com.rememberjava.midi.MidiUtils.onClassnameEquals;

import java.io.File;
import java.io.IOException;
import java.util.stream.IntStream;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiDevice.Info;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiFileFormat;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Track;
import javax.sound.midi.Transmitter;

import org.junit.Test;


/**
 * See also: https://docs.oracle.com/javase/tutorial/sound/overview-MIDI.html
 */
public class MidiApiTest {

  private static final String SOFT_SYNTHESIZER = "SoftSynthesizer";

  private static final String MIDI_FILE_PATH = "com/rememberjava/midi/test.mid";

  private static final File MIDI_FILE = new File(MIDI_FILE_PATH);

  @Test
  public void testGetMidiDeviceInfo() throws MidiUnavailableException {
    Info[] infos = MidiSystem.getMidiDeviceInfo();
    for (Info info : infos) {
      MidiDevice device = MidiSystem.getMidiDevice(info);
      boolean isSequencer = device instanceof Sequencer;
      boolean isSynthesizer = device instanceof Synthesizer;

      System.out.println(
          "Info: " + device + ", '" + 
              info.getName() + "',  '" + 
              info.getVendor() + "', '" + 
              info.getVersion() + "', '" + 
              info.getDescription() + "', " + 
              "sequencer=" + isSequencer + 
              ", synthesizer=" + isSynthesizer);
    }
  }

  @Test
  public void testGetSequencer() throws MidiUnavailableException {
    Sequencer sequencer = MidiSystem.getSequencer();
    Transmitter transmitter = sequencer.getTransmitter();

    System.out.println("Sequencer: " + sequencer.getDeviceInfo());
    System.out.println("Transmitter: " + transmitter);
  }

  @Test
  public void testGetSynthesizer() throws MidiUnavailableException {
    Synthesizer synthesizer = MidiSystem.getSynthesizer();
    Receiver receiver = synthesizer.getReceiver();

    System.out.println("Synthesizer: " + synthesizer.getDeviceInfo());
    System.out.println("Receiver: " + receiver);
  }

  @Test
  public void testPlayNote() throws InvalidMidiDataException, MidiUnavailableException {
    // 60 = middle C, C4 (or C3 or C5)
    // https://en.wikipedia.org/wiki/Scientific_pitch_notation#Similar_systems
    int channel = 0;
    int note = 60;
    int velocity = 127; // velocity (i.e. volume); 127 = high
    ShortMessage msg = new ShortMessage();
    msg.setMessage(ShortMessage.NOTE_ON, channel, note, velocity);

    long timeStamp = -1;
    MidiDevice synthesizer = getSynthesizer("Gervill");
    synthesizer.open();
    Receiver receiver = synthesizer.getReceiver();
    receiver.send(msg, timeStamp);
    receiver.close();
  }
  
  private MidiDevice getSynthesizer(String deviceName) throws MidiUnavailableException {
     return filteredDeviceStream(deviceName)
         .filter(onClassnameEquals(SOFT_SYNTHESIZER))
         .findFirst().get();
  }

  @Test
  public void testWriteFile() throws InvalidMidiDataException, IOException {
    int volume = 0x70;
    int tick = 0;

    Sequence sequence = new Sequence(Sequence.PPQ, 24);
    Track track = sequence.createTrack();

    MetaMessage meta = new MetaMessage();
    String trackName = "First track";
    meta.setMessage(0x03, trackName.getBytes(), trackName.length());
    track.add(new MidiEvent(meta, tick));

    int notes[] = IntStream.range(60, 73).toArray();

    for (int note : notes) {
      ShortMessage on = new ShortMessage(ShortMessage.NOTE_ON, note, volume);
      track.add(new MidiEvent(on, tick));
      tick += 4;

      ShortMessage off = new ShortMessage(ShortMessage.NOTE_OFF, note, 0);
      track.add(new MidiEvent(off, tick));
      tick += 4;
    }

    MidiSystem.write(sequence, 1, MIDI_FILE);
  }

  @Test
  public void testReadFile() throws InvalidMidiDataException, IOException {
    Sequence sequence = MidiSystem.getSequence(MIDI_FILE);
    
    System.out.println("tracks: " + sequence.getTracks().length);
    System.out.println("patches: " + sequence.getPatchList().length);
    
    for (Track track : sequence.getTracks()) {
      int size = track.size();
      System.out.println("track size: "+size);
      
      IntStream.range(0, size).boxed()
        .map(track::get)
        .map(RawMessage::new)
        .map(AbstractMessage::getNoteOctave)
        .map(str -> str + " ")
        .forEach(System.out::print);
      System.out.println();
    }
  }

  @Test
  public void testMidiFileFormat() throws InvalidMidiDataException, IOException {
    MidiFileFormat format = MidiSystem.getMidiFileFormat(MIDI_FILE);

    System.out.println("bytes: " + format.getByteLength());
    System.out.println("length ms: " + format.getMicrosecondLength());
    System.out.println("divisionType: " + format.getDivisionType());
    System.out.println("resolution: " + format.getResolution());
    System.out.println("type: " + format.getType());
    System.out.println("properties: " + format.properties().size());

    format.properties().entrySet().stream()
      .map(e -> e.getValue() + "=" + e.getKey())
      .forEach(System.out::println);
  }
}

Method references kills the Factory class

06 January 2017

When writing reusable code, we often want it to be as general and flexible as possible. So layers of abstractions are added; generic types; abstract class hierarchies; and let’s not forget the Factory pattern. Joel Spolsky had a famous rant about the factory factory factory pattern, and it can get ugly in the real world as well.

One of the reasons for the often clunky factory class is that it has not been possible to pass methods and constructors. Method references in Java 8 changes that, even for constructors. They can be passed for any class or array, e.g. String::new, String[]::new. Combined with generics, the type of the newly created object can also be specified.

In the example class below, the constructor happen to take two arguments, first a String and then an int. Therefore, the BiFunction function method fits, however, it would probably be more appropriate to define a more specific functional interface, which would also make the code more readable. The return value is of the type T, which should then be the same type as the generated object. The use is demonstrated in the test method below.

The restriction with this setup is of course that the number of arguments to the constructor is fixed. We could write fixes around that as well, but that would require the general class to know something about the classes it is instantiating, which defetes the purpose. There’s always the old Factory class, though.

  class Generator<T> {

    private final BiFunction<String, Integer, T> constructor;

    Generator(BiFunction<String, Integer, T> constructor) {
      this.constructor = constructor;
    }

    T generate() {
      return constructor.apply(TEST, _123);
    }
  }

  public void testGenerateA() {
    Generator<A> genA = new Generator<>(A::new);
    A a = genA.generate();
    assertEquals(TEST + _123, a.content);
  }

Now, it can be argued that this still constitutes a factory pattern, even if an external factory class is not used. The methods of Collectors highlights this, e.g.:

    public static <T, C extends Collection<T>>
    Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
        return new CollectorImpl<>(collectionFactory, Collection<T>::add,
                                   (r1, r2) -> { r1.addAll(r2); return r1; },
                                   CH_ID);
    }

The full code listing of the example:

GenericConstructor.java
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.lambda;

import static org.junit.Assert.assertEquals;

import java.util.function.BiFunction;

import org.junit.Test;

public class GenericConstructor {

  private static final String TEST = "test";
  private static final int _123 = 123;

  class Generator<T> {

    private final BiFunction<String, Integer, T> constructor;

    Generator(BiFunction<String, Integer, T> constructor) {
      this.constructor = constructor;
    }

    T generate() {
      return constructor.apply(TEST, _123);
    }
  }

  class A {
    final String content;

    A(String str, int i) {
      content = str + i;
    }
  }

  class B {
    final String str;
    final int i;

    B(String str, int i) {
      this.str = str;
      this.i = i;
    }
  }

  @Test
  public void testGenerateA() {
    Generator<A> genA = new Generator<>(A::new);
    A a = genA.generate();
    assertEquals(TEST + _123, a.content);
  }

  @Test
  public void testGenerateB() {
    Generator<B> genB = new Generator<>(B::new);
    B b = genB.generate();
    assertEquals(TEST, b.str);
    assertEquals(_123, b.i);
  }
}
Newer posts Older posts