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.checks.javadoc;
021
022import java.util.Arrays;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.Locale;
026import java.util.Map;
027import java.util.Set;
028
029import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser;
030import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseErrorMessage;
031import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser.ParseStatus;
032import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
033import com.puppycrawl.tools.checkstyle.api.DetailAST;
034import com.puppycrawl.tools.checkstyle.api.DetailNode;
035import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
036import com.puppycrawl.tools.checkstyle.api.TokenTypes;
037import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
038import com.puppycrawl.tools.checkstyle.utils.JavadocUtils;
039
040/**
041 * Base class for Checks that process Javadoc comments.
042 * @author Baratali Izmailov
043 */
044public abstract class AbstractJavadocCheck extends AbstractCheck {
045    /**
046     * Message key of error message. Missed close HTML tag breaks structure
047     * of parse tree, so parser stops parsing and generates such error
048     * message. This case is special because parser prints error like
049     * {@code "no viable alternative at input 'b \n *\n'"} and it is not
050     * clear that error is about missed close HTML tag.
051     */
052    public static final String MSG_JAVADOC_MISSED_HTML_CLOSE =
053            JavadocDetailNodeParser.MSG_JAVADOC_MISSED_HTML_CLOSE;
054
055    /**
056     * Message key of error message.
057     */
058    public static final String MSG_JAVADOC_WRONG_SINGLETON_TAG =
059            JavadocDetailNodeParser.MSG_JAVADOC_WRONG_SINGLETON_TAG;
060
061    /**
062     * Parse error while rule recognition.
063     */
064    public static final String MSG_JAVADOC_PARSE_RULE_ERROR =
065            JavadocDetailNodeParser.MSG_JAVADOC_PARSE_RULE_ERROR;
066
067    /**
068     * Error message key for common javadoc errors.
069     */
070    public static final String MSG_KEY_PARSE_ERROR =
071            JavadocDetailNodeParser.MSG_KEY_PARSE_ERROR;
072    /**
073     * Unrecognized error from antlr parser.
074     */
075    public static final String MSG_KEY_UNRECOGNIZED_ANTLR_ERROR =
076            JavadocDetailNodeParser.MSG_KEY_UNRECOGNIZED_ANTLR_ERROR;
077
078    /**
079     * Key is "line:column". Value is {@link DetailNode} tree. Map is stored in {@link ThreadLocal}
080     * to guarantee basic thread safety and avoid shared, mutable state when not necessary.
081     */
082    private static final ThreadLocal<Map<String, ParseStatus>> TREE_CACHE =
083        new ThreadLocal<Map<String, ParseStatus>>() {
084            @Override
085            protected Map<String, ParseStatus> initialValue() {
086                return new HashMap<>();
087            }
088        };
089
090    /**
091     * Parses content of Javadoc comment as DetailNode tree.
092     */
093    private final JavadocDetailNodeParser parser = new JavadocDetailNodeParser();
094
095    /** The javadoc tokens the check is interested in. */
096    private final Set<Integer> javadocTokens = new HashSet<>();
097
098    /**
099     * DetailAST node of considered Javadoc comment that is just a block comment
100     * in Java language syntax tree.
101     */
102    private DetailAST blockCommentAst;
103
104    /**
105     * Returns the default javadoc token types a check is interested in.
106     * @return the default javadoc token types
107     * @see JavadocTokenTypes
108     */
109    public abstract int[] getDefaultJavadocTokens();
110
111    /**
112     * Called to process a Javadoc token.
113     * @param ast
114     *        the token to process
115     */
116    public abstract void visitJavadocToken(DetailNode ast);
117
118    /**
119     * The configurable javadoc token set.
120     * Used to protect Checks against malicious users who specify an
121     * unacceptable javadoc token set in the configuration file.
122     * The default implementation returns the check's default javadoc tokens.
123     * @return the javadoc token set this check is designed for.
124     * @see JavadocTokenTypes
125     */
126    public int[] getAcceptableJavadocTokens() {
127        final int[] defaultJavadocTokens = getDefaultJavadocTokens();
128        final int[] copy = new int[defaultJavadocTokens.length];
129        System.arraycopy(defaultJavadocTokens, 0, copy, 0, defaultJavadocTokens.length);
130        return copy;
131    }
132
133    /**
134     * The javadoc tokens that this check must be registered for.
135     * @return the javadoc token set this must be registered for.
136     * @see JavadocTokenTypes
137     */
138    public int[] getRequiredJavadocTokens() {
139        return CommonUtils.EMPTY_INT_ARRAY;
140    }
141
142    /**
143     * Adds a set of tokens the check is interested in.
144     * @param strRep the string representation of the tokens interested in
145     */
146    public final void setJavadocTokens(String... strRep) {
147        javadocTokens.clear();
148        for (String str : strRep) {
149            javadocTokens.add(JavadocUtils.getTokenId(str));
150        }
151    }
152
153    @Override
154    public void init() {
155        validateDefaultJavadocTokens();
156        if (javadocTokens.isEmpty()) {
157            for (int id : getDefaultJavadocTokens()) {
158                javadocTokens.add(id);
159            }
160        }
161        else {
162            final int[] acceptableJavadocTokens = getAcceptableJavadocTokens();
163            Arrays.sort(acceptableJavadocTokens);
164            for (Integer javadocTokenId : javadocTokens) {
165                if (Arrays.binarySearch(acceptableJavadocTokens, javadocTokenId) < 0) {
166                    final String message = String.format(Locale.ROOT, "Javadoc Token \"%s\" was "
167                            + "not found in Acceptable javadoc tokens list in check %s",
168                            JavadocUtils.getTokenName(javadocTokenId), getClass().getName());
169                    throw new IllegalStateException(message);
170                }
171            }
172        }
173    }
174
175    /**
176     * Validates that check's required javadoc tokens are subset of default javadoc tokens.
177     * @throws IllegalStateException when validation of default javadoc tokens fails
178     */
179    private void validateDefaultJavadocTokens() {
180        if (getRequiredJavadocTokens().length != 0) {
181            final int[] defaultJavadocTokens = getDefaultJavadocTokens();
182            Arrays.sort(defaultJavadocTokens);
183            for (final int javadocToken : getRequiredJavadocTokens()) {
184                if (Arrays.binarySearch(defaultJavadocTokens, javadocToken) < 0) {
185                    final String message = String.format(Locale.ROOT,
186                            "Javadoc Token \"%s\" from required javadoc "
187                                + "tokens was not found in default "
188                                + "javadoc tokens list in check %s",
189                            javadocToken, getClass().getName());
190                    throw new IllegalStateException(message);
191                }
192            }
193        }
194    }
195
196    /**
197     * Called before the starting to process a tree.
198     * @param rootAst
199     *        the root of the tree
200     */
201    public void beginJavadocTree(DetailNode rootAst) {
202        // No code by default, should be overridden only by demand at subclasses
203    }
204
205    /**
206     * Called after finished processing a tree.
207     * @param rootAst
208     *        the root of the tree
209     */
210    public void finishJavadocTree(DetailNode rootAst) {
211        // No code by default, should be overridden only by demand at subclasses
212    }
213
214    /**
215     * Called after all the child nodes have been process.
216     * @param ast
217     *        the token leaving
218     */
219    public void leaveJavadocToken(DetailNode ast) {
220        // No code by default, should be overridden only by demand at subclasses
221    }
222
223    /**
224     * Defined final to not allow JavadocChecks to change default tokens.
225     * @return default tokens
226     */
227    @Override
228    public final int[] getDefaultTokens() {
229        return new int[] {TokenTypes.BLOCK_COMMENT_BEGIN };
230    }
231
232    @Override
233    public final int[] getAcceptableTokens() {
234        return getDefaultTokens();
235    }
236
237    @Override
238    public final int[] getRequiredTokens() {
239        return getDefaultTokens();
240    }
241
242    /**
243     * Defined final because all JavadocChecks require comment nodes.
244     * @return true
245     */
246    @Override
247    public final boolean isCommentNodesRequired() {
248        return true;
249    }
250
251    @Override
252    public final void beginTree(DetailAST rootAST) {
253        TREE_CACHE.get().clear();
254    }
255
256    @Override
257    public final void finishTree(DetailAST rootAST) {
258        TREE_CACHE.get().clear();
259    }
260
261    @Override
262    public final void visitToken(DetailAST blockCommentNode) {
263        if (JavadocUtils.isJavadocComment(blockCommentNode)) {
264            // store as field, to share with child Checks
265            blockCommentAst = blockCommentNode;
266
267            final String treeCacheKey = blockCommentNode.getLineNo() + ":"
268                    + blockCommentNode.getColumnNo();
269
270            final ParseStatus result;
271
272            if (TREE_CACHE.get().containsKey(treeCacheKey)) {
273                result = TREE_CACHE.get().get(treeCacheKey);
274            }
275            else {
276                result = parser.parseJavadocAsDetailNode(blockCommentNode);
277                TREE_CACHE.get().put(treeCacheKey, result);
278            }
279
280            if (result.getParseErrorMessage() == null) {
281                processTree(result.getTree());
282            }
283            else {
284                final ParseErrorMessage parseErrorMessage = result.getParseErrorMessage();
285                log(parseErrorMessage.getLineNumber(),
286                        parseErrorMessage.getMessageKey(),
287                        parseErrorMessage.getMessageArguments());
288            }
289        }
290
291    }
292
293    /**
294     * Getter for block comment in Java language syntax tree.
295     * @return A block comment in the syntax tree.
296     */
297    protected DetailAST getBlockCommentAst() {
298        return blockCommentAst;
299    }
300
301    /**
302     * Processes JavadocAST tree notifying Check.
303     * @param root
304     *        root of JavadocAST tree.
305     */
306    private void processTree(DetailNode root) {
307        beginJavadocTree(root);
308        walk(root);
309        finishJavadocTree(root);
310    }
311
312    /**
313     * Processes a node calling Check at interested nodes.
314     * @param root
315     *        the root of tree for process
316     */
317    private void walk(DetailNode root) {
318        DetailNode curNode = root;
319        while (curNode != null) {
320            boolean waitsForProcessing = shouldBeProcessed(curNode);
321
322            if (waitsForProcessing) {
323                visitJavadocToken(curNode);
324            }
325            DetailNode toVisit = JavadocUtils.getFirstChild(curNode);
326            while (curNode != null && toVisit == null) {
327
328                if (waitsForProcessing) {
329                    leaveJavadocToken(curNode);
330                }
331
332                toVisit = JavadocUtils.getNextSibling(curNode);
333                if (toVisit == null) {
334                    curNode = curNode.getParent();
335                    if (curNode != null) {
336                        waitsForProcessing = shouldBeProcessed(curNode);
337                    }
338                }
339            }
340            curNode = toVisit;
341        }
342    }
343
344    /**
345     * Checks whether the current node should be processed by the check.
346     * @param curNode current node.
347     * @return true if the current node should be processed by the check.
348     */
349    private boolean shouldBeProcessed(DetailNode curNode) {
350        return javadocTokens.contains(curNode.getType());
351    }
352
353}