KludJe: make any type auto-closeable

Not all resources implement AutoCloseable. The KludJe Res type enables RAII for any type. Use method references to define the release method:

  public void consumeEvents(Source source, Result result, UnaryOperator<XMLEvent> eventProcessor) {

    try (Res<XMLEventReader> reader = res(XMLEventReader::close, inputFactory.createXMLEventReader(source));
         Res<XMLEventWriter> writer = res(XMLEventWriter::close, outputFactory.createXMLEventWriter(result))) {

      while (reader.unwrap().hasNext()) {
        XMLEvent event = reader.unwrap().nextEvent();
        event = eventProcessor.apply(event);
        writer.unwrap().add(event);
      }
    } catch (XMLStreamException e) {
      throw new UncheckedXMLStreamException(e);
    }
  }
 

Expand example

KludJe: make any type auto-closeable

import uk.kludje.Res;

import javax.xml.stream.*;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import java.util.function.UnaryOperator;

import static uk.kludje.Res.res;

public class XmlEventProcessor {

  private final XMLInputFactory inputFactory;
  private final XMLOutputFactory outputFactory;

  public XmlEventProcessor(XMLInputFactory inputFactory, XMLOutputFactory outputFactory) {
    this.inputFactory = inputFactory;
    this.outputFactory = outputFactory;
  }

  public void consumeEvents(Source source, Result result, UnaryOperator<XMLEvent> eventProcessor) {
    try (Res<XMLEventReader> reader = res(XMLEventReader::close, inputFactory.createXMLEventReader(source));
         Res<XMLEventWriter> writer = res(XMLEventWriter::close, outputFactory.createXMLEventWriter(result))) {

      while (reader.unwrap().hasNext()) {
        XMLEvent event = reader.unwrap().nextEvent();
        event = eventProcessor.apply(event);
        writer.unwrap().add(event);
      }
    } catch (XMLStreamException e) {
      throw new UncheckedXMLStreamException(e);
    }
  }

  public static class UncheckedXMLStreamException extends RuntimeException {
    public UncheckedXMLStreamException(XMLStreamException e) {
      super(e);
    }
  }
}

Before:

import javax.xml.stream.*;
import javax.xml.stream.events.XMLEvent;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import java.util.function.UnaryOperator;

public class XmlEventProcessorVerbose {

  private final XMLInputFactory inputFactory;
  private final XMLOutputFactory outputFactory;

  public XmlEventProcessorVerbose(XMLInputFactory inputFactory, XMLOutputFactory outputFactory) {
    this.inputFactory = inputFactory;
    this.outputFactory = outputFactory;
  }

  public void consumeEvents(Source source, Result result, UnaryOperator<XMLEvent> consumer) {
    try {
      XMLEventReader reader = inputFactory.createXMLEventReader(source);
      try {
        XMLEventWriter writer = outputFactory.createXMLEventWriter(result);
        try {

          while (reader.hasNext()) {
            XMLEvent event = reader.nextEvent();
            event = consumer.apply(event);
            writer.add(event);
          }
        } finally {
          writer.close();
        }
      } finally {
        reader.close();
      }
    } catch (XMLStreamException e) {
      throw new UncheckedXMLStreamException(e);
    }
  }

  public static class UncheckedXMLStreamException extends RuntimeException {
    public UncheckedXMLStreamException(XMLStreamException e) {
      super(e);
    }
  }
}

KludJe: easy equals, hashCode and toString

Define significant properties a single time:

  private static final Meta<Product> META = Meta.meta(Product.class)
      .longs(p -> p.id)
      .objects(p -> p.description)
      .ints(p -> p.inventory);

  @Override
  public boolean equals(Object obj) {
    return META.equals(this, obj);
  }

  @Override
  public int hashCode() {
    return META.hashCode(this);
  }

  @Override
  public String toString() {
    return META.toString(this);
  }
 

Expand example

KludJe: easy equals, hashCode and toString

import uk.kludje.Meta;

import static uk.kludje.Meta.meta;

public class Product {
  private static final Meta<Product> META = meta(Product.class)
      .longs(p -> p.id)
      .objects(p -> p.description)
      .ints(p -> p.inventory);

  private final long id;
  private final String description;
  private final int inventory;

  public Product(long id, String description, int inventory) {
    this.id = id;
    this.description = description;
    this.inventory = inventory;
  }

