/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.addthis.hydra.job.spawn.search;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.sun.istack.Nullable;
/**
* Represents a single group of contiguous lines from one section of one 'file', which in itself may contain many
* matches.
*/
public class AdjacentMatchesBlock {
private static final int BUFFER_LINE_COUNT = 3;
@JsonIgnore
private final String[] allLines;
@JsonProperty
private final ArrayList<TextLocation> matches;
@JsonIgnore
private int lastMatchedLine;
@JsonIgnore
private int firstMatchedLine;
private AdjacentMatchesBlock(String[] allLines) {
this.matches = new ArrayList<>();
this.allLines = allLines;
this.firstMatchedLine = Integer.MAX_VALUE;
this.lastMatchedLine = Integer.MIN_VALUE;
}
/**
* Creates a list of {@link AdjacentMatchesBlock} from a list of {@link TextLocation}
*
* @param lines the entire content of the file where matches are contained
* @param matches the list of matches in the line
* @return the {@link AdjacentMatchesBlock} which contain every {@link TextLocation} provided
*/
public static List<AdjacentMatchesBlock> mergeMatchList(String[] lines, Collection<TextLocation> matches) {
List<TextLocation> sortedMatches = new ArrayList<>(matches);
Collections.sort(sortedMatches);
Iterator<TextLocation> it = sortedMatches.iterator();
List<AdjacentMatchesBlock> results = new ArrayList<>();
AdjacentMatchesBlock result = new AdjacentMatchesBlock(lines);
TextLocation match = null;
TextLocation prev = null;
while (it.hasNext()) {
prev = match;
match = it.next();
// ignore current match if it's duplicate of the previous one. Note that the matches are sorted by
// starting poisition, so for any duplicate, previous match sholud fully contain the current one.
if (prev != null && prev.fullyContains(match)) {
continue;
}
if (result.canAddMatchAtLine(match.lineNum)) {
result.addMatch(match);
} else {
results.add(result);
result = new AdjacentMatchesBlock(lines);
result.addMatch(match);
}
}
if (result.hasAnyMatches()) {
results.add(result);
}
return results;
}
@JsonProperty("startLine")
public int getStartLine() {
return Math.max(firstMatchedLine - BUFFER_LINE_COUNT, 0);
}
@Nullable
@JsonProperty("contextLines")
public String[] getContextLines() {
if (matches.size() == 0) {
return null;
}
final int start = getStartLine();
final int end = Math.min(lastMatchedLine + BUFFER_LINE_COUNT, allLines.length);
return Arrays.copyOfRange(allLines, start, end);
}
private void addMatch(TextLocation match) {
firstMatchedLine = Math.min(match.lineNum, firstMatchedLine);
lastMatchedLine = Math.max(match.lineNum, lastMatchedLine);
matches.add(match);
}
public boolean hasAnyMatches() {
return matches.size() > 0;
}
private boolean canAddMatchAtLine(int lineNum) {
final boolean startsAfter = lineNum > firstMatchedLine - BUFFER_LINE_COUNT;
final boolean endsBefore = lineNum < lastMatchedLine + BUFFER_LINE_COUNT;
return !hasAnyMatches() || (startsAfter && endsBefore);
}
}