Tuesday, May 08, 2018

Five for loops in Java

  1. List of strings
  2. Traditional for loop
  3. Enhanced for loop from Java 5
  4. forEach lambda from Java 8
  5. Stream.iterate with one lambda from Java 8
  6. Stream.iterate with two lambdas from Java 9
  7. All five for loops together

The main conceptual difference between for and while loops is that a for loop uses some form of index or counting variable to track what row, record or iteration you are up to whereas a while loop does not.

Here are five ways to use "for loops" in Java.

To top of post.

List of strings

I start out with a list of strings.

final List<String> strings = Arrays.asList("one", "two", "three", "four", "five");

To top of post.

Traditional for loop

The traditional for loop allows us to specify three things:

  1. The seed value: the initial value to give our index. The seed is 0 in this example.
  2. The hasNext condition (a.k.a. exit condition): a boolean expression to answer the question should we continue? In our example, we continue as long as index is less than the size of the strings list.
  3. The next value: an expression to generate the next index. In our example, we get next by adding 1 to index.
System.out.printf("Old school for loop.%n");
for (int index = 0; index < strings.size(); index++) {
   System.out.printf("Next string is %s%n", strings.get(index));
}

To top of post.

Enhanced for loop from Java 5

In Java 5, we got the enhanced for loop, which feels more like a while loop because we don't have an index any more. The enhanced for loop specifically helps us in situations where you are iterating over all items in a collection. You don't need to specify seed, hasNext or next: they are all implied. Start with the first element, end with a last element and make sure we loop over each element in between.

System.out.printf("%nEnhanced for loop from Java 5.%n");
for (final String string : strings) {
   System.out.printf("Next string is %s%n", string);
}

To top of post.

forEach lambda from Java 8

Java 8 gave us lambdas and a new way to loop over elements. Again, this feels more like a while in some ways because we don't have an index.

Just like the enhanced for loop, it is perfect for situations where you have a collection and you want to do something with each element. Any collection in Java has forEach (through the java.lang.Iterable interface).

System.out.printf("%nThe forEach lambda from Java 8.%n");
strings.forEach(string -> System.out.printf("Next string is %s%n", string));

Java 8 introduced java.util.stream.Stream and gave us a whole new way to think about looping. Instead of thinking about how to formulate a while or for loop (by specifying seed and exit conditions), with streams we focus on defining the set of values (a stream) that we want to operate on. Stream also has forEach, so we start thinking more about "iterating over a stream of values".

To top of post.

Stream.iterate with one lambda from Java 8

Stream.iterate provides a way to use for loop ideas with a stream of values.

System.out.printf("%nUsing Stream.iterate with one lambda from Java 8.%n");
Stream.iterate(0, index -> index + 1)
   .limit(strings.size())
   .forEach(index -> {
         System.out.printf("Next string is %s%n", strings.get(index));
});

This version of iterate() has only two arguments, allowing us to specify:

  1. The seed value: the initial value to give our index. The seed is 0 in this example.
  2. The next value: an expression to generate the next index. In our example, we get next by adding 1 to index.

We still need a way to provide the exit condition though, so I have added a limit() call from the Stream API.

To top of post.

Stream.iterate with two lambdas from Java 9

Stream.iterate in Java 9 got overloaded to give a way to specify all three things we use in a traditional for loop.

System.out.printf("%nUsing Stream.iterate with two lambdas from Java 9.%n");
Stream.iterate(0, index -> index < strings.size(), index -> index + 1).forEach(index -> {
   System.out.printf("Next string is %s%n", strings.get(index));
});

Now we can give:

  1. The seed value: the initial value to give our index. The seed is 0 in this example.
  2. The hasNext condition (a.k.a. exit condition): a boolean expression to answer the question should we continue? In our example, we continue as long as index is less than the size of the strings list.
  3. The next value: an expression to generate the next index. In our example, we get next by adding 1 to index.

To top of post.

All five for loops together

In summary, the techniques all together are below.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

public final class Scrapbook {

   public static void main(final String[] args) {
      final List<String> strings = Arrays.asList("one", "two", "three", "four", "five");

      System.out.printf("Old school for loop.%n");
      for (int index = 0; index < strings.size(); index++) {
         System.out.printf("Next string is %s%n", strings.get(index));
      }

      System.out.printf("%nEnhanced for loop from Java 5.%n");
      for (final String string : strings) {
         System.out.printf("Next string is %s%n", string);
      }

      System.out.printf("%nThe forEach lambda from Java 8.%n");
      strings.forEach(string -> System.out.printf("Next string is %s%n", string));

      System.out.printf("%nUsing Stream.iterate with one lambda from Java 8.%n");
      Stream.iterate(0, index -> index + 1).limit(strings.size()).forEach(index -> {
         System.out.printf("Next string is %s%n", strings.get(index));
      });

      System.out.printf("%nUsing Stream.iterate with two lambdas from Java 9.%n");
      Stream.iterate(0, index -> index < strings.size(), index -> index + 1).forEach(index -> {
         System.out.printf("Next string is %s%n", strings.get(index));
      });

   }
}

Which gives the following output.

Old school for loop.
Next string is one
Next string is two
Next string is three
Next string is four
Next string is five

Enhanced for loop from Java 5.
Next string is one
Next string is two
Next string is three
Next string is four
Next string is five

The forEach lambda from Java 8.
Next string is one
Next string is two
Next string is three
Next string is four
Next string is five

Using Stream.iterate with one lambda from Java 8.
Next string is one
Next string is two
Next string is three
Next string is four
Next string is five

Using Stream.iterate with two lambdas from Java 9.
Next string is one
Next string is two
Next string is three
Next string is four
Next string is five

To top of post.

Saturday, May 05, 2018

De-serialise JSON string to map with multiple value types

  1. Map of String to LocalDateTime
  2. Map of String to multiple date types using a custom class
  3. Map of String to multiple date types using a custom de-serialiser
  4. Maven dependencies

I have a map that contains dates and strings. I convert it to a JSON string and later want to convert it back. In Java, the Jackson API is perfect for this. In this post I explore three methods of doing this that cater for different levels of complexity involved in how many types of value are contained in the map.

I created this blog entry because I wanted to fully explore the options I found when trying to solve this problem in an app I am working on. The solution I ended up going ahead with came about from some great feedback in this Stack Overflow post, thanks to @aussie.

To top of post.

Map of String to LocalDateTime

First, here is the map of String keys to LocalDateTime object values.

