import javax.swing.tree.*;
static > JTree jDynamicTree(A root, IF1 makeChildren, O... _) {
  ret jDynamicTree(root, toF1(makeChildren), _);
}
static > JTree jDynamicTree(final A root, final F1 makeChildren, fO... _) {
  ret swing(func -> JTree {
    optPar IF1 valueToText;
    DefaultMutableTreeNode rootNode = new(root);
    rootNode.add(new DefaultMutableTreeNode(jDynamicTree_dummy()));
    final JTree tree = new JTree(rootNode) {
      public S convertValueToText(O value, bool selected, bool expanded, bool leaf, int row, bool hasFocus) {
        if (valueToText != null) {
          O userObject = ((DefaultMutableTreeNode) value).getUserObject();
          pcall { ret str(valueToText.get((A) userObject)); }
        }
        ret super.convertValueToText(value, selected, expanded, leaf, row, hasFocus);
      }
    };
    tree.setShowsRootHandles(true);
    tree.collapseRow(0);
    final bool keepChildrenOnCollapse = optParam(_, 'keepChildrenOnCollapse, false);
    bool makeChildrenIsFast = boolParam makeChildrenIsFast(_) || makeChildren == null;
    bool debug = boolPar debug(_);
    
    class jDynamicTree_Listener implements TreeWillExpandListener {
      public void treeWillCollapse(TreeExpansionEvent e) {}
      public void treeWillExpand(TreeExpansionEvent e) { pcall {
        if (debug) print("treeWillExpand");
        DefaultMutableTreeNode node = cast e.getPath().getLastPathComponent();
        if (!keepChildrenOnCollapse || jDynamicTree_isInDummyState(node)) {
          L l = asList(callF(makeChildren, (A) node.getUserObject()));
          if (makeChildrenIsFast
            ? replaceTreeNodeChildren_withDummyChild2(node, l, jDynamicTree_dummy(), makeChildren)
            : replaceTreeNodeChildren_withDummyChild(node, l, jDynamicTree_dummy()))
            ((DefaultTreeModel) tree.getModel()).nodeStructureChanged(node);
        }
      }}
    }
    tree.addTreeWillExpandListener(new jDynamicTree_Listener);
    
    ret tree;
  });
}