2016-07 Cool Stuff

Some of the interesting things I’ve been playing with lately



Creative Commons License
2016-07 Cool Stuff by Jim J. Moore is licensed under a Creative Commons Attribution 4.0 International License.
Presentation source at https://github.com/jdigger/201607-cool-stuff-preso

Jim Moore

Agenda

  1. Capsule
  2. Lombok
  3. JDK 8
    • Streams & Lambdas
    • Where’s Elvis? ?:
    • Misc
  4. Softened Throws
  5. Either
  6. Checker Framework
  7. Reactive Streams
  8. AsciiDoclet
  9. Typed Builders
  10. Static Analysis

Examples

To see these used “for reals”, please refer to the Grabbit CLI

Capsule

capsule
plugins {
    id "us.kirchmeier.capsule" version "1.0.2"
}

task fatCapsule(type: FatCapsule) {
    archiveName = "grabbit-cli-${version}"
    applicationClass "com.twcable.grabbit.tools.cli.GrabbitCli"

    capsuleManifest {
        minJavaVersion = defaults.compatibilityVersion
    }

    manifest = jar.manifest
    reallyExecutable
}

assemble.dependsOn fatCapsule

Capsule MANIFEST.MF

$ unzip -p build/libs/grabbit-cli-2.0.1 META-INF/MANIFEST.MF
warning [build/libs/grabbit-cli-2.0.1]:  34 extra bytes at
beginning or within zipfile
  (attempting to process anyway)
Manifest-Version: 1.0
Application-Class: com.twcable.grabbit.tools.cli.GrabbitCli
Min-Java-Version: 1.8
Premain-Class: Capsule
Main-Class: Capsule

First 34 Bytes

$ head -c34 build/libs/grabbit-cli-2.0.1
#!/bin/sh

exec java -jar $0 "$@"
$ unzip -l build/libs/grabbit-cli-2.0.1
  Length     Date   Time    Name
 --------    ----   ----    ----
        0  07-08-16 09:26   META-INF/
      402  07-08-16 09:26   META-INF/MANIFEST.MF
   166083  10-07-15 15:12   Capsule.class
    73693  07-08-16 09:26   grabbit-cli-2.0.1.jar
    52988  05-29-16 18:44   commons-cli-1.3.1.jar
   273599  05-30-16 06:26   snakeyaml-1.17.jar
     2042  09-27-15 20:42   reactive-streams-1.0.0.jar
   995032  06-28-16 12:25   reactor-core-2.5.0.M4.jar
 --------                   -------
  1563839                   8 files

Lombok

https://projectlombok.org/features/index.html

Lombok feature overview

@Value

@Value
public class HostAndJobIds {
    URI uri;
    Publisher<Long> jobIds;
}
public class HostAndJobIds {
    URI uri;
    Publisher<Long> jobIds;

    @java.beans.ConstructorProperties({"uri", "jobIds"})
    public HostAndJobIds(URI uri, Publisher<Long> jobIds) {
        this.uri = uri;
        this.jobIds = jobIds;
    }

    public URI uri() {
        return this.uri;
    }

    public Publisher<Long> jobIds() {
        return this.jobIds;
    }

    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof HostAndJobIds)) return false;
        final HostAndJobIds other = (HostAndJobIds)o;
        final Object this$uri = this.uri;
        final Object other$uri = other.uri;
        if (this$uri == null ? other$uri != null :
            !this$uri.equals(other$uri)) return false;
        final Object this$jobIds = this.jobIds;
        final Object other$jobIds = other.jobIds;
        if (this$jobIds == null ? other$jobIds != null :
            !this$jobIds.equals(other$jobIds)) return false;
        return true;
    }

    public int hashCode() {
        final int PRIME = 59;
        int result = 1;
        final Object $uri = this.uri;
        result = result * PRIME + ($uri == null ? 0 : $uri.hashCode());
        final Object $jobIds = this.jobIds;
        result = result * PRIME + ($jobIds == null ? 0 : $jobIds.hashCode());
        return result;
    }

    public String toString() {
        return "com.twcable.grabbit.tools.jobstarter.HostAndJobIds(uri=" +
            this.uri + ", jobIds=" + this.jobIds + ")";
    }
}

