1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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 antlr.ASTFactory;
26  import antlr.collections.AST;
27  import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.DetailNode;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  import com.puppycrawl.tools.checkstyle.gui.MainFrameModel.ParseMode;
32  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
33  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
34  
35  /**
36   * The model that backs the parse tree in the GUI.
37   *
38   */
39  public class ParseTreeTablePresentation {
40  
41      /** Exception message. */
42      private static final String UNKNOWN_COLUMN_MSG = "Unknown column";
43  
44      /** Column names. */
45      private static final String[] COLUMN_NAMES = {
46          "Tree",
47          "Type",
48          "Line",
49          "Column",
50          "Text",
51      };
52  
53      /**
54       * The root node of the tree table model.
55       */
56      private final Object root;
57  
58      /** Cache to store already parsed Javadoc comments. Used for optimisation purposes. */
59      private final Map<DetailAST, DetailNode> blockCommentToJavadocTree = new HashMap<>();
60  
61      /** Parsing mode. */
62      private ParseMode parseMode;
63  
64      /**
65       * Constructor initialise root node.
66       * @param parseTree DetailAST parse tree.
67       */
68      public ParseTreeTablePresentation(DetailAST parseTree) {
69          root = createArtificialTreeRoot();
70          setParseTree(parseTree);
71      }
72  
73      /**
74       * Set parse tree.
75       * @param parseTree DetailAST parse tree.
76       */
77      protected final void setParseTree(DetailAST parseTree) {
78          ((AST) root).setFirstChild(parseTree);
79      }
80  
81      /**
82       * Set parse mode.
83       * @param mode ParseMode enum
84       */
85      protected void setParseMode(ParseMode mode) {
86          parseMode = mode;
87      }
88  
89      /**
90       * Returns number of available columns.
91       * @return the number of available columns.
92       */
93      public int getColumnCount() {
94          return COLUMN_NAMES.length;
95      }
96  
97      /**
98       * Returns name for specified column number.
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      * @param column the column number
109      * @return the type for column number {@code column}.
110      */
111     // -@cs[ForbidWildcardAsReturnType] We need to satisfy javax.swing.table.AbstractTableModel
112     // public Class<?> getColumnClass(int columnIndex) {...}
113     public Class<?> getColumnClass(int column) {
114         final Class<?> columnClass;
115 
116         switch (column) {
117             case 0:
118                 columnClass = ParseTreeTableModel.class;
119                 break;
120             case 1:
121                 columnClass = String.class;
122                 break;
123             case 2:
124                 columnClass = Integer.class;
125                 break;
126             case 3:
127                 columnClass = Integer.class;
128                 break;
129             case 4:
130                 columnClass = String.class;
131                 break;
132             default:
133                 throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
134         }
135         return columnClass;
136     }
137 
138     /**
139      * Returns the value to be displayed for node at column number.
140      * @param node the node
141      * @param column the column number
142      * @return the value to be displayed for node {@code node}, at column number {@code column}.
143      */
144     public Object getValueAt(Object node, int column) {
145         final Object result;
146 
147         if (node instanceof DetailNode) {
148             result = getValueAtDetailNode((DetailNode) node, column);
149         }
150         else {
151             result = getValueAtDetailAST((DetailAST) node, column);
152         }
153 
154         return result;
155     }
156 
157     /**
158      * Returns the child of parent at index.
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      * @param parent the node to count children for.
179      * @return the number of children of the node parent.
180      */
181     public int getChildCount(Object parent) {
182         final int result;
183 
184         if (parent instanceof DetailNode) {
185             result = ((DetailNode) parent).getChildren().length;
186         }
187         else {
188             if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
189                     && ((AST) parent).getType() == TokenTypes.COMMENT_CONTENT
190                     && JavadocUtil.isJavadocComment(((DetailAST) parent).getParent())) {
191                 //getChildCount return 0 on COMMENT_CONTENT,
192                 //but we need to attach javadoc tree, that is separate tree
193                 result = 1;
194             }
195             else {
196                 result = ((DetailAST) parent).getChildCount();
197             }
198         }
199 
200         return result;
201     }
202 
203     /**
204      * Returns value of root.
205      * @return the root.
206      */
207     public Object getRoot() {
208         return root;
209     }
210 
211     /**
212      * Whether the node is a leaf.
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 the value for node {@code node}, at column number {@code column} is
245      * editable.
246      * @param column the column number
247      * @return true if editable
248      */
249     public boolean isCellEditable(int column) {
250         return false;
251     }
252 
253     /**
254      * Creates artificial tree root.
255      * @return artificial tree root.
256      */
257     private static DetailAST createArtificialTreeRoot() {
258         final ASTFactory factory = new ASTFactory();
259         factory.setASTNodeClass(DetailAST.class.getName());
260         return (DetailAST) factory.create(TokenTypes.EOF, "ROOT");
261     }
262 
263     /**
264      * Gets child of DetailAST node at specified index.
265      * @param parent DetailAST node
266      * @param index child index
267      * @return child DetailsAST or DetailNode if child is Javadoc node
268      *         and parseMode is JAVA_WITH_JAVADOC_AND_COMMENTS.
269      */
270     private Object getChildAtDetailAst(DetailAST parent, int index) {
271         final Object result;
272         if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
273                 && parent.getType() == TokenTypes.COMMENT_CONTENT
274                 && JavadocUtil.isJavadocComment(parent.getParent())) {
275             result = getJavadocTree(parent.getParent());
276         }
277         else {
278             int currentIndex = 0;
279             DetailAST child = parent.getFirstChild();
280             while (currentIndex < index) {
281                 child = child.getNextSibling();
282                 currentIndex++;
283             }
284             result = child;
285         }
286 
287         return result;
288     }
289 
290     /**
291      * Gets a value for DetailNode object.
292      * @param node DetailNode(Javadoc) node.
293      * @param column column index.
294      * @return value at specified column.
295      */
296     private static Object getValueAtDetailNode(DetailNode node, int column) {
297         final Object value;
298 
299         switch (column) {
300             case 0:
301                 // first column is tree model. no value needed
302                 value = null;
303                 break;
304             case 1:
305                 value = JavadocUtil.getTokenName(node.getType());
306                 break;
307             case 2:
308                 value = node.getLineNumber();
309                 break;
310             case 3:
311                 value = node.getColumnNumber();
312                 break;
313             case 4:
314                 value = node.getText();
315                 break;
316             default:
317                 throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
318         }
319         return value;
320     }
321 
322     /**
323      * Gets a value for DetailAST object.
324      * @param ast DetailAST node.
325      * @param column column index.
326      * @return value at specified column.
327      */
328     private static Object getValueAtDetailAST(DetailAST ast, int column) {
329         final Object value;
330 
331         switch (column) {
332             case 0:
333                 // first column is tree model. no value needed
334                 value = null;
335                 break;
336             case 1:
337                 value = TokenUtil.getTokenName(ast.getType());
338                 break;
339             case 2:
340                 value = ast.getLineNo();
341                 break;
342             case 3:
343                 value = ast.getColumnNo();
344                 break;
345             case 4:
346                 value = ast.getText();
347                 break;
348             default:
349                 throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
350         }
351         return value;
352     }
353 
354     /**
355      * Gets Javadoc (DetailNode) tree of specified block comments.
356      * @param blockComment Javadoc comment as a block comment
357      * @return DetailNode tree
358      */
359     private DetailNode getJavadocTree(DetailAST blockComment) {
360         DetailNode javadocTree = blockCommentToJavadocTree.get(blockComment);
361         if (javadocTree == null) {
362             javadocTree = new JavadocDetailNodeParser().parseJavadocAsDetailNode(blockComment)
363                     .getTree();
364             blockCommentToJavadocTree.put(blockComment, javadocTree);
365         }
366         return javadocTree;
367     }
368 
369 }