001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.gui;
021
022import javax.swing.JTree;
023import javax.swing.SwingUtilities;
024import javax.swing.event.TreeExpansionEvent;
025import javax.swing.event.TreeExpansionListener;
026import javax.swing.event.TreeModelEvent;
027import javax.swing.event.TreeModelListener;
028import javax.swing.table.AbstractTableModel;
029import javax.swing.tree.TreePath;
030
031/**
032 * This is a wrapper class takes a TreeTableModel and implements
033 * the table model interface. The implementation is trivial, with
034 * all the event dispatching support provided by the superclass:
035 * the AbstractTableModel.
036 * <a href=
037 * "https://docs.oracle.com/cd/E48246_01/apirefs.1111/e13403/oracle/ide/controls/TreeTableModel.html">
038 * Original&nbsp;Source&nbsp;Location</a>
039 *
040 */
041public class TreeTableModelAdapter extends AbstractTableModel {
042
043    /** A unique serial version identifier. */
044    private static final long serialVersionUID = 8269213416115369275L;
045
046    /** JTree component. */
047    private final JTree tree;
048    /** Tree table model. */
049    private final transient ParseTreeTableModel treeTableModel;
050
051    /**
052     * Initialise tree and treeTableModel class attributes.
053     *
054     * @param treeTableModel Tree table model.
055     * @param tree JTree component.
056     */
057    public TreeTableModelAdapter(ParseTreeTableModel treeTableModel, JTree tree) {
058        this.tree = tree;
059        this.treeTableModel = treeTableModel;
060
061        tree.addTreeExpansionListener(new UpdatingTreeExpansionListener());
062
063        // Install a TreeModelListener that can update the table when
064        // mTree changes. We use delayedFireTableDataChanged as we can
065        // not be guaranteed the mTree will have finished processing
066        // the event before us.
067        treeTableModel.addTreeModelListener(new UpdatingTreeModelListener());
068    }
069
070    // Wrappers, implementing TableModel interface.
071
072    @Override
073    public int getColumnCount() {
074        return treeTableModel.getColumnCount();
075    }
076
077    @Override
078    public String getColumnName(int column) {
079        return treeTableModel.getColumnName(column);
080    }
081
082    @Override
083    public Class<?> getColumnClass(int column) {
084        return treeTableModel.getColumnClass(column);
085    }
086
087    @Override
088    public int getRowCount() {
089        return tree.getRowCount();
090    }
091
092    @Override
093    public Object getValueAt(int row, int column) {
094        return treeTableModel.getValueAt(nodeForRow(row), column);
095    }
096
097    @Override
098    public boolean isCellEditable(int row, int column) {
099        return treeTableModel.isCellEditable(column);
100    }
101
102    /**
103     * Finds node for a given row.
104     *
105     * @param row Row for which to find a related node.
106     * @return Node for a given row.
107     */
108    private Object nodeForRow(int row) {
109        final TreePath treePath = tree.getPathForRow(row);
110        return treePath.getLastPathComponent();
111    }
112
113    /**
114     * TreeExpansionListener that can update the table when tree changes.
115     */
116    private final class UpdatingTreeExpansionListener implements TreeExpansionListener {
117
118        // Don't use fireTableRowsInserted() here; the selection model
119        // would get updated twice.
120        @Override
121        public void treeExpanded(TreeExpansionEvent event) {
122            fireTableDataChanged();
123        }
124
125        @Override
126        public void treeCollapsed(TreeExpansionEvent event) {
127            fireTableDataChanged();
128        }
129
130    }
131
132    /**
133     * TreeModelListener that can update the table when tree changes.
134     */
135    private final class UpdatingTreeModelListener implements TreeModelListener {
136
137        @Override
138        public void treeNodesChanged(TreeModelEvent event) {
139            delayedFireTableDataChanged();
140        }
141
142        @Override
143        public void treeNodesInserted(TreeModelEvent event) {
144            delayedFireTableDataChanged();
145        }
146
147        @Override
148        public void treeNodesRemoved(TreeModelEvent event) {
149            delayedFireTableDataChanged();
150        }
151
152        @Override
153        public void treeStructureChanged(TreeModelEvent event) {
154            delayedFireTableDataChanged();
155        }
156
157        /**
158         * Invokes fireTableDataChanged after all the pending events have been
159         * processed. SwingUtilities.invokeLater is used to handle this.
160         */
161        private void delayedFireTableDataChanged() {
162            SwingUtilities.invokeLater(TreeTableModelAdapter.this::fireTableDataChanged);
163        }
164
165    }
166
167}