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