package net.minecraft.util; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; import net.minecraft.resources.ResourceKey; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; public interface ProblemReporter { ProblemReporter DISCARDING = new ProblemReporter() { @Override public ProblemReporter forChild(ProblemReporter.PathElement child) { return this; } @Override public void report(ProblemReporter.Problem problem) { } }; ProblemReporter forChild(ProblemReporter.PathElement child); void report(ProblemReporter.Problem problem); public static class Collector implements ProblemReporter { public static final ProblemReporter.PathElement EMPTY_ROOT = () -> ""; @Nullable private final ProblemReporter.Collector parent; private final ProblemReporter.PathElement element; private final Set problems; public Collector() { this(EMPTY_ROOT); } public Collector(ProblemReporter.PathElement element) { this.parent = null; this.problems = new LinkedHashSet(); this.element = element; } private Collector(ProblemReporter.Collector parent, ProblemReporter.PathElement element) { this.problems = parent.problems; this.parent = parent; this.element = element; } @Override public ProblemReporter forChild(ProblemReporter.PathElement child) { return new ProblemReporter.Collector(this, child); } @Override public void report(ProblemReporter.Problem problem) { this.problems.add(new ProblemReporter.Collector.Entry(this, problem)); } public boolean isEmpty() { return this.problems.isEmpty(); } public void forEach(BiConsumer action) { List list = new ArrayList(); StringBuilder stringBuilder = new StringBuilder(); for (ProblemReporter.Collector.Entry entry : this.problems) { for (ProblemReporter.Collector collector = entry.source; collector != null; collector = collector.parent) { list.add(collector.element); } for (int i = list.size() - 1; i >= 0; i--) { stringBuilder.append(((ProblemReporter.PathElement)list.get(i)).get()); } action.accept(stringBuilder.toString(), entry.problem()); stringBuilder.setLength(0); list.clear(); } } public String getReport() { Multimap multimap = HashMultimap.create(); this.forEach(multimap::put); return (String)multimap.asMap() .entrySet() .stream() .map( entry -> " at " + (String)entry.getKey() + ": " + (String)((Collection)entry.getValue()).stream().map(ProblemReporter.Problem::description).collect(Collectors.joining("; ")) ) .collect(Collectors.joining("\n")); } public String getTreeReport() { List list = new ArrayList(); ProblemReporter.Collector.ProblemTreeNode problemTreeNode = new ProblemReporter.Collector.ProblemTreeNode(this.element); for (ProblemReporter.Collector.Entry entry : this.problems) { for (ProblemReporter.Collector collector = entry.source; collector != this; collector = collector.parent) { list.add(collector.element); } ProblemReporter.Collector.ProblemTreeNode problemTreeNode2 = problemTreeNode; for (int i = list.size() - 1; i >= 0; i--) { problemTreeNode2 = problemTreeNode2.child((ProblemReporter.PathElement)list.get(i)); } list.clear(); problemTreeNode2.problems.add(entry.problem); } return String.join("\n", problemTreeNode.getLines()); } record Entry(ProblemReporter.Collector source, ProblemReporter.Problem problem) { } record ProblemTreeNode( ProblemReporter.PathElement element, List problems, Map children ) { public ProblemTreeNode(ProblemReporter.PathElement element) { this(element, new ArrayList(), new LinkedHashMap()); } public ProblemReporter.Collector.ProblemTreeNode child(ProblemReporter.PathElement element) { return (ProblemReporter.Collector.ProblemTreeNode)this.children.computeIfAbsent(element, ProblemReporter.Collector.ProblemTreeNode::new); } public List getLines() { int i = this.problems.size(); int j = this.children.size(); if (i == 0 && j == 0) { return List.of(); } else if (i == 0 && j == 1) { List list = new ArrayList(); this.children.forEach((pathElement, problemTreeNode) -> list.addAll(problemTreeNode.getLines())); list.set(0, this.element.get() + (String)list.get(0)); return list; } else if (i == 1 && j == 0) { return List.of(this.element.get() + ": " + ((ProblemReporter.Problem)this.problems.getFirst()).description()); } else { List list = new ArrayList(); this.children.forEach((pathElement, problemTreeNode) -> list.addAll(problemTreeNode.getLines())); list.replaceAll(string -> " " + string); for (ProblemReporter.Problem problem : this.problems) { list.add(" " + problem.description()); } list.addFirst(this.element.get() + ":"); return list; } } } } public record ElementReferencePathElement(ResourceKey id) implements ProblemReporter.PathElement { @Override public String get() { return "->{" + this.id.location() + "@" + this.id.registry() + "}"; } } public record FieldPathElement(String name) implements ProblemReporter.PathElement { @Override public String get() { return "." + this.name; } } public record IndexedFieldPathElement(String name, int index) implements ProblemReporter.PathElement { @Override public String get() { return "." + this.name + "[" + this.index + "]"; } } public record IndexedPathElement(int index) implements ProblemReporter.PathElement { @Override public String get() { return "[" + this.index + "]"; } } @FunctionalInterface public interface PathElement { String get(); } public interface Problem { String description(); } public record RootElementPathElement(ResourceKey id) implements ProblemReporter.PathElement { @Override public String get() { return "{" + this.id.location() + "@" + this.id.registry() + "}"; } } public record RootFieldPathElement(String name) implements ProblemReporter.PathElement { @Override public String get() { return this.name; } } public static class ScopedCollector extends ProblemReporter.Collector implements AutoCloseable { private final Logger logger; public ScopedCollector(Logger logger) { this.logger = logger; } public ScopedCollector(ProblemReporter.PathElement element, Logger logger) { super(element); this.logger = logger; } public void close() { if (!this.isEmpty()) { this.logger.warn("[{}] Serialization errors:\n{}", this.logger.getName(), this.getTreeReport()); } } } }