001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2018 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.gui;
021
022import java.util.HashMap;
023import java.util.Map;
024
025import antlr.ASTFactory;
026import antlr.collections.AST;
027import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.DetailNode;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031import com.puppycrawl.tools.checkstyle.gui.MainFrameModel.ParseMode;
032import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
033import com.puppycrawl.tools.checkstyle.utils.TokenUtils;
034
035/**
036 * The model that backs the parse tree in the GUI.
037 *
038 */
039public class ParseTreeTablePresentation {
040
041    /** Exception message. */
042    private static final String UNKNOWN_COLUMN_MSG = "Unknown column";
043
044    /** Column names. */
045    private static final String[] COLUMN_NAMES = {
046        "Tree",
047        "Type",
048        "Line",
049        "Column",
050        "Text",
051    };
052
053    /**
054     * The root node of the tree table model.
055     */
056    private final Object root;
057
058    /** Cache to store already parsed Javadoc comments. Used for optimisation purposes. */
059    private final Map<DetailAST, DetailNode> blockCommentToJavadocTree = new HashMap<>();
060
061    /** Parsing mode. */
062    private ParseMode parseMode;
063
064    /**
065     * Constructor initialise root node.
066     * @param parseTree DetailAST parse tree.
067     */
068    public ParseTreeTablePresentation(DetailAST parseTree) {
069        root = createArtificialTreeRoot();
070        setParseTree(parseTree);
071    }
072
073    /**
074     * Set parse tree.
075     * @param parseTree DetailAST parse tree.
076     */
077    protected final void setParseTree(DetailAST parseTree) {
078        ((AST) root).setFirstChild(parseTree);
079    }
080
081    /**
082     * Set parse mode.
083     * @param mode ParseMode enum
084     */
085    protected void setParseMode(ParseMode mode) {
086        parseMode = mode;
087    }
088
089    /**
090     * Returns number of available columns.
091     * @return the number of available columns.
092     */
093    public int getColumnCount() {
094        return COLUMN_NAMES.length;
095    }
096
097    /**
098     * Returns name for specified column number.
099     * @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                    && JavadocUtils.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                && JavadocUtils.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 = JavadocUtils.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 = TokenUtils.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}