uoa-validator-engine2/src/main/java/eu/dnetlib/validator2/validation/guideline/GuidelineEvaluation.java

165 lines
6.8 KiB
Java

package eu.dnetlib.validator2.validation.guideline;
import eu.dnetlib.validator2.engine.*;
import eu.dnetlib.validator2.engine.Helper;
import eu.dnetlib.validator2.engine.builtins.StandardRuleDiagnostics;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class GuidelineEvaluation {
// TODO: Report all rule diagnostics to System out/err (we should remove this or allow clients to disable it)
private static final RuleDiagnostics<Document, Rule<Document>> OUT = Helper.Diagnostics.systemOut();
private static final RuleDiagnostics<Document, Rule<Document>> ERR = Helper.Diagnostics.systemErr();
private final String subjectId;
private final Document doc;
private final int weight;
private final List<String> warnings = new ArrayList<>();
private final List<String> errors = new ArrayList<>();
private Map<String, RequirementLevel> ruleIdToRequirementLevel = new HashMap<>();
private Map<String, NodeList> ruleIdToNodeList = new HashMap<>();
private final Diagnostics diagnostics = new Diagnostics();
private final Reporter<Document, SyntheticRule<Document>> reporter = new Reporter<>(diagnostics);
GuidelineEvaluation(String subjectId, Document doc, int weight) {
this.subjectId = subjectId;
this.doc = doc;
this.weight = weight;
}
Guideline.Result evaluate(CompilationResult result) {
ruleIdToRequirementLevel.putAll(result.ruleIdToRequirementLevel);
List<SyntheticRule<Document>> rules = new ArrayList<>();
rules.add(result.rootNodeRule);
rules.addAll(result.nodeRules);
// System.out.println("Evaluating " + rules);
for (SyntheticRule<Document> rule: rules) {
String id = rule.getContext().getIdProperty().getValue();
RuleEngine.applyAndReport(rule, doc, reporter);
Status status = diagnostics.getLastReportedStatus();
if (status == Status.ERROR) {
// fail fast in case of errors (no reason to proceed, since results may be rubbish)
return StandardResult.forError(diagnostics.getLastReportedError().getMessage());
}
if (status == Status.SUCCESS && getRequirementLevelOf(id) == RequirementLevel.NOT_APPLICABLE) {
// Report the non-applicability of a rule as a warning
// The check for both status and non-applicable requirement level is redundant
// (non-applicable rules always succeed), yet it is more clear hopefully.
// System.out.println("Warn for non-applicable: " + rule);
warnings.add(synthesizeFailureMessage(rule));
}
else if (status == Status.FAILURE) {
if (getRequirementLevelOf(id) == RequirementLevel.MANDATORY) {
// A mandatory rule has failed, yet we don't know whether we should report is as such.
// Let's check the parent of the rule
if (rule.parentRule() == null) {
// This is the root rule failing!
// Fail fast here, too (don't waste resources to evaluate other rules).
// We will "enable" it, if it is requested.
// System.out.println("Fail fast for root failure: " + rule);
errors.add(synthesizeFailureMessage(rule));
return StandardResult.forFailure(warnings, errors);
}
else {
// The current rule has a parent/ancestor which:
// (a) is non-mandatory or
// (b) it was successful.
// Thus, here we need only to warn and not to err, allowing the evaluation loop to proceed.
// System.out.println("Warn for mandatory failure: " + rule);
warnings.add(synthesizeFailureMessage(rule));
}
}
else {
// This is a warning: a non-mandatory rule has failed.
// Note that MA rules are treated as non-mandatory.
// We let the evaluation loop proceed.
// System.out.println("Warn for optional/recommended failure: " + rule);
warnings.add(synthesizeFailureMessage(rule));
}
}
}
int returnedWeight = weight;
if (rules.size() == warnings.size()) {
// TODO: I think this is the desired behavior, but we need to get confirmation for this.
// All rules have failed, but with warnings (thus being either optional or recommended).
// This indicates a success with 0 weight.
returnedWeight = 0;
}
return StandardResult.forSuccess(returnedWeight, warnings);
}
private String synthesizeFailureMessage(Rule<Document> rule) {
return subjectId + ": rule " + Helper.stringify(rule) + " has failed";
}
private String synthesizeNotApplicableMessage(Rule<Document> rule) {
return subjectId + ": rule " + Helper.stringify(rule) + " is not applicable";
}
void setNodesOf(String ruleId, NodeList nodes) {
ruleIdToNodeList.put(ruleId, nodes);
}
NodeList getNodesOf(String ruleId) {
return ruleIdToNodeList.get(ruleId);
}
void setRequirementLevelOf(String ruleId, RequirementLevel requirementLevel) {
ruleIdToRequirementLevel.put(ruleId, requirementLevel);
}
RequirementLevel getRequirementLevelOf(String ruleId) {
return ruleIdToRequirementLevel.get(ruleId);
}
private static final class Diagnostics extends StandardRuleDiagnostics<Document, SyntheticRule<Document>> {
private final Map<String, Status> statusByRuleId = new HashMap<>();
@Override
public void success(SyntheticRule<Document> rule, Document document) {
OUT.success(rule, document);
super.success(rule, document);
statusByRuleId.put(rule.getContext().getIdProperty().getValue(), Status.SUCCESS);
}
@Override
public void failure(SyntheticRule<Document> rule, Document document) {
OUT.failure(rule, document);
super.failure(rule, document);
statusByRuleId.put(rule.getContext().getIdProperty().getValue(), Status.FAILURE);
}
@Override
public void error(SyntheticRule<Document> rule, Document document, Throwable err) {
ERR.error(rule, document, err);
super.error(rule, document, err);
statusByRuleId.put(rule.getContext().getIdProperty().getValue(), Status.ERROR);
}
private Status statusFor(String ruleId) {
return statusByRuleId.get(ruleId);
}
}
}