OSDN Git Service

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