001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2016 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.checks.javadoc;
021
022import java.util.HashMap;
023import java.util.Map;
024
025import com.google.common.primitives.Ints;
026import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser;
027import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseErrorMessage;
028import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseStatus;
029import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
030import com.puppycrawl.tools.checkstyle.api.DetailAST;
031import com.puppycrawl.tools.checkstyle.api.DetailNode;
032import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
033import com.puppycrawl.tools.checkstyle.api.TokenTypes;
034import com.puppycrawl.tools.checkstyle.utils.BlockCommentPosition;
035import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
036
037/**
038 * Base class for Checks that process Javadoc comments.
039 * @author Baratali Izmailov
040 */
041public abstract class AbstractJavadocCheck extends AbstractCheck {
042
043    /**
044     * Message key of error message. Missed close HTML tag breaks structure
045     * of parse tree, so parser stops parsing and generates such error
046     * message. This case is special because parser prints error like
047     * {@code "no viable alternative at input 'b \n *\n'"} and it is not
048     * clear that error is about missed close HTML tag.
049     */
050    public static final String MSG_JAVADOC_MISSED_HTML_CLOSE =
051            JavadocDetailNodeParser.MSG_JAVADOC_MISSED_HTML_CLOSE;
052
053    /**
054     * Message key of error message.
055     */
056    public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG =
057            JavadocDetailNodeParser.MSG_JAVADOC_WRONG_SINGLETON_TAG;
058
059    /**
060     * Parse error while rule recognition.
061     */
062    public static final String MSG_JAVADOC_PARSE_RULE_ERROR =
063            JavadocDetailNodeParser.MSG_JAVADOC_PARSE_RULE_ERROR;
064
065    /**
066     * Error message key for common javadoc errors.
067     */
068    public static final String MSG_KEY_PARSE_ERROR =
069            JavadocDetailNodeParser.MSG_KEY_PARSE_ERROR;
070    /**
071     * Unrecognized error from antlr parser.
072     */
073    public static final String MSG_KEY_UNRECOGNIZED_ANTLR_ERROR =
074            JavadocDetailNodeParser.MSG_KEY_UNRECOGNIZED_ANTLR_ERROR;
075
076    /**
077     * Key is "line:column". Value is {@link DetailNode} tree. Map is stored in {@link ThreadLocal}
078     * to guarantee basic thread safety and avoid shared, mutable state when not necessary.
079     */
080    private static final ThreadLocal<Map<String, ParseStatus>> TREE_CACHE =
081        new ThreadLocal<Map<String, ParseStatus>>() {
082            @Override
083            protected Map<String, ParseStatus> initialValue() {
084                return new HashMap<>();
085            }
086        };
087
088    /**
089     * Parses content of Javadoc comment as DetailNode tree.
090     */
091    private final JavadocDetailNodeParser parser = new JavadocDetailNodeParser();
092
093    /**
094     * DetailAST node of considered Javadoc comment that is just a block comment
095     * in Java language syntax tree.
096     */
097    private DetailAST blockCommentAst;
098
099    /**
100     * Returns the default token types a check is interested in.
101     * @return the default token types
102     * @see JavadocTokenTypes
103     */
104    public abstract int[] getDefaultJavadocTokens();
105
106    /**
107     * Called to process a Javadoc token.
108     * @param ast
109     *        the token to process
110     */
111    public abstract void visitJavadocToken(DetailNode ast);
112
113    /**
114     * Called before the starting to process a tree.
115     * @param rootAst
116     *        the root of the tree
117     */
118    public void beginJavadocTree(DetailNode rootAst) {
119        // No code by default, should be overridden only by demand at subclasses
120    }
121
122    /**
123     * Called after finished processing a tree.
124     * @param rootAst
125     *        the root of the tree
126     */
127    public void finishJavadocTree(DetailNode rootAst) {
128        // No code by default, should be overridden only by demand at subclasses
129    }
130
131    /**
132     * Called after all the child nodes have been process.
133     * @param ast
134     *        the token leaving
135     */
136    public void leaveJavadocToken(DetailNode ast) {
137        // No code by default, should be overridden only by demand at subclasses
138    }
139
140    /**
141     * Defined final to not allow JavadocChecks to change default tokens.
142     * @return default tokens
143     */
144    @Override
145    public final int[] getDefaultTokens() {
146        return new int[] {TokenTypes.BLOCK_COMMENT_BEGIN };
147    }
148
149    /**
150     * Defined final because all JavadocChecks require comment nodes.
151     * @return true
152     */
153    @Override
154    public final boolean isCommentNodesRequired() {
155        return true;
156    }
157
158    @Override
159    public final void beginTree(DetailAST rootAST) {
160        TREE_CACHE.get().clear();
161    }
162
163    @Override
164    public final void finishTree(DetailAST rootAST) {
165        TREE_CACHE.get().clear();
166    }
167
168    @Override
169    public final void visitToken(DetailAST blockCommentNode) {
170        if (JavadocUtils.isJavadocComment(blockCommentNode)
171              && isCorrectJavadocPosition(blockCommentNode)) {
172            // store as field, to share with child Checks
173            blockCommentAst = blockCommentNode;
174
175            final String treeCacheKey = blockCommentNode.getLineNo() + ":"
176                    + blockCommentNode.getColumnNo();
177
178            final ParseStatus result;
179
180            if (TREE_CACHE.get().containsKey(treeCacheKey)) {
181                result = TREE_CACHE.get().get(treeCacheKey);
182            }
183            else {
184                result = parser.parseJavadocAsDetailNode(blockCommentNode);
185                TREE_CACHE.get().put(treeCacheKey, result);
186            }
187
188            if (result.getParseErrorMessage() == null) {
189                processTree(result.getTree());
190            }
191            else {
192                final ParseErrorMessage parseErrorMessage = result.getParseErrorMessage();
193                log(parseErrorMessage.getLineNumber(),
194                        parseErrorMessage.getMessageKey(),
195                        parseErrorMessage.getMessageArguments());
196            }
197        }
198
199    }
200
201    /**
202     * Getter for block comment in Java language syntax tree.
203     * @return A block comment in the syntax tree.
204     */
205    protected DetailAST getBlockCommentAst() {
206        return blockCommentAst;
207    }
208
209    /**
210     * Checks Javadoc comment it's in right place.
211     * From Javadoc util documentation:
212     * "Placement of comments - Documentation comments are recognized only when placed
213     * immediately before class, interface, constructor, method, or field
214     * declarations -- see the class example, method example, and field example.
215     * Documentation comments placed in the body of a method are ignored. Only one
216     * documentation comment per declaration statement is recognized by the Javadoc tool."
217     *
218     * @param blockComment Block comment AST
219     * @return true if Javadoc is in right place
220     */
221    private static boolean isCorrectJavadocPosition(DetailAST blockComment) {
222        return BlockCommentPosition.isOnClass(blockComment)
223                || BlockCommentPosition.isOnInterface(blockComment)
224                || BlockCommentPosition.isOnEnum(blockComment)
225                || BlockCommentPosition.isOnMethod(blockComment)
226                || BlockCommentPosition.isOnField(blockComment)
227                || BlockCommentPosition.isOnConstructor(blockComment)
228                || BlockCommentPosition.isOnEnumConstant(blockComment)
229                || BlockCommentPosition.isOnAnnotationDef(blockComment);
230    }
231
232    /**
233     * Processes JavadocAST tree notifying Check.
234     * @param root
235     *        root of JavadocAST tree.
236     */
237    private void processTree(DetailNode root) {
238        beginJavadocTree(root);
239        walk(root);
240        finishJavadocTree(root);
241    }
242
243    /**
244     * Processes a node calling Check at interested nodes.
245     * @param root
246     *        the root of tree for process
247     */
248    private void walk(DetailNode root) {
249        final int[] defaultTokenTypes = getDefaultJavadocTokens();
250
251        DetailNode curNode = root;
252        while (curNode != null) {
253            final boolean waitsFor = Ints.contains(defaultTokenTypes, curNode.getType());
254
255            if (waitsFor) {
256                visitJavadocToken(curNode);
257            }
258            DetailNode toVisit = JavadocUtils.getFirstChild(curNode);
259            while (curNode != null && toVisit == null) {
260
261                if (waitsFor) {
262                    leaveJavadocToken(curNode);
263                }
264
265                toVisit = JavadocUtils.getNextSibling(curNode);
266                if (toVisit == null) {
267                    curNode = curNode.getParent();
268                }
269            }
270            curNode = toVisit;
271        }
272    }
273
274}