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.checks.javadoc.utils; 021 022import java.util.ArrayList; 023import java.util.List; 024import java.util.regex.Matcher; 025import java.util.regex.Pattern; 026 027import com.puppycrawl.tools.checkstyle.api.LineColumn; 028 029/** 030 * Tools for extracting inline tags from Javadoc comments. 031 * 032 * @author Nathan Naze 033 */ 034public final class InlineTagUtils { 035 036 /** 037 * Inline tag pattern. 038 */ 039 private static final Pattern INLINE_TAG_PATTERN = Pattern.compile( 040 ".*?\\{@(\\p{Alpha}+)\\b(.*?)}", Pattern.DOTALL); 041 042 /** Pattern to recognize leading "*" characters in Javadoc. */ 043 private static final Pattern JAVADOC_PREFIX_PATTERN = Pattern.compile( 044 "^\\s*\\*", Pattern.MULTILINE); 045 046 /** Pattern matching whitespace, used by {@link InlineTagUtils#collapseWhitespace(String)}. */ 047 private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); 048 049 /** Pattern matching a newline. */ 050 private static final Pattern NEWLINE_PATTERN = Pattern.compile("\\n"); 051 052 /** Line feed character. */ 053 private static final String LINE_FEED = "\n"; 054 055 /** Carriage return character. */ 056 private static final String CARRIAGE_RETURN = "\r"; 057 058 /** Prevent instantiation. */ 059 private InlineTagUtils() { 060 } 061 062 /** 063 * Extract inline Javadoc tags from the given comment. 064 * @param lines The Javadoc comment (as lines). 065 * @return The extracted inline Javadoc tags. 066 */ 067 public static List<TagInfo> extractInlineTags(String... lines) { 068 for (String line : lines) { 069 if (line.contains(LINE_FEED) || line.contains(CARRIAGE_RETURN)) { 070 throw new IllegalArgumentException("comment lines cannot contain newlines"); 071 } 072 } 073 074 final String commentText = convertLinesToString(lines); 075 final Matcher inlineTagMatcher = INLINE_TAG_PATTERN.matcher(commentText); 076 077 final List<TagInfo> tags = new ArrayList<>(); 078 079 while (inlineTagMatcher.find()) { 080 final String tagName = inlineTagMatcher.group(1); 081 082 // Remove the leading asterisks (in case the tag spans a line) and collapse 083 // the whitespace. 084 String matchedTagValue = inlineTagMatcher.group(2); 085 matchedTagValue = removeLeadingJavaDoc(matchedTagValue); 086 matchedTagValue = collapseWhitespace(matchedTagValue); 087 088 final String tagValue = matchedTagValue; 089 090 final int startIndex = inlineTagMatcher.start(1); 091 final LineColumn position = getLineColumnOfIndex(commentText, 092 // correct start index offset 093 startIndex - 1); 094 095 tags.add(new TagInfo(tagName, tagValue, position)); 096 } 097 098 return tags; 099 } 100 101 /** 102 * Convert array of string to single String. 103 * @param lines A number of lines, in order. 104 * @return The lines, joined together with newlines, as a single string. 105 */ 106 private static String convertLinesToString(String... lines) { 107 final StringBuilder builder = new StringBuilder(1024); 108 for (String line : lines) { 109 builder.append(line); 110 builder.append(LINE_FEED); 111 } 112 return builder.toString(); 113 } 114 115 /** 116 * Get LineColumn from string till index. 117 * @param source Source string. 118 * @param index An index into the string. 119 * @return A position in the source representing what line and column that index appears on. 120 */ 121 private static LineColumn getLineColumnOfIndex(String source, int index) { 122 final String precedingText = source.subSequence(0, index).toString(); 123 final String[] precedingLines = NEWLINE_PATTERN.split(precedingText); 124 final String lastLine = precedingLines[precedingLines.length - 1]; 125 return new LineColumn(precedingLines.length, lastLine.length()); 126 } 127 128 /** 129 * Collapse whitespaces. 130 * @param str Source string. 131 * @return The given string with all whitespace collapsed. 132 */ 133 private static String collapseWhitespace(String str) { 134 final Matcher matcher = WHITESPACE_PATTERN.matcher(str); 135 return matcher.replaceAll(" ").trim(); 136 } 137 138 /** 139 * Remove leading JavaDoc. 140 * @param source A string to remove leading Javadoc from. 141 * @return The given string with leading Javadoc "*" characters from each line removed. 142 */ 143 private static String removeLeadingJavaDoc(String source) { 144 final Matcher matcher = JAVADOC_PREFIX_PATTERN.matcher(source); 145 return matcher.replaceAll(""); 146 } 147 148}