gt:                     TrieNode<T> nextNode = currentNode.getTransition(c);
context: package com.roklenarcic.util.strings;
  
  import java.io.IOException;
  import java.nio.CharBuffer;
  import java.util.Arrays;
  import java.util.Iterator;
  
  import com.roklenarcic.util.strings.threshold.RangeNodeThreshold;
  import com.roklenarcic.util.strings.threshold.Thresholder;
  
  // Matches leftmost longest matches. Useful when you want non-overlapping
  // matches with a string set that doesn't have strings that are prefix to other strings in the set.
  public class LongestMatchMap<T> implements StringMap<T> {
  
      private boolean caseSensitive = true;
      private int charBufferSize = 0;
      private TrieNode<T> root;
  
      public LongestMatchMap(final Iterable<String> keywords, final Iterable<? extends T> values, boolean caseSensitive) {
          this(keywords, values, caseSensitive, new RangeNodeThreshold());
      }
  
      public LongestMatchMap(final Iterable<String> keywords, final Iterable<? extends T> values, boolean caseSensitive, final Thresholder thresholdStrategy) {
          this.caseSensitive = caseSensitive;
          Iterator<String> keywordsIter = keywords.iterator();
          Iterator<? extends T> valuesIter = values.iterator();
          int longestKeyword = 0;
          // Create the root node
          root = new HashmapNode<T>(true, 0);
          // Add all keywords
          while (keywordsIter.hasNext() && valuesIter.hasNext()) {
              final String keyword = keywordsIter.next();
              T value = valuesIter.next();
              // Skip any empty keywords
              if (keyword != null && keyword.length() > 0) {
                  if (keyword.length() > longestKeyword) {
                      longestKeyword = keyword.length();
                  }
                  // Start with the current node and traverse the tree
                  // character by character. Add nodes as needed to
                  // fill out the tree.
                  HashmapNode<T> currentNode = (HashmapNode<T>) root;
                  for (int idx = 0; idx < keyword.length(); idx++) {
                      currentNode = currentNode.getOrAddChild(caseSensitive ? keyword.charAt(idx) : Character.toLowerCase(keyword.charAt(idx)));
                  }
                  // Last node will contains the keyword as a match.
                  // Suffix matches will be added later.
                  currentNode.matchLength = keyword.length();
                  currentNode.value = value;
              }
          }
          charBufferSize = longestKeyword > 2048 ? longestKeyword * 2 : 4096;
          // Calculate fail transitions and add suffix matches to nodes.
          // A lot of these properties are defined in a recursive fashion i.e.
          // calculating for a 3 letter word requires having done the calculation
          // for all 2 letter words.
          //
          final Queue<TrieNode<T>> queue = new Queue<TrieNode<T>>();
          root = root.optimizeNode(0, thresholdStrategy);
          queue.push(root);
          queue.push(null);
          // Need to use array to get mutateable state for anonymous class
          final int[] level = new int[] { 1 };
  
          EntryVisitor<T> failTransAndOutputsVisitor = new EntryVisitor<T>() {
  
              public void visit(TrieNode<T> parent, char key, TrieNode<T> value) {
                  // First optimize node
                  value = value.optimizeNode(level[0], thresholdStrategy);
                  parent.updateTransition(key, value);
  
                  // Get fail transiton of the parent.
                  TrieNode<T> parentFail = parent.getFailTransition();
                  // Since root node has null fail transition, first level nodes have null parentFail.
                  if (parentFail == null) {
                      // First level nodes have one possible fail transition, which is
                      // root because the only possible suffix to a one character
                      // string is an empty string
                      value.failTransition = parent;
                  } else {
                      // Dig up the tree until you find a fail transition.
                      do {
                          // Suffix of a parent + transition character from parent to this
                          // node is the suffix of this node.
  
                          // parent ---char---> value
                          // parentFail ----char----> valueFail
                          // e.g. "ab" -> c -> "abc"
                          // "b" -> c -> "bc"
                          final TrieNode<T> matchContinuation = parentFail.getTransition(key);
                          if (matchContinuation != null) {
                              value.failTransition = matchContinuation;
                          } else {
                              // If parentFail didn't have key mapping
                              // take parentFail's failTransition and try again
                              // The last fail transition is the root node, which
                              // always has a key mapping.
                              parentFail = parentFail.getFailTransition();
                          }
                      } while (value.failTransition == null);
                      // Now that we have a fail transition, this node matches all
                      // the matches of it's failTransition node in addition to any
                      // match it already has.
                      // e.g for keywords "abc", "bc", "c", "b", the "abc" node matches
                      // "abc" and also its failure transtion's matches ("bc", "c")
                      // "ab" has no match of its own, but it matches failure transition's
                      // match "b".
  
                      // Fail transitions are basically a linked list, because of the recursive fashion in which
                      // they are defined. But only some of them have matches on them. We want to skip those
                      // that don't, that is why we have suffix match references, which form a similar linked
                      // list like fail transitions but they skip over those without matches. Since the suffix
                      // matches for shorter suffixes have been sorted out, it's only a matter of linking to the
                      // first fail transition with a match. But there is another thing we want. We want to
                      // avoid the case where a node is without a match but it has suffix matches, as that would
                      // introduce another if. That is why in case of nodes without matches we store the suffix
                      // match directly on the node and instead link the next suffix match as this node's suffix
                      // match.
                      TrieNode<T> fail = value.failTransition;
                      while (fail != root && fail.matchLength == 0) {
                          fail = fail.failTransition;
                      }
                      if (fail.matchLength > 0) {
                          if (value.matchLength == 0) {
                              value.matchLength = fail.matchLength;
                              value.suffixMatch = fail.suffixMatch;
                              value.value = fail.value;
                          } else {
                              value.suffixMatch = fail;
                          }
                      }
                  }
                  // Queue the non-leaf node.
                  if (!value.isEmpty()) {
                      queue.push(value);
                  }
              }
  
          };
          while (!queue.isEmpty()) {
              TrieNode<T> n = queue.take();
              if (n == null) {
                  if (!queue.isEmpty()) {
                      queue.push(null);
                      level[0]++;
                  }
              } else {
                  n.mapEntries(failTransAndOutputsVisitor);
              }
          }
  
          // Range nodes represent a range of transitions without all the transitions in the range being
          // there. In case of hitting on an empty slot the logic in match loop runs down the fail transition
          // chain to find a node with a transition for that char. Instead of wasting space on empty slots
          // we can do that beforehand and add that transition to the node. We need to do that in depth first
          // fashion, otherwise an endless loop can form.
          EntryVisitor<T> enqueueNodesVisitor = new EntryVisitor<T>() {
  
              public void visit(TrieNode<T> parent, char key, TrieNode<T> value) {
                  // go depth first
                  if (!value.isEmpty()) {
                      queue.push(value);
                  }
              }
  
          };
          root.mapEntries(enqueueNodesVisitor);
          while (!queue.isEmpty()) {
              TrieNode<T> node = queue.pop();
              if (node == null) {
                  node = queue.pop();
                  if (node instanceof RangeNode) {
                      // Range nodes have gaps (null values) in their array. We can put this wasted
                      // memory to work by filling these gaps with the correct next node for that character
                      // which we can figure out by following failure transitions.
                      RangeNode<T> rangeNode = (RangeNode<T>) node;
                      for (int i = 0; i < rangeNode.size; i++) {
                          if (rangeNode.children[i] == null) {
                              char charOfMissingTransition = (char) (rangeNode.baseChar + i);
                              // Walk up fail transition until you run out of them (and do nothing)
                              // or one of them has a transition for this char. Put that node
                              // into the empty slot on the range node.
                              TrieNode<T> n = rangeNode.failTransition;
                              while (n != null) {
                                  TrieNode<T> nextNode = n.getTransition(charOfMissingTransition);
                                  if (nextNode == null) {
                                      n = n.failTransition;
                                  } else {
                                      rangeNode.children[i] = nextNode;
                                      break;
                                  }
                              }
                          }
                      }
                  }
              } else {
                  queue.push(null);
                  node.mapEntries(enqueueNodesVisitor);
              }
          }
      }
  
      public void match(final Readable haystack, final ReadableMatchListener<T> listener) throws IOException {
  
          // Start with the root node.
          TrieNode<T> currentNode = root;
          MapMatchQueue<T> queue = new MapMatchQueue<T>();
  
          CharBuffer buf = CharBuffer.allocate(charBufferSize);
  
          int idx = 0;
          // For each character.
          // Putting this if into the loop worsens the performance so we'll sadly
          // have to deal with duplicated code.
          if (caseSensitive) {
              while (haystack.read(buf) != -1) {
                  buf.flip();
                  while (buf.hasRemaining()) {
                      final char c = buf.get();
                      // Try to transition from the current node using the character
  