View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.gui;
21  
22  import java.util.HashMap;
23  import java.util.Map;
24  
25  import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser;
26  import com.puppycrawl.tools.checkstyle.api.DetailAST;
27  import com.puppycrawl.tools.checkstyle.api.DetailNode;
28  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29  import com.puppycrawl.tools.checkstyle.gui.MainFrameModel.ParseMode;
30  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
31  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
32  
33  /**
34   * The model that backs the parse tree in the GUI.
35   *
36   */
37  public class ParseTreeTablePresentation {
38  
39      /** Exception message. */
40      private static final String UNKNOWN_COLUMN_MSG = "Unknown column";
41  
42      /** Column names. */
43      private static final String[] COLUMN_NAMES = {
44          "Tree",
45          "Type",
46          "Line",
47          "Column",
48          "Text",
49      };
50  
51      /** Cache to store already parsed Javadoc comments. Used for optimisation purposes. */
52      private final Map<DetailAST, DetailNode> blockCommentToJavadocTree = new HashMap<>();
53  
54      /** The root node of the tree table model. */
55      private DetailAST root;
56  
57      /** Parsing mode. */
58      private ParseMode parseMode;
59  
60      /**
61       * Constructor initialise root node.
62       *
63       * @param parseTree DetailAST parse tree.
64       */
65      public ParseTreeTablePresentation(DetailAST parseTree) {
66          root = parseTree;
67      }
68  
69      /**
70       * Set parse tree.
71       *
72       * @param parseTree DetailAST parse tree.
73       */
74      protected final void setRoot(DetailAST parseTree) {
75          root = parseTree;
76      }
77  
78      /**
79       * Set parse mode.
80       *
81       * @param mode ParseMode enum
82       */
83      protected void setParseMode(ParseMode mode) {
84          parseMode = mode;
85      }
86  
87      /**
88       * Returns number of available columns.
89       *
90       * @return the number of available columns.
91       */
92      public int getColumnCount() {
93          return COLUMN_NAMES.length;
94      }
95  
96      /**
97       * Returns name for specified column number.
98       *
99       * @param column the column number
100      * @return the name for column number {@code column}.
101      */
102     public String getColumnName(int column) {
103         return COLUMN_NAMES[column];
104     }
105 
106     /**
107      * Returns type of specified column number.
108      *
109      * @param column the column number
110      * @return the type for column number {@code column}.
111      * @throws IllegalStateException if an unknown column index was specified.
112      */
113     // -@cs[ForbidWildcardAsReturnType] We need to satisfy javax.swing.table.AbstractTableModel
114     // public Class<?> getColumnClass(int columnIndex) {...}
115     public Class<?> getColumnClass(int column) {
116 
117         return switch (column) {
118             case 0 -> ParseTreeTableModel.class;
119             case 1, 4 -> String.class;
120             case 2, 3 -> Integer.class;
121             default -> throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
122         };
123     }
124 
125     /**
126      * Returns the value to be displayed for node at column number.
127      *
128      * @param node the node
129      * @param column the column number
130      * @return the value to be displayed for node {@code node}, at column number {@code column}.
131      */
132     public Object getValueAt(Object node, int column) {
133         final Object result;
134 
135         if (node instanceof DetailNode detailNode) {
136             result = getValueAtDetailNode(detailNode, column);
137         }
138         else {
139             result = getValueAtDetailAST((DetailAST) node, column);
140         }
141 
142         return result;
143     }
144 
145     /**
146      * Returns the child of parent at index.
147      *
148      * @param parent the node to get a child from.
149      * @param index the index of a child.
150      * @return the child of parent at index.
151      */
152     public Object getChild(Object parent, int index) {
153         final Object result;
154 
155         if (parent instanceof DetailNode parentNode) {
156             DetailNode node = parentNode.getFirstChild();
157             for (int nodeIndex = 0; nodeIndex < index; nodeIndex++) {
158                 node = node.getNextSibling();
159             }
160             result = node;
161         }
162         else {
163             result = getChildAtDetailAst((DetailAST) parent, index);
164         }
165 
166         return result;
167     }
168 
169     /**
170      * Returns the number of children of parent.
171      *
172      * @param parent the node to count children for.
173      * @return the number of children of the node parent.
174      */
175     public int getChildCount(Object parent) {
176         int result = 0;
177 
178         if (parent instanceof DetailNode parentNode) {
179             DetailNode node = parentNode.getFirstChild();
180             while (node != null) {
181                 node = node.getNextSibling();
182                 result++;
183             }
184         }
185         else {
186             if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
187                     && ((DetailAST) parent).getType() == TokenTypes.COMMENT_CONTENT
188                     && JavadocUtil.isJavadocComment(((DetailAST) parent).getParent())) {
189                 // getChildCount return 0 on COMMENT_CONTENT,
190                 // but we need to attach javadoc tree, that is separate tree
191                 result = 1;
192             }
193             else {
194                 result = ((DetailAST) parent).getChildCount();
195             }
196         }
197 
198         return result;
199     }
200 
201     /**
202      * Returns value of root.
203      *
204      * @return the root.
205      */
206     public Object getRoot() {
207         return root;
208     }
209 
210     /**
211      * Whether the node is a leaf.
212      *
213      * @param node the node to check.
214      * @return true if the node is a leaf.
215      */
216     public boolean isLeaf(Object node) {
217         return getChildCount(node) == 0;
218     }
219 
220     /**
221      * Return the index of child in parent.  If either {@code parent}
222      * or {@code child} is {@code null}, returns -1.
223      * If either {@code parent} or {@code child} don't
224      * belong to this tree model, returns -1.
225      *
226      * @param parent a node in the tree, obtained from this data source.
227      * @param child the node we are interested in.
228      * @return the index of the child in the parent, or -1 if either
229      *     {@code child} or {@code parent} are {@code null}
230      *     or don't belong to this tree model.
231      */
232     public int getIndexOfChild(Object parent, Object child) {
233         int index = -1;
234         for (int i = 0; i < getChildCount(parent); i++) {
235             if (getChild(parent, i).equals(child)) {
236                 index = i;
237                 break;
238             }
239         }
240         return index;
241     }
242 
243     /**
244      * Indicates whether the value for node {@code node}, at column number {@code column} is
245      * editable.
246      *
247      * @param column the column number
248      * @return true if editable
249      */
250     public boolean isCellEditable(int column) {
251         return false;
252     }
253 
254     /**
255      * Gets child of DetailAST node at specified index.
256      *
257      * @param parent DetailAST node
258      * @param index child index
259      * @return child DetailsAST or DetailNode if child is Javadoc node
260      *         and parseMode is JAVA_WITH_JAVADOC_AND_COMMENTS.
261      */
262     private Object getChildAtDetailAst(DetailAST parent, int index) {
263         final Object result;
264         if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
265                 && parent.getType() == TokenTypes.COMMENT_CONTENT
266                 && JavadocUtil.isJavadocComment(parent.getParent())) {
267             result = getJavadocTree(parent.getParent());
268         }
269         else {
270             int currentIndex = 0;
271             DetailAST child = parent.getFirstChild();
272             while (currentIndex < index) {
273                 child = child.getNextSibling();
274                 currentIndex++;
275             }
276             result = child;
277         }
278 
279         return result;
280     }
281 
282     /**
283      * Gets a value for DetailNode object.
284      *
285      * @param node DetailNode(Javadoc) node.
286      * @param column column index.
287      * @return value at specified column.
288      * @throws IllegalStateException if an unknown column index was specified.
289      */
290     private static Object getValueAtDetailNode(DetailNode node, int column) {
291 
292         return switch (column) {
293             case 0 ->
294                 // first column is tree model. no value needed
295                 null;
296             case 1 -> JavadocUtil.getTokenName(node.getType());
297             case 2 -> node.getLineNumber();
298             case 3 -> node.getColumnNumber();
299             case 4 -> node.getText();
300             default -> throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
301         };
302     }
303 
304     /**
305      * Gets a value for DetailAST object.
306      *
307      * @param ast DetailAST node.
308      * @param column column index.
309      * @return value at specified column.
310      * @throws IllegalStateException if an unknown column index was specified.
311      */
312     private static Object getValueAtDetailAST(DetailAST ast, int column) {
313 
314         return switch (column) {
315             case 0 ->
316                 // first column is tree model. no value needed
317                 null;
318             case 1 -> TokenUtil.getTokenName(ast.getType());
319             case 2 -> ast.getLineNo();
320             case 3 -> ast.getColumnNo();
321             case 4 -> ast.getText();
322             default -> throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
323         };
324     }
325 
326     /**
327      * Gets Javadoc (DetailNode) tree of specified block comments.
328      *
329      * @param blockComment Javadoc comment as a block comment
330      * @return root of DetailNode tree
331      */
332     private DetailNode getJavadocTree(DetailAST blockComment) {
333         return blockCommentToJavadocTree.computeIfAbsent(blockComment,
334                 ParseTreeTablePresentation::parseJavadocTree);
335     }
336 
337     /**
338      * Parses Javadoc (DetailNode) tree of specified block comments.
339      *
340      * @param blockComment Javadoc comment as a block comment
341      * @return root of DetailNode tree
342      */
343     private static DetailNode parseJavadocTree(DetailAST blockComment) {
344         return new JavadocDetailNodeParser().parseJavadocComment(blockComment).getTree();
345     }
346 
347 }