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.header;
21  
22  import java.io.File;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.List;
26  import java.util.regex.Pattern;
27  import java.util.regex.PatternSyntaxException;
28  
29  import com.puppycrawl.tools.checkstyle.StatelessCheck;
30  import com.puppycrawl.tools.checkstyle.api.FileText;
31  import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
32  
33  /**
34   * Checks the header of the source against a header file that contains a
35   * {@link Pattern regular expression}
36   * for each line of the source header. In default configuration,
37   * if header is not specified, the default value of header is set to null
38   * and the check does not rise any violations.
39   *
40   * @author Lars K├╝hne
41   * @author o_sukhodolsky
42   */
43  @StatelessCheck
44  public class RegexpHeaderCheck extends AbstractHeaderCheck {
45  
46      /**
47       * A key is pointing to the warning message text in "messages.properties"
48       * file.
49       */
50      public static final String MSG_HEADER_MISSING = "header.missing";
51  
52      /**
53       * A key is pointing to the warning message text in "messages.properties"
54       * file.
55       */
56      public static final String MSG_HEADER_MISMATCH = "header.mismatch";
57  
58      /** Empty array to avoid instantiations. */
59      private static final int[] EMPTY_INT_ARRAY = new int[0];
60  
61      /** The compiled regular expressions. */
62      private final List<Pattern> headerRegexps = new ArrayList<>();
63  
64      /** The header lines to repeat (0 or more) in the check, sorted. */
65      private int[] multiLines = EMPTY_INT_ARRAY;
66  
67      /**
68       * Set the lines numbers to repeat in the header check.
69       * @param list comma separated list of line numbers to repeat in header.
70       */
71      public void setMultiLines(int... list) {
72          if (list.length == 0) {
73              multiLines = EMPTY_INT_ARRAY;
74          }
75          else {
76              multiLines = new int[list.length];
77              System.arraycopy(list, 0, multiLines, 0, list.length);
78              Arrays.sort(multiLines);
79          }
80      }
81  
82      @Override
83      protected void processFiltered(File file, FileText fileText) {
84          final int headerSize = getHeaderLines().size();
85          final int fileSize = fileText.size();
86  
87          if (headerSize - multiLines.length > fileSize) {
88              log(1, MSG_HEADER_MISSING);
89          }
90          else {
91              int headerLineNo = 0;
92              int index;
93              for (index = 0; headerLineNo < headerSize && index < fileSize; index++) {
94                  final String line = fileText.get(index);
95                  boolean isMatch = isMatch(line, headerLineNo);
96                  while (!isMatch && isMultiLine(headerLineNo)) {
97                      headerLineNo++;
98                      isMatch = headerLineNo == headerSize
99                              || isMatch(line, headerLineNo);
100                 }
101                 if (!isMatch) {
102                     log(index + 1, MSG_HEADER_MISMATCH, getHeaderLines().get(
103                             headerLineNo));
104                     break;
105                 }
106                 if (!isMultiLine(headerLineNo)) {
107                     headerLineNo++;
108                 }
109             }
110             if (index == fileSize) {
111                 // if file finished, but we have at least one non-multi-line
112                 // header isn't completed
113                 logFirstSinglelineLine(headerLineNo, headerSize);
114             }
115         }
116     }
117 
118     /**
119      * Logs warning if any non-multiline lines left in header regexp.
120      * @param startHeaderLine header line number to start from
121      * @param headerSize whole header size
122      */
123     private void logFirstSinglelineLine(int startHeaderLine, int headerSize) {
124         for (int lineNum = startHeaderLine; lineNum < headerSize; lineNum++) {
125             if (!isMultiLine(lineNum)) {
126                 log(1, MSG_HEADER_MISSING);
127                 break;
128             }
129         }
130     }
131 
132     /**
133      * Checks if a code line matches the required header line.
134      * @param line the code line
135      * @param headerLineNo the header line number.
136      * @return true if and only if the line matches the required header line.
137      */
138     private boolean isMatch(String line, int headerLineNo) {
139         return headerRegexps.get(headerLineNo).matcher(line).find();
140     }
141 
142     /**
143      * Returns true if line is multiline header lines or false.
144      * @param lineNo a line number
145      * @return if {@code lineNo} is one of the repeat header lines.
146      */
147     private boolean isMultiLine(int lineNo) {
148         return Arrays.binarySearch(multiLines, lineNo + 1) >= 0;
149     }
150 
151     @Override
152     protected void postProcessHeaderLines() {
153         final List<String> headerLines = getHeaderLines();
154         for (String line : headerLines) {
155             try {
156                 headerRegexps.add(Pattern.compile(line));
157             }
158             catch (final PatternSyntaxException ex) {
159                 throw new IllegalArgumentException("line "
160                         + (headerRegexps.size() + 1)
161                         + " in header specification"
162                         + " is not a regular expression", ex);
163             }
164         }
165     }
166 
167     /**
168      * Validates the {@code header} by compiling it with
169      * {@link Pattern#compile(String) } and throws
170      * {@link IllegalArgumentException} if {@code header} isn't a valid pattern.
171      * @param header the header value to validate and set (in that order)
172      */
173     @Override
174     public void setHeader(String header) {
175         if (!CommonUtils.isBlank(header)) {
176             if (!CommonUtils.isPatternValid(header)) {
177                 throw new IllegalArgumentException("Unable to parse format: " + header);
178             }
179             super.setHeader(header);
180         }
181     }
182 
183 }