001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.regexp; 021 022import java.util.regex.Matcher; 023 024import com.puppycrawl.tools.checkstyle.api.FileText; 025import com.puppycrawl.tools.checkstyle.api.LineColumn; 026 027/** 028 * A detector that matches across multiple lines. 029 */ 030class MultilineDetector { 031 032 /** 033 * A key is pointing to the warning message text in "messages.properties" 034 * file. 035 */ 036 public static final String MSG_REGEXP_EXCEEDED = "regexp.exceeded"; 037 038 /** 039 * A key is pointing to the warning message text in "messages.properties" 040 * file. 041 */ 042 public static final String MSG_REGEXP_MINIMUM = "regexp.minimum"; 043 044 /** 045 * A key is pointing to the warning message text in "messages.properties" 046 * file. 047 */ 048 public static final String MSG_EMPTY = "regexp.empty"; 049 /** 050 * A key is pointing to the warning message text in "messages.properties" 051 * file. 052 */ 053 public static final String MSG_STACKOVERFLOW = "regexp.StackOverflowError"; 054 055 /** The detection options to use. */ 056 private final DetectorOptions options; 057 /** Tracks the number of matches. */ 058 private int currentMatches; 059 /** The matcher. */ 060 private Matcher matcher; 061 /** The file text content. */ 062 private FileText text; 063 064 /** 065 * Creates an instance. 066 * 067 * @param options the options to use. 068 */ 069 /* package */ MultilineDetector(DetectorOptions options) { 070 this.options = options; 071 } 072 073 /** 074 * Processes an entire text file looking for matches. 075 * 076 * @param fileText the text to process 077 */ 078 public void processLines(FileText fileText) { 079 text = new FileText(fileText); 080 resetState(); 081 082 final String format = options.getFormat(); 083 if (format == null || format.isEmpty()) { 084 options.getReporter().log(1, MSG_EMPTY); 085 } 086 else { 087 matcher = options.getPattern().matcher(fileText.getFullText()); 088 findMatch(); 089 finish(); 090 } 091 } 092 093 /** Method that finds the matches. */ 094 private void findMatch() { 095 try { 096 boolean foundMatch = matcher.find(); 097 098 while (foundMatch) { 099 currentMatches++; 100 if (currentMatches > options.getMaximum()) { 101 final LineColumn start = text.lineColumn(matcher.start()); 102 if (options.getMessage().isEmpty()) { 103 options.getReporter().log(start.getLine(), 104 MSG_REGEXP_EXCEEDED, matcher.pattern().toString()); 105 } 106 else { 107 options.getReporter() 108 .log(start.getLine(), options.getMessage()); 109 } 110 } 111 foundMatch = matcher.find(); 112 } 113 } 114 // see http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6337993 et al. 115 catch (StackOverflowError ignored) { 116 // OK http://blog.igorminar.com/2008/05/catching-stackoverflowerror-and-bug-in.html 117 // http://programmers.stackexchange.com/questions/ 118 // 209099/is-it-ever-okay-to-catch-stackoverflowerror-in-java 119 options.getReporter().log(1, MSG_STACKOVERFLOW, matcher.pattern().toString()); 120 } 121 } 122 123 /** Perform processing at the end of a set of lines. */ 124 private void finish() { 125 if (currentMatches < options.getMinimum()) { 126 if (options.getMessage().isEmpty()) { 127 options.getReporter().log(1, MSG_REGEXP_MINIMUM, 128 options.getMinimum(), options.getFormat()); 129 } 130 else { 131 options.getReporter().log(1, options.getMessage()); 132 } 133 } 134 } 135 136 /** 137 * Reset the state of the detector. 138 */ 139 private void resetState() { 140 currentMatches = 0; 141 } 142 143}