View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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         final Class<?> columnClass;
117 
118         switch (column) {
119             case 0:
120                 columnClass = ParseTreeTableModel.class;
121                 break;
122             case 1:
123             case 4:
124                 columnClass = String.class;
125                 break;
126             case 2:
127             case 3:
128                 columnClass = Integer.class;
129                 break;
130             default:
131                 throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
132         }
133         return columnClass;
134     }
135 
136     /**
137      * Returns the value to be displayed for node at column number.
138      *
139      * @param node the node
140      * @param column the column number
141      * @return the value to be displayed for node {@code node}, at column number {@code column}.
142      */
143     public Object getValueAt(Object node, int column) {
144         final Object result;
145 
146         if (node instanceof DetailNode) {
147             result = getValueAtDetailNode((DetailNode) node, column);
148         }
149         else {
150             result = getValueAtDetailAST((DetailAST) node, column);
151         }
152 
153         return result;
154     }
155 
156     /**
157      * Returns the child of parent at index.
158      *
159      * @param parent the node to get a child from.
160      * @param index the index of a child.
161      * @return the child of parent at index.
162      */
163     public Object getChild(Object parent, int index) {
164         final Object result;
165 
166         if (parent instanceof DetailNode) {
167             result = ((DetailNode) parent).getChildren()[index];
168         }
169         else {
170             result = getChildAtDetailAst((DetailAST) parent, index);
171         }
172 
173         return result;
174     }
175 
176     /**
177      * Returns the number of children of parent.
178      *
179      * @param parent the node to count children for.
180      * @return the number of children of the node parent.
181      */
182     public int getChildCount(Object parent) {
183         final int result;
184 
185         if (parent instanceof DetailNode) {
186             result = ((DetailNode) parent).getChildren().length;
187         }
188         else {
189             if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
190                     && ((DetailAST) parent).getType() == TokenTypes.COMMENT_CONTENT
191                     && JavadocUtil.isJavadocComment(((DetailAST) parent).getParent())) {
192                 // getChildCount return 0 on COMMENT_CONTENT,
193                 // but we need to attach javadoc tree, that is separate tree
194                 result = 1;
195             }
196             else {
197                 result = ((DetailAST) parent).getChildCount();
198             }
199         }
200 
201         return result;
202     }
203 
204     /**
205      * Returns value of root.
206      *
207      * @return the root.
208      */
209     public Object getRoot() {
210         return root;
211     }
212 
213     /**
214      * Whether the node is a leaf.
215      *
216      * @param node the node to check.
217      * @return true if the node is a leaf.
218      */
219     public boolean isLeaf(Object node) {
220         return getChildCount(node) == 0;
221     }
222 
223     /**
224      * Return the index of child in parent.  If either {@code parent}
225      * or {@code child} is {@code null}, returns -1.
226      * If either {@code parent} or {@code child} don't
227      * belong to this tree model, returns -1.
228      *
229      * @param parent a node in the tree, obtained from this data source.
230      * @param child the node we are interested in.
231      * @return the index of the child in the parent, or -1 if either
232      *     {@code child} or {@code parent} are {@code null}
233      *     or don't belong to this tree model.
234      */
235     public int getIndexOfChild(Object parent, Object child) {
236         int index = -1;
237         for (int i = 0; i < getChildCount(parent); i++) {
238             if (getChild(parent, i).equals(child)) {
239                 index = i;
240                 break;
241             }
242         }
243         return index;
244     }
245 
246     /**
247      * Indicates whether the value for node {@code node}, at column number {@code column} is
248      * editable.
249      *
250      * @param column the column number
251      * @return true if editable
252      */
253     public boolean isCellEditable(int column) {
254         return false;
255     }
256 
257     /**
258      * Gets child of DetailAST node at specified index.
259      *
260      * @param parent DetailAST node
261      * @param index child index
262      * @return child DetailsAST or DetailNode if child is Javadoc node
263      *         and parseMode is JAVA_WITH_JAVADOC_AND_COMMENTS.
264      */
265     private Object getChildAtDetailAst(DetailAST parent, int index) {
266         final Object result;
267         if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
268                 && parent.getType() == TokenTypes.COMMENT_CONTENT
269                 && JavadocUtil.isJavadocComment(parent.getParent())) {
270             result = getJavadocTree(parent.getParent());
271         }
272         else {
273             int currentIndex = 0;
274             DetailAST child = parent.getFirstChild();
275             while (currentIndex < index) {
276                 child = child.getNextSibling();
277                 currentIndex++;
278             }
279             result = child;
280         }
281 
282         return result;
283     }
284 
285     /**
286      * Gets a value for DetailNode object.
287      *
288      * @param node DetailNode(Javadoc) node.
289      * @param column column index.
290      * @return value at specified column.
291      * @throws IllegalStateException if an unknown column index was specified.
292      */
293     private static Object getValueAtDetailNode(DetailNode node, int column) {
294         final Object value;
295 
296         switch (column) {
297             case 0:
298                 // first column is tree model. no value needed
299                 value = null;
300                 break;
301             case 1:
302                 value = JavadocUtil.getTokenName(node.getType());
303                 break;
304             case 2:
305                 value = node.getLineNumber();
306                 break;
307             case 3:
308                 value = node.getColumnNumber();
309                 break;
310             case 4:
311                 value = node.getText();
312                 break;
313             default:
314                 throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
315         }
316         return value;
317     }
318 
319     /**
320      * Gets a value for DetailAST object.
321      *
322      * @param ast DetailAST node.
323      * @param column column index.
324      * @return value at specified column.
325      * @throws IllegalStateException if an unknown column index was specified.
326      */
327     private static Object getValueAtDetailAST(DetailAST ast, int column) {
328         final Object value;
329 
330         switch (column) {
331             case 0:
332                 // first column is tree model. no value needed
333                 value = null;
334                 break;
335             case 1:
336                 value = TokenUtil.getTokenName(ast.getType());
337                 break;
338             case 2:
339                 value = ast.getLineNo();
340                 break;
341             case 3:
342                 value = ast.getColumnNo();
343                 break;
344             case 4:
345                 value = ast.getText();
346                 break;
347             default:
348                 throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
349         }
350         return value;
351     }
352 
353     /**
354      * Gets Javadoc (DetailNode) tree of specified block comments.
355      *
356      * @param blockComment Javadoc comment as a block comment
357      * @return root of DetailNode tree
358      */
359     private DetailNode getJavadocTree(DetailAST blockComment) {
360         return blockCommentToJavadocTree.computeIfAbsent(blockComment,
361                 ParseTreeTablePresentation::parseJavadocTree);
362     }
363 
364     /**
365      * Parses Javadoc (DetailNode) tree of specified block comments.
366      *
367      * @param blockComment Javadoc comment as a block comment
368      * @return root of DetailNode tree
369      */
370     private static DetailNode parseJavadocTree(DetailAST blockComment) {
371         return new JavadocDetailNodeParser().parseJavadocAsDetailNode(blockComment).getTree();
372     }
373 
374 }