001    /*
002     * SonarQube, open source software quality management tool.
003     * Copyright (C) 2008-2014 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * SonarQube is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * SonarQube is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public License
017     * along with this program; if not, write to the Free Software Foundation,
018     * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
019     */
020    
021    package org.sonar.batch.issue.ignore.scanner;
022    
023    import org.sonar.batch.issue.ignore.pattern.IssueExclusionPatternInitializer;
024    import org.sonar.batch.issue.ignore.pattern.IssuePattern;
025    import org.sonar.batch.issue.ignore.pattern.LineRange;
026    
027    import com.google.common.collect.Lists;
028    import com.google.common.collect.Sets;
029    import org.apache.commons.io.FileUtils;
030    import org.apache.commons.lang.StringUtils;
031    import org.slf4j.Logger;
032    import org.slf4j.LoggerFactory;
033    import org.sonar.api.BatchExtension;
034    
035    import java.io.File;
036    import java.io.IOException;
037    import java.nio.charset.Charset;
038    import java.util.List;
039    import java.util.Set;
040    
041    public class IssueExclusionsRegexpScanner implements BatchExtension {
042    
043      private static final Logger LOG = LoggerFactory.getLogger(IssueExclusionsRegexpScanner.class);
044    
045      private IssueExclusionPatternInitializer exclusionPatternInitializer;
046      private List<java.util.regex.Pattern> allFilePatterns;
047      private List<DoubleRegexpMatcher> blockMatchers;
048    
049      // fields to be reset at every new scan
050      private DoubleRegexpMatcher currentMatcher;
051      private int fileLength;
052      private List<LineExclusion> lineExclusions;
053      private LineExclusion currentLineExclusion;
054    
055      public IssueExclusionsRegexpScanner(IssueExclusionPatternInitializer patternsInitializer) {
056        this.exclusionPatternInitializer = patternsInitializer;
057    
058        lineExclusions = Lists.newArrayList();
059        allFilePatterns = Lists.newArrayList();
060        blockMatchers = Lists.newArrayList();
061    
062        for (IssuePattern pattern : patternsInitializer.getAllFilePatterns()) {
063          allFilePatterns.add(java.util.regex.Pattern.compile(pattern.getAllFileRegexp()));
064        }
065        for (IssuePattern pattern : patternsInitializer.getBlockPatterns()) {
066          blockMatchers.add(new DoubleRegexpMatcher(
067              java.util.regex.Pattern.compile(pattern.getBeginBlockRegexp()),
068              java.util.regex.Pattern.compile(pattern.getEndBlockRegexp())));
069        }
070    
071        init();
072      }
073    
074      private void init() {
075        currentMatcher = null;
076        fileLength = 0;
077        lineExclusions.clear();
078        currentLineExclusion = null;
079      }
080    
081      public void scan(String resource, File file, Charset sourcesEncoding) throws IOException {
082        LOG.debug("Scanning {}", resource);
083        init();
084    
085        List<String> lines = FileUtils.readLines(file, sourcesEncoding.name());
086        int lineIndex = 0;
087        for (String line : lines) {
088          lineIndex++;
089          if (line.trim().length() == 0) {
090            continue;
091          }
092    
093          // first check the single regexp patterns that can be used to totally exclude a file
094          for (java.util.regex.Pattern pattern : allFilePatterns) {
095            if (pattern.matcher(line).find()) {
096              exclusionPatternInitializer.getPatternMatcher().addPatternToExcludeResource(resource);
097              // nothing more to do on this file
098              LOG.debug("- Exclusion pattern '{}': every violation in this file will be ignored.", pattern);
099              return;
100            }
101          }
102    
103          // then check the double regexps if we're still here
104          checkDoubleRegexps(line, lineIndex);
105        }
106    
107        if (currentMatcher != null && !currentMatcher.hasSecondPattern()) {
108          // this will happen when there is a start block regexp but no end block regexp
109          endExclusion(lineIndex + 1);
110        }
111    
112        // now create the new line-based pattern for this file if there are exclusions
113        fileLength = lineIndex;
114        if (!lineExclusions.isEmpty()) {
115          Set<LineRange> lineRanges = convertLineExclusionsToLineRanges();
116          LOG.debug("- Line exclusions found: {}", lineRanges);
117          exclusionPatternInitializer.getPatternMatcher().addPatternToExcludeLines(resource, lineRanges);
118        }
119      }
120    
121      private Set<LineRange> convertLineExclusionsToLineRanges() {
122        Set<LineRange> lineRanges = Sets.newHashSet();
123        for (LineExclusion lineExclusion : lineExclusions) {
124          lineRanges.add(lineExclusion.toLineRange());
125        }
126        return lineRanges;
127      }
128    
129      private void checkDoubleRegexps(String line, int lineIndex) {
130        if (currentMatcher == null) {
131          for (DoubleRegexpMatcher matcher : blockMatchers) {
132            if (matcher.matchesFirstPattern(line)) {
133              startExclusion(lineIndex);
134              currentMatcher = matcher;
135              break;
136            }
137          }
138        } else {
139          if (currentMatcher.matchesSecondPattern(line)) {
140            endExclusion(lineIndex);
141            currentMatcher = null;
142          }
143        }
144      }
145    
146      private void startExclusion(int lineIndex) {
147        currentLineExclusion = new LineExclusion(lineIndex);
148        lineExclusions.add(currentLineExclusion);
149      }
150    
151      private void endExclusion(int lineIndex) {
152        currentLineExclusion.setEnd(lineIndex);
153        currentLineExclusion = null;
154      }
155    
156      private class LineExclusion {
157    
158        private int start;
159        private int end;
160    
161        LineExclusion(int start) {
162          this.start = start;
163          this.end = -1;
164        }
165    
166        void setEnd(int end) {
167          this.end = end;
168        }
169    
170        public LineRange toLineRange() {
171          return new LineRange(start, end == -1 ? fileLength : end);
172        }
173    
174      }
175    
176      private static class DoubleRegexpMatcher {
177    
178        private java.util.regex.Pattern firstPattern;
179        private java.util.regex.Pattern secondPattern;
180    
181        DoubleRegexpMatcher(java.util.regex.Pattern firstPattern, java.util.regex.Pattern secondPattern) {
182          this.firstPattern = firstPattern;
183          this.secondPattern = secondPattern;
184        }
185    
186        boolean matchesFirstPattern(String line) {
187          return firstPattern.matcher(line).find();
188        }
189    
190        boolean matchesSecondPattern(String line) {
191          return hasSecondPattern() && secondPattern.matcher(line).find();
192        }
193    
194        boolean hasSecondPattern() {
195          return StringUtils.isNotEmpty(secondPattern.toString());
196        }
197      }
198    
199    }