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