package strip.javadoctrim;
import com.github.javaparser.Position;
import com.github.javaparser.Range;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.comments.JavadocComment;
import com.github.javaparser.ast.visitor.ModifierVisitorAdapter;
import one.util.streamex.StreamEx;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JavadocToTrimScanner extends ModifierVisitorAdapter<Void> {
public final List<Range> ranges = new ArrayList<>();
private final Consumer<String> logger;
public JavadocToTrimScanner(Consumer<String> logger) {
this.logger = logger;
}
@Override
public Node visit(JavadocComment n, Void arg) {
String[] javadocLines = n.getContent().split("\n");
List<String> cleanedUpLines = Arrays.stream(javadocLines).map(line -> line.replaceFirst("^\\s*\\*", "").trim()).collect(Collectors.toList());
if (allLinesEmpty(cleanedUpLines)) {
ranges.add(n.getRange());
return n;
}
int emptyLinesAtStartOfJavaDoc = numberOfEmptyLineAtStart(cleanedUpLines.stream());
if (emptyLinesAtStartOfJavaDoc > 1) {
int startLine = n.getRange().begin.line + 1; //keep the first line
int endLine = n.getRange().begin.line + emptyLinesAtStartOfJavaDoc - 1;
int lastLineLengthIncludingNewLine = javadocLines[emptyLinesAtStartOfJavaDoc - 1].length() + 1;
ranges.add(new Range(new Position(startLine, 1), new Position(endLine, lastLineLengthIncludingNewLine)));
}
ConsecutiveEmptyLineBuilder builder = new ConsecutiveEmptyLineBuilder(javadocLines, n.getRange().begin.line);
for (int currentLine = 0; currentLine < cleanedUpLines.size(); currentLine++) {
if (cleanedUpLines.get(currentLine).isEmpty()) {
builder.emptyLineAt(currentLine);
} else {
builder.maybeCompressableRange().ifPresent(ranges::add);
builder.reset();
}
}
int emptyLinesAtEndOfJavaDoc = numberOfEmptyLineAtStart(copyAndReverse(cleanedUpLines).stream());
if (emptyLinesAtEndOfJavaDoc > 1) {
int startLine = n.getRange().end.line - emptyLinesAtEndOfJavaDoc + 1;
int endLine = n.getRange().end.line - 1;
int lastLineLengthIncludingNewLine = javadocLines[javadocLines.length - emptyLinesAtEndOfJavaDoc].length() + 1;
ranges.add(new Range(new Position(startLine, 1), new Position(endLine, lastLineLengthIncludingNewLine)));
}
return n;
}
public static class ConsecutiveEmptyLineBuilder {
private final String[] javadocLines;
private int offset;
private int firstEmptyLine;
private int lastEmptyLine;
public ConsecutiveEmptyLineBuilder(String[] javadocLines, int offset) {
this.javadocLines = javadocLines;
this.offset = offset;
reset();
}
public ConsecutiveEmptyLineBuilder emptyLineAt(int line) {
if (firstEmptyLine == -1) {
firstEmptyLine = line;
}
lastEmptyLine = line;
return this;
}
public void reset() {
firstEmptyLine = -1;
lastEmptyLine = javadocLines.length;
}
public Optional<Range> maybeCompressableRange() {
if (firstEmptyLine == -1 || firstEmptyLine == 0 || lastEmptyLine == javadocLines.length) {
return Optional.empty();
}
if (lastEmptyLine - firstEmptyLine < 1) {
return Optional.empty();
}
Position begin = new Position(offset + firstEmptyLine, 0);
int keepLastEmptyLine = lastEmptyLine - 1;
Position end = new Position(offset + keepLastEmptyLine, javadocLines[keepLastEmptyLine].length() + 1);
return Optional.of(new Range(begin, end));
}
}
private List<String> copyAndReverse(List<String> cleanedUpLines) {
List<String> reversed = new ArrayList<>(cleanedUpLines);
Collections.reverse(reversed);
return reversed;
}
private int numberOfEmptyLineAtStart(Stream<String> lines) {
return (int) StreamEx.of(lines).map(String::isEmpty).takeWhile(isEmpty -> isEmpty).count();
}
private Boolean allLinesEmpty(List<String> cleanedUpLines) {
return cleanedUpLines.stream().map(String::isEmpty).reduce(Boolean.TRUE, (aBoolean, aBoolean2) -> aBoolean && aBoolean2);
}
}