Wednesday 22 July 2015

So THAT's what LinkedHashMap is for!

One often cited phrase in Software Craftsmanship is "Know Your Library". This came in quite handy in my latest contribution to the GreenMail project.

My current dayjob project uses GreenMail for simulating a receiving email server as part of testing the ones sent by our system. Every now and then, we have to restart the server, because the heap space has run out - by default, GreenMail stores all received emails in memory. Not much of an issue by itself, but if it happens too often, we'll just waste too much time.

We have similar simulators for other messaging channels such as SMS or Push Notifications. Like GreenMail, the Push simulators just stored all received messages in an unbounded ArrayList which was constantly growing until it exploded. We fixed this by replacing the ArrayList with Guava's EvictingQueue.

My initial idea for the GreenMail fix was the same, but the project coordinators are keen on keeping 3rd party dependencies as low as possible, so they rejected a Guava-based solution. However, I remembered how my former coworker Rafal had solved the issue for the SMS simulator which needed a Map- instead of a List- (or Queue-)based solution.

One standard Java class that a lot of people have heard of (or maybe even briefly scanned the first paragraph of Javadoc), but don't really know, is LinkedHashMap. Most Java devs will know this class as a HashMap with predictable iteration order - something we hardly ever need, so no need to feel bad if you (like me) never bothered to look at its full Javadoc in depth.

So in case you never did that, you will have most likely missed a very special method which is only part of this class and doesn't exist in the Map interface: protected boolean removeEldestEntry(Map.Entry<K,V> eldest).  Let's take a brief look at its JavaDoc:
Returns true if this map should remove its eldest entry. This method is invoked by put and putAll after inserting a new entry into the map. It provides the implementor with the opportunity to remove the eldest entry each time a new one is added. This is useful if the map represents a cache: it allows the map to reduce memory consumption by deleting stale entries.
Sample use: this override will allow the map to grow up to 100 entries and then delete the eldest entry each time a new entry is added, maintaining a steady state of 100 entries.
private static final int MAX_ENTRIES = 100;
protected boolean removeEldestEntry(Map.Entry eldest) {
   return size() > MAX_ENTRIES;
}
At the end of the day, an InMemoryStore is really no different from a cache. And the above example is pretty much exactly what we require it to do: Always purge the oldest message once a certain number is reached.

So on this base I created a simple utility class inside the GreenMail project which extends LinkedHashMap and provides exactly the required functionality. Starting from this, I was able to extract and abstract the methods needed to provide a somewhat easy way to exchange the existing List-based implementation for one based on the new Map class. Please feel free to explore the other parts of the changes for Issue #62 and ask any questions that may arise.

And always remember to Know Your Library.