  public long getId() {
    return id;
  }

  public String getDescription() {
    return description;
  }

  public int getInventory() {
    return inventory;
  }

  @Override
  public boolean equals(Object obj) {
    return META.equals(this, obj);
  }

  @Override
  public int hashCode() {
    return META.hashCode(this);
  }

  @Override
  public String toString() {
    return META.toString(this);
  }
}

Before:

public class ProductVerbose {

  private final long id;
  private final String description;
  private final int inventory;

  public ProductVerbose(long id, String description, int inventory) {
    this.id = id;
    this.description = description;
    this.inventory = inventory;
  }

  public long getId() {
    return id;
  }

  public String getDescription() {
    return description;
  }

  public int getInventory() {
    return inventory;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    ProductVerbose that = (ProductVerbose) o;

    if (id != that.id) return false;
    if (inventory != that.inventory) return false;
    return description != null ? description.equals(that.description) : that.description == null;

  }

  @Override
  public int hashCode() {
    int result = (int) (id ^ (id >>> 32));
    result = 31 * result + (description != null ? description.hashCode() : 0);
    result = 31 * result + inventory;
    return result;
  }

  @Override
  public String toString() {
    return "ProductVerbose {" +
      id
      + ", "
      + description
      + ", "
      + inventory
      + '}';
  }
}

KludJe: checked exceptions as unchecked

The linesIn method declares that it throws IOException which usually requires it to be handled in a catch block. The UFunction interface allows passing exceptions up through the lambda code without catch blocks:

  public Map<Path, Long> countLines(Collection<? extends Path> paths) throws IOException {
    return paths.stream()
        .parallel()
        .collect(toConcurrentMap(p -> p, asUFunction(this::linesIn)));
  }

  private long linesIn(Path path) throws IOException {
    try (Stream<String> lines = Files.lines(path, UTF_8)) {
      return lines.count();
    }
  }

As well as providing unchecked implementations standard library functional interfaces KludJe provides an annotation processor for generating your own types.

Expand example

KludJe: checked exceptions as unchecked

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.nio.charset.StandardCharsets.UTF_8;
import static uk.kludje.fn.function.UFunction.asUFunction;

/**
 * Line counter - stream approach with checked exception handling.
 */
public class LineCounter {

  public Map<Path, Long> countLines(Collection<? extends Path> paths) throws IOException {
    return paths.stream()
        .parallel()
        .collect(Collectors.<Path, Path, Long>toConcurrentMap(p -> p, asUFunction(this::linesIn)));
  }

  private long linesIn(Path path) throws IOException {
    try (Stream<String> lines = Files.lines(path, UTF_8)) {
      return lines.count();
    }
  }
}

Before:

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.nio.charset.StandardCharsets.UTF_8;

public class LineCounterVerbose {

  public Map<Path, Long> countLines(Collection paths) throws IOException {
    try {
      ConcurrentMap<Path, Long> result = paths.stream()
          .parallel()
          .collect(Collectors.<Path, Path, Long>toConcurrentMap(p -> p, this::linesIn));
      return result;
    } catch (UncheckedIOException e) {
      throw e.getCause();
    }
  }

  private Long linesIn(Path path) {
    try (Stream<String> lines = Files.lines(path, UTF_8)) {
      return lines.count();
    } catch (IOException e) {
      throw new UncheckedIOException(e);
    }
  }
}

KludJe: reduce null check verbosity

This code avoids making explicit null checks in a deep object graph:


    // import uk.kludje.Nullifier;
    F f = Nullifier.eval(a, A::getB, B::getC, C::getD, D::getE, E::getF);
        

This replaces this kind of code:


    if (a != null) {
      B b = a.getB();
      if (b != null) {
        C c = b.getC();
            etc.
        

KludJe: Download

Binaries are available from Maven central:

Sources (Apache 2.0 license) are available on GitHub.

KludJe

kludge n. Slang

A system, especially a computer system, that is constituted of poorly matched elements or of elements originally intended for other applications.

KludJe is a Java lambda API for use with Java 8 and above.

Examples:

  1. Reduce equals/hashCode/toString verbosity
  2. Reduce checked exception clutter
  3. Refactor nested getter null checks to a single expression
  4. Make any type auto-closeable and eliminate nested try blocks