private void traverseTerminalStrings(Item i) {
    if (i.shifts != null) {
      for (Transition t : i.shifts) {
        q.add(t);
        traverseTerminalStrings(t.target);
        q.pop();
      }
    } else {
      Symbol[] str = new Symbol[q.size()];
      for (int j = 0; j < q.size(); ++j) {
        str[j] = q.get(j).label;
      }

      // add it to minimalStrings
      for (Transition t : i.reduces) {
        for (Transition shift : t.shifts) {
          Symbol[] prev = minimalStrings.get(shift);
          if (prev == null || prev.length > str.length) {
            minimalStrings.put(shift, str);
          }
        }
      }
    }
  }
  public ShareableHashMap<Transition, Symbol[]> getMinimalStrings(NFA nfa) {

    // find all begins of productions and fill initial minimal strings
    Set<Item> begins = new ShareableHashSet<Item>();
    for (Item i : nfa.items) {
      if (i.atBegin()) {
        if (i.production.containsNonTerminals()) {
          begins.add(i);
        } else {
          traverseTerminalStrings(i);
        }
      }
    }

    for (Transition t : nfa.transitions) {
      if (!(t.label instanceof NonTerminal)) {
        minimalStrings.put(t, new Symbol[] {t.label});
      }
    }

    boolean changed;
    do {
      changed = false;

      for (Item i : begins) {
        ShareableHashMap<Item, StackNode> level = new ShareableHashMap<Item, StackNode>();
        level.put(i, new StackNode(null, null));

        // breadth first traverse all shift transitions
        // stacks join and continue with stack with minimal string length
        for (int j = i.production.getLength() - 1; j >= 0 && level.size() > 0; --j) {
          ShareableHashMap<Item, StackNode> nextLevel = new ShareableHashMap<Item, StackNode>();
          for (Entry<Item, StackNode> e : level) {
            for (Transition t : e.getKey().shifts) {
              Symbol[] tstr = minimalStrings.get(t);
              if (tstr != null) {
                StackNode n = e.getValue();
                StackNode nn = nextLevel.get(t.target);
                if (nn == null) {
                  nextLevel.put(t.target, new StackNode(tstr, n));
                } else {
                  if (nn.len > n.len + tstr.length) {
                    // replace nn
                    nextLevel.put(t.target, new StackNode(tstr, n));
                  }
                }
              }
            }
          }
          level = nextLevel;
        }

        if (level.size() == 0) {
          continue;
        }

        // add minimalStrings for stacks that have reached the end of i.production
        for (Entry<Item, StackNode> e : level) {
          StackNode n = e.getValue();
          Symbol[] nstr = null;
          for (Transition r : e.getKey().reduces) {
            for (Transition shift : r.shifts) {
              Symbol[] sstr = minimalStrings.get(shift);
              if (sstr == null || sstr.length > n.len) {
                if (nstr == null) {
                  nstr = new Symbol[n.len];
                  int j = n.len;
                  StackNode tail = n;
                  while (tail.prev != null) {
                    for (int k = tail.tstr.length - 1; k >= 0; --k) {
                      nstr[--j] = tail.tstr[k];
                    }
                    tail = tail.prev;
                  }
                }
                minimalStrings.put(shift, nstr);
                changed = true; // TODO can we make a todo set?
              }
            }
          }
        }
      }
    } while (changed);

    return minimalStrings;
  }