// Create dates.
final LocalDateTime nowLocal = new LocalDateTime();
final LocalDateTime notNowLocal =
      new LocalDateTime(2007, 3, 25, 2, 30, 0);

// Put them into a map.
final Map<String, LocalDateTime> dateMap = new HashMap<>();
dateMap.put("nowLocal", nowLocal);
dateMap.put("notNowLocal", notNowLocal);

Here is how I serialise it to a JSON string using Jackson.

// Create a mapper.
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());
mapper.configure(com.fasterxml.jackson.databind
      .SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);

// Serialise the map as a JSON string.
final String dateMapJson = mapper.writeValueAsString(dateMap);
System.out.println(dateMapJson);

The result of this code is below.

{"notNowLocal":"2007-03-25T02:30:00.000","nowLocal":"2018-05-05T15:57:25.108"}

This is how I de-serialise the string back into a map.

/*
 * Create a type definition of my map: something that will tell Jackson
 * I want back a HashMap whose keys are String type objects and whose
 * values are LocalDateTime objects.
 */
final TypeFactory typeFactory = mapper.getTypeFactory();
final MapType mapType = typeFactory.constructMapType(
      HashMap.class, String.class, LocalDateTime.class);

// Use the mapper from above to convert the JSON string back to a map.
final Map<String, LocalDateTime> dateMapFromJson =
      mapper.readValue(dateMapJson, mapType);

Here is a complete example of this code.

import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS;
import static java.lang.String.format;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.joda.time.LocalDateTime;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.joda.JodaModule;

/**
 * Test converting a map of {@link LocalDateTime}s to a JSON string and back.
 */
public class JodaTimeMapTestOneDateType {

   public static void main(final String[] args) throws Exception {

      // Map with dates.
      final LocalDateTime nowLocal = new LocalDateTime();
      final LocalDateTime notNowLocal =
            new LocalDateTime(2007, 3, 25, 2, 30, 0);
      final Map<String, LocalDateTime> dateMap = new HashMap<>();
      dateMap.put("nowLocal", nowLocal);
      dateMap.put("notNowLocal", notNowLocal);

      // Serialise map to string.
      final ObjectMapper mapper = mapper();
      final String dateMapJson = mapper.writeValueAsString(dateMap);

      // De-serialise string to map.
      final TypeFactory typeFactory = mapper.getTypeFactory();
      final MapType mapType = typeFactory.constructMapType(HashMap.class,
            String.class, LocalDateTime.class);
      final Map<String, LocalDateTime> dateMapFromJson =
            mapper.readValue(dateMapJson, mapType);

      // Print off the maps, JSON string and proof that the maps are equal.
      System.out.printf("Starting map.%s%n%n", mapToString(dateMap));
      System.out.printf("Map serialised to a JSON string.%n   %s%n%n",
            dateMapJson);
      System.out.printf("Map de-serialised from JSON.%s%n%n",
            mapToString(dateMapFromJson));
      System.out.printf("Maps are equal: %s%n",
            dateMap.equals(dateMapFromJson));
   }

   /**
    * @param map
    *           with strings and dates.
    * @return string that tells me what the keys, value classes and values are.
    */
   private static String mapToString(final Map<String, LocalDateTime> map) {
      return format("%n   %-13s %-30s %s", "key", "value class", "value") //
            + format("%n   %-13s %-30s %s", "---", "-----------", "-----") //
            + map.entrySet().stream().map(entry -> {
               final Object value = entry.getValue();
               return format("%n   %-13s %-30s %s", entry.getKey(),
                     value.getClass().getName(), value);
            }).collect(Collectors.joining());
   }

   /**
    * @return object that transform my maps to strings and back.
    */
   private static ObjectMapper mapper() {
      final ObjectMapper mapper = new ObjectMapper();
      mapper.registerModule(new JodaModule());
      mapper.configure(WRITE_DATES_AS_TIMESTAMPS, false);
      return mapper;

   }

}

The result of running this code is below.

Starting map.
   key           value class                    value
   ---           -----------                    -----
   notNowLocal   org.joda.time.LocalDateTime    2007-03-25T02:30:00.000
   nowLocal      org.joda.time.LocalDateTime    2018-05-05T16:16:23.572

Map serialised to a JSON string.
   {"notNowLocal":"2007-03-25T02:30:00.000","nowLocal":"2018-05-05T16:16:23.572"}

Map de-serialised from JSON.
   key           value class                    value
   ---           -----------                    -----
   notNowLocal   org.joda.time.LocalDateTime    2007-03-25T02:30:00.000
   nowLocal      org.joda.time.LocalDateTime    2018-05-05T16:16:23.572

Maps are equal: true

To top of post.

Map of String to multiple date types using a custom class

What about if my map has multiple types, not just LocalDateTime?

// Different date objects.
final DateTime now = new DateTime().withZone(DateTimeZone.UTC);
final LocalDateTime nowLocal = new LocalDateTime();
final LocalDateTime notNowLocal =
      new LocalDateTime(2007, 3, 25, 2, 30, 0);

// Make a map with the dates.
final Map<String, Object> dateMap = new HashMap<>();
dateMap.put("now", now);
dateMap.put("nowLocal", nowLocal);
dateMap.put("notNowLocal", notNowLocal);

// Create object mapper that knows how to write Jodatime dates.
final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());
mapper.configure(WRITE_DATES_AS_TIMESTAMPS, false);

// Serialise map to JSON string.
final String dateMapJson = mapper.writeValueAsString(dateMap);
System.out.println(dateMapJson);

The output of this code is below.

{"now":"2018-05-05T06:35:21.330Z","notNowLocal":"2007-03-25T02:30:00.000","nowLocal":"2018-05-05T16:35:21.441"}

The problem is how to tell Jackson about the combination of types to expect. In the previous section, I created a type definition to tell Jackson I want back a HashMap whose keys are String type objects and whose values are LocalDateTime objects.

final TypeFactory typeFactory = mapper.getTypeFactory();
final MapType mapType = typeFactory.constructMapType(
      HashMap.class, String.class, LocalDateTime.class);

There is no other constructMapType method that lets me define multiple type definitions. However, look at the JSON string in another way.

{
           "now" : "2018-05-05T06:35:21.330Z",
   "notNowLocal" : "2007-03-25T02:30:00.000",
      "nowLocal" : "2018-05-05T16:35:21.441"
}

Doesn't this JSON map look like it could fit a Java class with three date variables called now, notNowLocal and nowLocal?

