grokking hard

code smarter, not harder

I finally found a good use case of Java Local Class

Posted at — 2023-Jun-11

A Basic Problem

Today I needed to come up with a method to mask some certain string (as Apache’s Common Lang’s StringUtils did not support it yet).

The masking logic is simple though:

Using Java 17, this is what I came up with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/* Rules:
* If it is less than 2, mask everything. 
* If 3 <= length < 5, only allow first and last character.
* If 5 <= length <= 12, allow first and last TWO characters.
* If length > 12, allow first and last 4 characters.
*/
public static String mask(String string) {
  if (string == null) { return null; }
  if (string.isEmpty()) { return string; }

  var length = string.length();
  var allowCharacters = length <= 2 ? 0 : (length <= 5 ? 1 : (length <= 12 ? 2 : 4)); 
  var maskingCharacter = "*";

  var start = string.substring(0, allowCharacters);
  var end = string.substring(length - allowCharacters, length);
  var maskedPart = maskingCharacter.repeat(length - allowCharacters * 2);
  return start + maskedPart + end;
}

The code was pretty simple and straight forward.

How about some “cache” or “memoization”?

Then I thought to myself: “Short strings are pretty common, so the masked parts for length from 1 to 10 could be cached right?”. Let’s try that!

Initially, I came up a solution such that:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class StringMasker {

  private static final String[] COMMON_MASKED_PARTS = cachedCommonMaskedPartUpto((byte)20);

  private static String[] cachedCommonMaskedPartUpto(byte number) {
    var cached = new String[number];
    for (int i = 0; i < number; i++) {
      cached[i] = "*".repeat(i);
    }
    return cached;
  }

  public static String mask(String string) {
    if (string == null) { return null; }
    if (string.isEmpty()) { return string; }

    var length = string.length();
    var allowCharacters = length <= 2 ? 0 : (length <= 5 ? 1 : (length <= 12 ? 2 : 4)); 
    var maskingCharacter = "*";

    var start = string.substring(0, allowCharacters);
    var end = string.substring(length - allowCharacters, length);

    var maskedCharactersLength = length - allowCharacters * 2;
    var maskedPart = maskedCharactersLength <= COMMON_MASKED_PARTS.length?
      COMMON_MASKED_PARTS[maskedCharactersLength] : maskingCharacter.repeat(length - allowCharacters * 2);
    return start + maskedPart + end;
  }
}

The code worked. However, I didn’t like the fact that I had to introduce one constant and another static method into the enclosing class (whatever that class may be). I wanted the entire implemenation is concealed in one method. I wondered if a method could define a small class within itself just for the sake of holding the constants?

Turned out that it was possible!!! The answer was Java’s Local Classes 1Classes defined within a block.

The method could be re-implemented to make it self-contained:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public static String mask(String string) {
  if (string == null) { return null; }
  if (string.isEmpty()) { return string; }

  var length = string.length();
  var allowCharacters = length <= 2 ? 0 : (length <= 5 ? 1 : (length <= 12 ? 2 : 4)); 
  var maskingCharacter = "*";

  var start = string.substring(0, allowCharacters);
  var end = string.substring(length - allowCharacters, length);

  class CachingMaskedParts {

    private static final String[] COMMON_MASKED_PARTS = cachedCommonMaskedPartUpto((byte)20);

    private static String[] cachedCommonMaskedPartUpto(byte number) {
      var cached = new String[number];
      for (int i = 0; i < number; i++) {
        cached[i] = "*".repeat(i);
      }
      return cached;
    }
  }

  var maskedCharactersLength = length - allowCharacters * 2;
  var maskedPart = (maskedCharactersLength <= CachingMaskedParts.COMMON_MASKED_PARTS.length) ?
    CachingMaskedParts.COMMON_MASKED_PARTS[maskedCharactersLength] 
      : maskingCharacter.repeat(length - allowCharacters * 2);
  return start + maskedPart + end;
}

Here I got best of both world, small caching for common masked parts and they are contained inside the method. Another win: JVM only loads the CachingMaskedParts class once.