ru.vyarus.yaml.updater.update.CommentsParserValidator Maven / Gradle / Ivy
Show all versions of yaml-config-updater Show documentation
package ru.vyarus.yaml.updater.update;
import ru.vyarus.yaml.updater.parse.comments.model.CmtNode;
import ru.vyarus.yaml.updater.parse.comments.model.CmtTree;
import ru.vyarus.yaml.updater.parse.common.TreeStringUtils;
import ru.vyarus.yaml.updater.parse.common.model.TreeNode;
import ru.vyarus.yaml.updater.parse.common.model.YamlLine;
import ru.vyarus.yaml.updater.parse.struct.model.StructNode;
import ru.vyarus.yaml.updater.parse.struct.model.StructTree;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Comments parse is a very simple yaml parser and to make sure it works correctly comparing parsed structure with
* snakeyaml parse result (parsed trees are unified to represent equal trees).
*
* Comments parser and snakeyaml models are unified to get equally trees (easier for comparison).
*
* During comparison, property values parsed by snakeyaml are assigned in comments tree. This is required for
* list items matching logic accuracy (otherwise different comments near values could prevent values matching).
*
* @author Vyacheslav Rusakov
* @since 18.05.2021
*/
public final class CommentsParserValidator {
private CommentsParserValidator() {
}
/**
* Compare that parsed trees are structurally the same to verify comments parser correctness (assuming snakeyaml
* works perfectly).
*
* @param comments comments parser result
* @param struct snakeyaml result
*/
public static void validate(final CmtTree comments, final StructTree struct) {
validateSubtrees(comments, struct);
}
@SuppressWarnings("checkstyle:MultipleStringLiterals")
private static void validateSubtrees(final TreeNode comments, final TreeNode struct) {
final List children = comments.getChildren();
final List childrenStruct = struct.getChildren();
if (children.size() < childrenStruct.size()) {
throw new IllegalStateException("Comments parser validation problem on line " + comments.getLineNum()
+ ": " + children.size() + " child nodes found but should be at least " + childrenStruct.size()
+ " (this is a parser bug, please report it!)\n" + debugTees(comments, struct));
}
final Iterator cmtIt = children.iterator();
final Iterator strIt = childrenStruct.iterator();
while (cmtIt.hasNext()) {
final CmtNode line = cmtIt.next();
if (line.isCommentOnly()) {
// "fake" node in comments parser for preserving trailing comment
continue;
}
if (!strIt.hasNext()) {
throw new IllegalStateException("Comments parser validation problem on line "
+ line.getLineNum() + ": line should not exist (this is a parser bug, please report it!)\n"
+ debugTees(line, null));
}
final StructNode match = strIt.next();
if (line.isProperty()) {
if (!line.getKey().equals(match.getKey())) {
throw new IllegalStateException("Comments parser validation problem on line "
+ line.getLineNum()
+ ": line should be different: \"" + match
+ "\" (this is a parser bug, please report it!)\n"
+ debugTees(comments, struct));
}
// store correctly parsed value (without comments) for precise list items matching
line.setParsedValue(match.getValue());
}
// validate subtree (even for non properties because structures must be equal)
validateSubtrees(line, match);
}
}
/**
* Prints comments model tree nearby snakeyaml model tree to visually see the difference.
*
* @param comments comments subtree
* @param struct snakeyaml subtree
* @return rendered trees string
*/
private static String debugTees(final TreeNode comments, final TreeNode struct) {
final ReportLines cmtTree = debugTree(comments);
final List res = new ArrayList<>();
if (struct != null) {
res.add("Comments parser subtree:");
}
res.addAll(cmtTree.lines);
if (struct != null) {
final ReportLines strTree = debugTree(struct);
final String[] merge = new String[Math.max(res.size(), strTree.lines.size() + 1)];
final int pad = Math.max(cmtTree.length, res.get(0).length()) + 4;
// align lines
int i = 0;
for (String line : res) {
merge[i++] = TreeStringUtils.fillTo(line, pad);
}
// align lines count
while (i < merge.length) {
merge[i++] = TreeStringUtils.whitespace(pad);
}
res.clear();
res.add(merge[0] + "Structure parser subtree:");
for (int j = 1; j < strTree.lines.size() + 1; j++) {
res.add(merge[j] + strTree.lines.get(j - 1));
}
}
// shift result to fit exception output
final StringBuilder collect = new StringBuilder();
for (String line : res) {
collect.append(TreeStringUtils.shiftRight(line, 6)).append('\n');
}
return collect.toString();
}
@SuppressWarnings("unchecked")
private static > ReportLines debugTree(final TreeNode node) {
final ReportLines lines = new ReportLines();
if (node instanceof YamlLine) {
debugTreeLeaf(lines, (T) node, 0);
} else {
// for tree root print entire tree
for (T child : node.getChildren()) {
debugTreeLeaf(lines, child, 0);
}
}
return lines;
}
private static > void debugTreeLeaf(
final ReportLines lines,
final T line,
final int padding) {
// for property list items, when first property is on the same line as dash, do not render it separately
// (it was already rendered by upper list item node), but still render its subtree
if (line.getRoot() == null || !line.getRoot().isListItemWithProperty()
|| line.getRoot().getChildren().indexOf(line) != 0) {
final String ln = String.format("%4s| ", line.getLineNum())
+ TreeStringUtils.shiftRight(line.toString(), padding);
lines.add(ln);
}
for (T child : line.getChildren()) {
debugTreeLeaf(lines, child, padding + 2);
}
}
@SuppressWarnings({"checkstyle:VisibilityModifier", "PMD.DefaultPackage"})
private static class ReportLines {
List lines = new ArrayList<>();
int length;
public void add(final String line) {
length = Math.max(length, line.length());
lines.add(line);
}
}
}