View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2017 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.JavadocUtils;
33  import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
34  
35  /**
36   * The model that backs the parse tree in the GUI.
37   *
38   * @author Lars K├╝hne
39   */
40  public class ParseTreeTablePresentation {
41  
42      /** Exception message. */
43      private static final String UNKNOWN_COLUMN_MSG = "Unknown column";
44  
45      /** Column names. */
46      private static final String[] COLUMN_NAMES = {
47          "Tree",
48          "Type",
49          "Line",
50          "Column",
51          "Text",
52      };
53  
54      /**
55       * The root node of the tree table model.
56       */
57      private final Object root;
58  
59      /** Cache to store already parsed Javadoc comments. Used for optimisation purposes. */
60      private final Map<DetailAST, DetailNode> blockCommentToJavadocTree = new HashMap<>();
61  
62      /** Parsing mode. */
63      private ParseMode parseMode;
64  
65      /**
66       * Constructor initialise root node.
67       * @param parseTree DetailAST parse tree.
68       */
69      public ParseTreeTablePresentation(DetailAST parseTree) {
70          root = createArtificialTreeRoot();
71          setParseTree(parseTree);
72      }
73  
74      /**
75       * Set parse tree.
76       * @param parseTree DetailAST parse tree.
77       */
78      protected final void setParseTree(DetailAST parseTree) {
79          ((AST) root).setFirstChild(parseTree);
80      }
81  
82      /**
83       * Set parse mode.
84       * @param mode ParseMode enum
85       */
86      protected void setParseMode(ParseMode mode) {
87          parseMode = mode;
88      }
89  
90      /**
91       * Returns number of available columns.
92       * @return the number of available columns.
93       */
94      public int getColumnCount() {
95          return COLUMN_NAMES.length;
96      }
97  
98      /**
99       * Returns name for specified column number.
100      * @param column the column number
101      * @return the name for column number {@code column}.
102      */
103     public String getColumnName(int column) {
104         return COLUMN_NAMES[column];
105     }
106 
107     /**
108      * Returns type of specified column number.
109      * @param column the column number
110      * @return the type for column number {@code column}.
111      */
112     // -@cs[ForbidWildcardAsReturnType] We need to satisfy javax.swing.table.AbstractTableModel
113     // public Class<?> getColumnClass(int columnIndex) {...}
114     public Class<?> getColumnClass(int column) {
115         final Class<?> columnClass;
116 
117         switch (column) {
118             case 0:
119                 columnClass = ParseTreeTableModel.class;
120                 break;
121             case 1:
122                 columnClass = String.class;
123                 break;
124             case 2:
125                 columnClass = Integer.class;
126                 break;
127             case 3:
128                 columnClass = Integer.class;
129                 break;
130             case 4:
131                 columnClass = String.class;
132                 break;
133             default:
134                 throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
135         }
136         return columnClass;
137     }
138 
139     /**
140      * Returns the value to be displayed for node at column number.
141      * @param node the node
142      * @param column the column number
143      * @return the value to be displayed for node {@code node}, at column number {@code column}.
144      */
145     public Object getValueAt(Object node, int column) {
146         final Object result;
147 
148         if (node instanceof DetailNode) {
149             result = getValueAtDetailNode((DetailNode) node, column);
150         }
151         else {
152             result = getValueAtDetailAST((DetailAST) node, column);
153         }
154 
155         return result;
156     }
157 
158     /**
159      * Returns the child of parent at index.
160      * @param parent the node to get a child from.
161      * @param index the index of a child.
162      * @return the child of parent at index.
163      */
164     public Object getChild(Object parent, int index) {
165         final Object result;
166 
167         if (parent instanceof DetailNode) {
168             result = ((DetailNode) parent).getChildren()[index];
169         }
170         else {
171             result = getChildAtDetailAst((DetailAST) parent, index);
172         }
173 
174         return result;
175     }
176 
177     /**
178      * Returns the number of children of parent.
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                     && ((AST) parent).getType() == TokenTypes.COMMENT_CONTENT
191                     && JavadocUtils.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      * @return the root.
207      */
208     public Object getRoot() {
209         return root;
210     }
211 
212     /**
213      * Whether the node is a leaf.
214      * @param node the node to check.
215      * @return true if the node is a leaf.
216      */
217     public boolean isLeaf(Object node) {
218         return getChildCount(node) == 0;
219     }
220 
221     /**
222      * Return the index of child in parent.  If either {@code parent}
223      * or {@code child} is {@code null}, returns -1.
224      * If either {@code parent} or {@code child} don't
225      * belong to this tree model, returns -1.
226      *
227      * @param parent a node in the tree, obtained from this data source.
228      * @param child the node we are interested in.
229      * @return the index of the child in the parent, or -1 if either
230      *     {@code child} or {@code parent} are {@code null}
231      *     or don't belong to this tree model.
232      */
233     public int getIndexOfChild(Object parent, Object child) {
234         int index = -1;
235         for (int i = 0; i < getChildCount(parent); i++) {
236             if (getChild(parent, i).equals(child)) {
237                 index = i;
238                 break;
239             }
240         }
241         return index;
242     }
243 
244     /**
245      * Indicates whether the the value for node {@code node}, at column number {@code column} is
246      * editable.
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      * Creates artificial tree root.
256      * @return artificial tree root.
257      */
258     private static DetailAST createArtificialTreeRoot() {
259         final ASTFactory factory = new ASTFactory();
260         factory.setASTNodeClass(DetailAST.class.getName());
261         return (DetailAST) factory.create(TokenTypes.EOF, "ROOT");
262     }
263 
264     /**
265      * Gets child of DetailAST node at specified index.
266      * @param parent DetailAST node
267      * @param index child index
268      * @return child DetailsAST or DetailNode if child is Javadoc node
269      *         and parseMode is JAVA_WITH_JAVADOC_AND_COMMENTS.
270      */
271     private Object getChildAtDetailAst(DetailAST parent, int index) {
272         final Object result;
273         if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
274                 && parent.getType() == TokenTypes.COMMENT_CONTENT
275                 && JavadocUtils.isJavadocComment(parent.getParent())) {
276             result = getJavadocTree(parent.getParent());
277         }
278         else {
279             int currentIndex = 0;
280             DetailAST child = parent.getFirstChild();
281             while (currentIndex < index) {
282                 child = child.getNextSibling();
283                 currentIndex++;
284             }
285             result = child;
286         }
287 
288         return result;
289     }
290 
291     /**
292      * Gets a value for DetailNode object.
293      * @param node DetailNode(Javadoc) node.
294      * @param column column index.
295      * @return value at specified column.
296      */
297     private static Object getValueAtDetailNode(DetailNode node, int column) {
298         final Object value;
299 
300         switch (column) {
301             case 0:
302                 // first column is tree model. no value needed
303                 value = null;
304                 break;
305             case 1:
306                 value = JavadocUtils.getTokenName(node.getType());
307                 break;
308             case 2:
309                 value = node.getLineNumber();
310                 break;
311             case 3:
312                 value = node.getColumnNumber();
313                 break;
314             case 4:
315                 value = node.getText();
316                 break;
317             default:
318                 throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
319         }
320         return value;
321     }
322 
323     /**
324      * Gets a value for DetailAST object.
325      * @param ast DetailAST node.
326      * @param column column index.
327      * @return value at specified column.
328      */
329     private static Object getValueAtDetailAST(DetailAST ast, int column) {
330         final Object value;
331 
332         switch (column) {
333             case 0:
334                 // first column is tree model. no value needed
335                 value = null;
336                 break;
337             case 1:
338                 value = TokenUtils.getTokenName(ast.getType());
339                 break;
340             case 2:
341                 value = ast.getLineNo();
342                 break;
343             case 3:
344                 value = ast.getColumnNo();
345                 break;
346             case 4:
347                 value = ast.getText();
348                 break;
349             default:
350                 throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
351         }
352         return value;
353     }
354 
355     /**
356      * Gets Javadoc (DetailNode) tree of specified block comments.
357      * @param blockComment Javadoc comment as a block comment
358      * @return DetailNode tree
359      */
360     private DetailNode getJavadocTree(DetailAST blockComment) {
361         DetailNode javadocTree = blockCommentToJavadocTree.get(blockComment);
362         if (javadocTree == null) {
363             javadocTree = new JavadocDetailNodeParser().parseJavadocAsDetailNode(blockComment)
364                     .getTree();
365             blockCommentToJavadocTree.put(blockComment, javadocTree);
366         }
367         return javadocTree;
368     }
369 }