OSDN Git Service

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