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.utils;
021
022import java.lang.reflect.Field;
023import java.lang.reflect.Modifier;
024import java.util.ArrayList;
025import java.util.List;
026import java.util.regex.Pattern;
027
028import com.google.common.collect.ImmutableMap;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.DetailNode;
031import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
032import com.puppycrawl.tools.checkstyle.api.TextBlock;
033import com.puppycrawl.tools.checkstyle.api.TokenTypes;
034import com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag;
035import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
036import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo;
037import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTags;
038import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.BlockTagUtils;
039import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.InlineTagUtils;
040import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.TagInfo;
041
042/**
043 * Contains utility methods for working with Javadoc.
044 * @author Lyle Hanson
045 */
046public final class JavadocUtils {
047
048    /**
049     * The type of Javadoc tag we want returned.
050     */
051    public enum JavadocTagType {
052
053        /** Block type. */
054        BLOCK,
055        /** Inline type. */
056        INLINE,
057        /** All validTags. */
058        ALL
059
060    }
061
062    /** Maps from a token name to value. */
063    private static final ImmutableMap<String, Integer> TOKEN_NAME_TO_VALUE;
064    /** Maps from a token value to name. */
065    private static final String[] TOKEN_VALUE_TO_NAME;
066
067    /** Exception message for unknown JavaDoc token id. */
068    private static final String UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE = "Unknown javadoc"
069            + " token id. Given id: ";
070
071    /** Newline pattern. */
072    private static final Pattern NEWLINE = Pattern.compile("\n");
073
074    /** Return pattern. */
075    private static final Pattern RETURN = Pattern.compile("\r");
076
077    /** Tab pattern. */
078    private static final Pattern TAB = Pattern.compile("\t");
079
080    // Using reflection gets all token names and values from JavadocTokenTypes class
081    // and saves to TOKEN_NAME_TO_VALUE and TOKEN_VALUE_TO_NAME collections.
082    static {
083        final ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder();
084
085        final Field[] fields = JavadocTokenTypes.class.getDeclaredFields();
086
087        String[] tempTokenValueToName = CommonUtils.EMPTY_STRING_ARRAY;
088
089        for (final Field field : fields) {
090            // Only process public int fields.
091            if (!Modifier.isPublic(field.getModifiers())
092                    || field.getType() != Integer.TYPE) {
093                continue;
094            }
095
096            final String name = field.getName();
097
098            final int tokenValue = TokenUtils.getIntFromField(field, name);
099            builder.put(name, tokenValue);
100            if (tokenValue > tempTokenValueToName.length - 1) {
101                final String[] temp = new String[tokenValue + 1];
102                System.arraycopy(tempTokenValueToName, 0, temp, 0, tempTokenValueToName.length);
103                tempTokenValueToName = temp;
104            }
105            if (tokenValue == -1) {
106                tempTokenValueToName[0] = name;
107            }
108            else {
109                tempTokenValueToName[tokenValue] = name;
110            }
111        }
112
113        TOKEN_NAME_TO_VALUE = builder.build();
114        TOKEN_VALUE_TO_NAME = tempTokenValueToName;
115    }
116
117    /** Prevent instantiation. */
118    private JavadocUtils() {
119    }
120
121    /**
122     * Gets validTags from a given piece of Javadoc.
123     * @param textBlock
124     *        the Javadoc comment to process.
125     * @param tagType
126     *        the type of validTags we're interested in
127     * @return all standalone validTags from the given javadoc.
128     */
129    public static JavadocTags getJavadocTags(TextBlock textBlock,
130            JavadocTagType tagType) {
131        final boolean getBlockTags = tagType == JavadocTagType.ALL
132                                         || tagType == JavadocTagType.BLOCK;
133        final boolean getInlineTags = tagType == JavadocTagType.ALL
134                                          || tagType == JavadocTagType.INLINE;
135
136        final List<TagInfo> tags = new ArrayList<>();
137
138        if (getBlockTags) {
139            tags.addAll(BlockTagUtils.extractBlockTags(textBlock.getText()));
140        }
141
142        if (getInlineTags) {
143            tags.addAll(InlineTagUtils.extractInlineTags(textBlock.getText()));
144        }
145
146        final List<JavadocTag> validTags = new ArrayList<>();
147        final List<InvalidJavadocTag> invalidTags = new ArrayList<>();
148
149        for (TagInfo tag : tags) {
150            final int col = tag.getPosition().getColumn();
151
152            // Add the starting line of the comment to the line number to get the actual line number
153            // in the source.
154            // Lines are one-indexed, so need a off-by-one correction.
155            final int line = textBlock.getStartLineNo() + tag.getPosition().getLine() - 1;
156
157            if (JavadocTagInfo.isValidName(tag.getName())) {
158                validTags.add(
159                    new JavadocTag(line, col, tag.getName(), tag.getValue()));
160            }
161            else {
162                invalidTags.add(new InvalidJavadocTag(line, col, tag.getName()));
163            }
164        }
165
166        return new JavadocTags(validTags, invalidTags);
167    }
168
169    /**
170     * Checks that commentContent starts with '*' javadoc comment identifier.
171     * @param commentContent
172     *        content of block comment
173     * @return true if commentContent starts with '*' javadoc comment
174     *         identifier.
175     */
176    public static boolean isJavadocComment(String commentContent) {
177        boolean result = false;
178
179        if (!commentContent.isEmpty()) {
180            final char docCommentIdentifier = commentContent.charAt(0);
181            result = docCommentIdentifier == '*';
182        }
183
184        return result;
185    }
186
187    /**
188     * Checks block comment content starts with '*' javadoc comment identifier.
189     * @param blockCommentBegin
190     *        block comment AST
191     * @return true if block comment content starts with '*' javadoc comment
192     *         identifier.
193     */
194    public static boolean isJavadocComment(DetailAST blockCommentBegin) {
195        final String commentContent = getBlockCommentContent(blockCommentBegin);
196        return isJavadocComment(commentContent) && isCorrectJavadocPosition(blockCommentBegin);
197    }
198
199    /**
200     * Gets content of block comment.
201     * @param blockCommentBegin
202     *        block comment AST.
203     * @return content of block comment.
204     */
205    private static String getBlockCommentContent(DetailAST blockCommentBegin) {
206        final DetailAST commentContent = blockCommentBegin.getFirstChild();
207        return commentContent.getText();
208    }
209
210    /**
211     * Get content of Javadoc comment.
212     * @param javadocCommentBegin
213     *        Javadoc comment AST
214     * @return content of Javadoc comment.
215     */
216    public static String getJavadocCommentContent(DetailAST javadocCommentBegin) {
217        final DetailAST commentContent = javadocCommentBegin.getFirstChild();
218        return commentContent.getText().substring(1);
219    }
220
221    /**
222     * Returns the first child token that has a specified type.
223     * @param detailNode
224     *        Javadoc AST node
225     * @param type
226     *        the token type to match
227     * @return the matching token, or null if no match
228     */
229    public static DetailNode findFirstToken(DetailNode detailNode, int type) {
230        DetailNode returnValue = null;
231        DetailNode node = getFirstChild(detailNode);
232        while (node != null) {
233            if (node.getType() == type) {
234                returnValue = node;
235                break;
236            }
237            node = getNextSibling(node);
238        }
239        return returnValue;
240    }
241
242    /**
243     * Gets first child node of specified node.
244     *
245     * @param node DetailNode
246     * @return first child
247     */
248    public static DetailNode getFirstChild(DetailNode node) {
249        DetailNode resultNode = null;
250
251        if (node.getChildren().length > 0) {
252            resultNode = node.getChildren()[0];
253        }
254        return resultNode;
255    }
256
257    /**
258     * Checks whether node contains any node of specified type among children on any deep level.
259     *
260     * @param node DetailNode
261     * @param type token type
262     * @return true if node contains any node of type type among children on any deep level.
263     */
264    public static boolean containsInBranch(DetailNode node, int type) {
265        boolean result = true;
266        DetailNode curNode = node;
267        while (type != curNode.getType()) {
268            DetailNode toVisit = getFirstChild(curNode);
269            while (curNode != null && toVisit == null) {
270                toVisit = getNextSibling(curNode);
271                if (toVisit == null) {
272                    curNode = curNode.getParent();
273                }
274            }
275
276            if (curNode == toVisit) {
277                result = false;
278                break;
279            }
280
281            curNode = toVisit;
282        }
283        return result;
284    }
285
286    /**
287     * Gets next sibling of specified node.
288     *
289     * @param node DetailNode
290     * @return next sibling.
291     */
292    public static DetailNode getNextSibling(DetailNode node) {
293        DetailNode nextSibling = null;
294        final DetailNode parent = node.getParent();
295        if (parent != null) {
296            final int nextSiblingIndex = node.getIndex() + 1;
297            final DetailNode[] children = parent.getChildren();
298            if (nextSiblingIndex <= children.length - 1) {
299                nextSibling = children[nextSiblingIndex];
300            }
301        }
302        return nextSibling;
303    }
304
305    /**
306     * Gets next sibling of specified node with the specified type.
307     *
308     * @param node DetailNode
309     * @param tokenType javadoc token type
310     * @return next sibling.
311     */
312    public static DetailNode getNextSibling(DetailNode node, int tokenType) {
313        DetailNode nextSibling = getNextSibling(node);
314        while (nextSibling != null && nextSibling.getType() != tokenType) {
315            nextSibling = getNextSibling(nextSibling);
316        }
317        return nextSibling;
318    }
319
320    /**
321     * Gets previous sibling of specified node.
322     * @param node DetailNode
323     * @return previous sibling
324     */
325    public static DetailNode getPreviousSibling(DetailNode node) {
326        DetailNode previousSibling = null;
327        final int previousSiblingIndex = node.getIndex() - 1;
328        if (previousSiblingIndex >= 0) {
329            final DetailNode parent = node.getParent();
330            final DetailNode[] children = parent.getChildren();
331            previousSibling = children[previousSiblingIndex];
332        }
333        return previousSibling;
334    }
335
336    /**
337     * Returns the name of a token for a given ID.
338     * @param id
339     *        the ID of the token name to get
340     * @return a token name
341     */
342    public static String getTokenName(int id) {
343        final String name;
344        if (id == JavadocTokenTypes.EOF) {
345            name = "EOF";
346        }
347        else if (id > TOKEN_VALUE_TO_NAME.length - 1) {
348            throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id);
349        }
350        else {
351            name = TOKEN_VALUE_TO_NAME[id];
352            if (name == null) {
353                throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id);
354            }
355        }
356        return name;
357    }
358
359    /**
360     * Returns the ID of a token for a given name.
361     * @param name
362     *        the name of the token ID to get
363     * @return a token ID
364     */
365    public static int getTokenId(String name) {
366        final Integer id = TOKEN_NAME_TO_VALUE.get(name);
367        if (id == null) {
368            throw new IllegalArgumentException("Unknown javadoc token name. Given name " + name);
369        }
370        return id;
371    }
372
373    /**
374     * Gets tag name from javadocTagSection.
375     *
376     * @param javadocTagSection to get tag name from.
377     * @return name, of the javadocTagSection's tag.
378     */
379    public static String getTagName(DetailNode javadocTagSection) {
380        final String javadocTagName;
381        if (javadocTagSection.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) {
382            javadocTagName = getNextSibling(
383                    getFirstChild(javadocTagSection)).getText();
384        }
385        else {
386            javadocTagName = getFirstChild(javadocTagSection).getText();
387        }
388        return javadocTagName;
389    }
390
391    /**
392     * Replace all control chars with escaped symbols.
393     * @param text the String to process.
394     * @return the processed String with all control chars escaped.
395     */
396    public static String escapeAllControlChars(String text) {
397        final String textWithoutNewlines = NEWLINE.matcher(text).replaceAll("\\\\n");
398        final String textWithoutReturns = RETURN.matcher(textWithoutNewlines).replaceAll("\\\\r");
399        return TAB.matcher(textWithoutReturns).replaceAll("\\\\t");
400    }
401
402    /**
403     * Checks Javadoc comment it's in right place.
404     * <p>From Javadoc util documentation:
405     * "Placement of comments - Documentation comments are recognized only when placed
406     * immediately before class, interface, constructor, method, field or annotation field
407     * declarations -- see the class example, method example, and field example.
408     * Documentation comments placed in the body of a method are ignored."</p>
409     * <p>If there are many documentation comments per declaration statement,
410     * only the last one will be recognized.</p>
411     *
412     * @param blockComment Block comment AST
413     * @return true if Javadoc is in right place
414     * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/javadoc.html">
415     *     Javadoc util documentation</a>
416     */
417    private static boolean isCorrectJavadocPosition(DetailAST blockComment) {
418        // We must be sure that after this one there are no other documentation comments.
419        DetailAST sibling = blockComment.getNextSibling();
420        while (sibling != null) {
421            if (sibling.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
422                if (isJavadocComment(getBlockCommentContent(sibling))) {
423                    // Found another javadoc comment, so this one should be ignored.
424                    break;
425                }
426                sibling = sibling.getNextSibling();
427            }
428            else if (sibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
429                sibling = sibling.getNextSibling();
430            }
431            else {
432                // Annotation, declaration or modifier is here. Do not check further.
433                sibling = null;
434            }
435        }
436        return sibling == null
437            && (BlockCommentPosition.isOnType(blockComment)
438                || BlockCommentPosition.isOnMember(blockComment));
439    }
440
441}