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) {
136             result = getValueAtDetailNode((DetailNode) node, 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) {
156             result = ((DetailNode) parent).getChildren()[index];
157         }
158         else {
159             result = getChildAtDetailAst((DetailAST) parent, index);
160         }
161 
162         return result;
163     }
164 
165     /**
166      * Returns the number of children of parent.
167      *
168      * @param parent the node to count children for.
169      * @return the number of children of the node parent.
170      */
171     public int getChildCount(Object parent) {
172         final int result;
173 
174         if (parent instanceof DetailNode) {
175             result = ((DetailNode) parent).getChildren().length;
176         }
177         else {
178             if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
179                     && ((DetailAST) parent).getType() == TokenTypes.COMMENT_CONTENT
180                     && JavadocUtil.isJavadocComment(((DetailAST) parent).getParent())) {
181                 // getChildCount return 0 on COMMENT_CONTENT,
182                 // but we need to attach javadoc tree, that is separate tree
183                 result = 1;
184             }
185             else {
186                 result = ((DetailAST) parent).getChildCount();
187             }
188         }
189 
190         return result;
191     }
192 
193     /**
194      * Returns value of root.
195      *
196      * @return the root.
197      */
198     public Object getRoot() {
199         return root;
200     }
201 
202     /**
203      * Whether the node is a leaf.
204      *
205      * @param node the node to check.
206      * @return true if the node is a leaf.
207      */
208     public boolean isLeaf(Object node) {
209         return getChildCount(node) == 0;
210     }
211 
212     /**
213      * Return the index of child in parent.  If either {@code parent}
214      * or {@code child} is {@code null}, returns -1.
215      * If either {@code parent} or {@code child} don't
216      * belong to this tree model, returns -1.
217      *
218      * @param parent a node in the tree, obtained from this data source.
219      * @param child the node we are interested in.
220      * @return the index of the child in the parent, or -1 if either
221      *     {@code child} or {@code parent} are {@code null}
222      *     or don't belong to this tree model.
223      */
224     public int getIndexOfChild(Object parent, Object child) {
225         int index = -1;
226         for (int i = 0; i < getChildCount(parent); i++) {
227             if (getChild(parent, i).equals(child)) {
228                 index = i;
229                 break;
230             }
231         }
232         return index;
233     }
234 
235     /**
236      * Indicates whether the value for node {@code node}, at column number {@code column} is
237      * editable.
238      *
239      * @param column the column number
240      * @return true if editable
241      */
242     public boolean isCellEditable(int column) {
243         return false;
244     }
245 
246     /**
247      * Gets child of DetailAST node at specified index.
248      *
249      * @param parent DetailAST node
250      * @param index child index
251      * @return child DetailsAST or DetailNode if child is Javadoc node
252      *         and parseMode is JAVA_WITH_JAVADOC_AND_COMMENTS.
253      */
254     private Object getChildAtDetailAst(DetailAST parent, int index) {
255         final Object result;
256         if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
257                 && parent.getType() == TokenTypes.COMMENT_CONTENT
258                 && JavadocUtil.isJavadocComment(parent.getParent())) {
259             result = getJavadocTree(parent.getParent());
260         }
261         else {
262             int currentIndex = 0;
263             DetailAST child = parent.getFirstChild();
264             while (currentIndex < index) {
265                 child = child.getNextSibling();
266                 currentIndex++;
267             }
268             result = child;
269         }
270 
271         return result;
272     }
273 
274     /**
275      * Gets a value for DetailNode object.
276      *
277      * @param node DetailNode(Javadoc) node.
278      * @param column column index.
279      * @return value at specified column.
280      * @throws IllegalStateException if an unknown column index was specified.
281      */
282     private static Object getValueAtDetailNode(DetailNode node, int column) {
283 
284         return switch (column) {
285             case 0 ->
286                 // first column is tree model. no value needed
287                 null;
288             case 1 -> JavadocUtil.getTokenName(node.getType());
289             case 2 -> node.getLineNumber();
290             case 3 -> node.getColumnNumber();
291             case 4 -> node.getText();
292             default -> throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
293         };
294     }
295 
296     /**
297      * Gets a value for DetailAST object.
298      *
299      * @param ast DetailAST node.
300      * @param column column index.
301      * @return value at specified column.
302      * @throws IllegalStateException if an unknown column index was specified.
303      */
304     private static Object getValueAtDetailAST(DetailAST ast, int column) {
305 
306         return switch (column) {
307             case 0 ->
308                 // first column is tree model. no value needed
309                 null;
310             case 1 -> TokenUtil.getTokenName(ast.getType());
311             case 2 -> ast.getLineNo();
312             case 3 -> ast.getColumnNo();
313             case 4 -> ast.getText();
314             default -> throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
315         };
316     }
317 
318     /**
319      * Gets Javadoc (DetailNode) tree of specified block comments.
320      *
321      * @param blockComment Javadoc comment as a block comment
322      * @return root of DetailNode tree
323      */
324     private DetailNode getJavadocTree(DetailAST blockComment) {
325         return blockCommentToJavadocTree.computeIfAbsent(blockComment,
326                 ParseTreeTablePresentation::parseJavadocTree);
327     }
328 
329     /**
330      * Parses Javadoc (DetailNode) tree of specified block comments.
331      *
332      * @param blockComment Javadoc comment as a block comment
333      * @return root of DetailNode tree
334      */
335     private static DetailNode parseJavadocTree(DetailAST blockComment) {
336         return new JavadocDetailNodeParser().parseJavadocAsDetailNode(blockComment).getTree();
337     }
338 
339 }