-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
/
TreeTable.java
418 lines (378 loc) · 14.9 KB
/
TreeTable.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
///////////////////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
// Copyright (C) 2001-2022 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
///////////////////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle.gui;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.EventObject;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.LookAndFeel;
import javax.swing.table.TableCellEditor;
import javax.swing.tree.TreePath;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.utils.XpathUtil;
import com.puppycrawl.tools.checkstyle.xpath.AbstractNode;
import com.puppycrawl.tools.checkstyle.xpath.RootNode;
import com.puppycrawl.tools.checkstyle.xpath.XpathQueryGenerator;
import net.sf.saxon.trans.XPathException;
/**
* This example shows how to create a simple TreeTable component,
* by using a JTree as a renderer (and editor) for the cells in a
* particular column in the JTable.
*
* <a href=
* "https://docs.oracle.com/cd/E48246_01/apirefs.1111/e13403/oracle/ide/controls/TreeTableModel.html">
* Original Source Location</a>
*
* @noinspection ThisEscapedInObjectConstruction
* @noinspectionreason ThisEscapedInObjectConstruction - only reference is used and not
* accessed until initialized
*/
public final class TreeTable extends JTable {
/** A unique serial version identifier. */
private static final long serialVersionUID = -8493693409423365387L;
/** The newline character. */
private static final String NEWLINE = "\n";
/** A subclass of JTree. */
private final TreeTableCellRenderer tree;
/** JTextArea editor. */
private JTextArea editor;
/** JTextArea xpathEditor. */
private JTextArea xpathEditor;
/** Line position map. */
private List<Integer> linePositionList;
/**
* Creates TreeTable base on TreeTableModel.
*
* @param treeTableModel Tree table model
*/
public TreeTable(ParseTreeTableModel treeTableModel) {
// Create the tree. It will be used as a renderer and editor.
tree = new TreeTableCellRenderer(this, treeTableModel);
// Install a tableModel representing the visible rows in the tree.
setModel(new TreeTableModelAdapter(treeTableModel, tree));
// Force the JTable and JTree to share their row selection models.
final ListToTreeSelectionModelWrapper selectionWrapper = new
ListToTreeSelectionModelWrapper(this);
tree.setSelectionModel(selectionWrapper);
setSelectionModel(selectionWrapper.getListSelectionModel());
// Install the tree editor renderer and editor.
setDefaultRenderer(ParseTreeTableModel.class, tree);
setDefaultEditor(ParseTreeTableModel.class, new TreeTableCellEditor());
// No grid.
setShowGrid(false);
// No intercell spacing
setIntercellSpacing(new Dimension(0, 0));
// And update the height of the trees row to match that of
// the table.
if (tree.getRowHeight() < 1) {
// Metal looks better like this.
setRowHeight(getRowHeight());
}
setColumnsInitialWidth();
final Action expand = new AbstractAction() {
private static final long serialVersionUID = -5859674518660156121L;
@Override
public void actionPerformed(ActionEvent event) {
expandSelectedNode();
}
};
final KeyStroke stroke = KeyStroke.getKeyStroke("ENTER");
final String command = "expand/collapse";
getInputMap().put(stroke, command);
getActionMap().put(command, expand);
addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent event) {
if (event.getClickCount() == 2) {
expandSelectedNode();
}
}
});
}
/**
* Do expansion of a tree node.
*/
private void expandSelectedNode() {
final TreePath selected = tree.getSelectionPath();
makeCodeSelection();
generateXpath();
if (tree.isExpanded(selected)) {
tree.collapsePath(selected);
}
else {
tree.expandPath(selected);
}
tree.setSelectionPath(selected);
}
/**
* Make selection of code in a text area.
*/
private void makeCodeSelection() {
new CodeSelector(tree.getLastSelectedPathComponent(), editor, linePositionList).select();
}
/**
* Generate Xpath.
*/
private void generateXpath() {
if (tree.getLastSelectedPathComponent() instanceof DetailAST) {
final DetailAST ast = (DetailAST) tree.getLastSelectedPathComponent();
final String xpath = XpathQueryGenerator.generateXpathQuery(ast);
xpathEditor.setText(xpath);
}
else {
xpathEditor.setText("Xpath is not supported yet for javadoc nodes");
}
}
/**
* Set initial value of width for columns in table.
*/
private void setColumnsInitialWidth() {
final FontMetrics fontMetrics = getFontMetrics(getFont());
// Six character string to contain "Column" column.
final int widthOfSixCharacterString = fontMetrics.stringWidth("XXXXXX");
// Padding must be added to width for columns to make them fully
// visible in table header.
final int padding = 10;
final int widthOfColumnContainingSixCharacterString =
widthOfSixCharacterString + padding;
getColumn("Line").setMaxWidth(widthOfColumnContainingSixCharacterString);
getColumn("Column").setMaxWidth(widthOfColumnContainingSixCharacterString);
final int preferredTreeColumnWidth =
Math.toIntExact(Math.round(getPreferredSize().getWidth() * 0.6));
getColumn("Tree").setPreferredWidth(preferredTreeColumnWidth);
// Twenty-eight character string to contain "Type" column
final int widthOfTwentyEightCharacterString =
fontMetrics.stringWidth("XXXXXXXXXXXXXXXXXXXXXXXXXXXX");
final int preferredTypeColumnWidth = widthOfTwentyEightCharacterString + padding;
getColumn("Type").setPreferredWidth(preferredTypeColumnWidth);
}
/**
* Select Node by Xpath.
*/
public void selectNodeByXpath() {
final DetailAST rootAST = (DetailAST) tree.getModel().getRoot();
if (rootAST.hasChildren()) {
final String xpath = xpathEditor.getText();
try {
final Deque<DetailAST> nodes =
XpathUtil.getXpathItems(xpath, new RootNode(rootAST))
.stream()
.map(AbstractNode.class::cast)
.map(AbstractNode::getUnderlyingNode)
.collect(Collectors.toCollection(ArrayDeque::new));
updateTreeTable(xpath, nodes);
}
catch (XPathException exception) {
xpathEditor.setText(xpathEditor.getText() + NEWLINE + exception.getMessage());
}
}
else {
xpathEditor.setText("No file Opened");
}
}
/**
* Updates the Treetable by expanding paths in the tree and highlighting
* associated code.
*
* @param xpath the XPath query to show in case of no match
* @param nodes the deque of DetailAST nodes to match in TreeTable and XPath editor
*/
private void updateTreeTable(String xpath, Deque<DetailAST> nodes) {
if (nodes.isEmpty()) {
xpathEditor.setText("No elements matching XPath query '"
+ xpath + "' found.");
}
else {
for (DetailAST node : nodes) {
expandTreeTableByPath(node);
makeCodeSelection();
}
xpathEditor.setText(getAllMatchingXpathQueriesText(nodes));
}
}
/**
* Expands path in tree table to given node so that user can
* see the node.
*
* @param node node to expand table by
*/
private void expandTreeTableByPath(DetailAST node) {
TreePath path = new TreePath(node);
path = path.pathByAddingChild(node);
if (!tree.isExpanded(path)) {
tree.expandPath(path);
}
tree.setSelectionPath(path);
}
/**
* Generates a String with all matching XPath queries separated
* by newlines.
*
* @param nodes deque of nodes to generate queries for
* @return complete text of all XPath expressions separated by newlines.
*/
private static String getAllMatchingXpathQueriesText(Deque<DetailAST> nodes) {
return nodes.stream()
.map(XpathQueryGenerator::generateXpathQuery)
.collect(Collectors.joining(NEWLINE, "", NEWLINE));
}
/**
* Overridden to message super and forward the method to the tree.
* Since the tree is not actually in the component hierarchy it will
* never receive this unless we forward it in this manner.
*/
@Override
public void updateUI() {
super.updateUI();
if (tree != null) {
tree.updateUI();
}
// Use the tree's default foreground and background colors in the
// table.
LookAndFeel.installColorsAndFont(this, "Tree.background",
"Tree.foreground", "Tree.font");
}
/* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
* paint the editor. The UI currently uses different techniques to
* paint the renderers and editors and overriding setBounds() below
* is not the right thing to do for an editor. Returning -1 for the
* editing row in this case, ensures the editor is never painted.
*/
@Override
public int getEditingRow() {
int rowIndex = -1;
final Class<?> editingClass = getColumnClass(editingColumn);
if (editingClass != ParseTreeTableModel.class) {
rowIndex = editingRow;
}
return rowIndex;
}
/**
* Overridden to pass the new rowHeight to the tree.
*/
@Override
public void setRowHeight(int newRowHeight) {
super.setRowHeight(newRowHeight);
if (tree != null && tree.getRowHeight() != newRowHeight) {
tree.setRowHeight(getRowHeight());
}
}
/**
* Returns tree.
*
* @return the tree that is being shared between the model.
*/
public JTree getTree() {
return tree;
}
/**
* Sets text area editor.
*
* @param textArea JTextArea component.
*/
public void setEditor(JTextArea textArea) {
editor = textArea;
}
/**
* Sets text area xpathEditor.
*
* @param xpathTextArea JTextArea component.
*/
public void setXpathEditor(JTextArea xpathTextArea) {
xpathEditor = xpathTextArea;
}
/**
* Sets line positions.
*
* @param linePositionList positions of lines.
*/
public void setLinePositionList(Collection<Integer> linePositionList) {
this.linePositionList = new ArrayList<>(linePositionList);
}
/**
* TreeTableCellEditor implementation. Component returned is the
* JTree.
*/
private class TreeTableCellEditor extends BaseCellEditor implements
TableCellEditor {
@Override
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int row, int column) {
return tree;
}
/**
* Overridden to return false, and if the event is a mouse event
* it is forwarded to the tree.
*
* <p>The behavior for this is debatable, and should really be offered
* as a property. By returning false, all keyboard actions are
* implemented in terms of the table. By returning true, the
* tree would get a chance to do something with the keyboard
* events. For the most part this is ok. But for certain keys,
* such as left/right, the tree will expand/collapse where as
* the table focus should really move to a different column. Page
* up/down should also be implemented in terms of the table.
* By returning false this also has the added benefit that clicking
* outside of the bounds of the tree node, but still in the tree
* column will select the row, whereas if this returned true
* that wouldn't be the case.
*
* <p>By returning false we are also enforcing the policy that
* the tree will never be editable (at least by a key sequence).
*
* @see TableCellEditor
*/
@Override
public boolean isCellEditable(EventObject event) {
if (event instanceof MouseEvent) {
for (int counter = getColumnCount() - 1; counter >= 0;
counter--) {
if (getColumnClass(counter) == ParseTreeTableModel.class) {
final MouseEvent mouseEvent = (MouseEvent) event;
final MouseEvent newMouseEvent = new MouseEvent(tree, mouseEvent.getID(),
mouseEvent.getWhen(), mouseEvent.getModifiersEx(),
mouseEvent.getX() - getCellRect(0, counter, true).x,
mouseEvent.getY(), mouseEvent.getClickCount(),
mouseEvent.isPopupTrigger());
tree.dispatchEvent(newMouseEvent);
break;
}
}
}
return false;
}
}
}