001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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 * @author Lars Kühne
039 */
040public class ParseTreeTablePModel {
041
042    /** Exception message. */
043    private static final String UNKNOWN_COLUMN_MSG = "Unknown column";
044
045    /** Column names. */
046    private static final String[] COLUMN_NAMES = {
047        "Tree",
048        "Type",
049        "Line",
050        "Column",
051        "Text",
052    };
053
054    /**
055     * The root node of the tree table model.
056     */
057    private final Object root;
058
059    /** Cache to store already parsed Javadoc comments. Used for optimisation purposes. */
060    private final Map<DetailAST, DetailNode> blockCommentToJavadocTree = new HashMap<>();
061
062    /** Parsing mode. */
063    private ParseMode parseMode;
064
065    /**
066     * @param parseTree DetailAST parse tree.
067     */
068    public ParseTreeTablePModel(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     * @return the number of available columns.
091     */
092    public int getColumnCount() {
093        return COLUMN_NAMES.length;
094    }
095
096    /**
097     * @param column the column number
098     * @return the name for column number {@code column}.
099     */
100    public String getColumnName(int column) {
101        return COLUMN_NAMES[column];
102    }
103
104    /**
105     * @param column the column number
106     * @return the type for column number {@code column}.
107     */
108    // -@cs[ForbidWildcardAsReturnType] We need to satisfy javax.swing.table.AbstractTableModel
109    // public Class<?> getColumnClass(int columnIndex) {...}
110    public Class<?> getColumnClass(int column) {
111        final Class<?> columnClass;
112
113        switch (column) {
114            case 0:
115                columnClass = ParseTreeTableModel.class;
116                break;
117            case 1:
118                columnClass = String.class;
119                break;
120            case 2:
121                columnClass = Integer.class;
122                break;
123            case 3:
124                columnClass = Integer.class;
125                break;
126            case 4:
127                columnClass = String.class;
128                break;
129            default:
130                throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
131        }
132        return columnClass;
133    }
134
135    /**
136     * @param node the node
137     * @param column the column number
138     * @return the value to be displayed for node {@code node}, at column number {@code column}.
139     */
140    public Object getValueAt(Object node, int column) {
141        final Object result;
142
143        if (node instanceof DetailNode) {
144            result = getValueAtDetailNode((DetailNode) node, column);
145        }
146        else {
147            result = getValueAtDetailAST((DetailAST) node, column);
148        }
149
150        return result;
151    }
152
153    /**
154     * Returns the child of parent at index.
155     * @param parent the node to get a child from.
156     * @param index the index of a child.
157     * @return the child of parent at index.
158     */
159    public Object getChild(Object parent, int index) {
160        final Object result;
161
162        if (parent instanceof DetailNode) {
163            result = ((DetailNode) parent).getChildren()[index];
164        }
165        else {
166            result = getChildAtDetailAst((DetailAST) parent, index);
167        }
168
169        return result;
170    }
171
172    /**
173     * Returns the number of children of parent.
174     * @param parent the node to count children for.
175     * @return the number of children of the node parent.
176     */
177    public int getChildCount(Object parent) {
178        final int result;
179
180        if (parent instanceof DetailNode) {
181            result = ((DetailNode) parent).getChildren().length;
182        }
183        else {
184            if (parseMode == ParseMode.JAVA_WITH_JAVADOC_AND_COMMENTS
185                    && ((AST) parent).getType() == TokenTypes.COMMENT_CONTENT
186                    && JavadocUtils.isJavadocComment(((DetailAST) parent).getParent())) {
187                //getChildCount return 0 on COMMENT_CONTENT,
188                //but we need to attach javadoc tree, that is separate tree
189                result = 1;
190            }
191            else {
192                result = ((DetailAST) parent).getChildCount();
193            }
194        }
195
196        return result;
197    }
198
199    /**
200     * @return the root.
201     */
202    public Object getRoot() {
203        return root;
204    }
205
206    /**
207     * Whether the node is a leaf.
208     * @param node the node to check.
209     * @return true if the node is a leaf.
210     */
211    public boolean isLeaf(Object node) {
212        return getChildCount(node) == 0;
213    }
214
215    /**
216     * Return the index of child in parent.  If either <code>parent</code>
217     * or <code>child</code> is <code>null</code>, returns -1.
218     * If either <code>parent</code> or <code>child</code> don't
219     * belong to this tree model, returns -1.
220     *
221     * @param parent a node in the tree, obtained from this data source.
222     * @param child the node we are interested in.
223     * @return the index of the child in the parent, or -1 if either
224     *     <code>child</code> or <code>parent</code> are <code>null</code>
225     *     or don't belong to this tree model.
226     */
227    public int getIndexOfChild(Object parent, Object child) {
228        int index = -1;
229        for (int i = 0; i < getChildCount(parent); i++) {
230            if (getChild(parent, i).equals(child)) {
231                index = i;
232                break;
233            }
234        }
235        return index;
236    }
237
238    /**
239     * Indicates whether the the value for node {@code node}, at column number {@code column} is
240     * editable.
241     * @param column the column number
242     * @return true if editable
243     */
244    public boolean isCellEditable(int column) {
245        return false;
246    }
247
248    /**
249     * Creates artificial tree root.
250     * @return artificial tree root.
251     */
252    private static DetailAST createArtificialTreeRoot() {
253        final ASTFactory factory = new ASTFactory();
254        factory.setASTNodeClass(DetailAST.class.getName());
255        return (DetailAST) factory.create(TokenTypes.EOF, "ROOT");
256    }
257
258    /**
259     * Gets child of DetailAST node at specified index.
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                && JavadocUtils.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     * @param node DetailNode(Javadoc) node.
288     * @param column column index.
289     * @return value at specified column.
290     */
291    private Object getValueAtDetailNode(DetailNode node, int column) {
292        final Object value;
293
294        switch (column) {
295            case 0:
296                // first column is tree model. no value needed
297                value = null;
298                break;
299            case 1:
300                value = JavadocUtils.getTokenName(node.getType());
301                break;
302            case 2:
303                value = node.getLineNumber();
304                break;
305            case 3:
306                value = node.getColumnNumber();
307                break;
308            case 4:
309                value = node.getText();
310                break;
311            default:
312                throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
313        }
314        return value;
315    }
316
317    /**
318     * Gets a value for DetailAST object.
319     * @param ast DetailAST node.
320     * @param column column index.
321     * @return value at specified column.
322     */
323    private Object getValueAtDetailAST(DetailAST ast, int column) {
324        final Object value;
325
326        switch (column) {
327            case 0:
328                // first column is tree model. no value needed
329                value = null;
330                break;
331            case 1:
332                value = TokenUtils.getTokenName(ast.getType());
333                break;
334            case 2:
335                value = ast.getLineNo();
336                break;
337            case 3:
338                value = ast.getColumnNo();
339                break;
340            case 4:
341                value = ast.getText();
342                break;
343            default:
344                throw new IllegalStateException(UNKNOWN_COLUMN_MSG);
345        }
346        return value;
347    }
348
349    /**
350     * Gets Javadoc (DetailNode) tree of specified block comments.
351     * @param blockComment Javadoc comment as a block comment
352     * @return DetailNode tree
353     */
354    private DetailNode getJavadocTree(DetailAST blockComment) {
355        DetailNode javadocTree = blockCommentToJavadocTree.get(blockComment);
356        if (javadocTree == null) {
357            javadocTree = new JavadocDetailNodeParser().parseJavadocAsDetailNode(blockComment)
358                    .getTree();
359            blockCommentToJavadocTree.put(blockComment, javadocTree);
360        }
361        return javadocTree;
362    }
363}