001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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 */
030public class MultilineDetector {
031
032    /** The detection options to use. */
033    private final DetectorOptions options;
034    /** The message key for exceeded matches. */
035    private final String exceededMessage;
036    /** The message key for minimum matches not met. */
037    private final String minimumMessage;
038    /** The message key for empty format. */
039    private final String emptyMessage;
040    /** The message key for StackOverflow error. */
041    private final String stackOverflowMessage;
042    /** Tracks the number of matches. */
043    private int currentMatches;
044    /** The matcher. */
045    private Matcher matcher;
046    /** The file text content. */
047    private FileText text;
048
049    /**
050     * Creates an instance.
051     *
052     * @param options the options to use.
053     * @param exceededMessage the message key for exceeded matches.
054     * @param minimumMessage the message key for minimum matches not met.
055     * @param emptyMessage the message key for empty format.
056     * @param stackOverflowMessage the message key for StackOverflow error.
057     */
058    /* package */ MultilineDetector(DetectorOptions options,
059            String exceededMessage, String minimumMessage,
060            String emptyMessage, String stackOverflowMessage) {
061        this.options = options;
062        this.exceededMessage = exceededMessage;
063        this.minimumMessage = minimumMessage;
064        this.emptyMessage = emptyMessage;
065        this.stackOverflowMessage = stackOverflowMessage;
066    }
067
068    /**
069     * Processes an entire text file looking for matches.
070     *
071     * @param fileText the text to process
072     */
073    public void processLines(FileText fileText) {
074        text = new FileText(fileText);
075        resetState();
076
077        final String format = options.getFormat();
078        if (format == null || format.isEmpty()) {
079            options.getReporter().log(1, emptyMessage);
080        }
081        else {
082            matcher = options.getPattern().matcher(fileText.getFullText());
083            findMatch();
084            finish();
085        }
086    }
087
088    /** Method that finds the matches. */
089    private void findMatch() {
090        try {
091            boolean foundMatch = matcher.find();
092
093            while (foundMatch) {
094                currentMatches++;
095                if (currentMatches > options.getMaximum()) {
096                    final LineColumn start = text.lineColumn(matcher.start());
097                    if (options.getMessage().isEmpty()) {
098                        options.getReporter().log(start.getLine(),
099                                exceededMessage,
100                                        matcher.pattern().toString());
101                    }
102                    else {
103                        options.getReporter()
104                                .log(start.getLine(), options.getMessage());
105                    }
106                }
107                foundMatch = matcher.find();
108            }
109        }
110        // see http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6337993 et al.
111        catch (StackOverflowError ignored) {
112            // ok http://blog.igorminar.com/2008/05/catching-stackoverflowerror-and-bug-in.html
113            // http://programmers.stackexchange.com/questions/
114            //        209099/is-it-ever-okay-to-catch-stackoverflowerror-in-java
115            options.getReporter().log(1, stackOverflowMessage,
116                        matcher.pattern().toString());
117        }
118    }
119
120    /** Perform processing at the end of a set of lines. */
121    private void finish() {
122        if (currentMatches < options.getMinimum()) {
123            if (options.getMessage().isEmpty()) {
124                options.getReporter().log(1, minimumMessage,
125                        options.getMinimum(), options.getFormat());
126            }
127            else {
128                options.getReporter().log(1, options.getMessage());
129            }
130        }
131    }
132
133    /**
134     * Reset the state of the detector.
135     */
136    private void resetState() {
137        currentMatches = 0;
138    }
139
140}