001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2015 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.List;
025import java.util.regex.Matcher;
026import java.util.regex.Pattern;
027
028import org.apache.commons.lang3.ArrayUtils;
029
030import com.google.common.collect.ImmutableMap;
031import com.google.common.collect.Lists;
032import com.puppycrawl.tools.checkstyle.api.DetailAST;
033import com.puppycrawl.tools.checkstyle.api.DetailNode;
034import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
035import com.puppycrawl.tools.checkstyle.api.TextBlock;
036import com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag;
037import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
038import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo;
039import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTags;
040
041/**
042 * Contains utility methods for working with Javadoc.
043 * @author Lyle Hanson
044 */
045public final class JavadocUtils {
046    /** Maps from a token name to value. */
047    private static final ImmutableMap<String, Integer> TOKEN_NAME_TO_VALUE;
048    /** Maps from a token value to name. */
049    private static final String[] TOKEN_VALUE_TO_NAME;
050
051    /** Exception message for unknown JavaDoc token id. */
052    private static final String UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE = "Unknown javadoc"
053            + " token id. Given id: ";
054
055    // Using reflection gets all token names and values from JavadocTokenTypes class
056    // and saves to TOKEN_NAME_TO_VALUE and TOKEN_VALUE_TO_NAME collections.
057    static {
058        final ImmutableMap.Builder<String, Integer> builder = ImmutableMap.builder();
059
060        final Field[] fields = JavadocTokenTypes.class.getDeclaredFields();
061
062        String[] tempTokenValueToName = ArrayUtils.EMPTY_STRING_ARRAY;
063
064        for (final Field field : fields) {
065
066            // Only process public int fields.
067            if (!Modifier.isPublic(field.getModifiers())
068                    || field.getType() != Integer.TYPE) {
069                continue;
070            }
071
072            final String name = field.getName();
073
074            final int tokenValue = TokenUtils.getIntFromField(field, name);
075            builder.put(name, tokenValue);
076            if (tokenValue > tempTokenValueToName.length - 1) {
077                final String[] temp = new String[tokenValue + 1];
078                System.arraycopy(tempTokenValueToName, 0, temp, 0, tempTokenValueToName.length);
079                tempTokenValueToName = temp;
080            }
081            if (tokenValue == -1) {
082                tempTokenValueToName[0] = name;
083            }
084            else {
085                tempTokenValueToName[tokenValue] = name;
086            }
087        }
088
089        TOKEN_NAME_TO_VALUE = builder.build();
090        TOKEN_VALUE_TO_NAME = tempTokenValueToName;
091    }
092
093    /** Prevent instantiation. */
094    private JavadocUtils() {
095    }
096
097    /**
098     * Gets validTags from a given piece of Javadoc.
099     * @param textBlock
100     *        the Javadoc comment to process.
101     * @param tagType
102     *        the type of validTags we're interested in
103     * @return all standalone validTags from the given javadoc.
104     */
105    public static JavadocTags getJavadocTags(TextBlock textBlock,
106            JavadocTagType tagType) {
107        final String[] text = textBlock.getText();
108        final List<JavadocTag> tags = Lists.newArrayList();
109        final List<InvalidJavadocTag> invalidTags = Lists.newArrayList();
110        Pattern blockTagPattern = Pattern.compile("/\\*{2,}\\s*@(\\p{Alpha}+)\\s");
111        for (int i = 0; i < text.length; i++) {
112            final String textValue = text[i];
113            final Matcher blockTagMatcher = blockTagPattern.matcher(textValue);
114            if ((tagType == JavadocTagType.ALL || tagType == JavadocTagType.BLOCK)
115                    && blockTagMatcher.find()) {
116                final String tagName = blockTagMatcher.group(1);
117                String content = textValue.substring(blockTagMatcher.end(1));
118                if (content.endsWith("*/")) {
119                    content = content.substring(0, content.length() - 2);
120                }
121                final int line = textBlock.getStartLineNo() + i;
122                int col = blockTagMatcher.start(1) - 1;
123                if (i == 0) {
124                    col += textBlock.getStartColNo();
125                }
126                if (JavadocTagInfo.isValidName(tagName)) {
127                    tags.add(
128                            new JavadocTag(line, col, tagName, content.trim()));
129                }
130                else {
131                    invalidTags.add(new InvalidJavadocTag(line, col, tagName));
132                }
133            }
134            // No block tag, so look for inline validTags
135            else if (tagType == JavadocTagType.ALL || tagType == JavadocTagType.INLINE) {
136                lookForInlineTags(textBlock, i, tags, invalidTags);
137            }
138            blockTagPattern = Pattern.compile("^\\s*\\**\\s*@(\\p{Alpha}+)\\s");
139        }
140        return new JavadocTags(tags, invalidTags);
141    }
142
143    /**
144     * Looks for inline tags in comment and adds them to the proper tags collection.
145     * @param comment comment text block
146     * @param lineNumber line number in the comment
147     * @param validTags collection of valid tags
148     * @param invalidTags collection of invalid tags
149     */
150    private static void lookForInlineTags(TextBlock comment, int lineNumber,
151            final List<JavadocTag> validTags, final List<InvalidJavadocTag> invalidTags) {
152        final String text = comment.getText()[lineNumber];
153        // Match Javadoc text after comment characters
154        final Pattern commentPattern = Pattern.compile("^\\s*(?:/\\*{2,}|\\*+)\\s*(.*)");
155        final Matcher commentMatcher = commentPattern.matcher(text);
156        final String commentContents;
157
158        // offset including comment characters
159        final int commentOffset;
160
161        if (commentMatcher.find()) {
162            commentContents = commentMatcher.group(1);
163            commentOffset = commentMatcher.start(1) - 1;
164        }
165        else {
166            // No leading asterisks, still valid
167            commentContents = text;
168            commentOffset = 0;
169        }
170        final Pattern tagPattern = Pattern.compile(".*?\\{@(\\p{Alpha}+)\\s+(.*?)\\}");
171        final Matcher tagMatcher = tagPattern.matcher(commentContents);
172        while (tagMatcher.find()) {
173            final String tagName = tagMatcher.group(1);
174            final String tagValue = tagMatcher.group(2).trim();
175            final int line = comment.getStartLineNo() + lineNumber;
176            int col = commentOffset + tagMatcher.start(1) - 1;
177            if (lineNumber == 0) {
178                col += comment.getStartColNo();
179            }
180            if (JavadocTagInfo.isValidName(tagName)) {
181                validTags.add(new JavadocTag(line, col, tagName,
182                        tagValue));
183            }
184            else {
185                invalidTags.add(new InvalidJavadocTag(line, col,
186                        tagName));
187            }
188        }
189    }
190
191    /**
192     * The type of Javadoc tag we want returned.
193     */
194    public enum JavadocTagType {
195        /** Block type. */
196        BLOCK,
197        /** Inline type. */
198        INLINE,
199        /** All validTags. */
200        ALL
201    }
202
203    /**
204     * Checks that commentContent starts with '*' javadoc comment identifier.
205     * @param commentContent
206     *        content of block comment
207     * @return true if commentContent starts with '*' javadoc comment
208     *         identifier.
209     */
210    public static boolean isJavadocComment(String commentContent) {
211        boolean result = false;
212
213        if (!commentContent.isEmpty()) {
214            final char docCommentIdentificator = commentContent.charAt(0);
215            result = docCommentIdentificator == '*';
216        }
217
218        return result;
219    }
220
221    /**
222     * Checks block comment content starts with '*' javadoc comment identifier.
223     * @param blockCommentBegin
224     *        block comment AST
225     * @return true if block comment content starts with '*' javadoc comment
226     *         identifier.
227     */
228    public static boolean isJavadocComment(DetailAST blockCommentBegin) {
229        final String commentContent = getBlockCommentContent(blockCommentBegin);
230        return isJavadocComment(commentContent);
231    }
232
233    /**
234     * Gets content of block comment.
235     * @param blockCommentBegin
236     *        block comment AST.
237     * @return content of block comment.
238     */
239    private static String getBlockCommentContent(DetailAST blockCommentBegin) {
240        final DetailAST commentContent = blockCommentBegin.getFirstChild();
241        return commentContent.getText();
242    }
243
244    /**
245     * Get content of Javadoc comment.
246     * @param javadocCommentBegin
247     *        Javadoc comment AST
248     * @return content of Javadoc comment.
249     */
250    public static String getJavadocCommentContent(DetailAST javadocCommentBegin) {
251        final DetailAST commentContent = javadocCommentBegin.getFirstChild();
252        return commentContent.getText().substring(1);
253    }
254
255    /**
256     * Returns the first child token that has a specified type.
257     * @param detailNode
258     *        Javadoc AST node
259     * @param type
260     *        the token type to match
261     * @return the matching token, or null if no match
262     */
263    public static DetailNode findFirstToken(DetailNode detailNode, int type) {
264        DetailNode returnValue = null;
265        DetailNode node = getFirstChild(detailNode);
266        while (node != null) {
267            if (node.getType() == type) {
268                returnValue = node;
269                break;
270            }
271            node = getNextSibling(node);
272        }
273        return returnValue;
274    }
275
276    /**
277     * Gets first child node of specified node.
278     *
279     * @param node DetailNode
280     * @return first child
281     */
282    public static DetailNode getFirstChild(DetailNode node) {
283        DetailNode resultNode = null;
284
285        if (node.getChildren().length > 0) {
286            resultNode = node.getChildren()[0];
287        }
288        return resultNode;
289    }
290
291    /**
292     * Checks whether node contains any node of specified type among children on any deep level.
293     *
294     * @param node DetailNode
295     * @param type token type
296     * @return true if node contains any node of type type among children on any deep level.
297     */
298    public static boolean containsInBranch(DetailNode node, int type) {
299        DetailNode curNode = node;
300        while (true) {
301
302            if (type == curNode.getType()) {
303                return true;
304            }
305
306            DetailNode toVisit = getFirstChild(curNode);
307            while (curNode != null && toVisit == null) {
308                toVisit = getNextSibling(curNode);
309                if (toVisit == null) {
310                    curNode = curNode.getParent();
311                }
312            }
313
314            if (curNode == toVisit) {
315                break;
316            }
317
318            curNode = toVisit;
319        }
320
321        return false;
322    }
323
324    /**
325     * Gets next sibling of specified node.
326     *
327     * @param node DetailNode
328     * @return next sibling.
329     */
330    public static DetailNode getNextSibling(DetailNode node) {
331        final DetailNode parent = node.getParent();
332        if (parent != null) {
333            final int nextSiblingIndex = node.getIndex() + 1;
334            final DetailNode[] children = parent.getChildren();
335            if (nextSiblingIndex <= children.length - 1) {
336                return children[nextSiblingIndex];
337            }
338        }
339        return null;
340    }
341
342    /**
343     * Gets next sibling of specified node with the specified type.
344     *
345     * @param node DetailNode
346     * @param tokenType javadoc token type
347     * @return next sibling.
348     */
349    public static DetailNode getNextSibling(DetailNode node, int tokenType) {
350        DetailNode nextSibling = getNextSibling(node);
351        while (nextSibling != null && nextSibling.getType() != tokenType) {
352            nextSibling = getNextSibling(nextSibling);
353        }
354        return nextSibling;
355    }
356
357    /**
358     * Gets previous sibling of specified node.
359     * @param node DetailNode
360     * @return previous sibling
361     */
362    public static DetailNode getPreviousSibling(DetailNode node) {
363        final DetailNode parent = node.getParent();
364        final int previousSiblingIndex = node.getIndex() - 1;
365        final DetailNode[] children = parent.getChildren();
366        if (previousSiblingIndex >= 0) {
367            return children[previousSiblingIndex];
368        }
369        return null;
370    }
371
372    /**
373     * Returns the name of a token for a given ID.
374     * @param id
375     *        the ID of the token name to get
376     * @return a token name
377     */
378    public static String getTokenName(int id) {
379        if (id == JavadocTokenTypes.EOF) {
380            return "EOF";
381        }
382        if (id > TOKEN_VALUE_TO_NAME.length - 1) {
383            throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id);
384        }
385        final String name = TOKEN_VALUE_TO_NAME[id];
386        if (name == null) {
387            throw new IllegalArgumentException(UNKNOWN_JAVADOC_TOKEN_ID_EXCEPTION_MESSAGE + id);
388        }
389        return name;
390    }
391
392    /**
393     * Returns the ID of a token for a given name.
394     * @param name
395     *        the name of the token ID to get
396     * @return a token ID
397     */
398    public static int getTokenId(String name) {
399        final Integer id = TOKEN_NAME_TO_VALUE.get(name);
400        if (id == null) {
401            throw new IllegalArgumentException("Unknown javadoc token name. Given name " + name);
402        }
403        return id;
404    }
405
406    /**
407     * Gets tag name from javadocTagSection.
408     *
409     * @param javadocTagSection to get tag name from.
410     * @return name, of the javadocTagSection's tag.
411     */
412    public static String getTagName(DetailNode javadocTagSection) {
413        String javadocTagName;
414        if (javadocTagSection.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG) {
415            javadocTagName = getNextSibling(
416                    getFirstChild(javadocTagSection)).getText();
417        }
418        else {
419            javadocTagName = getFirstChild(javadocTagSection).getText();
420        }
421        return javadocTagName;
422    }
423
424}