001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2019 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; 021 022import java.io.File; 023import java.io.IOException; 024import java.io.RandomAccessFile; 025import java.util.Locale; 026 027import com.puppycrawl.tools.checkstyle.StatelessCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck; 029import com.puppycrawl.tools.checkstyle.api.FileText; 030 031/** 032 *<p> 033 * Checks whether files end with a line separator. 034 * </p> 035 * <p> 036 * Rationale: Any source files and text files in general should end with a line 037 * separator to let other easily add new content at the end of file and "diff" 038 * command does not show previous lines as changed. 039 * </p> 040 * <p> 041 * Example (line 36 should not be in diff): 042 * </p> 043 * <pre> 044 * @@ -32,4 +32,5 @@ ForbidWildcardAsReturnTypeCheck.returnTypeClassNamesIgnoreRegex 045 * PublicReferenceToPrivateTypeCheck.name = Public Reference To Private Type 046 * 047 * StaticMethodCandidateCheck.name = Static Method Candidate 048 * -StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static. 049 * \ No newline at end of file 050 * +StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static. 051 * +StaticMethodCandidateCheck.skippedMethods = Method names to skip during the check. 052 * </pre> 053 * <p> 054 * It can also trick the VCS to report the wrong owner for such lines. 055 * An engineer who has added nothing but a newline character becomes the last 056 * known author for the entire line. As a result, a mate can ask him a question 057 * to which he will not give the correct answer. 058 * </p> 059 * <p> 060 * Old Rationale: CVS source control management systems will even print 061 * a warning when it encounters a file that doesn't end with a line separator. 062 * </p> 063 * <p> 064 * Attention: property fileExtensions works with files that are passed by similar 065 * property for at <a href="https://checkstyle.org/config.html#Checker">Checker</a>. 066 * Please make sure required file extensions are mentioned at Checker's fileExtensions property. 067 * </p> 068 * <p> 069 * This will check against the platform-specific default line separator. 070 * </p> 071 * <p> 072 * It is also possible to enforce the use of a specific line-separator across 073 * platforms, with the {@code lineSeparator} property. 074 * </p> 075 * <ul> 076 * <li> 077 * Property {@code lineSeparator} - Specify the type of line separator. 078 * Default value is {@code lf_cr_crlf}. 079 * </li> 080 * <li> 081 * Property {@code fileExtensions} - Specify the file type extension of the files to check. 082 * Default value is {@code all files}. 083 * </li> 084 * </ul> 085 * <p> 086 * To configure the check: 087 * </p> 088 * <pre> 089 * <module name="NewlineAtEndOfFile"/> 090 * </pre> 091 * <p> 092 * To configure the check to always use Unix-style line separators: 093 * </p> 094 * <pre> 095 * <module name="NewlineAtEndOfFile"> 096 * <property name="lineSeparator" value="lf"/> 097 * </module> 098 * </pre> 099 * <p> 100 * To configure the check to work only on Java, XML and Python files: 101 * </p> 102 * <pre> 103 * <module name="NewlineAtEndOfFile"> 104 * <property name="fileExtensions" value="java, xml, py"/> 105 * </module> 106 * </pre> 107 * 108 * @since 3.1 109 */ 110@StatelessCheck 111public class NewlineAtEndOfFileCheck 112 extends AbstractFileSetCheck { 113 114 /** 115 * A key is pointing to the warning message text in "messages.properties" 116 * file. 117 */ 118 public static final String MSG_KEY_UNABLE_OPEN = "unable.open"; 119 120 /** 121 * A key is pointing to the warning message text in "messages.properties" 122 * file. 123 */ 124 public static final String MSG_KEY_NO_NEWLINE_EOF = "noNewlineAtEOF"; 125 126 /** Specify the type of line separator. */ 127 private LineSeparatorOption lineSeparator = LineSeparatorOption.LF_CR_CRLF; 128 129 @Override 130 protected void processFiltered(File file, FileText fileText) { 131 try { 132 readAndCheckFile(file); 133 } 134 catch (final IOException ignored) { 135 log(1, MSG_KEY_UNABLE_OPEN, file.getPath()); 136 } 137 } 138 139 /** 140 * Setter to specify the type of line separator. 141 * 142 * @param lineSeparatorParam The line separator to set 143 * @throws IllegalArgumentException If the specified line separator is not 144 * one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system' 145 */ 146 public void setLineSeparator(String lineSeparatorParam) { 147 lineSeparator = 148 Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim() 149 .toUpperCase(Locale.ENGLISH)); 150 } 151 152 /** 153 * Reads the file provided and checks line separators. 154 * @param file the file to be processed 155 * @throws IOException When an IO error occurred while reading from the 156 * file provided 157 */ 158 private void readAndCheckFile(File file) throws IOException { 159 // Cannot use lines as the line separators have been removed! 160 try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) { 161 if (!endsWithNewline(randomAccessFile)) { 162 log(1, MSG_KEY_NO_NEWLINE_EOF, file.getPath()); 163 } 164 } 165 } 166 167 /** 168 * Checks whether the content provided by the Reader ends with the platform 169 * specific line separator. 170 * @param randomAccessFile The reader for the content to check 171 * @return boolean Whether the content ends with a line separator 172 * @throws IOException When an IO error occurred while reading from the 173 * provided reader 174 */ 175 private boolean endsWithNewline(RandomAccessFile randomAccessFile) 176 throws IOException { 177 final boolean result; 178 final int len = lineSeparator.length(); 179 if (randomAccessFile.length() < len) { 180 result = false; 181 } 182 else { 183 randomAccessFile.seek(randomAccessFile.length() - len); 184 final byte[] lastBytes = new byte[len]; 185 final int readBytes = randomAccessFile.read(lastBytes); 186 if (readBytes != len) { 187 throw new IOException("Unable to read " + len + " bytes, got " 188 + readBytes); 189 } 190 result = lineSeparator.matches(lastBytes); 191 } 192 return result; 193 } 194 195}