public class DateTimeHolder {

   private DateTime now;
   private LocalDateTime nowLocal;
   private LocalDateTime notNowLocal;

   public Map<String, Object> buildMap() {
      final Map<String, Object> dateMap = new HashMap<>();
      dateMap.put("now", now);
      dateMap.put("nowLocal", nowLocal);
      dateMap.put("notNowLocal", notNowLocal);
      return dateMap;
   }

   // Getters and setters...

}

Now I have a class with variables whose names match the keys in my map, and whose variable types match the types of my map values. I can use this to de-serialise the JSON string, as below.

// Tell Jackson to read the string and convert it to a DateTimeHolder.
final DateTimeHolder holder =
      mapper.readValue(dateMapJson, DateTimeHolder.class);

// Which I can now convert back into a map.
final Map<String, Object> dateMapFromJson = holder.buildMap();

This works fine. Here is the complete example.

import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS;
import static java.lang.String.format;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.joda.JodaModule;

/**
 * Test converting a map of multiple date types to a JSON string and back.
 */
public class JodaTimeMapTestUsingCustomType {

   public static void main(final String[] args) throws Exception {
      // Map with dates.
      final DateTime now = new DateTime().withZone(DateTimeZone.UTC);
      final LocalDateTime nowLocal = new LocalDateTime();
      final LocalDateTime notNowLocal =
            new LocalDateTime(2007, 3, 25, 2, 30, 0);
      final Map<String, Object> dateMap = new HashMap<>();
      dateMap.put("now", now);
      dateMap.put("nowLocal", nowLocal);
      dateMap.put("notNowLocal", notNowLocal);

      // Serialise map to string.
      final ObjectMapper mapper = mapper();
      final String dateMapJson = mapper.writeValueAsString(dateMap);

      // De-serialise string to map.
      final DateTimeHolder holder =
            mapper.readValue(dateMapJson, DateTimeHolder.class);
      final Map<String, Object> dateMapFromJson = holder.buildMap();

      // Print off the maps, JSON string and proof that the maps are equal.
      System.out.printf("Starting map.%s%n%n", mapToString(dateMap));
      System.out.printf("Map serialised to a JSON string.%n   %s%n%n",
            dateMapJson);
      System.out.printf("Map de-serialised from JSON.%s%n%n",
            mapToString(dateMapFromJson));
      System.out.printf("Maps are equal: %s%n",
            dateMap.equals(dateMapFromJson));
   }

   /**
    * @param map
    *           with strings and dates.
    * @return string that tells me what the keys, value classes and values are.
    */
   private static String mapToString(final Map<String, Object> map) {
      return format("%n   %-13s %-30s %s", "key", "value class", "value") //
            + format("%n   %-13s %-30s %s", "---", "-----------", "-----") //
            + map.entrySet().stream().map(entry -> {
               final Object value = entry.getValue();
               return format("%n   %-13s %-30s %s", entry.getKey(),
                     value.getClass().getName(), value);
            }).collect(Collectors.joining());
   }

   /**
    * @return object that transform my maps to strings and back.
    */
   private static ObjectMapper mapper() {
      final ObjectMapper mapper = new ObjectMapper();
      mapper.registerModule(new JodaModule());
      mapper.configure(WRITE_DATES_AS_TIMESTAMPS, false);

      return mapper;

   }

}

This is the DateHolder class.

import java.util.HashMap;
import java.util.Map;

import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;

/**
 * This class defines dates whose variable names match the keys in my map.
 */
public class DateTimeHolder {

   private DateTime now;
   private LocalDateTime nowLocal;
   private LocalDateTime notNowLocal;

   public Map<String, Object> buildMap() {
      final Map<String, Object> dateMap = new HashMap<>();
      dateMap.put("now", now);
      dateMap.put("nowLocal", nowLocal);
      dateMap.put("notNowLocal", notNowLocal);
      return dateMap;
   }

   public DateTime getNow() {
      return now;
   }

   public void setNow(final DateTime now) {
      this.now = now;
   }

   public LocalDateTime getNowLocal() {
      return nowLocal;
   }

   public void setNowLocal(final LocalDateTime nowLocal) {
      this.nowLocal = nowLocal;
   }

   public LocalDateTime getNotNowLocal() {
      return notNowLocal;
   }

   public void setNotNowLocal(final LocalDateTime notNowLocal) {
      this.notNowLocal = notNowLocal;
   }

}

The output of running this code is below.

Starting map.
   key           value class                    value
   ---           -----------                    -----
   now           org.joda.time.DateTime         2018-05-05T06:58:52.104Z
   notNowLocal   org.joda.time.LocalDateTime    2007-03-25T02:30:00.000
   nowLocal      org.joda.time.LocalDateTime    2018-05-05T16:58:52.260

Map serialised to a JSON string.
   {"now":"2018-05-05T06:58:52.104Z","notNowLocal":"2007-03-25T02:30:00.000","nowLocal":"2018-05-05T16:58:52.260"}

Map de-serialised from JSON.
   key           value class                    value
   ---           -----------                    -----
   now           org.joda.time.DateTime         2018-05-05T06:58:52.104Z
   notNowLocal   org.joda.time.LocalDateTime    2007-03-25T02:30:00.000
   nowLocal      org.joda.time.LocalDateTime    2018-05-05T16:58:52.260

Maps are equal: true

This is great, and works. However, if I am going to write a custom class for this, it is likely that I don't need a map at all. I should just use DateHolder everywhere.

The vast majority of times I use Jackson it is for just this case: I want to serialise a custom class to JSON and back again (not a map). This technique is preferable particularly when you have objects with more than a few fields of multiple types, and even more so when you are talking about compound objects - when your variables are other custom classes and so on.

To top of post.

Map of String to multiple date types using a custom de-serialiser

The whole reason I started looking into this was because I had a situation with the following characteristics.

  • Lots of maps with different sets of keys.
  • The values are only strings or dates.
  • The keys used for dates are always the same, e.g. a key of abc will always map to a DateTime, whereas a key of xyz will always map to a LocalDateTime.
  • I didn't need the whole map in my application, just a few specific fields that were common across all the maps.

All of this means is that if I wrote a custom class, the only use it would have would be for translation purposes and would not be used anywhere else. It felt like it would make more sense to see if I could provided a better way to tell Jackson how to translate my maps.

One way to do this is to provide a custom de-serialiser. Here is the class declaration and the single method I need to override.

