View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 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.nio.file.Files;
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 file uses a specific line-ending sequence
34   * ({@code LF}, {@code CRLF}, or {@code CR}).
35   * </div>
36   *
37   * <p>
38   * A violation is reported for each line whose actual line ending does not match
39   * the configured one.
40   * </p>
41   *
42   * <p>
43   * Notes:
44   * Files containing mixed line endings may produce multiple violations,
45   * one for each non-matching line.
46   * </p>
47   *
48   * @since 13.3.0
49   */
50  @StatelessCheck
51  public class LineEndingCheck extends AbstractFileSetCheck {
52  
53      /**
54       * A key is pointing to the warning message text in "message.properties"
55       * file.
56       */
57      public static final String MSG_KEY_UNABLE_OPEN = "unable.open";
58  
59      /**
60       * A key is pointing to the warning message text in "message.properties"
61       * file.
62       */
63      public static final String MSG_KEY_WRONG_ENDING = "wrong.line.ending";
64  
65      /**
66       * Default line ending LF.
67       */
68      private LineEndingOption lineEnding = LineEndingOption.LF;
69  
70      /**
71       * Setter to set lineEnding.
72       *
73       * @param ending string of value LF or CRLF
74       * @since 13.3.0
75       */
76      public void setLineEnding(String ending) {
77          lineEnding = Enum.valueOf(LineEndingOption.class,
78                  ending.trim().toUpperCase(Locale.ENGLISH));
79      }
80  
81      @Override
82      protected void processFiltered(File file, FileText fileText) {
83          try {
84              final byte[] content = Files.readAllBytes(file.toPath());
85              checkLineEndings(lineEnding, content);
86          }
87          catch (final IOException ignore) {
88              log(0, MSG_KEY_UNABLE_OPEN, file.getPath());
89          }
90      }
91  
92      /**
93       * Checks that the file content uses the configured line ending
94       * and logs a violation for each line that does not match.
95       *
96       * @param expected the expected line ending
97       * @param content the file content as bytes
98       */
99      private void checkLineEndings(LineEndingOption expected, byte... content) {
100         int line = 1;
101 
102         for (int index = 0; index < content.length; index++) {
103             final byte current = content[index];
104             if (LineEndingOption.CR.matches(current)) {
105                 final boolean nextIsLf = index + 1 < content.length
106                         && LineEndingOption.LF.matches(content[index + 1]);
107                 final LineEndingOption actual;
108                 if (nextIsLf) {
109                     actual = LineEndingOption.CRLF;
110                 }
111                 else {
112                     actual = LineEndingOption.CR;
113                 }
114                 if (actual != expected) {
115                     logIncorrectLineEnding(line, actual);
116                 }
117                 line++;
118             }
119             else if (LineEndingOption.LF.matches(current)) {
120                 final boolean prevIsCr = index > 0
121                         && LineEndingOption.CR.matches(content[index - 1]);
122                 if (!prevIsCr) {
123                     final LineEndingOption actual = LineEndingOption.LF;
124                     if (actual != expected) {
125                         logIncorrectLineEnding(line, actual);
126                     }
127                     line++;
128                 }
129             }
130         }
131     }
132 
133     /**
134      * Logs an incorrect line ending detected at the given line.
135      *
136      * @param line the line number where the issue was found
137      * @param wrongLineEnding the detected incorrect line ending
138      */
139     private void logIncorrectLineEnding(int line,
140                                         LineEndingOption wrongLineEnding) {
141         log(line, MSG_KEY_WRONG_ENDING, lineEnding, wrongLineEnding);
142     }
143 
144 }