delombok

Lombok val

public static JobStatus fromJson(URI uri, @Nullable String jsonStr) {
    val json = (jsonStr == null || jsonStr.trim().isEmpty()) ? "{}" : jsonStr;

    val map = (Map<String, Object>)new Yaml().loadAs(json, Map.class);
    val transactionId = (Long)map.getOrDefault("transactionID", -1L);
    val jobExecutionId = (Long)map.getOrDefault("jobExecutionId", -1L);
    val startTimeStr = (String)map.getOrDefault("startTime", DATE_TIME_FORMATTER.format(now()));
    val startTime = OffsetDateTime.parse(startTimeStr, DATE_TIME_FORMATTER);
    val endTimeStr = (String)map.get("endTime");
    val endTime = (endTimeStr != null) ?
        OffsetDateTime.parse(endTimeStr, DATE_TIME_FORMATTER) : (OffsetDateTime)null;
    val path = (String)map.getOrDefault("path", "/MISSING_PATH");
    val existStatusMap = (Map<String, Object>)map.getOrDefault("exitStatus", emptyMap());
    val exitDescription = (String)existStatusMap.getOrDefault("exitDescription", "");
    val exitCode = (String)existStatusMap.getOrDefault("exitCode", "UNKNOWN");
    val running = (Boolean)existStatusMap.getOrDefault("running", Boolean.FALSE);
    val timeTaken = Long.valueOf(map.getOrDefault("timeTaken", -1L).toString());
    val jcrNodesWritten = Long.parseLong(map.getOrDefault("jcrNodesWritten", -1L).toString());

    return new JobStatus(uri, transactionId, jobExecutionId, startTime, endTime, path, timeTaken,
        jcrNodesWritten, exitDescription, exitCode, running);
}

val “gotchas”

JDK 8

Streams & Lambdas

Some Things To Realize About Streams

First a REALLY Simple Look at Monads

Monads, Functors, etc. are fancy ways of saying “a value that is expressed as a function.”

A monad, specifically, needs map(i → o) and flatMap(i → M{o}) functions.

public interface Stream<T> {
    <R> Stream<R> map(Function<T, R> mapper);
    <R> Stream<R> flatMap(Function<T, Stream<R>> mapper);
}

Monad Use

What that means is that you can transform that value in extremely flexible/powerful ways regardless of such things as:

Monad “Extractions”

Monad Reading

There’s a LOT of stuff explaining monads, monoids, functors, endofunctions, etc. Just Google it.

The best I’ve found for explaining it in Java code instead of mathematics notation is Functor and monad examples in plain Java

Streams as Monads

Extracting from Streams - findAny and collect

public @Nullable HostJobState put(HostJobState entry) {
    val existing = entries().stream().
        filter(e -> match(e, entry)).
        findAny().orElse(null);

    if (existing == null || !existing.state().equals(entry.state())) {
        if (existing == null) { // new entry
            this.hostJobStates = new ArrayList<>(entries());
            hostJobStates.add(entry);
        }
        else { // modified entry
            hostJobStates = entries().stream().
                map(e -> match(e, entry) ? entry : e).
                collect(Collectors.toList());
        }
    }

    return existing;
}

Extracting from Streams - forEach and method handles

private void parseStartJobsOutput(String startJobsOutput,
                                  Subscriber<Long> jobIdSub) {
    // the output from starting a job looks like "[123,125]"
    val matcher = JOB_IDS_PATTERN.matcher(startJobsOutput);
    if (matcher.matches()) {
        val jobIdsStr = (@NonNull String)matcher.group("jobIds");
        Arrays.stream(jobIdsStr.split(",")).
            map(String::trim).
            map(Long::valueOf).
            forEach(jobId -> jobIdSub.onNext(jobId));
        jobIdSub.onComplete();
    }
    else {
        jobIdSub.onError(new IllegalStateException("Could not parse job " +
            "ids from: " + startJobsOutput));
    }
}