public class JodaMapDeserialiser extends StdDeserializer<Object> {

   @Override
   public Object deserialize(final JsonParser p,
         final DeserializationContext ctxt)
         throws IOException, JsonProcessingException {
   }

}

The first step is to create a way to translate my keys in such a way that I can tell what type of Jodatime a specific key would map to (otherwise I treat the value as a string).

/** Mapping between keys in the map to a type of Joda time. */
static enum DateType {
   DATE_TIME("now"), LOCAL_DATE_TIME("notNowLocal", "nowLocal");

   final List<String> keys;

   DateType(final String... keys) {
      this.keys = Arrays.asList(keys);
   }

   public static DateType forKeyString(final String keyString) {
      return Stream.of(values()).filter(dateTypes -> dateTypes.keys.contains(keyString)) //
            .findFirst().orElse(null);
   }
}

Here is some code to briefly show how this enum works.

System.out.printf("%s: %s.%n", "now", DateType.forKeyString("now"));
System.out.printf("%s: %s.%n", "nowLocal", DateType.forKeyString("nowLocal"));
System.out.printf("%s: %s.%n", "notNowLocal", DateType.forKeyString("notNowLocal"));
System.out.printf("%s: %s.%n", "anythingElse", DateType.forKeyString("anythingElse"));

The output from the above code is below.

now: DATE_TIME.
nowLocal: LOCAL_DATE_TIME.
notNowLocal: LOCAL_DATE_TIME.
anythingElse: null.

Now assume I have a method that gets the key and value from every map entry. Here is how I can translate the value into a Jodatime date or leave the value as a string, all depending on the key.

// Each entry in the map has a key and value.
final String value;     // Actual code has
final String key;       // values for these two.

// Convert the value depending on what the key is.
switch (DateType.forKeyString(key)) {
   case DATE_TIME:
      return DateTime.parse(value);

   case LOCAL_DATE_TIME:
      return LocalDateTime.parse(value);

   default:
      return value;
}

Again, I create a map with dates in it.

// Create dates.
final DateTime now = new DateTime().withZone(DateTimeZone.UTC);
final LocalDateTime nowLocal = new LocalDateTime();
final LocalDateTime notNowLocal =
      new LocalDateTime(2007, 3, 25, 2, 30, 0);

// Put them into a map.
final Map<String, Object> dateMap = new HashMap<>();
dateMap.put("now", now);
dateMap.put("nowLocal", nowLocal);
dateMap.put("notNowLocal", notNowLocal);

This time, the mapper is more complicated because I have to register my custom de-serialiser module.

final ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JodaModule());
mapper.configure(WRITE_DATES_AS_TIMESTAMPS, false);

final SimpleModule dateDeserializerModule = new SimpleModule();
dateDeserializerModule.addDeserializer(Object.class,
      new JodaMapDeserialiser());
mapper.registerModule(dateDeserializerModule);

The code to serialise and then de-serialise the map looks quite familiar. Note that I am still creating a type definition to tell Jackson I want back a HashMap with String keys and Object values.

// Serialise map to string.
final ObjectMapper mapper = mapper();
final String dateMapJson = mapper.writeValueAsString(dateMap);

// De-serialise string to map.
final TypeFactory typeFactory = mapper.getTypeFactory();
final MapType mapType = typeFactory.constructMapType(HashMap.class,
      String.class, Object.class);
final Map<String, Object> dateMapFromJson =
      mapper.readValue(dateMapJson, mapType);

Here is the complete example. First is the de-serialiser.

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import org.joda.time.DateTime;
import org.joda.time.LocalDateTime;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;

/** De-serialise values from a map that contains Joda times and strings. */
public class JodaMapDeserialiser extends StdDeserializer<Object> {

   /** Mapping between keys in the map to a type of Joda time. */
   static enum DateType {
      DATE_TIME("now"), LOCAL_DATE_TIME("notNowLocal", "nowLocal");

      final List<String> keys;

      DateType(final String... keys) {
         this.keys = Arrays.asList(keys);
      }

      public static DateType forKeyString(final String keyString) {
         return Stream.of(values())
               .filter(dateTypes -> dateTypes.keys.contains(keyString)) //
               .findFirst().orElse(null);
      }
   }

   public JodaMapDeserialiser() {
      super(Object.class);
   }

   @Override
   public Object deserialize(final JsonParser p,
         final DeserializationContext ctxt)
         throws IOException, JsonProcessingException {

      // Each entry in the map has a key and value.
      final String value = p.readValueAs(String.class);
      final String key = p.getCurrentName();

      // Convert the value depending on what the key is.
      switch (DateType.forKeyString(key)) {
         case DATE_TIME:
            return DateTime.parse(value);

         case LOCAL_DATE_TIME:
            return LocalDateTime.parse(value);

         default:
            return value;
      }

   }

   /**
    * Briefly test this enum.
    *
    * @param args
    *           not used.
    */
   public static void main(final String[] args) {
      System.out.printf("%s: %s.%n", "now", DateType.forKeyString("now"));
      System.out.printf("%s: %s.%n", "nowLocal",
            DateType.forKeyString("nowLocal"));
      System.out.printf("%s: %s.%n", "notNowLocal",
            DateType.forKeyString("notNowLocal"));
      System.out.printf("%s: %s.%n", "anythingElse",
            DateType.forKeyString("anythingElse"));
   }

}

And the class that creates the map and does serialisation etc.

import static com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS;
import static java.lang.String.format;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.LocalDateTime;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.joda.JodaModule;

/**
 * Test converting a map of multiple date types to a JSON string and back.
 */
public class JodaTimeMapTestMultipleDateTypes {

   public static void main(final String[] args) throws Exception {
      // Map with dates.
      final DateTime now = new DateTime().withZone(DateTimeZone.UTC);
      final LocalDateTime nowLocal = new LocalDateTime();
      final LocalDateTime notNowLocal =
            new LocalDateTime(2007, 3, 25, 2, 30, 0);
      final Map<String, Object> dateMap = new HashMap<>();
      dateMap.put("now", now);
      dateMap.put("nowLocal", nowLocal);
      dateMap.put("notNowLocal", notNowLocal);

      // Serialise map to string.
      final ObjectMapper mapper = mapper();
      final String dateMapJson = mapper.writeValueAsString(dateMap);

      // De-serialise string to map.
      final TypeFactory typeFactory = mapper.getTypeFactory();
      final MapType mapType = typeFactory.constructMapType(HashMap.class,
            String.class, Object.class);
      final Map<String, Object> dateMapFromJson =
            mapper.readValue(dateMapJson, mapType);

      // Print off the maps, JSON string and proof that the maps are equal.
      System.out.printf("Starting map.%s%n%n", mapToString(dateMap));
      System.out.printf("Map serialised to a JSON string.%n   %s%n%n",
            dateMapJson);
      System.out.printf("Map de-serialised from JSON.%s%n%n",
            mapToString(dateMapFromJson));
      System.out.printf("Maps are equal: %s%n",
            dateMap.equals(dateMapFromJson));
   }

