/*
* This file is part of TextScout.
*
* TextScout is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* TextScout is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with TextScout. If not, see <http://www.gnu.org/licenses/>.
*/
package logic;
import data.Line;
import data.LineMatch;
import data.SearchResult;
import data.properties.AppProperties;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import static logic.logger.Logger.*;
import static logic.logger.Logger.LogLevel.*;
import logic.matchers.Matcher;
import logic.matchers.MatcherFactory;
public class SearchEngine extends SearchObservable implements Runnable {
private final AppProperties props;
private Set<String> directories = null;
private Set<String> fileMasks = null;
private List<File> foundFiles = null;
private List<SearchResult> results = null;
private String pattern = "";
private String origPattern = "";
private boolean newFileSearchNeccessary = true;
private boolean running = false;
private Thread searchThread = null;
private Matcher fileMatcher = null;
private Matcher lineMatcher = null;
public SearchEngine(AppProperties props) {
this.props = props;
fileMasks = new HashSet<>();
directories = new HashSet<>();
foundFiles = new LinkedList<>();
results = new LinkedList<>();
}
public void setup(String pattern, String[] fileMask, String[] directories) {
setupMatchers();
this.pattern = pattern;
//Check if the file masks and directories are the same like before
//ToDo: Maybe check on a other way. Because if there are double entries in the array it won't work
this.newFileSearchNeccessary = false;
if (this.directories.size() != directories.length || fileMasks.size() != fileMask.length) {
newFileSearchNeccessary = true;
}
for (String str : directories) {
if (!this.directories.contains(str)) {
newFileSearchNeccessary = true;
break;
}
}
if (!newFileSearchNeccessary) {
for (String str : fileMask) {
if (!fileMasks.contains(str)) {
newFileSearchNeccessary = true;
break;
}
}
}
if (newFileSearchNeccessary) {
this.directories.clear();
this.fileMasks.clear();
fileMasks.addAll(Arrays.asList(fileMask));
this.directories.addAll(Arrays.asList(directories));
if (this.fileMasks.size() == 0) {
fileMasks.add("");
}
}// if (newFileSearchNecessary)
}//setup()
private void setupMatchers() {
MatcherFactory factory = new MatcherFactory();
lineMatcher = factory.createLineMatcher(props.getSearchProperties().isSearchMaskRegEx());
fileMatcher = factory.createFileMatcher(props.getSearchProperties().isFileMaskRegEx());
}
public void process() {
if (searchThread == null) {
searchThread = new Thread(this, "TextScout-Search");
}
running = true;
searchThread.start();
}
public void stop() {
running = false;
}
public boolean isRunning() {
return running && searchThread != null;
}
private void findFiles() {
File currentDir = null;
this.foundFiles.clear();
for (String directory : directories) {
if (!running) {
break;
}
currentDir = new File(directory);
if (!currentDir.exists() || !currentDir.isDirectory()) {
continue;
}
findFilesRecursiv(currentDir);
}
}
private void findFilesRecursiv(File parent) {
if (!running) {
return;
}
enteredDirectory(parent.getAbsolutePath());
for (File child : parent.listFiles()) {
if (child.isDirectory()) {
findFilesRecursiv(child);
} else if (isFileMatchingAMask(child)) {
foundFiles.add(child);
}
}
}
private boolean isFileMatchingAMask(File file) {
for (String mask : fileMasks) {
if (fileMatcher.match(mask, file.getName())) {
return true;
}
}
return false;
}
private void findMatches() {
SearchResult result = null;
int matchCount = 0;
results.clear();
for (File f : foundFiles) {
if (!running) {
break;
}
enteredDirectory(f.getAbsolutePath());
result = new SearchResult(f);
matchFile(f, result);
if (result.getMatchCount() > 0) {
results.add(result);
matchCount += result.getMatchCount();
foundMatch(result, matchCount, results.size());
}
}
}
private void matchFile(File found, SearchResult result) {
int lineTolerance = props.getSearchProperties().getLineTolerance();
List<String> lineBuffer = new ArrayList<String>();
List<Line> linesToCheck = new ArrayList<>();
String line = "";
List<Integer> singleRowMatches = new ArrayList<Integer>();
int a = 0;
int b = 0;
readFile(found, lineBuffer);
findSingleLineMatches(lineBuffer, singleRowMatches);
for (int i = 0; i < lineBuffer.size(); i++) {
for (Integer singleRow : singleRowMatches) {
if (i >= singleRow - lineTolerance && i <= singleRow + lineTolerance) {
i = singleRow;
break;
}
}
a = Math.max(0, i - lineTolerance);
b = Math.min(lineBuffer.size() - 1, i + lineTolerance);
line = "";
linesToCheck.clear();
for (int j = a; j <= b; j++) {
linesToCheck.add(new Line(lineBuffer.get(j), j + 1)); //j+1 represents the line number
line += lineBuffer.get(j) + "\f";
}
if (lineMatcher.match(pattern, line)) {
LineMatch lineMatch = new LineMatch(pattern);
addDisplayWindowLines(lineBuffer, linesToCheck);
for (Line l : linesToCheck) {
lineMatch.addLine(l);
}
result.addLineMatch(lineMatch);
i = b + (lineTolerance + 1);
}
}
}
private void addDisplayWindowLines(List<String> lineBuffer, List<Line> lines) {
Line firstLine = null;
Line lastLine = null;
int a = 0;
int b = 0;
int displayWindow = props.getDisplayProperties().getDisplayWindow();
if (lineBuffer == null || lines == null) {
throw new IllegalArgumentException("One ore more parameters are null!");
}
if (displayWindow == 0 || lines.size() == 0) {
return;
}
firstLine = lines.get(0);
lastLine = lines.get(lines.size() - 1);
a = Math.max(0, firstLine.getLineNumber() - 1 - displayWindow);
b = firstLine.getLineNumber() - 1;
for (int i = b - 1; i >= a; i--) {
lines.add(0, new Line(lineBuffer.get(i), i + 1));
}
a = lastLine.getLineNumber() - 1;
b = Math.min(lineBuffer.size() - 1, lastLine.getLineNumber() - 1 + displayWindow);
for (int i = a + 1; i <= b; i++) {
lines.add(new Line(lineBuffer.get(i), i + 1));
}
}
private void findSingleLineMatches(List<String> lines, List<Integer> singleMatches) {
for (int i = 0; i < lines.size(); i++) {
if (lineMatcher.match(pattern, lines.get(i))) {
singleMatches.add(i);
}
}
}
private void readFile(File found, List<String> lines) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(found)));
while (reader.ready()) {
lines.add(reader.readLine());
}
reader.close();
} catch (Exception ex) {
log(ERROR, ex); //ToDo
}
}
@Override
public void run() {
try {
started();
if (newFileSearchNeccessary) {
findFiles();
}
findMatches();
} catch (Exception ex) {
log(ERROR, ex);
} finally {
running = false;
searchThread = null;
finished();
}
}
}