Venkat Talks Lambdas

venkat lambdas

Useful Stream Creation Classes

java.util.Collection

default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

default Stream<E> parallelStream() {
    return StreamSupport.stream(spliterator(), true);
}

java.util.stream.StreamSupport

A set of factory methods that take take a Spliterator (of different types, including for primitives) to create a Stream.

java.util.Arrays

Has .spliterator(..) and .stream(..) factory methods to wrap arrays.

java.util.stream.Collectors

A set of functions for

Where’s Elvis?

java.util.Optional

val existing = entries().stream().
    filter(e -> match(e, entry)).
    findAny().orElse(null);

Still Not Quite Groovy’s Elvis…​

public String getIssuesUrl() {
    return issuesUrl ?: "${siteUrl}/issues"
}
public String getIssuesUrl() {
    return (issuesUrl != null && !issuesUrl.isEmpty()) ?
        issues : "${siteUrl}/issues"
}

Try Again

/**
 * Convert the String into an Optional that only has value if the
 * input was both non-null and non-empty.
 */
public static Optional<String> opt(String val) {
    return Optional.ofNullable(val).filter(s -> !s.isEmpty());
}

public String getIssuesUrl() {
    return opt(issuesUrl).orElseGet(() -> getSiteUrl() + "/issues");
}

public String getBintrayPkg() {
    return opt(bintrayPkg).orElse(project.getName());
}

Optional vs Groovy’s ?: and ?.

Groovy’s elvis and null-safe-dereference operators are a very succinct way of handling a very common situation in Java. But they are very specialized.

Because Optional is a monad (though not entirely pure) there is much, much more that you can do with it.

Optional is great if you treat it as a monad instead of simply a null-wrapper (i.e., you should almost never call .isPresent() and .get()).

http://reversecoding.net/java-8-optional-replace-get-examples/

Misc JDK8

Softened Throws

The Problem With Checked Exceptions

Often we don’t want to expose underlying checked exceptions in our APIs, or it’s not our API to change (e.g., the Stream API).

(We’ll look later at an easy way of returning errors that aren’t “exceptional.”)

Ignoring the Exception for Now…​

Assuming there isn’t anything reasonable to do with the exception at that point in the code, that means we end up either swallowing or wrapping the exception.

(Of course many JVM languages simply ignore the checked nature of exceptions, but I’m focussing on the Java Language.)

Wrapping Exceptions

try {
    // ...
}
catch (IOException e) {
    throw new RuntimeException(e);
}

The exception that gets thrown is, well, wrong.

To get to the actual exception you have to call .getCause().

What would be great is if we can be unchecked and unwrapped…​

Generics type erasure to the rescue!

Softened Exception

/**
 * Remove checked-ness from the exception. The same exception is returned
 * (checked or unchecked), but this removes the compiler's checks.
 */
public static <T extends Throwable> RuntimeException softened(T exp) {
    return Utils.<RuntimeException>uncheck(exp);
}

private static <T extends Throwable> T uncheck(Throwable throwable) throws T {
    throw (T)throwable;
}
protected void writeFile() {
    Writer fileWriter = null;
    try {
        // ...
    }
    catch (IOException e) {
        throw softened(e);
    }
    finally {
        Utils.flushAndClose(fileWriter);
    }
}

Checker Framework

http://types.cs.washington.edu/checker-framework/current/checker-framework-manual.html

checker intro

Nullness Checker

It hooks into javac and makes it extremely difficult to get an NPE.

There’s no runtime component/impact, besides safer code.

@NonNull

