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 mTagRE; 072 /** compiled regexp to match tag content **/ 073 private Pattern mTagFormatRE; 074 075 /** regexp to match tag */ 076 private String mTag; 077 /** regexp to match tag content */ 078 private String mTagFormat; 079 /** the severity level of found tag reports */ 080 private SeverityLevel mTagSeverityLevel = SeverityLevel.INFO; 081 082 /** 083 * Sets the tag to check. 084 * @param aTag tag to check 085 * @throws ConversionException If the tag is not a valid regular exception. 086 */ 087 public void setTag(String aTag) 088 throws ConversionException 089 { 090 try { 091 mTag = aTag; 092 mTagRE = Utils.getPattern(aTag + "\\s*(.*$)"); 093 } 094 catch (final PatternSyntaxException e) { 095 throw new ConversionException("unable to parse " + aTag, e); 096 } 097 } 098 099 /** 100 * Set the tag format. 101 * @param aFormat a <code>String</code> value 102 * @throws ConversionException unable to parse aFormat 103 */ 104 public void setTagFormat(String aFormat) 105 throws ConversionException 106 { 107 try { 108 mTagFormat = aFormat; 109 mTagFormatRE = Utils.getPattern(aFormat); 110 } 111 catch (final PatternSyntaxException e) { 112 throw new ConversionException("unable to parse " + aFormat, 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 aSeverity The new severity level 121 * @see SeverityLevel 122 */ 123 public final void setTagSeverity(String aSeverity) 124 { 125 mTagSeverityLevel = SeverityLevel.getInstance(aSeverity); 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 aAST) 154 { 155 final FileContents contents = getFileContents(); 156 final int lineNo = aAST.getLineNo(); 157 final TextBlock cmt = 158 contents.getJavadocBefore(lineNo); 159 if (cmt == null) { 160 log(lineNo, "type.missingTag", mTag); 161 } 162 else { 163 checkTag(lineNo, cmt.getText(), mTag, mTagRE, mTagFormatRE, 164 mTagFormat); 165 } 166 } 167 168 /** 169 * Verifies that a type definition has a required tag. 170 * @param aLineNo the line number for the type definition. 171 * @param aComment the Javadoc comment for the type definition. 172 * @param aTag the required tag name. 173 * @param aTagRE regexp for the full tag. 174 * @param aFormatRE regexp for the tag value. 175 * @param aFormat pattern for the tag value. 176 */ 177 private void checkTag( 178 int aLineNo, 179 String[] aComment, 180 String aTag, 181 Pattern aTagRE, 182 Pattern aFormatRE, 183 String aFormat) 184 { 185 if (aTagRE == null) { 186 return; 187 } 188 189 int tagCount = 0; 190 for (int i = 0; i < aComment.length; i++) { 191 final String s = aComment[i]; 192 final Matcher matcher = aTagRE.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 ((aFormatRE != null) && !aFormatRE.matcher(content).find()) { 198 log(aLineNo + i - aComment.length, "type.tagFormat", aTag, 199 aFormat); 200 } 201 else { 202 logTag(aLineNo + i - aComment.length, aTag, content); 203 } 204 205 } 206 } 207 if (tagCount == 0) { 208 log(aLineNo, "type.missingTag", aTag); 209 } 210 211 } 212 213 214 /** 215 * Log a message. 216 * 217 * @param aLine the line number where the error was found 218 * @param aTag the javdoc tag to be logged 219 * @param aTagValue the contents of the tag 220 * 221 * @see java.text.MessageFormat 222 */ 223 protected final void logTag(int aLine, String aTag, String aTagValue) 224 { 225 final String originalSeverity = getSeverity(); 226 setSeverity(mTagSeverityLevel.getName()); 227 228 log(aLine, "javadoc.writeTag", aTag, aTagValue); 229 230 setSeverity(originalSeverity); 231 } 232}