OSDN Git Service

fixed bug: semicolon was not added when select only one node
[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                     throw new RuntimeException(ex);
604                 }
605             }
606         });
607     }
608
609     /**
610      * Clears (root).
611      */
612     void clear() {
613         for (TreeWillExpandListener listener : getListeners(TreeWillExpandListener.class).clone()) {
614             removeTreeWillExpandListener(listener);
615         }
616         setModel(new DefaultTreeModel(null));
617         currentConnector = null;
618         dbmeta = null;
619         if (log.isDebugEnabled()) {
620             log.debug("cleared");
621         }
622     }
623
624     // subclasses
625
626     private static final class ListMap extends LinkedHashMap<String, List<String>> {
627
628         ListMap() {
629             // empty
630         }
631
632         void add(String key, String... values) {
633             if (get(key) == null) {
634                 put(key, new ArrayList<String>());
635             }
636             for (String value : values) {
637                 get(key).add(value);
638             }
639         }
640
641     }
642
643     private static class Renderer extends DefaultTreeCellRenderer {
644
645         Renderer() {
646             // empty
647         }
648
649         @Override
650         public Component getTreeCellRendererComponent(JTree tree,
651                                                       Object value,
652                                                       boolean sel,
653                                                       boolean expanded,
654                                                       boolean leaf,
655                                                       int row,
656                                                       boolean hasFocus) {
657             super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
658             if (value instanceof InfoNode) {
659                 setIcon(Utilities.getImageIcon(((InfoNode)value).getIconName()));
660             }
661             if (value instanceof ColumnNode) {
662                 if (showColumnNumber) {
663                     TreePath path = tree.getPathForRow(row);
664                     if (path != null) {
665                         TreePath parent = path.getParentPath();
666                         if (parent != null) {
667                             final int index = row - tree.getRowForPath(parent);
668                             setText(String.format("%d %s", index, getText()));
669                         }
670                     }
671                 }
672             }
673             return this;
674         }
675
676     }
677
678     private abstract static class InfoNode extends DefaultMutableTreeNode {
679
680         InfoNode(Object userObject) {
681             super(userObject, true);
682         }
683
684         @Override
685         public boolean isLeaf() {
686             return false;
687         }
688
689         abstract protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException;
690
691         String getIconName() {
692             final String className = getClass().getName();
693             final String nodeType = className.replaceFirst(".+?([^\\$]+)Node$", "$1");
694             return "node-" + nodeType.toLowerCase() + ".png";
695         }
696
697         protected String getNodeFullName() {
698             return String.valueOf(userObject);
699         }
700
701         static List<TableTypeNode> getTableTypeNodes(DatabaseMetaData dbmeta,
702                                                      String catalog,
703                                                      String schema) throws SQLException {
704             List<TableTypeNode> a = new ArrayList<TableTypeNode>();
705             ResultSet rs = dbmeta.getTableTypes();
706             try {
707                 while (rs.next()) {
708                     TableTypeNode typeNode = new TableTypeNode(catalog, schema, rs.getString(1));
709                     if (typeNode.hasItems(dbmeta)) {
710                         a.add(typeNode);
711                     }
712                 }
713             } finally {
714                 rs.close();
715             }
716             if (a.isEmpty()) {
717                 a.add(new TableTypeNode(catalog, schema, "TABLE"));
718             }
719             return a;
720         }
721
722     }
723
724     private static class ConnectorNode extends InfoNode {
725
726         ConnectorNode(String name) {
727             super(name);
728         }
729
730         @Override
731         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
732             List<InfoNode> a = new ArrayList<InfoNode>();
733             if (dbmeta.supportsCatalogsInDataManipulation()) {
734                 ResultSet rs = dbmeta.getCatalogs();
735                 try {
736                     while (rs.next()) {
737                         a.add(new CatalogNode(rs.getString(1)));
738                     }
739                 } finally {
740                     rs.close();
741                 }
742             } else if (dbmeta.supportsSchemasInDataManipulation()) {
743                 ResultSet rs = dbmeta.getSchemas();
744                 try {
745                     while (rs.next()) {
746                         a.add(new SchemaNode(null, rs.getString(1)));
747                     }
748                 } finally {
749                     rs.close();
750                 }
751             } else {
752                 a.addAll(getTableTypeNodes(dbmeta, null, null));
753             }
754             return a;
755         }
756
757     }
758
759     private static final class CatalogNode extends InfoNode {
760
761         private final String name;
762
763         CatalogNode(String name) {
764             super(name);
765             this.name = name;
766         }
767
768         @Override
769         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
770             List<InfoNode> a = new ArrayList<InfoNode>();
771             if (dbmeta.supportsSchemasInDataManipulation()) {
772                 ResultSet rs = dbmeta.getSchemas();
773                 try {
774                     while (rs.next()) {
775                         a.add(new SchemaNode(name, rs.getString(1)));
776                     }
777                 } finally {
778                     rs.close();
779                 }
780             } else {
781                 a.addAll(getTableTypeNodes(dbmeta, name, null));
782             }
783             return a;
784         }
785
786     }
787
788     private static final class SchemaNode extends InfoNode {
789
790         private final String catalog;
791         private final String schema;
792
793         SchemaNode(String catalog, String schema) {
794             super(schema);
795             this.catalog = catalog;
796             this.schema = schema;
797         }
798
799         @Override
800         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
801             List<InfoNode> a = new ArrayList<InfoNode>();
802             a.addAll(getTableTypeNodes(dbmeta, catalog, schema));
803             return a;
804         }
805
806     }
807
808     private static final class TableTypeNode extends InfoNode {
809
810         private static final String ICON_NAME_FORMAT = "node-tabletype-%s.png";
811
812         private final String catalog;
813         private final String schema;
814         private final String tableType;
815
816         TableTypeNode(String catalog, String schema, String tableType) {
817             super(tableType);
818             this.catalog = catalog;
819             this.schema = schema;
820             this.tableType = tableType;
821         }
822
823         @Override
824         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
825             List<InfoNode> a = new ArrayList<InfoNode>();
826             ResultSet rs = dbmeta.getTables(catalog, schema, null, new String[]{tableType});
827             try {
828                 while (rs.next()) {
829                     final String table = rs.getString(3);
830                     final String type = rs.getString(4);
831                     final boolean kindOfTable = type.matches("TABLE|VIEW|SYNONYM");
832                     a.add(new TableNode(catalog, schema, table, kindOfTable));
833                 }
834             } finally {
835                 rs.close();
836             }
837             return a;
838         }
839
840         @Override
841         String getIconName() {
842             final String name = String.format(ICON_NAME_FORMAT, getUserObject());
843             if (getClass().getResource("icon/" + name) == null) {
844                 return String.format(ICON_NAME_FORMAT, "");
845             }
846             return name;
847         }
848
849         boolean hasItems(DatabaseMetaData dbmeta) throws SQLException {
850             ResultSet rs = dbmeta.getTables(catalog, schema, null, new String[]{tableType});
851             try {
852                 return rs.next();
853             } finally {
854                 rs.close();
855             }
856         }
857
858     }
859
860     static final class TableNode extends InfoNode {
861
862         private final String catalog;
863         private final String schema;
864         private final String name;
865         private final boolean kindOfTable;
866
867         TableNode(String catalog, String schema, String name, boolean kindOfTable) {
868             super(name);
869             this.catalog = catalog;
870             this.schema = schema;
871             this.name = name;
872             this.kindOfTable = kindOfTable;
873         }
874
875         @Override
876         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
877             List<InfoNode> a = new ArrayList<InfoNode>();
878             ResultSet rs = dbmeta.getColumns(catalog, schema, name, null);
879             try {
880                 while (rs.next()) {
881                     a.add(new ColumnNode(rs.getString(4),
882                                          rs.getString(6),
883                                          rs.getInt(7),
884                                          rs.getString(18),
885                                          this));
886                 }
887             } finally {
888                 rs.close();
889             }
890             return a;
891         }
892
893         @Override
894         public boolean isLeaf() {
895             return false;
896         }
897
898         @Override
899         protected String getNodeFullName() {
900             List<String> a = new ArrayList<String>();
901             if (catalog != null) {
902                 a.add(catalog);
903             }
904             if (schema != null) {
905                 a.add(schema);
906             }
907             a.add(name);
908             return join(".", a);
909         }
910
911         String getName() {
912             return name;
913         }
914
915         boolean isKindOfTable() {
916             return kindOfTable;
917         }
918
919     }
920
921     static final class ColumnNode extends InfoNode {
922
923         private final String name;
924         private final TableNode tableNode;
925
926         ColumnNode(String name, String type, int size, String nulls, TableNode tableNode) {
927             super(format(name, type, size, nulls));
928             setAllowsChildren(false);
929             this.name = name;
930             this.tableNode = tableNode;
931         }
932
933         String getName() {
934             return name;
935         }
936
937         TableNode getTableNode() {
938             return tableNode;
939         }
940
941         private static String format(String name, String type, int size, String nulls) {
942             final String nonNull = "NO".equals(nulls) ? " NOT NULL" : "";
943             return String.format("%s [%s(%d)%s]", name, type, size, nonNull);
944         }
945
946         @Override
947         public boolean isLeaf() {
948             return true;
949         }
950
951         @Override
952         protected List<InfoNode> createChildren(DatabaseMetaData dbmeta) throws SQLException {
953             return emptyList();
954         }
955
956         @Override
957         protected String getNodeFullName() {
958             return String.format("%s.%s", tableNode.getNodeFullName(), name);
959         }
960
961     }
962
963 }