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 }