private void printJoins(Vector<LogicalJoinNode> js, 
                           PlanCache pc,
                           HashMap<String, TableStats> stats,
                           HashMap<String,Double> selectivities ) {

        JFrame f = new JFrame("Join Plan for " + p.getQuery());
 
        // Set the default close operation for the window, 
        // or else the program won't exit when clicking close button
        f.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
 
        f.setVisible(true);

        f.setSize(300,500);

        HashMap<String, DefaultMutableTreeNode> m = new HashMap<String, DefaultMutableTreeNode>();

        int numTabs = 0;
        
        int k;
        DefaultMutableTreeNode root = null, treetop = null;
        HashSet<LogicalJoinNode> pathSoFar = new HashSet<LogicalJoinNode>();
        boolean neither;

        System.out.println(js);
        for (LogicalJoinNode j : js) {
            pathSoFar.add(j);
            System.out.println("PATH SO FAR = " + pathSoFar);

            Double c = pc.getCost(pathSoFar);
            neither = true;

            root = new DefaultMutableTreeNode("Join " + j + " (Cost =" + pc.getCost(pathSoFar) + 
                                              ", card = " + pc.getCard(pathSoFar)+ ")");
            DefaultMutableTreeNode n = m.get(j.t1);
            if (n == null) {  //never seen this table before
                n  = new DefaultMutableTreeNode(j.t1 + " (Cost = " + stats.get(j.t1).estimateScanCost()+ ", card = " +
                                                stats.get(j.t1).estimateTableCardinality(selectivities.get(j.t1))+ ")");
                root.add(n);
            }  else {
                //make left child root n
                root.add(n);
                neither = false;
            }
            m.put(j.t1, root);

            n = m.get(j.t2);
            if (n == null) { //never seen this table before
                
                n  = new DefaultMutableTreeNode(j.t2==null?"Subplan":(j.t2 + " (Cost = " + 
                                                                      stats.get(j.t2).estimateScanCost()+ ", card = " +
                                                                      stats.get(j.t2).estimateTableCardinality(selectivities.get(j.t1))+ 
                                                                      ")"));
                root.add(n);
            }  else {
                //make right child root n
                root.add(n);
                neither = false;
            }
            m.put(j.t2, root);
            
            //unless this table doesn't join with other tables,
            // all tables are accessed from root
            if (!neither) {
                for (String key : m.keySet()) {
                    m.put(key, root);
                }
            }

            treetop = root;
        }

        JTree tree = new JTree(treetop);
        JScrollPane treeView = new JScrollPane(tree);

        tree.setShowsRootHandles(true);

        //Set the icon for leaf nodes.
        ImageIcon leafIcon = new ImageIcon("join.jpg");
        if (leafIcon != null) 
        {
            DefaultTreeCellRenderer renderer = new DefaultTreeCellRenderer();
            renderer.setOpenIcon(leafIcon);
            renderer.setClosedIcon(leafIcon);

            tree.setCellRenderer(renderer);
        } 
        f.setSize(300,500);

        f.add(treeView);
        for (int i = 0; i < tree.getRowCount(); i++) {
            tree.expandRow(i);
        }

        if (js.size() == 0) {
            f.add(new JLabel("No joins in plan."));
        }

        f.pack();

    }