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