1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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 that there is a newline at the end of each file.
34   * </p>
35   * <p>
36   * An example of how to configure the check is:
37   * </p>
38   * <pre>
39   * &lt;module name="NewlineAtEndOfFile"/&gt;</pre>
40   * <p>
41   * This will check against the platform-specific default line separator.
42   * </p>
43   * <p>
44   * It is also possible to enforce the use of a specific line-separator across
45   * platforms, with the 'lineSeparator' property:
46   * </p>
47   * <pre>
48   * &lt;module name="NewlineAtEndOfFile"&gt;
49   *   &lt;property name="lineSeparator" value="lf"/&gt;
50   * &lt;/module&gt;</pre>
51   * <p>
52   * Valid values for the 'lineSeparator' property are 'system' (system default),
53   * 'crlf' (windows), 'cr' (mac), 'lf' (unix) and 'lf_cr_crlf' (lf, cr or crlf).
54   * </p>
55   *
56   */
57  @StatelessCheck
58  public class NewlineAtEndOfFileCheck
59      extends AbstractFileSetCheck {
60  
61      /**
62       * A key is pointing to the warning message text in "messages.properties"
63       * file.
64       */
65      public static final String MSG_KEY_UNABLE_OPEN = "unable.open";
66  
67      /**
68       * A key is pointing to the warning message text in "messages.properties"
69       * file.
70       */
71      public static final String MSG_KEY_NO_NEWLINE_EOF = "noNewlineAtEOF";
72  
73      /** The line separator to check against. */
74      private LineSeparatorOption lineSeparator = LineSeparatorOption.SYSTEM;
75  
76      @Override
77      protected void processFiltered(File file, FileText fileText) {
78          try {
79              readAndCheckFile(file);
80          }
81          catch (final IOException ignored) {
82              log(1, MSG_KEY_UNABLE_OPEN, file.getPath());
83          }
84      }
85  
86      /**
87       * Sets the line separator to one of 'crlf', 'lf','cr', 'lf_cr_crlf' or 'system'.
88       *
89       * @param lineSeparatorParam The line separator to set
90       * @throws IllegalArgumentException If the specified line separator is not
91       *         one of 'crlf', 'lf', 'cr', 'lf_cr_crlf' or 'system'
92       */
93      public void setLineSeparator(String lineSeparatorParam) {
94          lineSeparator =
95              Enum.valueOf(LineSeparatorOption.class, lineSeparatorParam.trim()
96                  .toUpperCase(Locale.ENGLISH));
97      }
98  
99      /**
100      * Reads the file provided and checks line separators.
101      * @param file the file to be processed
102      * @throws IOException When an IO error occurred while reading from the
103      *         file provided
104      */
105     private void readAndCheckFile(File file) throws IOException {
106         // Cannot use lines as the line separators have been removed!
107         try (RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r")) {
108             if (!endsWithNewline(randomAccessFile)) {
109                 log(1, MSG_KEY_NO_NEWLINE_EOF, file.getPath());
110             }
111         }
112     }
113 
114     /**
115      * Checks whether the content provided by the Reader ends with the platform
116      * specific line separator.
117      * @param randomAccessFile The reader for the content to check
118      * @return boolean Whether the content ends with a line separator
119      * @throws IOException When an IO error occurred while reading from the
120      *         provided reader
121      */
122     private boolean endsWithNewline(RandomAccessFile randomAccessFile)
123             throws IOException {
124         final boolean result;
125         final int len = lineSeparator.length();
126         if (randomAccessFile.length() < len) {
127             result = false;
128         }
129         else {
130             randomAccessFile.seek(randomAccessFile.length() - len);
131             final byte[] lastBytes = new byte[len];
132             final int readBytes = randomAccessFile.read(lastBytes);
133             if (readBytes != len) {
134                 throw new IOException("Unable to read " + len + " bytes, got "
135                         + readBytes);
136             }
137             result = lineSeparator.matches(lastBytes);
138         }
139         return result;
140     }
141 
142 }