1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  package com.puppycrawl.tools.checkstyle.gui;
21  
22  import java.awt.Component;
23  import java.awt.Dimension;
24  import java.awt.FontMetrics;
25  import java.awt.event.ActionEvent;
26  import java.awt.event.MouseAdapter;
27  import java.awt.event.MouseEvent;
28  import java.io.Serial;
29  import java.util.ArrayDeque;
30  import java.util.ArrayList;
31  import java.util.Collection;
32  import java.util.Deque;
33  import java.util.EventObject;
34  import java.util.List;
35  import java.util.stream.Collectors;
36  
37  import javax.swing.AbstractAction;
38  import javax.swing.Action;
39  import javax.swing.JTable;
40  import javax.swing.JTextArea;
41  import javax.swing.JTree;
42  import javax.swing.KeyStroke;
43  import javax.swing.LookAndFeel;
44  import javax.swing.table.TableCellEditor;
45  import javax.swing.tree.TreePath;
46  
47  import com.puppycrawl.tools.checkstyle.api.DetailAST;
48  import com.puppycrawl.tools.checkstyle.utils.XpathUtil;
49  import com.puppycrawl.tools.checkstyle.xpath.ElementNode;
50  import com.puppycrawl.tools.checkstyle.xpath.RootNode;
51  import com.puppycrawl.tools.checkstyle.xpath.XpathQueryGenerator;
52  import net.sf.saxon.trans.XPathException;
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  public final class TreeTable extends JTable {
67  
68      
69      @Serial
70      private static final long serialVersionUID = -8493693409423365387L;
71      
72      private static final String NEWLINE = "\n";
73      
74      private final TreeTableCellRenderer tree;
75      
76      private JTextArea editor;
77      
78      private JTextArea xpathEditor;
79      
80      private List<Integer> linePositionList;
81  
82      
83  
84  
85  
86  
87      public TreeTable(ParseTreeTableModel treeTableModel) {
88          
89          tree = new TreeTableCellRenderer(this, treeTableModel);
90  
91          
92          setModel(new TreeTableModelAdapter(treeTableModel, tree));
93  
94          
95          final ListToTreeSelectionModelWrapper selectionWrapper = new
96                  ListToTreeSelectionModelWrapper(this);
97          tree.setSelectionModel(selectionWrapper);
98          setSelectionModel(selectionWrapper.getListSelectionModel());
99  
100         
101         setDefaultRenderer(ParseTreeTableModel.class, tree);
102         setDefaultEditor(ParseTreeTableModel.class, new TreeTableCellEditor());
103 
104         
105         setShowGrid(false);
106 
107         
108         setIntercellSpacing(new Dimension(0, 0));
109 
110         
111         
112         if (tree.getRowHeight() < 1) {
113             
114             final int height = getRowHeight();
115             setRowHeight(height);
116         }
117 
118         setColumnsInitialWidth();
119 
120         final Action expand = new AbstractAction() {
121             @Serial
122             private static final long serialVersionUID = -5859674518660156121L;
123 
124             @Override
125             public void actionPerformed(ActionEvent event) {
126                 expandSelectedNode();
127             }
128         };
129         final KeyStroke stroke = KeyStroke.getKeyStroke("ENTER");
130         final String command = "expand/collapse";
131         getInputMap().put(stroke, command);
132         getActionMap().put(command, expand);
133 
134         addMouseListener(new MouseAdapter() {
135             @Override
136             public void mouseClicked(MouseEvent event) {
137                 if (event.getClickCount() == 2) {
138                     expandSelectedNode();
139                 }
140             }
141         });
142     }
143 
144     
145 
146 
147     private void expandSelectedNode() {
148         final TreePath selected = tree.getSelectionPath();
149         makeCodeSelection();
150         generateXpath();
151 
152         if (tree.isExpanded(selected)) {
153             tree.collapsePath(selected);
154         }
155         else {
156             tree.expandPath(selected);
157         }
158         tree.setSelectionPath(selected);
159     }
160 
161     
162 
163 
164     private void makeCodeSelection() {
165         new CodeSelector(tree.getLastSelectedPathComponent(), editor, linePositionList).select();
166     }
167 
168     
169 
170 
171     private void generateXpath() {
172         if (tree.getLastSelectedPathComponent() instanceof DetailAST ast) {
173             final String xpath = XpathQueryGenerator.generateXpathQuery(ast);
174             xpathEditor.setText(xpath);
175         }
176         else {
177             xpathEditor.setText("Xpath is not supported yet for javadoc nodes");
178         }
179     }
180 
181     
182 
183 
184     private void setColumnsInitialWidth() {
185         final FontMetrics fontMetrics = getFontMetrics(getFont());
186         
187         final int widthOfSixCharacterString = fontMetrics.stringWidth("XXXXXX");
188         
189         
190         final int padding = 10;
191         final int widthOfColumnContainingSixCharacterString =
192                 widthOfSixCharacterString + padding;
193         getColumn("Line").setMaxWidth(widthOfColumnContainingSixCharacterString);
194         getColumn("Column").setMaxWidth(widthOfColumnContainingSixCharacterString);
195         final int preferredTreeColumnWidth =
196                 Math.toIntExact(Math.round(getPreferredSize().getWidth() * 0.6));
197         getColumn("Tree").setPreferredWidth(preferredTreeColumnWidth);
198         
199         final int widthOfTwentyEightCharacterString =
200                 fontMetrics.stringWidth("XXXXXXXXXXXXXXXXXXXXXXXXXXXX");
201         final int preferredTypeColumnWidth = widthOfTwentyEightCharacterString + padding;
202         getColumn("Type").setPreferredWidth(preferredTypeColumnWidth);
203     }
204 
205     
206 
207 
208     public void selectNodeByXpath() {
209         final DetailAST rootAST = (DetailAST) tree.getModel().getRoot();
210         if (rootAST.hasChildren()) {
211             final String xpath = xpathEditor.getText();
212 
213             try {
214                 final Deque<DetailAST> nodes =
215                         XpathUtil.getXpathItems(xpath, new RootNode(rootAST))
216                               .stream()
217                               .map(ElementNode.class::cast)
218                               .map(ElementNode::getUnderlyingNode)
219                               .collect(Collectors.toCollection(ArrayDeque::new));
220                 updateTreeTable(xpath, nodes);
221             }
222             catch (XPathException exception) {
223                 xpathEditor.setText(xpathEditor.getText() + NEWLINE + exception.getMessage());
224             }
225         }
226         else {
227             xpathEditor.setText("No file Opened");
228         }
229     }
230 
231     
232 
233 
234 
235 
236 
237 
238     private void updateTreeTable(String xpath, Deque<DetailAST> nodes) {
239         if (nodes.isEmpty()) {
240             xpathEditor.setText("No elements matching XPath query '"
241                     + xpath + "' found.");
242         }
243         else {
244             for (DetailAST node : nodes) {
245                 expandTreeTableByPath(node);
246                 makeCodeSelection();
247             }
248             xpathEditor.setText(getAllMatchingXpathQueriesText(nodes));
249         }
250     }
251 
252     
253 
254 
255 
256 
257 
258     private void expandTreeTableByPath(DetailAST node) {
259         TreePath path = new TreePath(node);
260         path = path.pathByAddingChild(node);
261         if (!tree.isExpanded(path)) {
262             tree.expandPath(path);
263         }
264         tree.setSelectionPath(path);
265     }
266 
267     
268 
269 
270 
271 
272 
273 
274     private static String getAllMatchingXpathQueriesText(Deque<DetailAST> nodes) {
275         return nodes.stream()
276                 .map(XpathQueryGenerator::generateXpathQuery)
277                 .collect(Collectors.joining(NEWLINE, "", NEWLINE));
278     }
279 
280     
281 
282 
283 
284 
285     @Override
286     public void updateUI() {
287         super.updateUI();
288         if (tree != null) {
289             tree.updateUI();
290         }
291         
292         
293         LookAndFeel.installColorsAndFont(this, "Tree.background",
294                 "Tree.foreground", "Tree.font");
295     }
296 
297     
298 
299 
300 
301 
302 
303     @Override
304     public int getEditingRow() {
305         int rowIndex = -1;
306         final Class<?> editingClass = getColumnClass(editingColumn);
307         if (editingClass != ParseTreeTableModel.class) {
308             rowIndex = editingRow;
309         }
310         return rowIndex;
311     }
312 
313     
314 
315 
316     @Override
317     public void setRowHeight(int newRowHeight) {
318         super.setRowHeight(newRowHeight);
319         if (tree != null && tree.getRowHeight() != newRowHeight) {
320             tree.setRowHeight(getRowHeight());
321         }
322     }
323 
324     
325 
326 
327 
328 
329     public JTree getTree() {
330         return tree;
331     }
332 
333     
334 
335 
336 
337 
338     public void setEditor(JTextArea textArea) {
339         editor = textArea;
340     }
341 
342     
343 
344 
345 
346 
347     public void setXpathEditor(JTextArea xpathTextArea) {
348         xpathEditor = xpathTextArea;
349     }
350 
351     
352 
353 
354 
355 
356     public void setLinePositionList(Collection<Integer> linePositionList) {
357         this.linePositionList = new ArrayList<>(linePositionList);
358     }
359 
360     
361 
362 
363 
364     private final class TreeTableCellEditor extends BaseCellEditor implements
365             TableCellEditor {
366 
367         @Override
368         public Component getTableCellEditorComponent(JTable table,
369                 Object value,
370                 boolean isSelected,
371                 int row, int column) {
372             return tree;
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         @Override
398         public boolean isCellEditable(EventObject event) {
399             if (event instanceof MouseEvent mouseEvent) {
400                 for (int counter = getColumnCount() - 1; counter >= 0;
401                      counter--) {
402                     if (getColumnClass(counter) == ParseTreeTableModel.class) {
403                         final MouseEvent newMouseEvent = new MouseEvent(tree, mouseEvent.getID(),
404                                 mouseEvent.getWhen(), mouseEvent.getModifiersEx(),
405                                 mouseEvent.getX() - getCellRect(0, counter, true).x,
406                                 mouseEvent.getY(), mouseEvent.getClickCount(),
407                                 mouseEvent.isPopupTrigger());
408                         tree.dispatchEvent(newMouseEvent);
409                         break;
410                     }
411                 }
412             }
413 
414             return false;
415         }
416 
417     }
418 
419 }