View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 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 not empty 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   * <div class="wrapper"><pre class="prettyprint"><code class="language-text">
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   * </code></pre></div>
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   * Notes:
76   * This will check against the platform-specific default line separator.
77   * </p>
78   *
79   * <p>
80   * It is also possible to enforce the use of a specific line-separator across
81   * platforms, with the {@code lineSeparator} property.
82   * </p>
83   *
84   * @since 3.1
85   */
86  @StatelessCheck
87  public class NewlineAtEndOfFileCheck
88      extends AbstractFileSetCheck {
89  
90      /**
91       * A key is pointing to the warning message text in "messages.properties"
92       * file.
93       */
94      public static final String MSG_KEY_UNABLE_OPEN = "unable.open";
95  
96      /**
97       * A key is pointing to the warning message text in "messages.properties"
98       * file.
99       */
100     public static final String MSG_KEY_NO_NEWLINE_EOF = "noNewlineAtEOF";
101 
102     /**
103      * A key is pointing to the warning message text in "messages.properties"
104      * file.
105      */
106     public static final String MSG_KEY_WRONG_ENDING = "wrong.line.end";
107 
108     /** Specify the type of line separator. */
109     private LineSeparatorOption lineSeparator = LineSeparatorOption.LF_CR_CRLF;
110 
111     @Override
112     protected void processFiltered(File file, FileText fileText) {
113         try {
114             readAndCheckFile(file);
115         }
116         catch (final IOException ignored) {
117             log(1, MSG_KEY_UNABLE_OPEN, file.getPath());
118         }
119     }
120 
121     /**
122      * Setter to specify the type of line separator.
123      *
124      * @param lineSeparatorParam The line separator to set
125      * @throws IllegalArgumentException If the specified line separator is not
126      *         one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system'
127      * @since 3.1
128      */
129     public void setLineSeparator(String lineSeparatorParam) {
130         lineSeparator =
131             Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim()
132                 .toUpperCase(Locale.ENGLISH));
133     }
134 
135     /**
136      * Reads the file provided and checks line separators.
137      *
138      * @param file the file to be processed
139      * @throws IOException When an IO error occurred while reading from the
140      *         file provided
141      */
142     private void readAndCheckFile(File file) throws IOException {
143         // Cannot use lines as the line separators have been removed!
144         try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
145             if (lineSeparator == LineSeparatorOption.LF
146                     && endsWithNewline(randomAccessFile, LineSeparatorOption.CRLF)) {
147                 log(1, MSG_KEY_WRONG_ENDING);
148             }
149             else if (!endsWithNewline(randomAccessFile, lineSeparator)) {
150                 log(1, MSG_KEY_NO_NEWLINE_EOF);
151             }
152         }
153     }
154 
155     /**
156      * Checks whether the content provided by the Reader ends with the platform
157      * specific line separator.
158      *
159      * @param file The reader for the content to check
160      * @param separator The line separator
161      * @return boolean Whether the content ends with a line separator
162      * @throws IOException When an IO error occurred while reading from the
163      *         provided reader
164      */
165     private static boolean endsWithNewline(RandomAccessFile file, LineSeparatorOption separator)
166             throws IOException {
167         final boolean result;
168         final int len = separator.length();
169         if (file.length() == 0) {
170             result = true;
171         }
172         else if (file.length() < len) {
173             result = false;
174         }
175         else {
176             file.seek(file.length() - len);
177             final byte[] lastBytes = new byte[len];
178             final int readBytes = file.read(lastBytes);
179             if (readBytes != len) {
180                 throw new IOException("Unable to read " + len + " bytes, got "
181                         + readBytes);
182             }
183             result = separator.matches(lastBytes);
184         }
185         return result;
186     }
187 
188 }