By default every parameter, field, variable, return value, etc. is effectively marked as @NonNull

Doing anything that may potentially cause the reference to be null (including not explicitly initializing it) causes a compile-time error

@Nullable

If you mark a reference as being @Nullable then it will be a compile-time error to dereference it unless a prior nullness check (e.g., if (myVar != null) ) guarantees that the execution branch can not be null.

Much Safer Code

I’m pretty good about making sure I don’t get an NPE, but this has saved me a few times.

Also, because it does the flow-analysis, it highlights execution branches you might not have thought of.

Better Use of the Type System

It’s very much like static typing (which is what it’s leveraging).

Sometimes it’s useful to be “loosely typed” (or not explicitly nullness checked), but most of the time it’s much better to let the computer be anal-retentive.

It’s Not Free

Of course the more the compiler has to do, the longer the compiler takes.

In general it’s an easy call that spending a little more time compiling is worth it to get the additional safety and “correctness” checking.

Tutorial

See https://github.com/glts/safer-spring-petclinic/wiki/Our-mission

(a wizard-y wiki walking through the classic PetClinic sample)

for a good walk-through

Either<L, R>

There are times when you need to be able to return one of two types that have no reasonable common ancestor (i.e., they are “disjoint” types).

What that often means is returning Object and then a series of instanceof calls.

/**
 * An either/or (XOR) value represented as Left/Right.
 *
 * By convention, Left is an error value whereas Right is a success value.
 */
public interface Either<L, R> extends Supplier<R> {
    boolean isLeft();

    L getLeft();

    boolean isRight();

    R get();

    static <L, R> Left<L, R> left(L value) {
        return new Left<>(value);
    }

    static <L, R> Right<L, R> right(R value) {
        return new Right<>(value);
    }
    // ...
}

Example Either Return

final class CliOptions {
    /**
     * Parse the arguments.
     *
     * @param args the command line arguments
     * @return Right if successfully parsed; otherwise Left with
     *         the error message/help
     */
    public static Either<String, CliOptions> create(String[] args) {
        // ...
    }
}
public static void main(String[] args) {
    val cliOptions = CliOptions.create(args);

    if (cliOptions.isRight()) {
        run(cliOptions.get());
        System.exit(0);
    }
    else {
        System.err.println(cliOptions.getLeft());
        System.exit(1);
    }
}
cli main

Grabbit CLI uses a much simplified version of Either based on the work in Javaslang, a really awesome library that helps with Scala-envy.

(Didn’t want to include Javaslang in the fat-jar for that single class.)

Reactive Streams

http://www.reactive-streams.org/

The Basics

Similar to java.util.Stream juiced WAAAAAAAAY up.

Three core simple interfaces that enable the capabilities of

The Reactive Manifesto

Will be a core part of JDK 9 as java.util.concurrent.Flow.*

The Interfaces

With a forth “combo” interface:

reactive sequence
public interface Subscriber<T> {
    /**
     * Invoked after calling Publisher#subscribe(Subscriber).
     *
     * No data will start flowing until Subscription#request(long) is invoked.
     *
     * It is the responsibility of this Subscriber instance to call Subscription#request(long) whenever more data is wanted.
     *
     * The Publisher will send notifications only in response to Subscription#request(long).
     *
     * @param s Subscription that allows requesting data via Subscription#request(long)
     */
    public void onSubscribe(Subscription s);

    /**
     * Data notification sent by the Publisher in response to requests to Subscription#request(long).
     *
     * @param t the element signaled
     */
    public void onNext(T t);

    /**
     * Failed terminal state.
     *
     * No further events will be sent even if Subscription#request(long) is invoked again.
     *
     * @param t the throwable signaled
     */
    public void onError(Throwable t);