   /**
    * @param map
    *           with strings and dates.
    * @return string that tells me what the keys, value classes and values are.
    */
   private static String mapToString(final Map<String, Object> map) {
      return format("%n   %-13s %-30s %s", "key", "value class", "value") //
            + format("%n   %-13s %-30s %s", "---", "-----------", "-----") //
            + map.entrySet().stream().map(entry -> {
               final Object value = entry.getValue();
               return format("%n   %-13s %-30s %s", entry.getKey(),
                     value.getClass().getName(), value);
            }).collect(Collectors.joining());
   }

   /**
    * @return object that transform my maps to strings and back.
    */
   private static ObjectMapper mapper() {
      final ObjectMapper mapper = new ObjectMapper();
      mapper.registerModule(new JodaModule());
      mapper.configure(WRITE_DATES_AS_TIMESTAMPS, false);

      final SimpleModule dateDeserializerModule = new SimpleModule();
      dateDeserializerModule.addDeserializer(Object.class,
            new JodaMapDeserialiser());
      mapper.registerModule(dateDeserializerModule);

      return mapper;

   }

}

The output from running JodaTimeMapTestMultipleDateTypes is below.

Starting map.
   key           value class                    value
   ---           -----------                    -----
   now           org.joda.time.DateTime         2018-05-05T08:01:39.033Z
   notNowLocal   org.joda.time.LocalDateTime    2007-03-25T02:30:00.000
   nowLocal      org.joda.time.LocalDateTime    2018-05-05T18:01:39.204

Map serialised to a JSON string.
   {"now":"2018-05-05T08:01:39.033Z","notNowLocal":"2007-03-25T02:30:00.000","nowLocal":"2018-05-05T18:01:39.204"}

Map de-serialised from JSON.
   key           value class                    value
   ---           -----------                    -----
   now           org.joda.time.DateTime         2018-05-05T08:01:39.033Z
   notNowLocal   org.joda.time.LocalDateTime    2007-03-25T02:30:00.000
   nowLocal      org.joda.time.LocalDateTime    2018-05-05T18:01:39.204

Maps are equal: true

To top of post.

Maven dependencies

Finally, my maven dependencies (joda time is included in jackson-datatype-joda).

<dependency>
   <groupId>com.fasterxml.jackson.core</groupId>
   <artifactId>jackson-core</artifactId>
   <version>2.9.5</version>
</dependency>
<dependency>
   <groupId>com.fasterxml.jackson.datatype</groupId>
   <artifactId>jackson-datatype-joda</artifactId>
   <version>2.9.5</version>
</dependency>

To top of post.

Updates to post.

  • Monday, 7th of May 2018, 09:58:20 AM. Added a TOC.

Monday, April 16, 2018

Data driven tests in JUnit 5 with exception handling

My previous post on data driven tests with TestNG (Debug TestNG data driven tests when you only want to debug one row of data) showed how handy parameterized tests are and how to get around some limitations of TestNG in Eclipse.

JUnit is in my opinion a better test engine, and JUnit 5 has some great new features and offers a far superior set of tooling and mechanisms for running data driven tests, some of which I shall show here.

Data driven testing in JUnit

In this example, I am testing the ability to use regular expressions in Java: specifically the ability to find the first capturing group.

public String searchString(final String regex, final String stringToMatchAgainst) {
   final Pattern pattern = Pattern.compile(regex);
   final Matcher matcher = pattern.matcher(stringToMatchAgainst);
   if (matcher.matches()) {
      return matcher.group(1);
   }
   return null;
}

A bit about this method.

  1. This method accepts two parameters:
    1. The regular expression, named regex.
    2. The string we will apply the regular expression to, in oder to find a match. It is called stringToMatchAgainst.
  2. The method returns a string, specifically the first capturing group.
    • Quick example of regex groups. Groups are bracketed portions of a regular expression that can be referred to later as backreferences. If you were to apply the regular expression "([a-z]+) ([0-9]+)" against the string "letters 334", the first group would be "letters" and the second group would be "334".

This is the test data I am using.

