OSDN Git Service

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