    /**
     * Successful terminal state.
     *
     * No further events will be sent even if Subscription#request(long) is invoked again.
     */
    public void onComplete();
}
public interface Publisher<T> {
    /**
     * Request Publisher to start streaming data.
     *
     * This is a "factory method" and can be called multiple times,
     * each time starting a new Subscription.
     *
     * Each Subscription will work for only a single Subscriber.
     *
     * A Subscriber should only subscribe once to a single Publisher.
     *
     * If the Publisher rejects the subscription attempt or otherwise fails it
     * will signal the error via Subscriber#onError.
     *
     * @param s the Subscriber that will consume signals from this Publisher
     */
    public void subscribe(Subscriber<? super T> s);
}
public interface Subscription {
    /**
     * No events will be sent by a Publisher until demand is signaled via this method.
     *
     * It can be called however often and whenever needed - but the outstanding
     * cumulative demand must never exceed Long.MAX_VALUE. An outstanding
     * cumulative demand of Long.MAX_VALUE may be treated by the Publisher as
     * "effectively unbounded".
     *
     * Whatever has been requested can be sent by the Publisher so only signal
     * demand for what can be safely handled.
     *
     * A Publisher can send less than is requested if the stream ends but then
     * must emit either Subscriber#onError(Throwable) or Subscriber#onComplete().
     *
     * @param n the strictly positive number of elements to requests to the upstream Publisher
     */
    public void request(long n);

    /**
     * Request the Publisher to stop sending data and clean up resources.
     *
     * Data may still be sent to meet previously signalled demand after calling cancel as
     * this request is asynchronous.
     */
    public void cancel();
}
public interface Processor<T, R> extends Subscriber<T>, Publisher<R> {
}

Implementations

The primary implementations are:

Project Reactor

For our purposes, the primary classes are Flux and Mono, both of which implement Publisher

Mono

Will publish at most one element.

Essentially a very, very fancy java.util.Optional.

Provides .block(..) to do the “extraction.”

Flux

More like java.util.stream.Stream, with no limit on size.

Example - Starting Jobs

/**
 * Start the jobs on the Grabbit clients, returning the hosts
 * and the jobs ids.
 */
public Publisher<HostAndJobIds> startJobs() {
    return Flux.fromIterable(hosts).
        map(this::startJobsForHost);
}

private HostAndJobIds startJobsForHost(HostInfo hostInfo) {
    Publisher<Long> jobIds = startJobsOnHost(hostInfo.baseUri(),
                                             hostInfo.credentials());
    return new HostAndJobIds(hostInfo.baseUri(), jobIds);
}

Example - Consuming the Started Jobs

/**
 * Print the started jobs to the PrintStream, waiting up to 30
 * minutes for the jobs to finish starting across all the hosts.
 */
static void printStartedJobs(Publisher<HostAndJobIds> startedJobs,
                             PrintStream out) {
    Flux.from(startedJobs).
        flatMap(GrabbitCli::hostAndJobIdsToStrings).
        doOnNext(out::println).
        then().block(Duration.ofMinutes(30));
}

Example - Processor and Subscriber

/**
 * Start a process on {@link #executorService} that connects to the Grabbit client at `baseUri` and
 * publishes the job ids.
 */
private Publisher<Long> startJobsOnHost(final URI baseUri, UsernameAndPassword credentials) {
    final Processor<Long, Long> processor = WorkQueueProcessor.share(executorService);
    executorService.execute(() -> startJobsOnHostWithSubscriber(processor, baseUri, credentials));
    return processor;
}

private void startJobsOnHostWithSubscriber(Subscriber<Long> jobIdSubscriber, URI baseUri,
                                           UsernameAndPassword credentials) {
    // ...
}

private void parseStartJobsOutput(String startJobsOutput, Subscriber<Long> jobIdSub) {
    // the output from starting a job looks like "[123,125]"
    val matcher = JOB_IDS_PATTERN.matcher(startJobsOutput);
    if (matcher.matches()) {
        val jobIdsStr = (@NonNull String)matcher.group("jobIds");
        Arrays.stream(jobIdsStr.split(",")).
            map(String::trim).
            map(Long::valueOf).
            forEach(jobId -> jobIdSub.onNext(jobId));
        jobIdSub.onComplete();
    }
    else {
        jobIdSub.onError(new IllegalStateException("Could not parse job ids from: " + startJobsOutput));
    }
}

