001package gudusoft.gsqlparser.lineage.v2.fold; 002 003import gudusoft.gsqlparser.lineage.v2.Finding; 004import gudusoft.gsqlparser.lineage.v2.validation.DocumentIndex; 005import gudusoft.gsqlparser.lineage.v2.validation.SemanticStage; 006 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.Collections; 010import java.util.HashMap; 011import java.util.HashSet; 012import java.util.LinkedHashMap; 013import java.util.LinkedHashSet; 014import java.util.List; 015import java.util.Map; 016import java.util.Set; 017import java.util.TreeMap; 018import java.util.TreeSet; 019 020import static gudusoft.gsqlparser.lineage.v2.validation.DocumentIndex.intRef; 021import static gudusoft.gsqlparser.lineage.v2.validation.DocumentIndex.items; 022 023/** 024 * The {@code PROJECTOR} checks of {@code algorithms/fold-v1.md}, Section 11: folds each statement with 025 * {@link FoldKernel} and compares the result with the Fact layer of the same document, at stage 026 * {@code SEMANTIC}. 027 * 028 * <p>The comparison is a port of the reference runner's ({@code conformance/run.py}, class {@code Projector}), 029 * so a document gets the same diagnostic codes from both, with one declared difference: the reference leaves out 030 * the derived-unit rules of Section 7.3, and this check applies them, so a statement of a derived unit whose text 031 * is only {@code OBSERVED} folds to {@code ASSUMED} Occurrences and writes bounded by {@code PARTIAL}.</p> 032 * 033 * @since 4.2.17 034 */ 035public final class Projector implements SemanticStage.FoldCheck { 036 037 /** 038 * The check to give {@link gudusoft.gsqlparser.lineage.v2.validation.LineageValidator#standard}, with the 039 * kernel's default limits. 040 */ 041 public static final SemanticStage.FoldCheck CHECK = new Projector(FoldKernel.DEFAULT_MAX_STATES, 042 FoldKernel.DEFAULT_MAX_WORLDS); 043 044 private final int maxStates; 045 private final int maxWorlds; 046 047 private Projector(int maxStates, int maxWorlds) { 048 this.maxStates = maxStates; 049 this.maxWorlds = maxWorlds; 050 } 051 052 /** The check with other limits on the work of folding one statement (see {@link FoldKernel}). */ 053 public static SemanticStage.FoldCheck withLimits(int maxStates, int maxWorlds) { 054 return new Projector(maxStates, maxWorlds); 055 } 056 057 @Override 058 public void check(DocumentIndex index, List<Finding> findings, List<String> notes) { 059 new Run(new FoldKernel(index, maxStates, maxWorlds), findings).run(); 060 } 061 062 /** One comparison: the document, the kernel and the findings. */ 063 private static final class Run { 064 private final FoldKernel kernel; 065 private final DocumentIndex index; 066 private final List<Finding> findings; 067 private final Map<List<Object>, List<Long>> factsByProposition = new HashMap<List<Object>, List<Long>>(); 068 069 Run(FoldKernel kernel, List<Finding> findings) { 070 this.kernel = kernel; 071 this.index = kernel.getIndex(); 072 this.findings = findings; 073 for (Map<?, ?> fact : index.all("fact")) { 074 List<Object> key = Arrays.<Object>asList(FoldKernel.norm(fact.get("sourceRef")), 075 FoldKernel.norm(fact.get("targetRef")), labelOfFact(fact)); 076 List<Long> refs = factsByProposition.get(key); 077 if (refs == null) { 078 refs = new ArrayList<Long>(); 079 factsByProposition.put(key, refs); 080 } 081 refs.add(intRef(fact.get("ref"))); 082 } 083 } 084 085 void run() { 086 findings.addAll(kernel.malformedEdges()); 087 for (Long statement : kernel.getStatements()) { 088 compare(statement, kernel.fold(statement)); 089 } 090 } 091 092 private static FoldLabel labelOfFact(Map<?, ?> obj) { 093 return FoldLabel.of(obj.get("kind"), obj.get("role")); 094 } 095 096 /** (source, target, label, path, ordinal): the key of a Fact Occurrence. */ 097 private static List<Object> factKey(Object source, Object target, FoldLabel label, Object path, Object ordinal) { 098 return Arrays.<Object>asList(FoldKernel.norm(source), FoldKernel.norm(target), label, 099 new FoldKernel.PathKey(path), FoldKernel.norm(ordinal)); 100 } 101 102 private void compare(long statement, StatementFold folded) { 103 boolean complete = "COMPLETE".equals(kernel.completeness(statement)); 104 List<Map<?, ?>> occurrences = new ArrayList<Map<?, ?>>(); 105 for (Map<?, ?> occurrence : index.all("occurrence")) { 106 if (FoldKernel.same(occurrence.get("statementRef"), statement)) { 107 occurrences.add(occurrence); 108 } 109 } 110 String where = "statement ref " + statement; 111 112 LinkedHashMap<List<Object>, StatementFold.FactOccurrence> expectedFacts = 113 new LinkedHashMap<List<Object>, StatementFold.FactOccurrence>(); 114 for (StatementFold.FactOccurrence fact : folded.getFacts()) { 115 expectedFacts.put(factKey(fact.getSourceRef(), fact.getTargetRef(), fact.getLabel(), fact.getPath(), 116 (long) fact.getOrdinal()), fact); 117 } 118 Set<List<Object>> matchedFactKeys = new LinkedHashSet<List<Object>>(); 119 for (Map<?, ?> occurrence : occurrences) { 120 if (!occurrence.containsKey("factRef")) { 121 continue; 122 } 123 Map<?, ?> fact = index.get("fact", occurrence.get("factRef")); 124 if (fact == null) { 125 continue; 126 } 127 List<Object> key = factKey(fact.get("sourceRef"), fact.get("targetRef"), labelOfFact(fact), 128 occurrence.get("path"), occurrence.get("ordinal")); 129 StatementFold.FactOccurrence expected = expectedFacts.get(key); 130 if (expected != null) { 131 matchedFactKeys.add(key); 132 checkOccurrence(occurrence, expected.getProofStatus(), expected.getSupport(), complete, expected, null); 133 continue; 134 } 135 if (candidateHolds(folded, labelOfFact(fact), fact.get("sourceRef"), fact.get("targetRef"))) { 136 findings.add(new Finding("SEMANTIC_FOLD_CANDIDATE_AS_FACT", "occurrences ref " + occurrence.get("ref"), 137 "the fold yields only a FactCandidate for this proposition")); 138 } else if (complete) { 139 findings.add(new Finding("SEMANTIC_FOLD_UNSUPPORTED", "occurrences ref " + occurrence.get("ref"), 140 "no fold walk yields this Fact Occurrence")); 141 } 142 } 143 for (Map.Entry<List<Object>, StatementFold.FactOccurrence> entry : expectedFacts.entrySet()) { 144 if (!matchedFactKeys.contains(entry.getKey())) { 145 findings.add(new Finding("SEMANTIC_FOLD_MISSING_FACT", where, 146 "no Occurrence for fold result " + entry.getValue())); 147 } 148 } 149 150 // Candidate results: exact matches first, then the near misses on the same path and kind. 151 List<Object[]> declared = new ArrayList<Object[]>(); 152 for (Map<?, ?> occurrence : occurrences) { 153 Map<?, ?> candidate = index.get("factCandidate", occurrence.get("factCandidateRef")); 154 if (candidate != null) { 155 declared.add(new Object[] {occurrence, candidate}); 156 } 157 } 158 LinkedHashMap<Object, StatementFold.Candidate> candidateMatches = 159 new LinkedHashMap<Object, StatementFold.Candidate>(); 160 List<StatementFold.Candidate> unmatched = new ArrayList<StatementFold.Candidate>(); 161 for (StatementFold.Candidate result : folded.getCandidates()) { 162 Map<?, ?> hit = null; 163 for (Object[] pair : declared) { 164 Map<?, ?> occurrence = (Map<?, ?>) pair[0]; 165 if (!candidateMatches.containsKey(FoldKernel.norm(occurrence.get("ref"))) 166 && sameCandidate(occurrence, (Map<?, ?>) pair[1], result)) { 167 hit = occurrence; 168 break; 169 } 170 } 171 if (hit == null) { 172 unmatched.add(result); 173 } else { 174 candidateMatches.put(FoldKernel.norm(hit.get("ref")), result); 175 checkOccurrence(hit, result.getProofStatus(), result.getSupport(), complete, null, result); 176 } 177 } 178 List<Object[]> leftover = new ArrayList<Object[]>(); 179 for (Object[] pair : declared) { 180 if (!candidateMatches.containsKey(FoldKernel.norm(((Map<?, ?>) pair[0]).get("ref")))) { 181 leftover.add(pair); 182 } 183 } 184 for (StatementFold.Candidate result : unmatched) { 185 Object[] near = null; 186 for (Object[] pair : leftover) { 187 Map<?, ?> occurrence = (Map<?, ?>) pair[0]; 188 if (FoldKernel.same(occurrence.get("path"), result.getPath()) 189 && labelOfFact((Map<?, ?>) pair[1]).equals(result.getLabel())) { 190 near = pair; 191 break; 192 } 193 } 194 if (near != null) { 195 leftover.remove(near); 196 findings.add(new Finding("SEMANTIC_FOLD_CANDIDATE_MISMATCH", 197 "occurrences ref " + ((Map<?, ?>) near[0]).get("ref"), "the fold yields " + result)); 198 } else { 199 findings.add(new Finding("SEMANTIC_FOLD_MISSING_FACT", where, "no FactCandidate for fold result " 200 + result)); 201 } 202 } 203 if (complete) { 204 for (Object[] pair : leftover) { 205 findings.add(new Finding("SEMANTIC_FOLD_UNSUPPORTED", "occurrences ref " + ((Map<?, ?>) pair[0]).get("ref"), 206 "no fold walk yields this FactCandidate")); 207 } 208 } 209 checkWrites(statement, matchedFactKeys, candidateMatches); 210 } 211 212 private static boolean candidateHolds(StatementFold folded, FoldLabel label, Object source, Object target) { 213 StatementFold.Pair pair = new StatementFold.Pair(source, target); 214 for (StatementFold.Candidate candidate : folded.getCandidates()) { 215 if (candidate.getLabel().equals(label)) { 216 for (Set<StatementFold.Pair> outcome : candidate.getEffectiveOutcomes()) { 217 if (outcome.contains(pair)) { 218 return true; 219 } 220 } 221 } 222 } 223 return false; 224 } 225 226 /** 227 * The decision of a published FactCandidate end (fold-v1 Section 7.1, item 5): the deciding set it names, a set of 228 * the document's candidateSets, compared by its members and membership; null without decidedBy. 229 */ 230 private StatementFold.Decision declaredDecision(Map<?, ?> cset) { 231 if (!(cset.get("decidedBy") instanceof Map<?, ?>)) { 232 return null; 233 } 234 Map<?, ?> decided = (Map<?, ?>) cset.get("decidedBy"); 235 Map<?, ?> deciding = null; 236 for (Map<?, ?> root : items(index.document().get("candidateSets"))) { 237 if (FoldKernel.same(FoldKernel.norm(root.get("ref")), FoldKernel.norm(decided.get("candidateSetRef")))) { 238 deciding = root; 239 } 240 } 241 List<Object> alternatives = new ArrayList<Object>(); 242 if (deciding != null && deciding.get("candidateRefs") instanceof List<?>) { 243 for (Object member : (List<?>) deciding.get("candidateRefs")) { 244 alternatives.add(FoldKernel.norm(member)); 245 } 246 } 247 List<StatementFold.Pair> alignment = new ArrayList<StatementFold.Pair>(); 248 for (Map<?, ?> pair : items(decided.get("alignment"))) { 249 alignment.add(new StatementFold.Pair(FoldKernel.norm(pair.get("alternativeRef")), 250 FoldKernel.norm(pair.get("candidateRef")))); 251 } 252 return new StatementFold.Decision(decided.get("candidateSetRef"), alternatives, 253 deciding == null ? null : deciding.get("membership"), alignment); 254 } 255 256 /** (source end, target end, explicit outcomes or null, effective outcomes) of a FactCandidate record. */ 257 private Object[] declaredCandidate(Map<?, ?> candidate) { 258 StatementFold.End[] ends = new StatementFold.End[2]; 259 String[] sides = {"source", "target"}; 260 for (int i = 0; i < 2; i++) { 261 Map<?, ?> end = candidate.get(sides[i]) instanceof Map<?, ?> ? (Map<?, ?>) candidate.get(sides[i]) 262 : Collections.emptyMap(); 263 if (end.containsKey("endpointRef")) { 264 ends[i] = StatementFold.End.fixed(FoldKernel.norm(end.get("endpointRef"))); 265 } else { 266 Map<?, ?> cset = end.get("candidateSet") instanceof Map<?, ?> ? (Map<?, ?>) end.get("candidateSet") 267 : Collections.emptyMap(); 268 List<Object> list = new ArrayList<Object>(); 269 if (cset.get("candidateRefs") instanceof List<?>) { 270 for (Object member : (List<?>) cset.get("candidateRefs")) { 271 if (intRef(member) != null) { 272 list.add(intRef(member)); 273 } 274 } 275 } 276 // sorted with duplicates kept, as a sorted tuple keeps them 277 Collections.sort(list, FoldKernel.REF_ORDER); 278 ends[i] = StatementFold.End.set(list, cset.get("membership"), declaredDecision(cset)); 279 } 280 } 281 Set<Set<StatementFold.Pair>> explicit = null; 282 if (candidate.get("outcomes") instanceof List<?>) { 283 explicit = new LinkedHashSet<Set<StatementFold.Pair>>(); 284 for (Map<?, ?> outcome : items(candidate.get("outcomes"))) { 285 Set<StatementFold.Pair> pairs = new LinkedHashSet<StatementFold.Pair>(); 286 for (Map<?, ?> pair : items(outcome.get("pairs"))) { 287 pairs.add(new StatementFold.Pair(FoldKernel.norm(pair.get("sourceRef")), 288 FoldKernel.norm(pair.get("targetRef")))); 289 } 290 explicit.add(pairs); 291 } 292 } 293 return new Object[] {ends[0], ends[1], explicit, 294 explicit != null ? explicit : FoldKernel.impliedOutcomes(ends[0], ends[1])}; 295 } 296 297 /** 298 * The reference matches a FactCandidate Occurrence by kind, path, ends and outcomes; this check also matches 299 * the ordinal (identity-v1, Section 5.4), so that of two FactCandidates with one subject on one path each 300 * Occurrence is compared with its own result (a declared difference from the reference). 301 */ 302 private boolean sameCandidate(Map<?, ?> occurrence, Map<?, ?> candidate, StatementFold.Candidate result) { 303 if (!labelOfFact(candidate).equals(result.getLabel()) || !FoldKernel.same(occurrence.get("path"), result.getPath()) 304 || !FoldKernel.same(occurrence.get("ordinal"), (long) result.getOrdinal())) { 305 return false; 306 } 307 Object[] ends = declaredCandidate(candidate); 308 return ends[0].equals(result.getSource()) && ends[1].equals(result.getTarget()) 309 && (ends[2] == null) == (result.getOutcomes() == null) && ends[3].equals(result.getEffectiveOutcomes()); 310 } 311 312 private void checkOccurrence(Map<?, ?> occurrence, String folded, Set<Long> support, boolean complete, 313 StatementFold.FactOccurrence fact, StatementFold.Candidate candidate) { 314 String where = "occurrences ref " + occurrence.get("ref"); 315 Object declared = occurrence.get("proofStatus"); 316 if ("ASSUMED".equals(declared) && "PROVEN".equals(folded)) { 317 findings.add(new Finding("SEMANTIC_FOLD_PROOF_MISMATCH", where, "ASSUMED, the fold proves it")); 318 } 319 if ("PROVEN".equals(declared) && "ASSUMED".equals(folded) && complete) { 320 findings.add(new Finding("SEMANTIC_FOLD_PROOF_MISMATCH", where, "PROVEN, the fold yields ASSUMED")); 321 } 322 Set<Object> listed = new HashSet<Object>(); 323 Object pathRefs = occurrence.get("supportPathRefs"); 324 if (pathRefs instanceof List<?>) { 325 for (Object pathRef : (List<?>) pathRefs) { 326 Map<?, ?> path = index.get("supportPath", pathRef); 327 Object refs = path == null ? null : path.get("microEdgeRefs"); 328 List<?> list = refs instanceof List<?> ? (List<?>) refs : Collections.emptyList(); 329 if (!yields(list, occurrence, fact, candidate)) { 330 findings.add(new Finding("SEMANTIC_FOLD_BAD_SUPPORT_PATH", where, 331 "support path " + pathRef + " is not a fold walk yielding this record")); 332 } 333 for (Object r : list) { 334 listed.add(FoldKernel.norm(r)); 335 } 336 } 337 } 338 if (complete) { 339 Set<Long> missing = new TreeSet<Long>(); 340 for (Long edge : support) { 341 if (!listed.contains(edge)) { 342 missing.add(edge); 343 } 344 } 345 if (!missing.isEmpty()) { 346 findings.add(new Finding("SEMANTIC_FOLD_SUPPORT_NOT_COVERED", where, 347 "micro-edges " + missing + " are not covered")); 348 } 349 } 350 } 351 352 /** 353 * True when the listed micro-edges form a fold walk yielding the record of the Occurrence. Beyond the 354 * reference's test, the walk must start at the Occurrence's own reference (its position, for a Fact) or take 355 * only the references of the FactCandidate's group (fold-v1, Section 8.2, item 3; a declared difference). 356 */ 357 private boolean yields(List<?> refs, Map<?, ?> occurrence, StatementFold.FactOccurrence expectedFact, 358 StatementFold.Candidate expectedCandidate) { 359 List<Map<?, ?>> edges = new ArrayList<Map<?, ?>>(); 360 for (Object r : refs) { 361 Map<?, ?> edge = index.get("microEdge", r); 362 if (edge == null) { 363 return false; 364 } 365 edges.add(edge); 366 } 367 if (edges.isEmpty()) { 368 return false; 369 } 370 Object statement = occurrence.get("statementRef"); 371 for (Map<?, ?> edge : edges) { 372 if (!FoldKernel.same(edge.get("statementRef"), statement)) { 373 return false; 374 } 375 } 376 Map<?, ?> first = edges.get(0); 377 Map<?, ?> last = edges.get(edges.size() - 1); 378 if (!(kernel.kept(first.get("sourceRef")) || kernel.graphCset(first.get("sourceRef")))) { 379 return false; 380 } 381 if (!kernel.kept(last.get("targetRef"))) { 382 return false; 383 } 384 FoldLabel label = FoldKernel.edgeLabel(first); 385 FoldKernel.Sigma sigma = FoldKernel.Sigma.EMPTY; 386 for (int i = 0; i < edges.size(); i++) { 387 Map<?, ?> edge = edges.get(i); 388 if (i > 0 && !FoldKernel.same(edge.get("sourceRef"), edges.get(i - 1).get("targetRef"))) { 389 return false; 390 } 391 if (i < edges.size() - 1 && !kernel.folded(edge.get("targetRef"))) { 392 return false; 393 } 394 if (i > 0) { 395 label = label.compose(FoldKernel.edgeLabel(edge)); 396 } 397 sigma = sigma.extend(kernel.choices(edge)); 398 if (sigma == null) { 399 return false; 400 } 401 } 402 Map<?, ?> fact = index.get("fact", occurrence.get("factRef")); 403 if (fact != null) { 404 return label.equals(labelOfFact(fact)) && FoldKernel.same(first.get("sourceRef"), fact.get("sourceRef")) 405 && FoldKernel.same(last.get("targetRef"), fact.get("targetRef")) 406 && FoldKernel.same(first.get("referencePath"), occurrence.get("path")) 407 && (expectedFact == null 408 || FoldKernel.same(FoldKernel.offsetOf(first.get("referenceStart")), expectedFact.getOffset())); 409 } 410 if (expectedCandidate != null) { 411 for (Object ref : sigma.refs) { 412 if (!FoldKernel.containsRef(expectedCandidate.getReferences(), ref)) { 413 return false; 414 } 415 } 416 } 417 Map<?, ?> candidate = index.get("factCandidate", occurrence.get("factCandidateRef")); 418 if (candidate == null || sigma.isEmpty() || !label.equals(labelOfFact(candidate))) { 419 return false; 420 } 421 Object[] ends = declaredCandidate(candidate); 422 StatementFold.End targetEnd = (StatementFold.End) ends[1]; 423 if (kernel.graphCset(first.get("sourceRef"))) { 424 List<Object> targets = targetEnd.isFixed() ? Collections.singletonList(targetEnd.getEndpointRef()) 425 : targetEnd.getMembers(); 426 return FoldKernel.containsRef(targets, last.get("targetRef")); 427 } 428 StatementFold.Pair pair = new StatementFold.Pair(first.get("sourceRef"), last.get("targetRef")); 429 @SuppressWarnings("unchecked") 430 Set<Set<StatementFold.Pair>> effective = (Set<Set<StatementFold.Pair>>) ends[3]; 431 for (Set<StatementFold.Pair> outcome : effective) { 432 if (outcome.contains(pair)) { 433 return true; 434 } 435 } 436 return false; 437 } 438 439 private static final class Need { 440 final Set<Object> facts = new LinkedHashSet<Object>(); 441 final Set<Object> candidates = new LinkedHashSet<Object>(); 442 final Set<StatementFold.TerminalInput> terminals = new LinkedHashSet<StatementFold.TerminalInput>(); 443 String bound = "COMPLETE"; 444 final Set<Long> diagnostics = new TreeSet<Long>(); 445 } 446 447 /** Sections 7.4 and 7.5, as the reference checks them against the write records of the statement. */ 448 private void checkWrites(long statement, Set<List<Object>> matchedFactKeys, 449 Map<Object, StatementFold.Candidate> candidateMatches) { 450 Map<Object, List<Map<?, ?>>> out = kernel.outOf(statement); 451 TreeMap<Object, Need> required = new TreeMap<Object, Need>(FoldKernel.REF_ORDER); 452 for (Map<?, ?> edge : kernel.edgesOf(statement)) { 453 if (kernel.isField(edge.get("targetRef"))) { 454 need(required, edge.get("targetRef")); 455 } 456 } 457 List<Map<?, ?>> writes = new ArrayList<Map<?, ?>>(); 458 for (Map<?, ?> write : index.all("tfo")) { 459 if (FoldKernel.same(write.get("statementRef"), statement)) { 460 writes.add(write); 461 if (kernel.isField(write.get("targetRef"))) { 462 need(required, write.get("targetRef")); 463 } 464 } 465 } 466 for (List<Object> key : matchedFactKeys) { 467 List<Long> facts = factsByProposition.get(key.subList(0, 3)); 468 if (kernel.isField(key.get(1))) { 469 Need need = need(required, key.get(1)); 470 if (facts != null) { 471 need.facts.addAll(facts); 472 } 473 } 474 } 475 for (Map.Entry<Object, StatementFold.Candidate> entry : candidateMatches.entrySet()) { 476 Map<?, ?> occurrence = index.get("occurrence", entry.getKey()); 477 StatementFold.End target = entry.getValue().getTarget(); 478 List<Object> targets = target.isFixed() ? Collections.singletonList(target.getEndpointRef()) 479 : target.getMembers(); 480 for (Object t : targets) { 481 if (kernel.isField(t)) { 482 Need need = need(required, t); 483 need.candidates.add(FoldKernel.norm(occurrence.get("factCandidateRef"))); 484 need.bound = "PARTIAL"; 485 } 486 } 487 } 488 for (Map.Entry<Object, Set<StatementFold.TerminalInput>> entry : kernel.terminalInputs(statement, out).entrySet()) { 489 need(required, entry.getKey()).terminals.addAll(entry.getValue()); 490 } 491 // the reference also lowers through micro-edges that take two alternatives of one reference; fold-v1 492 // counts walks only (a declared difference) 493 Map<Object, Object[]> lowered = kernel.lowering(statement, out); 494 boolean observed = kernel.observed(statement); 495 for (Map.Entry<Object, Need> entry : required.entrySet()) { 496 Need need = entry.getValue(); 497 if (observed) { 498 need.bound = FoldKernel.lower(need.bound, "PARTIAL"); 499 } 500 if (kernel.enteredByChoice(statement, entry.getKey())) { 501 need.bound = FoldKernel.lower(need.bound, "PARTIAL"); // written only in some worlds (Section 7.4) 502 } 503 Object[] lowering = lowered.get(entry.getKey()); 504 if (lowering != null) { 505 need.bound = FoldKernel.lower(need.bound, lowering[0]); 506 @SuppressWarnings("unchecked") 507 Set<Long> diagnostics = (Set<Long>) lowering[1]; 508 need.diagnostics.addAll(diagnostics); 509 } 510 } 511 for (Map.Entry<Object, Need> entry : required.entrySet()) { 512 Need need = entry.getValue(); 513 List<Map<?, ?>> mine = new ArrayList<Map<?, ?>>(); 514 for (Map<?, ?> write : writes) { 515 if (FoldKernel.same(write.get("targetRef"), entry.getKey())) { 516 mine.add(write); 517 } 518 } 519 if (mine.isEmpty()) { 520 findings.add(new Finding("SEMANTIC_FOLD_INPUT_MISSING", "statement ref " + statement, 521 "field ref " + entry.getKey() + " is written but has no TargetFieldOccurrence")); 522 continue; 523 } 524 for (Map<?, ?> write : mine) { 525 String where = "targetFieldOccurrences ref " + write.get("ref"); 526 Set<Object> haveFacts = new HashSet<Object>(); 527 Set<Object> haveCandidates = new HashSet<Object>(); 528 Set<StatementFold.TerminalInput> haveTerminals = new HashSet<StatementFold.TerminalInput>(); 529 for (Map<?, ?> input : items(write.get("inputs"))) { 530 if (input.containsKey("factRef")) { 531 haveFacts.add(FoldKernel.norm(input.get("factRef"))); 532 } 533 if (input.containsKey("factCandidateRef")) { 534 haveCandidates.add(FoldKernel.norm(input.get("factCandidateRef"))); 535 } 536 if (!"FIELD_REF".equals(input.get("kind"))) { 537 haveTerminals.add(FoldKernel.terminalInput(input.get("kind"), input)); 538 } 539 } 540 List<Object> missing = new ArrayList<Object>(); 541 for (Object fact : need.facts) { 542 if (!haveFacts.contains(fact)) { 543 missing.add(fact); 544 } 545 } 546 for (Object candidate : need.candidates) { 547 if (!haveCandidates.contains(candidate)) { 548 missing.add(candidate); 549 } 550 } 551 for (StatementFold.TerminalInput terminal : need.terminals) { 552 if (!haveTerminals.contains(terminal)) { 553 missing.add(terminal); 554 } 555 } 556 if (!missing.isEmpty()) { 557 findings.add(new Finding("SEMANTIC_FOLD_INPUT_MISSING", where, "missing inputs " + missing)); 558 } 559 Set<Long> linked = new HashSet<Long>(); 560 FoldKernel.addRefs(linked, write.get("diagnosticRefs")); 561 Set<Long> lowering = new TreeSet<Long>(need.diagnostics); 562 lowering.removeAll(linked); 563 if (FoldKernel.rank(write.get("upstreamCompleteness")) > FoldKernel.rank(need.bound) 564 || !lowering.isEmpty()) { 565 findings.add(new Finding("SEMANTIC_FOLD_COMPLETENESS_EXCEEDS_BOUND", where, "declared " 566 + write.get("upstreamCompleteness") + ", bound " + need.bound + ", unlinked diagnostics " 567 + lowering)); 568 } 569 } 570 } 571 } 572 573 private static Need need(Map<Object, Need> required, Object target) { 574 Object key = FoldKernel.norm(target); 575 Need need = required.get(key); 576 if (need == null) { 577 need = new Need(); 578 required.put(key, need); 579 } 580 return need; 581 } 582 } 583}