View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks;
21  
22  import java.io.File;
23  import java.io.IOException;
24  import java.io.RandomAccessFile;
25  import java.util.Locale;
26  
27  import com.puppycrawl.tools.checkstyle.StatelessCheck;
28  import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
29  import com.puppycrawl.tools.checkstyle.api.FileText;
30  
31  /**
32   * <div>
33   * Checks whether files end with a line separator.
34   * </div>
35   *
36   * <p>
37   * Rationale: Any source files and text files in general should end with a line
38   * separator to let other easily add new content at the end of file and "diff"
39   * command does not show previous lines as changed.
40   * </p>
41   *
42   * <p>
43   * Example (the line with 'No newline at end of file' should not be in the diff):
44   * </p>
45   * <pre>
46   * &#64;&#64; -32,4 +32,5 &#64;&#64; ForbidWildcardAsReturnTypeCheck.returnTypeClassNamesIgnoreRegex
47   * PublicReferenceToPrivateTypeCheck.name = Public Reference To Private Type
48   *
49   * StaticMethodCandidateCheck.name = Static Method Candidate
50   * -StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static.
51   * \ No newline at end of file
52   * +StaticMethodCandidateCheck.desc = Checks whether private methods should be declared as static.
53   * +StaticMethodCandidateCheck.skippedMethods = Method names to skip during the check.
54   * </pre>
55   *
56   * <p>
57   * It can also trick the VCS to report the wrong owner for such lines.
58   * An engineer who has added nothing but a newline character becomes the last
59   * known author for the entire line. As a result, a mate can ask him a question
60   * to which he will not give the correct answer.
61   * </p>
62   *
63   * <p>
64   * Old Rationale: CVS source control management systems will even print
65   * a warning when it encounters a file that doesn't end with a line separator.
66   * </p>
67   *
68   * <p>
69   * Attention: property fileExtensions works with files that are passed by similar
70   * property for at <a href="https://checkstyle.org/config.html#Checker">Checker</a>.
71   * Please make sure required file extensions are mentioned at Checker's fileExtensions property.
72   * </p>
73   *
74   * <p>
75   * This will check against the platform-specific default line separator.
76   * </p>
77   *
78   * <p>
79   * It is also possible to enforce the use of a specific line-separator across
80   * platforms, with the {@code lineSeparator} property.
81   * </p>
82   * <ul>
83   * <li>
84   * Property {@code fileExtensions} - Specify the file extensions of the files to process.
85   * Type is {@code java.lang.String[]}.
86   * Default value is {@code ""}.
87   * </li>
88   * <li>
89   * Property {@code lineSeparator} - Specify the type of line separator.
90   * Type is {@code com.puppycrawl.tools.checkstyle.checks.LineSeparatorOption}.
91   * Default value is {@code lf_cr_crlf}.
92   * </li>
93   * </ul>
94   *
95   * <p>
96   * Parent is {@code com.puppycrawl.tools.checkstyle.Checker}
97   * </p>
98   *
99   * <p>
100  * Violation Message Keys:
101  * </p>
102  * <ul>
103  * <li>
104  * {@code noNewlineAtEOF}
105  * </li>
106  * <li>
107  * {@code unable.open}
108  * </li>
109  * <li>
110  * {@code wrong.line.end}
111  * </li>
112  * </ul>
113  *
114  * @since 3.1
115  */
116 @StatelessCheck
117 public class NewlineAtEndOfFileCheck
118     extends AbstractFileSetCheck {
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_UNABLE_OPEN = "unable.open";
125 
126     /**
127      * A key is pointing to the warning message text in "messages.properties"
128      * file.
129      */
130     public static final String MSG_KEY_NO_NEWLINE_EOF = "noNewlineAtEOF";
131 
132     /**
133      * A key is pointing to the warning message text in "messages.properties"
134      * file.
135      */
136     public static final String MSG_KEY_WRONG_ENDING = "wrong.line.end";
137 
138     /** Specify the type of line separator. */
139     private LineSeparatorOption lineSeparator = LineSeparatorOption.LF_CR_CRLF;
140 
141     @Override
142     protected void processFiltered(File file, FileText fileText) {
143         try {
144             readAndCheckFile(file);
145         }
146         catch (final IOException ignored) {
147             log(1, MSG_KEY_UNABLE_OPEN, file.getPath());
148         }
149     }
150 
151     /**
152      * Setter to specify the type of line separator.
153      *
154      * @param lineSeparatorParam The line separator to set
155      * @throws IllegalArgumentException If the specified line separator is not
156      *         one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system'
157      * @since 3.1
158      */
159     public void setLineSeparator(String lineSeparatorParam) {
160         lineSeparator =
161             Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim()
162                 .toUpperCase(Locale.ENGLISH));
163     }
164 
165     /**
166      * Reads the file provided and checks line separators.
167      *
168      * @param file the file to be processed
169      * @throws IOException When an IO error occurred while reading from the
170      *         file provided
171      */
172     private void readAndCheckFile(File file) throws IOException {
173         // Cannot use lines as the line separators have been removed!
174         try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
175             if (lineSeparator == LineSeparatorOption.LF
176                     && endsWithNewline(randomAccessFile, LineSeparatorOption.CRLF)) {
177                 log(1, MSG_KEY_WRONG_ENDING);
178             }
179             else if (!endsWithNewline(randomAccessFile, lineSeparator)) {
180                 log(1, MSG_KEY_NO_NEWLINE_EOF);
181             }
182         }
183     }
184 
185     /**
186      * Checks whether the content provided by the Reader ends with the platform
187      * specific line separator.
188      *
189      * @param file The reader for the content to check
190      * @param separator The line separator
191      * @return boolean Whether the content ends with a line separator
192      * @throws IOException When an IO error occurred while reading from the
193      *         provided reader
194      */
195     private static boolean endsWithNewline(RandomAccessFile file, LineSeparatorOption separator)
196             throws IOException {
197         final boolean result;
198         final int len = separator.length();
199         if (file.length() < len) {
200             result = false;
201         }
202         else {
203             file.seek(file.length() - len);
204             final byte[] lastBytes = new byte[len];
205             final int readBytes = file.read(lastBytes);
206             if (readBytes != len) {
207                 throw new IOException("Unable to read " + len + " bytes, got "
208                         + readBytes);
209             }
210             result = separator.matches(lastBytes);
211         }
212         return result;
213     }
214 
215 }