Test label Regular expression String to apply regex to Expected first group
Any string matches any string. (.*) xyz xyz
Pick year digits out of a date. [0-9]{2}/[0-9]{2}/([0-9]{4}) 31/12/2032 2032
No digits found. [0-9]+ not a digit null
First two words. (?<firstTwoWords>\\w+ \\w+) .* abc xyz one two abc xyz
Bad regex. ([0-9+) 123 PatternSyntaxException: (?s)Unclosed character class.*

(This test case will throw an exception because the regular expression is invalid.)

As described earlier, the regular expressions here use capturing groups. In the fourth data set, I have also used named capturing groups - a cool regex feature added to Java in JDK 8.

Data driven testing (a.k.a. parameterized tests) is about being able to call a unit test method multiple times, giving it the actual data used to run the tests. This is as opposed to unit tests where the data is hard-coded within the test. JUnit 5's parameterized tests gives you a lot of flexibility around how to generate your test data.

  • @ValueSource - an array initialised within the annotation.
  • @EnumSource - an enum
  • @MethodSource - a factory method
  • >@CsvFileSource - a CSV file

The @MethodSource is arguably the most flexible because you can do so many things within a method to generate the returned data, including generating the data from an array, an enum, a CSV file, even doing a database lookup or calling some other service to get the data. My example will use @MethodSource and will generate a stream of JUnit 5 arguments.

private static Stream<Arguments> dataForTestSearchString() {
   return Stream.of(//
         Arguments.of("Any string matches any string.", "(.*)", "xyz", "xyz") //
         , Arguments.of("Pick year digits out of a date.", "[0-9]{2}/[0-9]{2}/([0-9]{4})", "31/12/2032", "2032") //
         , Arguments.of("No digits found.", "[0-9]+", "not a digit", null) //
         , Arguments.of("First two words.", "(?<firstTwoWords>\\w+ \\w+) .*", "abc xyz one two", "abc xyz") //
         , Arguments.of("Bad regex.", "([0-9+)", "123", "PatternSyntaxException: (?s)Unclosed character class.*") //
   );
}

Notes about this method:

  • It has no annotations, but it returns a stream of JUnit specific objects - org.junit.jupiter.params.provider.Arguments
  • Use the Java single line comment // by itself to ensure a line of code gets broken off when you use automatic formatting.
  • When creating a Java list (or array, or any collection really), putting the comma at the start of each line (after the first) makes it easy to copy and paste a line to form the next list element without having to worry about removing the comma from the end of the last element.

Here is the @Test method that consumes this test data.

@ParameterizedTest(name = "#{index} - [{0}]")
@MethodSource("dataForTestSearchString")
public void testFilesFromDirectoriesAndPattern(final String label, final String regex,
      final String stringToMatchAgainst, final String expected) {
   final String actual = searchString(regex, stringToMatchAgainst);
   assertEquals(expected, actual);
}

Notes about this method.

  • The @ParameterizedTest annotation lets you specify the "name" of each test data, which affects how each call to your test method is labelled in Eclipse. I have used the pattern #{index} - [{0}], which means each test case will be labelled with the index of that test data and whatever the first argument to the test method is (so the first argument becomes the test case label). See the screenshot below to see how wonderful this is.
  • The @MethodSource annotation allows you to name the method (or give an array naming multiple methods) that will generate data to be consumed by this test.
    • In the background, JUnit will use reflection to call the method, which has a side effect in Eclipse: if nothing else calls that method, Eclipse will give you a warning that your data generating method is unused (because it cannot work out from this annotation that you are really using it).

This is what you see in Eclipse when running this class as a JUnit test.

One of the most important advantages of JUnit in Eclipse over TestNG is that you can run individual test cases by right-clicking on the one you want to re-run and select "Run" or "Debug" (TestNG would re-run the whole lot).

Here is version 1 the full test case, incorporating the method being tested.

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class JunitDataDrivenTest {

   /**
    * @return test data. Each set of arguments should consist of
    *         <ol>
    *         <li>test label</li>
    *         <li>regex</li>
    *         <li>string we apply the regex to</li>
    *         <li>first group matched by the regex</li>
    *         </ol>
    */
   @SuppressWarnings({ "unused" }) // Eclipse thinks this method is not used.
   private static Stream<Arguments> dataForTestSearchString() {
      return Stream.of(//
            Arguments.of("Any string matches any string.", "(.*)", "xyz", "xyz") //
            , Arguments.of("Pick year digits out of a date.", "[0-9]{2}/[0-9]{2}/([0-9]{4})", "31/12/2032", "2032") //
            , Arguments.of("No digits found.", "[0-9]+", "not a digit", null) //
            , Arguments.of("First two words.", "(?<firstTwoWords>\\w+ \\w+) .*", "abc xyz one two", "abc xyz") //
            , Arguments.of("Bad regex.", "([0-9+)", "123", "PatternSyntaxException: (?s)Unclosed character class.*") //
      );
   }

   /**
    * @param label
    *           description of the current test. Data is used for reporting only.
    * @param regex
    *           regular expression.
    * @param stringToMatchAgainst
    *           string that we will apply the regex to
    * @param expected
    *           first group matched by the regex. Will be null if no match is expected.
    */
   @ParameterizedTest(name = "#{index} - [{0}]")
   @MethodSource("dataForTestSearchString")
   public void testFilesFromDirectoriesAndPattern(final String label, final String regex,
         final String stringToMatchAgainst, final String expected) {
      final String actual = searchString(regex, stringToMatchAgainst);
      assertEquals(expected, actual);
   }

   /**
    * A method being tested.
    *
    * @param regex
    *           regular expression.
    * @param stringToMatchAgainst
    *           string that we will apply the regex to
    * @return first group matched by the regex. Will be null if no match is expected.
    */
   public String searchString(final String regex, final String stringToMatchAgainst) {
      final Pattern pattern = Pattern.compile(regex);
      final Matcher matcher = pattern.matcher(stringToMatchAgainst);
      if (matcher.matches()) {
         return matcher.group(1);
      }
      return null;
   }

}

Testing for exceptions

Another new aspect of JUnit 5 is the set of assertions that use lambdas from JDK 8, particularly the assertThrows method which I will demonstrate next.

As mentioned above, the last test case throws an exception because of an invalid regular expression ("([0-9+)" doesn't close the square brackets: it should be "([0-9]+)"). A normal part of unit testing should be ensuring that code throws appropriate exceptions when something goes wrong. Here is the unit test adjusted so that if our parameterized data indicates that an exception is expected, we test for it. We test for the expected exception type and have a regular expression to test the exception's message.

@ParameterizedTest(name = "#{index} - [{0}]")
@MethodSource("dataForTestSearchString")
public void testFilesFromDirectoriesAndPattern(final String label, final String regex,
      final String stringToMatchAgainst, final String expected) {

   // Types of exceptions we test for.
   final String patternSyntaxException = "PatternSyntaxException: ";

   // PatternSyntaxException
   if (expected != null && expected.startsWith(patternSyntaxException)) {
      final PatternSyntaxException pse = assertThrows(PatternSyntaxException.class, () -> {
         searchString(regex, stringToMatchAgainst);
      });
      final Pattern exceptionPattern = Pattern.compile(expected.substring(patternSyntaxException.length()));
      final Matcher exceptionMatcher = exceptionPattern.matcher(pse.getMessage());
      assertTrue(exceptionMatcher.matches());
   } else {
      // Normal operation.
      final String actual = searchString(regex, stringToMatchAgainst);
      assertEquals(expected, actual);
   }

}

Notes about this method.

  • We use one of the method parameters, a string named expected to tell the method what result we should expect under normal (successful) operation.
  • I have extended the use of that string to also tell me what to expect in exceptional circumstances. In my code, I know what exceptions I am testing for and have decided that for test cases that should throw an exception, the expected string should start with the class name of the exception and the rest of the string will be a regular expression that should match the exception's message (getMessage()).
    • To do this I need some string processing: a test to see if the string starts with an exception class name and then a substring operation to pull out the regex.
    • I then need code to create the Pattern, the Matcher and then test if the regex matches the exception message.

The unit test now passes in all cases.

Here is the final version of the full test case, incorporating exception testing.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Stream;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

public class JunitDataDrivenTest {

   /**
    * @return test data. Each set of arguments should consist of
    *         <ol>
    *         <li>test label</li>
    *         <li>regex</li>
    *         <li>string we apply the regex to</li>
    *         <li>first group matched by the regex</li>
    *         </ol>
    */
   @SuppressWarnings({ "unused" }) // Eclipse thinks this method is not used.
   private static Stream<Arguments> dataForTestSearchString() {
      return Stream.of(//
            Arguments.of("Any string matches any string.", "(.*)", "xyz", "xyz") //
            , Arguments.of("Pick year digits out of a date.", "[0-9]{2}/[0-9]{2}/([0-9]{4})", "31/12/2032", "2032") //
            , Arguments.of("No digits found.", "[0-9]+", "not a digit", null) //
            , Arguments.of("First two words.", "(?<firstTwoWords>\\w+ \\w+) .*", "abc xyz one two", "abc xyz") //
            , Arguments.of("Bad regex.", "([0-9+)", "123", "PatternSyntaxException: (?s)Unclosed character class.*") //
      );
   }

   /**
    * @param label
    *           description of the current test. Data is used for reporting only.
    * @param regex
    *           regular expression.
    * @param stringToMatchAgainst
    *           string that we will apply the regex to
    * @param expected
    *           first group matched by the regex. Will be null if no match is expected.
    */
   @ParameterizedTest(name = "#{index} - [{0}]")
   @MethodSource("dataForTestSearchString")
   public void testFilesFromDirectoriesAndPattern(final String label, final String regex,
         final String stringToMatchAgainst, final String expected) {

      // Types of exceptions we test for.
      final String patternSyntaxException = "PatternSyntaxException: ";

      // PatternSyntaxException
      if (expected != null && expected.startsWith(patternSyntaxException)) {
         final PatternSyntaxException pse = assertThrows(PatternSyntaxException.class, () -> {
            searchString(regex, stringToMatchAgainst);
         });
         final Pattern exceptionPattern = Pattern.compile(expected.substring(patternSyntaxException.length()));
         final Matcher exceptionMatcher = exceptionPattern.matcher(pse.getMessage());
         assertTrue(exceptionMatcher.matches());
      } else {
         // Normal operation.
         final String actual = searchString(regex, stringToMatchAgainst);
         assertEquals(expected, actual);
      }

   }

   /**
    * A method being tested.
    *
    * @param regex
    *           regular expression.
    * @param stringToMatchAgainst
    *           string that we will apply the regex to
    * @return first group matched by the regex. Will be null if no match is expected.
    */
   public String searchString(final String regex, final String stringToMatchAgainst) {
      final Pattern pattern = Pattern.compile(regex);
      final Matcher matcher = pattern.matcher(stringToMatchAgainst);
      if (matcher.matches()) {
         return matcher.group(1);
      }
      return null;
   }

}

Versions

Software used in this post.

  • JDK 1.8 (1.8.0_40)
  • Eclipse is Spring Tool Suite 3.9.2.RELEASE (build on Eclipse Oxygen.2 (4.7.2))
  • Junit plugin is built in
  • JUnit dependencies in my pom.xml:
    
    <!-- JUnit Jupiter is the API we write tests against. -->
    <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-engine</artifactId>
       <version>5.1.0</version>
       <scope>test</scope>
    </dependency>
    <!-- Parameterised tests is separate download because it is currently an experimental feature. -->
    <dependency>
       <groupId>org.junit.jupiter</groupId>
       <artifactId>junit-jupiter-params</artifactId>
       <version>5.1.0</version>
       <scope>test</scope>
    </dependency>
    <!-- The engined used by the IDE to run tests. JUnit is built in to Eclipse, but apparently my version of Eclipse can't run JUnit 5 tests. -->
    <dependency>
       <groupId>org.junit.platform</groupId>
       <artifactId>junit-platform-launcher</artifactId>
       <version>1.1.0</version>
       <scope>test</scope>
    </dependency>
    

Saturday, April 14, 2018

Debug TestNG data driven tests when you only want to debug one row of data

Update Sunday, 15th of April 2018, 05:34:43 PM: added versioning information at the bottom of the post.

TestNG has a relatively straightforward mechanism for data driven tests that lets you write a method (annotated with @DataProvider) that generates data that will be used to invoke a test method (annotated by @Test). Here is an example where one of the tests will fail with a NullPointerException.

import static org.junit.Assert.assertTrue;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class TestString{

   @DataProvider(name = "dataForTestStringLength")
   public Object[][] dataForTestStringLength() {
      return new Object[][] { //
            new Object[] { "string" }, //
            new Object[] { null }, //
            new Object[] { "longer string" }, //
            new Object[] { "an even longer string" }, //
      };
   }

   @Test(dataProvider = "dataForTestStringLength")
   public void testStringLength(final String stringToTest) {
      assertTrue(stringToTest.length() > 3);
   }

}

This is what the result looks like in Eclipse.

This is fine and expected, but the problem starts when you want to run just that one failing test again, or to debug it.

Even though you right click on just the one data set, TestNG will re-run all of them (all four in this case). This makes it hard to debug tests, because if you set a debug point, you have to keep skipping until you get the actual test case you want. This becomes much more frustrating when you have a large data set. It might be easy to deal with just just four data sets, but not so easy when you have twenty or thirty or more. Plus, more complex tests won't have just one column of data to test, but four, five or more - it will be hard to even identify which test case failed when each of the combinations look similar.

Here is a technique I use to make it much easier to debug specific test cases.

import static org.junit.Assert.assertTrue;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class TestString {

   @DataProvider(name = "dataForTestStringLength")
   public Object[][] dataForTestStringLength() {
      int count = 0;
      final Object[][] data = new Object[][] { //
            new Object[] { count++, "string" }, //
            new Object[] { count++, null }, //
            new Object[] { count++, "longer string" }, //
            new Object[] { count++, "an even longer string" }, //
      };
      // Use this to test specific cases only.
      final Object[][] debuggingData = new Object[][] { /* data[1] */ };
      if (debuggingData.length > 0) {
         return debuggingData;
      }
      return data;
   }

   @Test(dataProvider = "dataForTestStringLength")
   public void testStringLength(final int index, final String stringToTest) {
      assertTrue(stringToTest.length() > 3);
   }

}

I have made two important changes here.

  1. I have modified the @DataProvider method so that it doesn't automatically return every test case. Modify the debuggingData line to specify the index of the test case you want to test.
  2. How do we know which test case to debug? That's the reason for the extra parameter added to the data returned from the @DataProvider method: an int count that gets incremented for each test case and received in the @Test as final int index.

Now look what Eclipse shows when a test fails:

The index of the failed test is now clearly visible, so it's easy for me to adjust the @DataProvider method to only return the data set I actually want to debug.

final Object[][] debuggingData = new Object[][] { data[1] };

Versions used in this post.

  • Eclipse is Spring Tool Suite 3.9.2.RELEASE (build on Eclipse Oxygen.2 (4.7.2))
  • TestNG 6.14 plugin
  • TestNG dependency in my pom.xml:
    
      org.testng
      testng
      6.8
      test
    
    

Monday, September 11, 2017

Bash script to repeat a task until you tell it to stop

For example, when I am running some task that outputs to the database but takes time to finish, rather than running the SQL myself over and over, I will script it and run the script to repeat. Then I hit ENTER whenever I am ready to find out when it has finished. This becomes more effective when I script the whole task: start the asynchronous job that takes awhile, then repeat-run the monitoring script.

#!/bin/bash
weContinue=true
while "${weContinue}" == true ; do
   "$@"
   echo "[$(date)] Press ENTER to continue; press any other key (then ENTER) to stop."
   read response
   if [ -n "${response}" ] ; then
      weContinue=false
   fi
done

Example running it.

Mon Sep 11 - 11:11 AM > repeat echo "Do something important"
Do something important
[Mon, Sep 11, 2017 11:19:54 AM] Press ENTER to continue; press any other key (then ENTER) to stop.

Do something important
[Mon, Sep 11, 2017 11:19:58 AM] Press ENTER to continue; press any other key (then ENTER) to stop.

Do something important
[Mon, Sep 11, 2017 11:20:01 AM] Press ENTER to continue; press any other key (then ENTER) to stop.
stop thanks

Sunday, July 30, 2017

Scripts and .screenrc to make GNU Screen splits easier

Update Thursday, 3rd of August 2017, 11:04:30 AM: much better to use bind over stuff to run a script within the .screenrc file.

I enjoy GNU screen via Cygwin on Windows a lot, but some of the commands get a bit fiddly. Creating splits is like that: you create a split, then move to it, then assign a window to it.

My first set of shortcuts were to create scripts to do those jobs.

To make a vertical split:

To make a horizontal script:

Lastly, key bindings that go in my .screenrc file to run those scripts.

# ------------------------------
# SPLIT HORIZONTALLY OR VERTICALLY.
# ------------------------------
# Uses split scripts.
# Control+a, V for vertical; control+a, H for horizontal.
# bind V stuff 'screenSplitVertical'\012''
# bind H stuff 'screenSplitHorizontal'\012''
bind V exec $HOME/bin/screenSplitVertical.sh
bind H exec $HOME/bin/screenSplitHorizontal.sh

Explanation for version that uses exec:

  1. bind V says the rest of this line will be executed upon "control+a, shift+v"
  2. exec means run the rest of the line as a command.
  3. $HOME/bin/screenSplitVertical.sh is the full path to the command (script) I want to run.

Explanation for version that uses stuff:

  1. bind V says the rest of this line will be executed upon "control+a, shift+v"
  2. stuff will "write stuff to the command line" or "stuff text onto the command line".
  3. 'screenSplitVertical'\012'' will write out the command screenSplitVertical.sh and then '\012' which outputs a newline (ENTER key) to actually cause the script to run.

In general, exec is more suitable than stuff in this situation because stuff will leave the command in my command history.

More screen magic? Check out joaopizani's .screenrc config for some great ideas about resizing splits and moving between them.

Sunday, November 08, 2015

Maven project with Spring, Log4j2 and properties

This a reference I use to quickly come up with a standalone app (no server) that uses Spring Boot, Log4j2 and a properties file.

The pom.xml file.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <groupId>org.testproject</groupId>
   <artifactId>TestProject</artifactId>
   <packaging>jar</packaging>
   <version>1.0-SNAPSHOT</version>
   <name>TestProject</name>
   <url>http://maven.apache.org</url>

   <properties>
      <maven.compiler.source>1.8</maven.compiler.source>
      <maven.compiler.target>1.8</maven.compiler.target>
      <jodatime.version>2.5</jodatime.version>
      <junit.version>4.12</junit.version>
      <log4j.version>2.4.1</log4j.version>
      <spring.boot.version>1.2.5.RELEASE</spring.boot.version>
   </properties>

   <dependencies>
      <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
         <version>${junit.version}</version>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>org.apache.logging.log4j</groupId>
         <artifactId>log4j-core</artifactId>
         <version>${log4j.version}</version>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot</artifactId>
         <version>${spring.boot.version}</version>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-autoconfigure</artifactId>
         <version>${spring.boot.version}</version>
      </dependency>
   </dependencies>

</project>

The configuration class that launches the app.

package org.testproject;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * Configuration and runner for the test app.
 */
@Configuration
@ComponentScan(basePackages = { "org.testproject.*" })
@EnableAutoConfiguration
public final class TestApp {

   /**
    * Launch app.
    *
    * @param args
    *           not used
    */
   public static void main(final String[] args) {
      SpringApplication.run(Runner.class, args);
   }

}

The runner - the class that does the actual work.

package org.testproject;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.PropertySources;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;

/**
 * Read in a message file from Touch Copy and transform the contents to be diary
 * ready.
 *
 * @author RobertMarkBram
 */
@Component
@PropertySources(value = {@PropertySource("classpath:/application.properties")})
public final class Runner implements CommandLineRunner {

   /** Logger for this class. */
   static final Logger LOG = LogManager.getLogger(Runner.class
         .getName());

   /** Use to access properties. */
   @Autowired
   private Environment env;

   /* (non-Javadoc)
    * @see org.springframework.boot.CommandLineRunner#run(java.lang.String[])
    */
   @Override
   public void run(final String... args) throws Exception {
      String testproperty = env.getProperty("testProperty");
      LOG.info(testproperty);
   }

}

The application property file.

testProperty=Hello World

The log4j2 configuration - note that number 2 is there.. this not old log4j anymore.

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
   <Appenders>
      <Console name="CONSOLE" target="SYSTEM_OUT">
         <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
      </Console>
   </Appenders>
   <Loggers>
      <logger name="org.testproject" level="DEBUG" />
      <Root level="ERROR">
         <AppenderRef ref="CONSOLE"/>
      </Root>
   </Loggers>
</Configuration>