/**
* Copyright 2015 Fabrizio Iannetti.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*/
package com.github.fabeclipse.textedgrep;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.github.fabeclipse.textedgrep.internal.core.GrepContext;
/**
* Class that encapsulate a grep operation using a given regular
* expression.
*
* Calling {@link #grep(IGrepTarget, boolean)} yelds a result with
* the filtered text and information on the source range for each line.
*
* @since 2.0
*/
public class GrepTool {
private static final int INITIAL_LINE_BUFFER_COUNT = 10000;
private String[] regexList;
private final boolean caseSensitive;
public GrepTool(String regex, boolean caseSensitive) {
this(new String[] {regex }, caseSensitive);
}
public GrepTool(String[] regexList, boolean caseSensitive) {
super();
this.regexList = regexList;
this.caseSensitive = caseSensitive;
}
/**
* Prepare for a grep operation.
*
* The function returns a context that can be used
* to perform the grep later.
*
* @since 3.0
*/
public IGrepContext grepStart(IGrepTarget target) {
GrepContext grepContext = new GrepContext(target);
return grepContext;
}
/**
* @since 3.0
*/
public IGrepContext grep(IGrepTarget target, boolean multiple) {
GrepContext grepContext = new GrepContext(target);
grep(grepContext, new GrepMonitor(), multiple);
return grepContext;
}
/**
*
* @throws InterruptedException
* @since 3.0
*/
public IGrepContext grep(IGrepContext gc, GrepMonitor monitor, boolean multiple) {
// check the grep context is of the right concrete type
if (!(gc instanceof GrepContext)) {
throw new IllegalArgumentException("Illegal Grep context implementation");
}
GrepContext grepContext = (GrepContext) gc;
IGrepTarget target = grepContext.getTarget();
// start with 10 thousands lines (in the grep result)
int[] lineMap = new int[INITIAL_LINE_BUFFER_COUNT];
int[] matchMap = new int[lineMap.length];
int[] colorMap = new int[lineMap.length];
int[] matchBegin = new int[INITIAL_LINE_BUFFER_COUNT];
int[] matchEnd = new int[matchBegin.length];
final Matcher[] matchers = new Matcher[regexList.length];
int matchCount = 0;
for (String rx : regexList)
matchers[matchCount++] = Pattern.compile(rx, caseSensitive ? 0 : Pattern.CASE_INSENSITIVE).matcher("");
int lineNum = 0;
int grepLineNum = 0;
int submittedGrepLineNum = 0;
int matchNum = 0;
int submittedMatchNum = 0;
int progressPercent = 0;
target.start();
monitor.fireProgress(progressPercent);
long readChars = 0;
final long targetLen = target.getLength();
while(target.hasNextLine() && !monitor.isCanceled()) {
String line = target.nextLine();
boolean found = false;
// run each matcher on the line, the first one wins, i.e.
// one line can't be matched by more than one matcher
for (int m = 0 ; m < matchCount && !found; m++) {
final Matcher matcher = matchers[m];
matcher.reset(line);
// look for matches on this line
while (matcher.find()) {
if (!found) {
// update grep line array/count only at first match
found = true;
if ((grepLineNum - submittedGrepLineNum) >= lineMap.length) {
// add full chunks to the grep context
grepContext.addLineChunks(lineMap, matchMap, colorMap);
monitor.fireChange(grepContext);
// allocate new chunks
lineMap = new int[lineMap.length];
matchMap = new int[lineMap.length];
colorMap = new int[lineMap.length];
submittedGrepLineNum += lineMap.length;
}
matchMap[grepLineNum - submittedGrepLineNum] = matchNum;
colorMap[grepLineNum - submittedGrepLineNum] = m;
lineMap[grepLineNum - submittedGrepLineNum] = lineNum;
grepLineNum++;
// add line to the grep text (only once!)
grepContext.addLine(line);
}
if ((matchNum - submittedMatchNum) >= matchBegin.length) {
grepContext.addMatchChunks(matchBegin, matchEnd);
matchBegin = new int[matchBegin.length];
matchEnd = new int[matchEnd.length];
submittedMatchNum += matchBegin.length;
}
matchBegin[matchNum - submittedMatchNum] = matcher.start();
matchEnd[matchNum - submittedMatchNum] = matcher.end();
matchNum++;
// exit while loop if multiple matches on the same line are not required
if (!multiple) break;
}
}
lineNum++;
// update progress
readChars += line.length() + 1; // assuming 1 char for line terminators
int newProgress = (int) (readChars * 100 / targetLen);
if (newProgress > progressPercent) {
// align with the actual position in grep target
// which might be different if the document has two chars
// as line terminator (windows)
readChars = target.getLineOffset(lineNum);
progressPercent = (int) (readChars * 100 / targetLen);
monitor.fireProgress(progressPercent);
}
}
if (grepLineNum - submittedGrepLineNum > 0) {
grepContext.addLineChunks(lineMap, matchMap, colorMap, grepLineNum - submittedGrepLineNum);
}
if (matchNum - submittedMatchNum > 0) {
grepContext.addMatchChunks(matchBegin, matchEnd, matchNum - submittedMatchNum);
}
target.stop();
grepContext.seal();
if (progressPercent < 100) {
monitor.fireProgress(100);
}
return grepContext;
}
}