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 com.puppycrawl.tools.checkstyle.api.Check; 022import com.puppycrawl.tools.checkstyle.api.DetailAST; 023import com.puppycrawl.tools.checkstyle.api.FileContents; 024import com.puppycrawl.tools.checkstyle.api.SeverityLevel; 025import com.puppycrawl.tools.checkstyle.api.TextBlock; 026import com.puppycrawl.tools.checkstyle.api.TokenTypes; 027import com.puppycrawl.tools.checkstyle.api.Utils; 028import java.util.regex.Matcher; 029import java.util.regex.Pattern; 030import java.util.regex.PatternSyntaxException; 031import org.apache.commons.beanutils.ConversionException; 032 033/** 034 * <p> 035 * Outputs a JavaDoc tag as information. Can be used e.g. with the stylesheets 036 * that sort the report by author name. 037 * To define the format for a tag, set property tagFormat to a 038 * regular expression. 039 * This check uses two different severity levels. The normal one is used for 040 * reporting when the tag is missing. The additional one (tagSeverity) is used 041 * for the level of reporting when the tag exists. The default value for 042 * tagSeverity is info. 043 * </p> 044 * <p> An example of how to configure the check for printing author name is: 045 *</p> 046 * <pre> 047 * <module name="WriteTag"> 048 * <property name="tag" value="@author"/> 049 * <property name="tagFormat" value="\S"/> 050 * </module> 051 * </pre> 052 * <p> An example of how to configure the check to print warnings if an 053 * "@incomplete" tag is found, and not print anything if it is not found: 054 *</p> 055 * <pre> 056 * <module name="WriteTag"> 057 * <property name="tag" value="@incomplete"/> 058 * <property name="tagFormat" value="\S"/> 059 * <property name="severity" value="ignore"/> 060 * <property name="tagSeverity" value="warning"/> 061 * </module> 062 * </pre> 063 * 064 * @author Daniel Grenner 065 * @version 1.0 066 */ 067public class WriteTagCheck 068 extends Check 069{ 070 /** compiled regexp to match tag **/ 071 private Pattern tagRE; 072 /** compiled regexp to match tag content **/ 073 private Pattern tagFormatRE; 074 075 /** regexp to match tag */ 076 private String tag; 077 /** regexp to match tag content */ 078 private String tagFormat; 079 /** the severity level of found tag reports */ 080 private SeverityLevel tagSeverityLevel = SeverityLevel.INFO; 081 082 /** 083 * Sets the tag to check. 084 * @param tag tag to check 085 * @throws ConversionException If the tag is not a valid regular exception. 086 */ 087 public void setTag(String tag) 088 throws ConversionException 089 { 090 try { 091 this.tag = tag; 092 tagRE = Utils.getPattern(tag + "\\s*(.*$)"); 093 } 094 catch (final PatternSyntaxException e) { 095 throw new ConversionException("unable to parse " + tag, e); 096 } 097 } 098 099 /** 100 * Set the tag format. 101 * @param format a <code>String</code> value 102 * @throws ConversionException unable to parse format 103 */ 104 public void setTagFormat(String format) 105 throws ConversionException 106 { 107 try { 108 tagFormat = format; 109 tagFormatRE = Utils.getPattern(format); 110 } 111 catch (final PatternSyntaxException e) { 112 throw new ConversionException("unable to parse " + format, e); 113 } 114 } 115 116 /** 117 * Sets the tag severity level. The string should be one of the names 118 * defined in the <code>SeverityLevel</code> class. 119 * 120 * @param severity The new severity level 121 * @see SeverityLevel 122 */ 123 public final void setTagSeverity(String severity) 124 { 125 tagSeverityLevel = SeverityLevel.getInstance(severity); 126 } 127 128 @Override 129 public int[] getDefaultTokens() 130 { 131 return new int[] {TokenTypes.INTERFACE_DEF, 132 TokenTypes.CLASS_DEF, 133 TokenTypes.ENUM_DEF, 134 TokenTypes.ANNOTATION_DEF, 135 }; 136 } 137 138 @Override 139 public int[] getAcceptableTokens() 140 { 141 return new int[] {TokenTypes.INTERFACE_DEF, 142 TokenTypes.CLASS_DEF, 143 TokenTypes.ENUM_DEF, 144 TokenTypes.ANNOTATION_DEF, 145 TokenTypes.METHOD_DEF, 146 TokenTypes.CTOR_DEF, 147 TokenTypes.ENUM_CONSTANT_DEF, 148 TokenTypes.ANNOTATION_FIELD_DEF, 149 }; 150 } 151 152 @Override 153 public void visitToken(DetailAST ast) 154 { 155 final FileContents contents = getFileContents(); 156 final int lineNo = ast.getLineNo(); 157 final TextBlock cmt = 158 contents.getJavadocBefore(lineNo); 159 if (cmt == null) { 160 log(lineNo, "type.missingTag", tag); 161 } 162 else { 163 checkTag(lineNo, cmt.getText(), tag, tagRE, tagFormatRE, 164 tagFormat); 165 } 166 } 167 168 /** 169 * Verifies that a type definition has a required tag. 170 * @param lineNo the line number for the type definition. 171 * @param comment the Javadoc comment for the type definition. 172 * @param tag the required tag name. 173 * @param tagRE regexp for the full tag. 174 * @param formatRE regexp for the tag value. 175 * @param format pattern for the tag value. 176 */ 177 private void checkTag( 178 int lineNo, 179 String[] comment, 180 String tag, 181 Pattern tagRE, 182 Pattern formatRE, 183 String format) 184 { 185 if (tagRE == null) { 186 return; 187 } 188 189 int tagCount = 0; 190 for (int i = 0; i < comment.length; i++) { 191 final String s = comment[i]; 192 final Matcher matcher = tagRE.matcher(s); 193 if (matcher.find()) { 194 tagCount += 1; 195 final int contentStart = matcher.start(1); 196 final String content = s.substring(contentStart); 197 if ((formatRE != null) && !formatRE.matcher(content).find()) { 198 log(lineNo + i - comment.length, "type.tagFormat", tag, 199 format); 200 } 201 else { 202 logTag(lineNo + i - comment.length, tag, content); 203 } 204 205 } 206 } 207 if (tagCount == 0) { 208 log(lineNo, "type.missingTag", tag); 209 } 210 211 } 212 213 214 /** 215 * Log a message. 216 * 217 * @param line the line number where the error was found 218 * @param tag the javadoc tag to be logged 219 * @param tagValue the contents of the tag 220 * 221 * @see java.text.MessageFormat 222 */ 223 protected final void logTag(int line, String tag, String tagValue) 224 { 225 final String originalSeverity = getSeverity(); 226 setSeverity(tagSeverityLevel.getName()); 227 228 log(line, "javadoc.writeTag", tag, tagValue); 229 230 setSeverity(originalSeverity); 231 } 232}