OSDN Git Service

Fix #32188
[stew/Stew4.git] / src / net / argius / stew / ui / window / DatabaseInfoTree.java
1 package net.argius.stew.ui.window;
2
3 import static java.awt.EventQueue.invokeLater;
4 import static java.awt.event.InputEvent.ALT_DOWN_MASK;
5 import static java.awt.event.InputEvent.SHIFT_DOWN_MASK;
6 import static java.awt.event.KeyEvent.VK_C;
7 import static java.util.Collections.emptyList;
8 import static java.util.Collections.nCopies;
9 import static javax.swing.KeyStroke.getKeyStroke;
10 import static net.argius.stew.text.TextUtilities.join;
11 import static net.argius.stew.ui.window.AnyActionKey.copy;
12 import static net.argius.stew.ui.window.AnyActionKey.refresh;
13 import static net.argius.stew.ui.window.DatabaseInfoTree.ActionKey.*;
14 import static net.argius.stew.ui.window.WindowOutputProcessor.showInformationMessageDialog;
15
16 import java.awt.*;
17 import java.awt.event.*;
18 import java.io.*;
19 import java.sql.*;
20 import java.util.*;
21 import java.util.Map.Entry;
22 import java.util.List;
23
24 import javax.swing.*;
25 import javax.swing.event.*;
26 import javax.swing.tree.*;
27
28 import net.argius.stew.*;
29
30 /**
31  * The Database Information Tree is a tree pane that provides to
32  * display database object information from DatabaseMetaData.
33  */
34 final class DatabaseInfoTree extends JTree implements AnyActionListener, TextSearch {
35
36     enum ActionKey {
37         copySimpleName,
38         copyFullName,
39         generateWherePhrase,
40         generateSelectPhrase,
41         generateUpdateStatement,
42         generateInsertStatement,
43         jumpToColumnByName,
44         toggleShowColumnNumber
45     }
46
47     private static final Logger log = Logger.getLogger(DatabaseInfoTree.class);
48     private static final ResourceManager res = ResourceManager.getInstance(DatabaseInfoTree.class);
49
50     static volatile boolean showColumnNumber;
51
52     private Connector currentConnector;
53     private DatabaseMetaData dbmeta;
54     private AnyActionListener anyActionListener;
55
56     DatabaseInfoTree(AnyActionListener anyActionListener) {
57         this.anyActionListener = anyActionListener;
58         setRootVisible(false);
59         setShowsRootHandles(false);
60         setScrollsOnExpand(true);
61         setCellRenderer(new Renderer());
62         setModel(new DefaultTreeModel(null));
63         // [Events]
64         int sckm = Utilities.getMenuShortcutKeyMask();
65         AnyAction aa = new AnyAction(this);
66         aa.bindKeyStroke(false, copy, KeyStroke.getKeyStroke(VK_C, sckm));
67         aa.bindSelf(copySimpleName, getKeyStroke(VK_C, sckm | ALT_DOWN_MASK));
68         aa.bindSelf(copyFullName, getKeyStroke(VK_C, sckm | SHIFT_DOWN_MASK));
69     }
70
71     @Override
72     protected void processMouseEvent(MouseEvent e) {
73         super.processMouseEvent(e);
74         if (e.getID() == MouseEvent.MOUSE_CLICKED && e.getClickCount() % 2 == 0) {
75             anyActionPerformed(new AnyActionEvent(this, jumpToColumnByName));
76         }
77     }
78
79     @Override
80     public void anyActionPerformed(AnyActionEvent ev) {
81         log.atEnter("anyActionPerformed", ev);
82         if (ev.isAnyOf(copySimpleName)) {
83             copySimpleName();
84         } else if (ev.isAnyOf(copyFullName)) {
85             copyFullName();
86         } else if (ev.isAnyOf(refresh)) {
87             for (TreePath path : getSelectionPaths()) {
88                 refresh((InfoNode)path.getLastPathComponent());
89             }
90         } else if (ev.isAnyOf(generateWherePhrase)) {
91             List<ColumnNode> columnNodes = new ArrayList<ColumnNode>();
92             for (TreeNode node : getSelectionNodes()) {
93                 if (node instanceof ColumnNode) {
94                     columnNodes.add((ColumnNode)node);
95                 }
96             }
97             final String phrase = generateEquivalentJoinClause(columnNodes);
98             if (phrase.length() > 0) {
99                 insertTextIntoTextArea(addCommas(phrase));
100             }
101         } else if (ev.isAnyOf(generateSelectPhrase)) {
102             final String phrase = generateSelectPhrase(getSelectionNodes());
103             if (phrase.length() > 0) {
104                 insertTextIntoTextArea(phrase + " WHERE ");
105             }
106         } else if (ev.isAnyOf(generateUpdateStatement, generateInsertStatement)) {
107             final boolean isInsert = ev.isAnyOf(generateInsertStatement);
108             try {
109                 final String phrase = generateUpdateOrInsertPhrase(getSelectionNodes(), isInsert);
110                 if (phrase.length() > 0) {
111                     if (isInsert) {
112                         insertTextIntoTextArea(addCommas(phrase));
113                     } else {
114                         insertTextIntoTextArea(phrase + " WHERE ");
115                     }
116                 }
117             } catch (IllegalArgumentException ex) {
118                 showInformationMessageDialog(this, ex.getMessage(), "");
119             }
120         } else if (ev.isAnyOf(jumpToColumnByName)) {
121             jumpToColumnByName();
122         } else if (ev.isAnyOf(toggleShowColumnNumber)) {
123             showColumnNumber = !showColumnNumber;
124             repaint();
125         } else {
126             log.warn("not expected: Event=%s", ev);
127         }
128         log.atExit("anyActionPerformed");
129     }
130
131     @Override
132     public TreePath[] getSelectionPaths() {
133         TreePath[] a = super.getSelectionPaths();
134         if (a == null) {
135             return new TreePath[0];
136         }
137         return a;
138     }
139
140     List<TreeNode> getSelectionNodes() {
141         List<TreeNode> a = new ArrayList<TreeNode>();
142         for (TreePath path : getSelectionPaths()) {
143             a.add((TreeNode)path.getLastPathComponent());
144         }
145         return a;
146     }
147
148     private void insertTextIntoTextArea(String s) {
149         AnyActionEvent ev = new AnyActionEvent(this, ConsoleTextArea.ActionKey.insertText, s);
150         anyActionListener.anyActionPerformed(ev);
151     }
152
153     private void copySimpleName() {
154         TreePath[] paths = getSelectionPaths();
155         if (paths == null || paths.length == 0) {
156             return;
157         }
158         List<String> names = new ArrayList<String>(paths.length);
159         for (TreePath path : paths) {
160             if (path == null) {
161                 continue;
162             }
163             Object o = path.getLastPathComponent();
164             assert o instanceof InfoNode;
165             final String name;
166             if (o instanceof ColumnNode) {
167                 name = ((ColumnNode)o).getName();
168             } else if (o instanceof TableNode) {
169                 name = ((TableNode)o).getName();
170             } else {
171                 name = o.toString();
172             }
173             names.add(name);
174         }
175         ClipboardHelper.setStrings(names);
176     }
177
178     private void copyFullName() {
179         TreePath[] paths = getSelectionPaths();
180         if (paths == null || paths.length == 0) {
181             return;
182         }
183         List<String> names = new ArrayList<String>(paths.length);
184         for (TreePath path : paths) {
185             if (path == null) {
186                 continue;
187             }
188             Object o = path.getLastPathComponent();
189             assert o instanceof InfoNode;
190             names.add(((InfoNode)o).getNodeFullName());
191         }
192         ClipboardHelper.setStrings(names);
193     }
194
195     private void jumpToColumnByName() {
196         TreePath[] paths = getSelectionPaths();
197         if (paths == null || paths.length == 0) {
198             return;
199         }
200         final TreePath path = paths[0];
201         Object o = path.getLastPathComponent();
202         if (o instanceof ColumnNode) {
203             ColumnNode node = (ColumnNode)o;
204             AnyActionEvent ev = new AnyActionEvent(this,
205                                                    ResultSetTable.ActionKey.jumpToColumn,
206                                                    node.getName());
207             anyActionListener.anyActionPerformed(ev);
208         }
209     }
210
211     private static String addCommas(String phrase) {
212         int c = 0;
213         for (final char ch : phrase.toCharArray()) {
214             if (ch == '?') {
215                 ++c;
216             }
217         }
218         if (c >= 1) {
219             return String.format("%s;%s", phrase, join("", nCopies(c - 1, ",")));
220         }
221         return phrase;
222     }
223
224     static String generateEquivalentJoinClause(List<ColumnNode> nodes) {
225         if (nodes.isEmpty()) {
226             return "";
227         }
228         Set<String> tableNames = new LinkedHashSet<String>();
229         ListMap columnMap = new ListMap();
230         for (ColumnNode node : nodes) {
231             final String tableName = node.getTableNode().getNodeFullName();
232             final String columnName = node.getName();
233             tableNames.add(tableName);
234             columnMap.add(columnName, String.format("%s.%s", tableName, columnName));
235         }
236         assert tableNames.size() >= 1;
237         List<String> expressions = new ArrayList<String>();
238         if (tableNames.size() == 1) {
239             for (ColumnNode node : nodes) {
240                 expressions.add(String.format("%s=?", node.getName()));
241             }
242         } else { // size >= 2
243             List<String> expressions2 = new ArrayList<String>();
244             for (Entry<String, List<String>> entry : columnMap.entrySet()) {
245                 List<String> a = entry.getValue();
246                 final int n = a.size();
247                 assert n >= 1;
248                 expressions2.add(String.format("%s=?", a.get(0)));
249                 if (n >= 2) {
250                     for (int i = 0; i < n; i++) {
251                         for (int j = i + 1; j < n; j++) {
252                             expressions.add(String.format("%s=%s", a.get(i), a.get(j)));
253                         }
254                     }
255                 }
256             }
257             expressions.addAll(expressions2);
258         }
259         return String.format("%s", join(" AND ", expressions));
260     }
261
262     static String generateSelectPhrase(List<TreeNode> nodes) {
263         Set<String> tableNames = new LinkedHashSet<String>();
264         ListMap columnMap = new ListMap();
265         for (TreeNode node : nodes) {
266             if (node instanceof TableNode) {
267                 final String tableFullName = ((TableNode)node).getNodeFullName();
268                 tableNames.add(tableFullName);
269                 columnMap.add(tableFullName);
270             } else if (node instanceof ColumnNode) {
271                 ColumnNode cn = (ColumnNode)node;
272                 final String tableFullName = cn.getTableNode().getNodeFullName();
273                 tableNames.add(tableFullName);
274                 columnMap.add(tableFullName, cn.getNodeFullName());
275             }
276         }
277         if (tableNames.isEmpty()) {
278             return "";
279         }
280         List<String> columnNames = new ArrayList<String>();
281         if (tableNames.size() == 1) {
282             List<String> a = new ArrayList<String>();
283             for (TreeNode node : nodes) {
284                 if (node instanceof ColumnNode) {
285                     ColumnNode cn = (ColumnNode)node;
286                     a.add(cn.getName());
287                 }
288             }
289             if (a.isEmpty()) {
290                 columnNames.add("*");
291             } else {
292                 columnNames.addAll(a);
293             }
294         } else { // size >= 2
295             for (Entry<String, List<String>> entry : columnMap.entrySet()) {
296                 final List<String> columnsInTable = entry.getValue();
297                 if (columnsInTable.isEmpty()) {
298                     columnNames.add(entry.getKey() + ".*");
299                 } else {
300                     columnNames.addAll(columnsInTable);
301                 }
302             }
303         }
304         return String.format("SELECT %s FROM %s", join(", ", columnNames), join(", ", tableNames));
305     }
306
307     static String generateUpdateOrInsertPhrase(List<TreeNode> nodes, boolean isInsert) {
308         Set<String> tableNames = new LinkedHashSet<String>();
309         ListMap columnMap = new ListMap();
310         for (TreeNode node : nodes) {
311             if (node instanceof TableNode) {
312                 final String tableFullName = ((TableNode)node).getNodeFullName();
313                 tableNames.add(tableFullName);
314                 columnMap.add(tableFullName);
315             } else if (node instanceof ColumnNode) {
316                 ColumnNode cn = (ColumnNode)node;
317                 final String tableFullName = cn.getTableNode().getNodeFullName();
318                 tableNames.add(tableFullName);
319                 columnMap.add(tableFullName, cn.getName());
320             }
321         }
322         if (tableNames.isEmpty()) {
323             return "";
324         }
325         if (tableNames.size() >= 2) {
326             throw new IllegalArgumentException(res.get("e.enables-select-just-1-table"));
327         }
328         final String tableName = join("", tableNames);
329         List<String> columnsInTable = columnMap.get(tableName);
330         if (columnsInTable.isEmpty()) {
331             if (isInsert) {
332                 List<TableNode> tableNodes = new ArrayList<TableNode>();
333                 for (TreeNode node : nodes) {
334                     if (node instanceof TableNode) {
335                         tableNodes.add((TableNode)node);
336                         break;
337                     }
338                 }
339                 TableNode tableNode = tableNodes.get(0);
340                 if (tableNode.getChildCount() == 0) {
341                     throw new IllegalArgumentException(res.get("i.can-only-use-after-tablenode-expanded"));
342                 }
343                 for (int i = 0, n = tableNode.getChildCount(); i < n; i++) {
344                     ColumnNode child = (ColumnNode)tableNode.getChildAt(i);
345                     columnsInTable.add(child.getName());
346                 }
347             } else {
348                 return "";
349             }
350         }
351         final String phrase;
352         if (isInsert) {
353             final int columnCount = columnsInTable.size();
354             phrase = String.format("INSERT INTO %s (%s) VALUES (%s)",
355                                    tableName,
356                                    join(",", columnsInTable),
357                                    join(",", nCopies(columnCount, "?")));
358         } else {
359             List<String> columnExpressions = new ArrayList<String>();
360             for (final String columnName : columnsInTable) {
361                 columnExpressions.add(columnName + "=?");
362             }
363             phrase = String.format("UPDATE %s SET %s", tableName, join(", ", columnExpressions));
364         }
365         return phrase;
366     }
367
368     // text-search
369
370     @Override
371     public boolean search(Matcher matcher) {
372         return search(resolveTargetPath(getSelectionPath()), matcher);
373     }
374
375     private static TreePath resolveTargetPath(TreePath path) {
376         if (path != null) {
377             TreePath parent = path.getParentPath();
378             if (parent != null) {
379                 return parent;
380             }
381         }
382         return path;
383     }
384
385     private boolean search(TreePath path, Matcher matcher) {
386         if (path == null) {
387             return false;
388         }
389         TreeNode node = (TreeNode)path.getLastPathComponent();
390         if (node == null) {
391             return false;
392         }
393         boolean found = false;
394         found = matcher.find(node.toString());
395         if (found) {
396             addSelectionPath(path);
397         } else {
398             removeSelectionPath(path);
399         }
400         if (!node.isLeaf() && node.getChildCount() >= 0) {
401             @SuppressWarnings("unchecked")
402             Iterable<DefaultMutableTreeNode> children = Collections.list(node.children());
403             for (DefaultMutableTreeNode child : children) {
404                 if (search(path.pathByAddingChild(child), matcher)) {
405                     found = true;
406                 }
407             }
408         }
409         return found;
410     }
411
412     @Override
413     public void reset() {
414         // empty
415     }
416
417     // node expansion
418
419     /**
420      * Refreshes the root and its children.
421      * @param env Environment
422      * @throws SQLException
423      */
424     void refreshRoot(Environment env) throws SQLException {
425         Connector c = env.getCurrentConnector();
426         if (c == null) {
427             if (log.isDebugEnabled()) {
428                 log.debug("not connected");
429             }
430             currentConnector = null;
431             return;
432         }
433         if (c == currentConnector && getModel().getRoot() != null) {
434             if (log.isDebugEnabled()) {
435                 log.debug("not changed");
436             }
437             return;
438         }
439         if (log.isDebugEnabled()) {
440             log.debug("updating");
441         }
442         // initializing models
443         ConnectorNode connectorNode = new ConnectorNode(c.getName());
444         DefaultTreeModel model = new DefaultTreeModel(connectorNode);
445         setModel(model);
446         final DefaultTreeSelectionModel m = new DefaultTreeSelectionModel();
447         m.setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
448         setSelectionModel(m);
449         // initializing nodes
450         final DatabaseMetaData dbmeta = env.getCurrentConnection().getMetaData();
451         final Set<InfoNode> createdStatusSet = new HashSet<InfoNode>();
452         expandNode(connectorNode, dbmeta);
453         createdStatusSet.add(connectorNode);
454         // events
455         addTreeWillExpandListener(new TreeWillExpandListener() {
456             @Override
457             public void treeWillExpand(TreeExpansionEvent event) throws ExpandVetoException {
458                 TreePath path = event.getPath();
459                 final Object lastPathComponent = path.getLastPathComponent();
460                 if (!createdStatusSet.contains(lastPathComponent)) {
461                     InfoNode node = (InfoNode)lastPathComponent;
462                     if (node.isLeaf()) {
463                         return;
464                     }
465                     createdStatusSet.add(node);
466                     try {
467                         expandNode(node, dbmeta);
468                     } catch (SQLException ex) {
469                         throw new RuntimeException(ex);
470                     }
471                 }
472             }
473             @Override
474             public void treeWillCollapse(TreeExpansionEvent event) throws ExpandVetoException {
475                 // ignore
476             }
477         });
478         this.dbmeta = dbmeta;
479         // showing
480         model.reload();
481         setRootVisible(true);
482         this.currentConnector = c;
483         // auto-expansion
484         try {
485             File confFile = Bootstrap.getSystemFile("autoexpansion.tsv");
486             if (confFile.exists() && confFile.length() > 0) {
487                 AnyAction aa = new AnyAction(this);
488                 Scanner r = new Scanner(confFile);
489                 try {
490                     while (r.hasNextLine()) {
491                         final String line = r.nextLine();
492                         if (line.matches("^\\s*#.*")) {
493                             continue;
494                         }
495                         aa.doParallel("expandNodes", Arrays.asList(line.split("\t")));
496                     }
497                 } finally {
498                     r.close();
499                 }
500             }
501         } catch (IOException ex) {
502             throw new IllegalStateException(ex);
503         }
504     }
505
506     void expandNodes(List<String> a) {
507         long startTime = System.currentTimeMillis();
508         AnyAction aa = new AnyAction(this);
509         int index = 1;
510         while (index < a.size()) {
511             final String s = a.subList(0, index + 1).toString();
512             for (int i = 0, n = getRowCount(); i < n; i++) {
513                 TreePath target;
514                 try {
515                     target = getPathForRow(i);
516                 } catch (IndexOutOfBoundsException ex) {
517                     // FIXME when IndexOutOfBoundsException was thrown at expandNodes
518                     log.warn(ex);
519                     break;
520                 }
521                 if (target != null && target.toString().equals(s)) {
522                     if (!isExpanded(target)) {
523                         aa.doLater("expandLater", target);
524                         Utilities.sleep(200L);
525                     }
526                     index++;
527                     break;
528                 }
529             }
530             if (System.currentTimeMillis() - startTime > 5000L) {
531                 break; // timeout
532             }
533         }
534     }
535
536     // called by expandNodes
537     @SuppressWarnings("unused")
538     private void expandLater(TreePath parent) {
539         expandPath(parent);
540     }
541
542     /**
543      * Refreshes a node and its children.
544      * @param node
545      */
546     void refresh(InfoNode node) {
547         if (dbmeta == null) {
548             return;
549         }
550         node.removeAllChildren();
551         final DefaultTreeModel model = (DefaultTreeModel)getModel();
552         model.reload(node);
553         try {
554             expandNode(node, dbmeta);
555         } catch (SQLException ex) {
556             throw new RuntimeException(ex);
557         }
558     }
559
560     /**
561      * Expands a node.
562      * @param parent
563      * @param dbmeta
564      * @throws SQLException
565      */
566     void expandNode(final InfoNode parent, final DatabaseMetaData dbmeta) throws SQLException {
567         if (parent.isLeaf()) {
568             return;
569         }
570         final DefaultTreeModel model = (DefaultTreeModel)getModel();
571         final InfoNode tmpNode = new InfoNode(res.get("i.paren-in-processing")) {
572             @Override
573             protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
574                 return Collections.emptyList();
575             }
576             @Override
577             public boolean isLeaf() {
578                 return true;
579             }
580         };
581         invokeLater(new Runnable() {
582             @Override
583             public void run() {
584                 model.insertNodeInto(tmpNode, parent, 0);
585             }
586         });
587         // asynchronous
588         DaemonThreadFactory.execute(new Runnable() {
589             @Override
590             public void run() {
591                 try {
592                     final List<InfoNode> children = new ArrayList<InfoNode>(parent.createChildren(dbmeta));
593                     invokeLater(new Runnable() {
594                         @Override
595                         public void run() {
596                             for (InfoNode child : children) {
597                                 model.insertNodeInto(child, parent, parent.getChildCount());
598                             }
599                             model.removeNodeFromParent(tmpNode);
600                         }
601                     });
602                 } catch (SQLException ex) {
603                     try {
604                         if (dbmeta.getConnection().isClosed())
605                             return;
606                     } catch (SQLException exx) {
607                         ex.setNextException(exx);
608                     }
609                     throw new RuntimeException(ex);
610                 }
611             }
612         });
613     }
614
615     /**
616      * Clears (root).
617      */
618     void clear() {
619         for (TreeWillExpandListener listener : getListeners(TreeWillExpandListener.class).clone()) {
620             removeTreeWillExpandListener(listener);
621         }
622         setModel(new DefaultTreeModel(null));
623         currentConnector = null;
624         dbmeta = null;
625         if (log.isDebugEnabled()) {
626             log.debug("cleared");
627         }
628     }
629
630     // subclasses
631
632     private static final class ListMap extends LinkedHashMap<String, List<String>> {
633
634         ListMap() {
635             // empty
636         }
637
638         void add(String key, String... values) {
639             if (get(key) == null) {
640                 put(key, new ArrayList<String>());
641             }
642             for (String value : values) {
643                 get(key).add(value);
644             }
645         }
646
647     }
648
649     private static class Renderer extends DefaultTreeCellRenderer {
650
651         Renderer() {
652             // empty
653         }
654
655         @Override
656         public Component getTreeCellRendererComponent(JTree tree,
657                                                       Object value,
658                                                       boolean sel,
659                                                       boolean expanded,
660                                                       boolean leaf,
661                                                       int row,
662                                                       boolean hasFocus) {
663             super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
664             if (value instanceof InfoNode) {
665                 setIcon(Utilities.getImageIcon(((InfoNode)value).getIconName()));
666             }
667             if (value instanceof ColumnNode) {
668                 if (showColumnNumber) {
669                     TreePath path = tree.getPathForRow(row);
670                     if (path != null) {
671                         TreePath parent = path.getParentPath();
672                         if (parent != null) {
673                             final int index = row - tree.getRowForPath(parent);
674                             setText(String.format("%d %s", index, getText()));
675                         }
676                     }
677                 }
678             }
679             return this;
680         }
681
682     }
683
684     private abstract static class InfoNode extends DefaultMutableTreeNode {
685
686         InfoNode(Object userObject) {
687             super(userObject, true);
688         }
689
690         @Override
691         public boolean isLeaf() {
692             return false;
693         }
694
695         abstract protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException;
696
697         String getIconName() {
698             final String className = getClass().getName();
699             final String nodeType = className.replaceFirst(".+?([^\\$]+)Node$", "$1");
700             return "node-" + nodeType.toLowerCase() + ".png";
701         }
702
703         protected String getNodeFullName() {
704             return String.valueOf(userObject);
705         }
706
707         static List<TableTypeNode> getTableTypeNodes(DatabaseMetaData dbmeta,
708                                                      String catalog,
709                                                      String schema) throws SQLException {
710             List<TableTypeNode> a = new ArrayList<TableTypeNode>();
711             ResultSet rs = dbmeta.getTableTypes();
712             try {
713                 while (rs.next()) {
714                     TableTypeNode typeNode = new TableTypeNode(catalog, schema, rs.getString(1));
715                     if (typeNode.hasItems(dbmeta)) {
716                         a.add(typeNode);
717                     }
718                 }
719             } finally {
720                 rs.close();
721             }
722             if (a.isEmpty()) {
723                 a.add(new TableTypeNode(catalog, schema, "TABLE"));
724             }
725             return a;
726         }
727
728     }
729
730     private static class ConnectorNode extends InfoNode {
731
732         ConnectorNode(String name) {
733             super(name);
734         }
735
736         @Override
737         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
738             List<InfoNode> a = new ArrayList<InfoNode>();
739             if (dbmeta.supportsCatalogsInDataManipulation()) {
740                 ResultSet rs = dbmeta.getCatalogs();
741                 try {
742                     while (rs.next()) {
743                         a.add(new CatalogNode(rs.getString(1)));
744                     }
745                 } finally {
746                     rs.close();
747                 }
748             } else if (dbmeta.supportsSchemasInDataManipulation()) {
749                 ResultSet rs = dbmeta.getSchemas();
750                 try {
751                     while (rs.next()) {
752                         a.add(new SchemaNode(null, rs.getString(1)));
753                     }
754                 } finally {
755                     rs.close();
756                 }
757             } else {
758                 a.addAll(getTableTypeNodes(dbmeta, null, null));
759             }
760             return a;
761         }
762
763     }
764
765     private static final class CatalogNode extends InfoNode {
766
767         private final String name;
768
769         CatalogNode(String name) {
770             super(name);
771             this.name = name;
772         }
773
774         @Override
775         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
776             List<InfoNode> a = new ArrayList<InfoNode>();
777             if (dbmeta.supportsSchemasInDataManipulation()) {
778                 ResultSet rs = dbmeta.getSchemas();
779                 try {
780                     while (rs.next()) {
781                         a.add(new SchemaNode(name, rs.getString(1)));
782                     }
783                 } finally {
784                     rs.close();
785                 }
786             } else {
787                 a.addAll(getTableTypeNodes(dbmeta, name, null));
788             }
789             return a;
790         }
791
792     }
793
794     private static final class SchemaNode extends InfoNode {
795
796         private final String catalog;
797         private final String schema;
798
799         SchemaNode(String catalog, String schema) {
800             super(schema);
801             this.catalog = catalog;
802             this.schema = schema;
803         }
804
805         @Override
806         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
807             List<InfoNode> a = new ArrayList<InfoNode>();
808             a.addAll(getTableTypeNodes(dbmeta, catalog, schema));
809             return a;
810         }
811
812     }
813
814     private static final class TableTypeNode extends InfoNode {
815
816         private static final String ICON_NAME_FORMAT = "node-tabletype-%s.png";
817
818         private final String catalog;
819         private final String schema;
820         private final String tableType;
821
822         TableTypeNode(String catalog, String schema, String tableType) {
823             super(tableType);
824             this.catalog = catalog;
825             this.schema = schema;
826             this.tableType = tableType;
827         }
828
829         @Override
830         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
831             List<InfoNode> a = new ArrayList<InfoNode>();
832             ResultSet rs = dbmeta.getTables(catalog, schema, null, new String[]{tableType});
833             try {
834                 while (rs.next()) {
835                     final String table = rs.getString(3);
836                     final String type = rs.getString(4);
837                     final boolean kindOfTable = type.matches("TABLE|VIEW|SYNONYM");
838                     a.add(new TableNode(catalog, schema, table, kindOfTable));
839                 }
840             } finally {
841                 rs.close();
842             }
843             return a;
844         }
845
846         @Override
847         String getIconName() {
848             final String name = String.format(ICON_NAME_FORMAT, getUserObject());
849             if (getClass().getResource("icon/" + name) == null) {
850                 return String.format(ICON_NAME_FORMAT, "");
851             }
852             return name;
853         }
854
855         boolean hasItems(DatabaseMetaData dbmeta) throws SQLException {
856             ResultSet rs = dbmeta.getTables(catalog, schema, null, new String[]{tableType});
857             try {
858                 return rs.next();
859             } finally {
860                 rs.close();
861             }
862         }
863
864     }
865
866     static final class TableNode extends InfoNode {
867
868         private final String catalog;
869         private final String schema;
870         private final String name;
871         private final boolean kindOfTable;
872
873         TableNode(String catalog, String schema, String name, boolean kindOfTable) {
874             super(name);
875             this.catalog = catalog;
876             this.schema = schema;
877             this.name = name;
878             this.kindOfTable = kindOfTable;
879         }
880
881         @Override
882         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
883             List<InfoNode> a = new ArrayList<InfoNode>();
884             ResultSet rs = dbmeta.getColumns(catalog, schema, name, null);
885             try {
886                 while (rs.next()) {
887                     a.add(new ColumnNode(rs.getString(4),
888                                          rs.getString(6),
889                                          rs.getInt(7),
890                                          rs.getString(18),
891                                          this));
892                 }
893             } finally {
894                 rs.close();
895             }
896             return a;
897         }
898
899         @Override
900         public boolean isLeaf() {
901             return false;
902         }
903
904         @Override
905         protected String getNodeFullName() {
906             List<String> a = new ArrayList<String>();
907             if (catalog != null) {
908                 a.add(catalog);
909             }
910             if (schema != null) {
911                 a.add(schema);
912             }
913             a.add(name);
914             return join(".", a);
915         }
916
917         String getName() {
918             return name;
919         }
920
921         boolean isKindOfTable() {
922             return kindOfTable;
923         }
924
925     }
926
927     static final class ColumnNode extends InfoNode {
928
929         private final String name;
930         private final TableNode tableNode;
931
932         ColumnNode(String name, String type, int size, String nulls, TableNode tableNode) {
933             super(format(name, type, size, nulls));
934             setAllowsChildren(false);
935             this.name = name;
936             this.tableNode = tableNode;
937         }
938
939         String getName() {
940             return name;
941         }
942
943         TableNode getTableNode() {
944             return tableNode;
945         }
946
947         private static String format(String name, String type, int size, String nulls) {
948             final String nonNull = "NO".equals(nulls) ? " NOT NULL" : "";
949             return String.format("%s [%s(%d)%s]", name, type, size, nonNull);
950         }
951
952         @Override
953         public boolean isLeaf() {
954             return true;
955         }
956
957         @Override
958         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
959             return emptyList();
960         }
961
962         @Override
963         protected String getNodeFullName() {
964             return String.format("%s.%s", tableNode.getNodeFullName(), name);
965         }
966
967     }
968
969 }