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