AsciiDoclet w/ AsciiDoctor Diagram

/**
 * Entry point for the command line arguments.
 *
 * [plantuml]
 * ....
 * start
 * :CliOptions.create(argv);
 * if (Either(error message, CliOptions)) then (error message)
 *     :print error message to STDERR;
 *     end
 * else (CliOptions)
 *     :run(cliOptions);
 *     stop
 * endif
 * ....
 *
 * @param args CLI arguments
 */
public static void main(String[] args) {
    // ...
}

/**
 * Runs the appropriate process(es) for the command line options.
 *
 * Output from the processes are sent to STDOUT.
 *
 * [plantuml]
 * ....
 * :CliOptions.create(argv);
 * if (start) then (true)
 *     if (monitor) then (true)
 *         :startWithMonitor(..);
 *     else (false)
 *         :startWithNoMonitor(..);
 *     endif
 * else (false)
 *     :monitorJobs(..);
 * endif
 * ....
 *
 * @see #startWithMonitor(String, String, String, PrintStream)
 * @see #startWithNoMonitor(String, String, String, PrintStream)
 * @see #monitorJobs(String, String, String, PrintStream)
 */
public static void run(CliOptions options) throws IOException {
    // ...
}
javadoc with uml

Typed Builders

public static PollingJobMonitor.Builder builder() {
    return new PollingJobMonitor.Builder();
}
public static class Builder {
    // ...
    public PollingJobMonitor build() throws IllegalStateException {
        // ...
    }
    public Builder jobStatusCache(JobStatusCache jobStatusCache) {
        this.jobStatusCache = jobStatusCache; return this;
    }
    public Builder executor(ExecutorService executorService) {
        this.executorService = executorService; return this;
    }
    public Builder environment(Environment environment) {
        this.environment = environment; return this;
    }
    public Builder poller(JobStatusPoller jobStatusPoller) {
        this.jobStatusPoller = jobStatusPoller; return this;
    }
    public Builder sleep(long sleepMs) {
        this.sleepMs = sleepMs; return this;
    }
}
  • You only need to set ExecutorService or Environment, but at least one of them must be set.
  • JobStatusCache is required, but JobStatusPoller and sleep are optional.
  • If build() is called without all the requirements being met, it throws an IllegalStateException.
public static B.WithJobStatusCache builder() {
    return new B.Builder();
}
public interface B { // namespacing
    class Builder implements Build, ExecutorOrEnvironment, WithJobStatusCache {
        //... virtually identical to before
    }
    interface Build {
        PollingJobMonitor build();
        Build poller(JobStatusPoller jobStatusPoller);
        Build sleep(long sleepMs);
    }
    interface WithExecutor {
        Build executor(ExecutorService executorService);
    }
    interface WithEnvironment {
        Build environment(Environment environment);
    }
    interface ExecutorOrEnvironment extends WithExecutor, WithEnvironment {
    }
    interface WithJobStatusCache {
        ExecutorOrEnvironment jobStatusCache(JobStatusCache jobStatusCache);
    }
}
  • The type system is acting as a compile-time state machine, ensuring that it’s impossible to call build() in a bad state.

Static Analysis (“Linting”)

Primary Java Linters

There’s a great deal of overlap between them.

They have different strengths/focusses, so use all three.

Review

  1. Capsule
  2. Lombok
  3. JDK 8
    • Streams & Lambdas
    • Where’s Elvis? ?:
    • Misc
  4. Softened Throws
  5. Either
  6. Checker Framework
  7. Reactive Streams
  8. AsciiDoclet
  9. Typed Builders
  10. Static Analysis

Q & A