001package gudusoft.gsqlparser.lineage.v2.fold; 002 003import gudusoft.gsqlparser.lineage.v2.Finding; 004import gudusoft.gsqlparser.lineage.v2.json.CanonicalJson; 005import gudusoft.gsqlparser.lineage.v2.validation.DocumentIndex; 006 007import java.util.ArrayDeque; 008import java.util.ArrayList; 009import java.util.Arrays; 010import java.util.Collections; 011import java.util.Comparator; 012import java.util.Deque; 013import java.util.HashMap; 014import java.util.HashSet; 015import java.util.Iterator; 016import java.util.LinkedHashMap; 017import java.util.LinkedHashSet; 018import java.util.List; 019import java.util.Map; 020import java.util.Set; 021import java.util.TreeMap; 022import java.util.TreeSet; 023 024import static gudusoft.gsqlparser.lineage.v2.validation.DocumentIndex.intRef; 025import static gudusoft.gsqlparser.lineage.v2.validation.DocumentIndex.items; 026 027/** 028 * FOLD-V1 ({@code lineage-spec/algorithms/fold-v1.md}): folds the ExplainGraph of one statement of a document 029 * into its Fact Occurrences, FactCandidates and write requirements. 030 * 031 * <p>The kernel reads the document the way the reference runner ({@code conformance/run.py}, class 032 * {@code Projector}) does, so that a validator built on it reports what the reference reports. It also computes 033 * what the reference leaves out and the specification defines: the derived-unit rules of Section 7.3 (every walk 034 * of a statement whose derived text is only {@code OBSERVED} is {@code ASSUMED}, every write at most 035 * {@code PARTIAL}), the bindings of every Occurrence (Sections 7.1, item 6 and 8.1, item 2), the ordinals of 036 * FactCandidates (identity-v1, Section 5.4), and the complete requirements of every write (Sections 7.4 and 037 * 7.5).</p> 038 * 039 * <p>Exploration is over states (node, label, choices, proof) and terminates on every graph (Section 6). Its 040 * cost grows with the states reached and, for a group of linked ambiguous references, with the number of worlds 041 * (the product of their alternatives); both are bounded, and a statement over a bound throws 042 * {@link FoldLimitException}.</p> 043 * 044 * @since 4.2.17 045 */ 046public final class FoldKernel { 047 048 /** The most exploration states one statement may reach (every state counts, the first and the last too). */ 049 public static final int DEFAULT_MAX_STATES = 500000; 050 /** The most worlds one group of linked references, or one Fact test, may examine. */ 051 public static final int DEFAULT_MAX_WORLDS = 1 << 16; 052 053 static final String SOURCE = "SOURCE"; 054 static final String TARGET = "TARGET"; 055 private static final String PROVEN = "PROVEN"; 056 private static final String ASSUMED = "ASSUMED"; 057 private static final List<String> CONTAINER_NODE_KINDS = Arrays.asList("RESULT_SET", "CTE", "SUBQUERY", 058 "SET_OPERATION", "TABLE_FUNCTION", "PIVOT"); 059 private static final Map<String, Integer> RANK = new HashMap<String, Integer>(); 060 061 static { 062 RANK.put("COMPLETE", 2); 063 RANK.put("PARTIAL", 1); 064 RANK.put("UNKNOWN", 0); 065 } 066 067 /** The alternative "something not listed" of an INCOMPLETE candidate set (Section 7.1, item 1). */ 068 static final Object NOT_LISTED = new Object() { 069 @Override 070 public String toString() { 071 return "NOT_LISTED"; 072 } 073 }; 074 075 private final DocumentIndex index; 076 private final int maxStates; 077 private final int maxWorlds; 078 private final Object defaultCompleteness; 079 private final Map<Long, Object> statementCompleteness = new HashMap<Long, Object>(); 080 /** The candidate sets of the ExplainGraph by ref; the last one with a ref wins, as in the reference. */ 081 private final Map<Long, Map<?, ?>> graphCsets = new HashMap<Long, Map<?, ?>>(); 082 private final Map<Object, Set<String>> refSide = new HashMap<Object, Set<String>>(); 083 private final Map<Object, Position> refPosition = new HashMap<Object, Position>(); 084 private final List<Map<?, ?>> allEdges; 085 private final TreeMap<Long, List<Map<?, ?>>> edgesByStatement = new TreeMap<Long, List<Map<?, ?>>>(); 086 private final List<Long> statements; 087 088 public FoldKernel(DocumentIndex index) { 089 this(index, DEFAULT_MAX_STATES, DEFAULT_MAX_WORLDS); 090 } 091 092 public FoldKernel(DocumentIndex index, int maxStates, int maxWorlds) { 093 this.index = index; 094 this.maxStates = maxStates; 095 this.maxWorlds = maxWorlds; 096 Map<?, ?> graph = index.graph(); 097 defaultCompleteness = graph.get("completeness"); 098 for (Map<?, ?> entry : items(graph.get("statementCompleteness"))) { 099 statementCompleteness.put(intRef(entry.get("statementRef")), entry.get("completeness")); 100 } 101 for (Map<?, ?> cset : items(graph.get("candidateSets"))) { 102 Long ref = intRef(cset.get("ref")); 103 if (ref != null) { 104 graphCsets.put(ref, cset); 105 } 106 } 107 allEdges = index.all("microEdge"); 108 for (Map<?, ?> edge : allEdges) { 109 for (Choice choice : choices(edge)) { 110 Set<String> sides = refSide.get(choice.ref); 111 if (sides == null) { 112 sides = new TreeSet<String>(); 113 refSide.put(choice.ref, sides); 114 } 115 sides.add(choice.side); 116 Position position = Position.of(edge); 117 Position known = refPosition.get(choice.ref); 118 if (known == null || position.compareTo(known) < 0) { 119 refPosition.put(choice.ref, position); 120 } 121 } 122 Long statement = intRef(edge.get("statementRef")); 123 if (statement != null) { 124 List<Map<?, ?>> list = edgesByStatement.get(statement); 125 if (list == null) { 126 list = new ArrayList<Map<?, ?>>(); 127 edgesByStatement.put(statement, list); 128 } 129 list.add(edge); 130 } 131 } 132 TreeSet<Long> all = new TreeSet<Long>(edgesByStatement.keySet()); 133 for (Map<?, ?> statement : index.all("statement")) { 134 all.add(intRef(statement.get("ref"))); 135 } 136 statements = Collections.unmodifiableList(new ArrayList<Long>(all)); 137 } 138 139 // --------------------------------------------------------------------------------------------- 140 // Values compared the way the reference compares them: integers by value, everything else by equals. 141 // --------------------------------------------------------------------------------------------- 142 143 static Object norm(Object value) { 144 Long ref = intRef(value); 145 return ref != null ? ref : value; 146 } 147 148 static boolean same(Object a, Object b) { 149 Object x = norm(a); 150 Object y = norm(b); 151 return x == null ? y == null : x.equals(y); 152 } 153 154 static int hash(Object value) { 155 Object x = norm(value); 156 return x == null ? 0 : x.hashCode(); 157 } 158 159 /** Orders refs (and null, the key of a reference without a candidate set) the way the reference sorts ints. */ 160 static final Comparator<Object> REF_ORDER = new Comparator<Object>() { 161 @Override 162 public int compare(Object a, Object b) { 163 Object x = norm(a); 164 Object y = norm(b); 165 if (x == null || y == null) { 166 return x == null ? (y == null ? 0 : -1) : 1; 167 } 168 if (x instanceof Long && y instanceof Long) { 169 return ((Long) x).compareTo((Long) y); 170 } 171 if (x instanceof Long || y instanceof Long) { 172 return x instanceof Long ? -1 : 1; 173 } 174 return compareCodePoints(String.valueOf(x), String.valueOf(y)); 175 } 176 }; 177 178 /** Unsigned UTF-8 byte order, which is code point order. */ 179 static int compareCodePoints(String a, String b) { 180 int i = 0; 181 int j = 0; 182 while (i < a.length() && j < b.length()) { 183 int x = a.codePointAt(i); 184 int y = b.codePointAt(j); 185 if (x != y) { 186 return x < y ? -1 : 1; 187 } 188 i += Character.charCount(x); 189 j += Character.charCount(y); 190 } 191 return (a.length() - i) == 0 ? ((b.length() - j) == 0 ? 0 : -1) : 1; 192 } 193 194 static Set<Set<StatementFold.Pair>> frozenOutcomes(Set<Set<StatementFold.Pair>> outcomes) { 195 Set<Set<StatementFold.Pair>> out = new LinkedHashSet<Set<StatementFold.Pair>>(); 196 for (Set<StatementFold.Pair> outcome : outcomes) { 197 out.add(Collections.unmodifiableSet(new LinkedHashSet<StatementFold.Pair>(outcome))); 198 } 199 return Collections.unmodifiableSet(out); 200 } 201 202 // --------------------------------------------------------------------------------------------- 203 // Classification (Section 3) 204 // --------------------------------------------------------------------------------------------- 205 206 public DocumentIndex getIndex() { 207 return index; 208 } 209 210 /** Every statement of the document and every statement a micro-edge names, in ref order. */ 211 public List<Long> getStatements() { 212 return statements; 213 } 214 215 /** The micro-edges of one statement, in ref order. */ 216 public List<Map<?, ?>> edgesOf(long statement) { 217 List<Map<?, ?>> edges = edgesByStatement.get(statement); 218 return edges == null ? Collections.<Map<?, ?>>emptyList() : Collections.unmodifiableList(edges); 219 } 220 221 /** The ExplainGraph completeness of a statement: its {@code statementCompleteness} entry or the graph's. */ 222 public Object completeness(long statement) { 223 return statementCompleteness.containsKey(statement) ? statementCompleteness.get(statement) 224 : defaultCompleteness; 225 } 226 227 public boolean kept(Object ref) { 228 return index.get("endpoint", ref) != null; 229 } 230 231 Map<?, ?> node(Object ref) { 232 return index.get("node", ref); 233 } 234 235 public boolean folded(Object ref) { 236 Map<?, ?> node = node(ref); 237 return node != null && !"TERMINAL".equals(node.get("kind")); 238 } 239 240 boolean terminal(Object ref) { 241 Map<?, ?> node = node(ref); 242 return node != null && "TERMINAL".equals(node.get("kind")); 243 } 244 245 /** True when the ref names a candidate set of the ExplainGraph (the source of an UNRESOLVED reference). */ 246 public boolean graphCset(Object ref) { 247 Long key = intRef(ref); 248 return key != null && graphCsets.containsKey(key); 249 } 250 251 /** 252 * The kept field-class endpoints (Section 3.1, 3.2): fields, declared variables and routine parameters, which have 253 * a value a later statement can read; a write targets one of them. 254 */ 255 static final java.util.List<String> FIELD_CLASS_KINDS = java.util.Arrays.asList("FIELD", "VARIABLE", "PARAMETER"); 256 257 boolean isField(Object ref) { 258 Map<?, ?> endpoint = index.get("endpoint", ref); 259 return endpoint != null && FIELD_CLASS_KINDS.contains(endpoint.get("kind")); 260 } 261 262 private String endClass(Object ref) { 263 Map<?, ?> endpoint = index.get("endpoint", ref); 264 if (endpoint != null) { 265 Object kind = endpoint.get("kind"); 266 return "ROW_SET".equals(kind) ? "ROWSET" : FIELD_CLASS_KINDS.contains(kind) ? "FIELD" : null; 267 } 268 Map<?, ?> node = node(ref); 269 if (node != null) { 270 Object kind = node.get("kind"); 271 if ("ROW_SET".equals(kind)) { 272 return "ROWSET"; 273 } 274 return CONTAINER_NODE_KINDS.contains(kind) ? null : "FIELD"; 275 } 276 return graphCset(ref) ? "FIELD" : null; 277 } 278 279 /** True when the statement lies in a derived source unit whose text is only observed (Section 7.3). */ 280 public boolean observed(long statement) { 281 Map<?, ?> record = index.get("statement", statement); 282 Map<?, ?> unit = record == null ? null : index.get("sourceUnit", record.get("sourceUnitRef")); 283 return unit != null && "OBSERVED".equals(unit.get("derivedTextStatus")); 284 } 285 286 // --------------------------------------------------------------------------------------------- 287 // Ambiguous references (Sections 6 and 7.1) 288 // --------------------------------------------------------------------------------------------- 289 290 static final class Choice { 291 final Object ref; 292 final Object alternative; 293 final String side; 294 295 Choice(Object ref, Object alternative, String side) { 296 this.ref = norm(ref); 297 this.alternative = alternative == NOT_LISTED || alternative == UNPAIRED ? alternative : norm(alternative); 298 this.side = side; 299 } 300 } 301 302 static boolean ambiguous(Object binding) { 303 if (!(binding instanceof Map<?, ?>)) { 304 return false; 305 } 306 Object status = ((Map<?, ?>) binding).get("status"); 307 return "CANDIDATE".equals(status) || "UNRESOLVED".equals(status); 308 } 309 310 /** 311 * (reference, alternative, side) for each ambiguous binding of a micro-edge, source side first. A walk keeps the 312 * references as written (they make its group, Section 7.1, item 4); the worlds are those of their deciding 313 * references (decidedSigma). 314 */ 315 List<Choice> choices(Map<?, ?> edge) { 316 return rawChoices(edge); 317 } 318 319 /** Whether a reference is decided by another (its candidate set has decidedBy, Section 4.3). */ 320 boolean isDecided(Object ref) { 321 Map<?, ?> cset = ref instanceof Long ? graphCsets.get(ref) : null; 322 return cset != null && cset.get("decidedBy") instanceof Map<?, ?>; 323 } 324 325 /** 326 * Sigma.extend, and two references decided by one reference take one of its alternatives (Section 7.1, item 1): 327 * null when they take two. 328 */ 329 Sigma extend(Sigma sigma, List<Choice> choices) { 330 Sigma out = sigma.extend(choices); 331 if (out == null || choices.isEmpty()) { 332 return out; 333 } 334 boolean decided = false; 335 for (Object r : out.refs) { 336 decided |= isDecided(r); 337 } 338 if (!decided) { 339 return out; 340 } 341 // every choice as worlds see it, the deciding reference's own included (Codex 13a #4) 342 Map<Object, Object> taken = new HashMap<Object, Object>(); 343 for (int i = 0; i < out.refs.length; i++) { 344 Choice d = decide(new Choice(out.refs[i], out.alternatives[i], null)); 345 if (taken.containsKey(d.ref) && !sameAlternative(taken.get(d.ref), d.alternative)) { 346 return null; 347 } 348 taken.put(d.ref, d.alternative); 349 } 350 return out; 351 } 352 353 /** The choices of a walk as worlds see them: a decided reference's as its deciding reference's (Section 7.1). */ 354 Sigma decidedSigma(Sigma sigma) { 355 boolean decided = false; 356 for (Object r : sigma.refs) { 357 decided |= isDecided(r); 358 } 359 if (!decided) { 360 return sigma; 361 } 362 TreeMap<Object, Object> chosen = new TreeMap<Object, Object>(REF_ORDER); 363 for (int i = 0; i < sigma.refs.length; i++) { 364 Choice d = decide(new Choice(sigma.refs[i], sigma.alternatives[i], null)); 365 if (!chosen.containsKey(d.ref)) { 366 chosen.put(d.ref, d.alternative); 367 } 368 } 369 return new Sigma(chosen.keySet().toArray(), chosen.values().toArray()); 370 } 371 372 /** A decided reference's alternative of an edge that its alignment pairs with no alternative. */ 373 static final Object UNPAIRED = new Object() { 374 @Override 375 public String toString() { 376 return "UNPAIRED"; 377 } 378 }; 379 380 /** 381 * The choice of the deciding reference that a decided reference's choice is: the alternative its alignment pairs 382 * with the candidate (UNPAIRED when none, which the shape check reports). Other choices are returned as they are. 383 */ 384 Choice decide(Choice choice) { 385 Map<?, ?> cset = choice.ref instanceof Long ? graphCsets.get(choice.ref) : null; 386 Object decided = cset == null ? null : cset.get("decidedBy"); 387 if (!(decided instanceof Map<?, ?>)) { 388 return choice; 389 } 390 Object deciding = ((Map<?, ?>) decided).get("candidateSetRef"); 391 if (choice.alternative != NOT_LISTED) { 392 for (Map<?, ?> pair : items(((Map<?, ?>) decided).get("alignment"))) { 393 if (sameAlternative(norm(pair.get("candidateRef")), choice.alternative)) { 394 return new Choice(deciding, pair.get("alternativeRef"), choice.side); 395 } 396 } 397 } 398 return new Choice(deciding, UNPAIRED, choice.side); 399 } 400 401 /** The candidate set a binding names, as the reference the fold chooses for: its deciding set when it is decided. */ 402 Object choiceRef(Object candidateSetRef) { 403 Long key = intRef(candidateSetRef); 404 Map<?, ?> cset = key == null ? null : graphCsets.get(key); 405 Object decided = cset == null ? null : cset.get("decidedBy"); 406 return decided instanceof Map<?, ?> ? norm(((Map<?, ?>) decided).get("candidateSetRef")) : norm(candidateSetRef); 407 } 408 409 /** (reference, alternative, side) for each ambiguous binding of a micro-edge as written, source side first. */ 410 List<Choice> rawChoices(Map<?, ?> edge) { 411 List<Choice> out = new ArrayList<Choice>(2); 412 Object binding = edge.get("sourceBinding"); 413 if (ambiguous(binding)) { 414 Object source = edge.get("sourceRef"); 415 out.add(new Choice(((Map<?, ?>) binding).get("candidateSetRef"), graphCset(source) ? NOT_LISTED : source, 416 SOURCE)); 417 } 418 binding = edge.get("targetBinding"); 419 if (ambiguous(binding)) { 420 out.add(new Choice(((Map<?, ?>) binding).get("candidateSetRef"), edge.get("targetRef"), TARGET)); 421 } 422 return out; 423 } 424 425 /** The members of a candidate set of the graph. */ 426 List<Object> members(Object ref) { 427 List<Object> out = alternatives(ref); 428 out.remove(NOT_LISTED); 429 return out; 430 } 431 432 /** The alternatives of a reference: its members, and NOT_LISTED unless its membership is COMPLETE. */ 433 List<Object> alternatives(Object ref) { 434 Long key = intRef(ref); 435 Map<?, ?> cset = key == null ? null : graphCsets.get(key); 436 List<Object> out = new ArrayList<Object>(); 437 if (cset != null && cset.get("candidateRefs") instanceof List<?>) { 438 for (Object member : (List<?>) cset.get("candidateRefs")) { 439 if (intRef(member) != null) { 440 out.add(intRef(member)); 441 } 442 } 443 } 444 if (cset == null || !"COMPLETE".equals(cset.get("membership"))) { 445 out.add(NOT_LISTED); 446 } 447 return out; 448 } 449 450 boolean referenceComplete(Object ref) { 451 Long key = intRef(ref); 452 Map<?, ?> cset = key == null ? null : graphCsets.get(key); 453 return cset != null && "COMPLETE".equals(cset.get("membership")); 454 } 455 456 Set<String> sidesOf(Object ref) { 457 Set<String> sides = refSide.get(norm(ref)); 458 return sides == null ? Collections.<String>emptySet() : sides; 459 } 460 461 /** The choices of a walk: one alternative per reference, as an immutable sorted map. */ 462 static final class Sigma { 463 static final Sigma EMPTY = new Sigma(new Object[0], new Object[0]); 464 final Object[] refs; 465 final Object[] alternatives; 466 private final int hash; 467 468 private Sigma(Object[] refs, Object[] alternatives) { 469 this.refs = refs; 470 this.alternatives = alternatives; 471 int h = 1; 472 for (int i = 0; i < refs.length; i++) { 473 h = h * 31 + hash(refs[i]); 474 h = h * 31 + (alternatives[i] == NOT_LISTED ? 7 : hash(alternatives[i])); 475 } 476 this.hash = h; 477 } 478 479 boolean isEmpty() { 480 return refs.length == 0; 481 } 482 483 int indexOf(Object ref) { 484 for (int i = 0; i < refs.length; i++) { 485 if (same(refs[i], ref)) { 486 return i; 487 } 488 } 489 return -1; 490 } 491 492 /** The choices extended by one micro-edge's, or null when the edge takes another alternative. */ 493 Sigma extend(List<Choice> choices) { 494 TreeMap<Object, Object> chosen = null; 495 for (Choice choice : choices) { 496 boolean has; 497 Object current; 498 if (chosen != null) { 499 has = chosen.containsKey(choice.ref); 500 current = chosen.get(choice.ref); 501 } else { 502 int at = indexOf(choice.ref); 503 has = at >= 0; 504 current = has ? alternatives[at] : null; 505 } 506 if (has) { 507 if (!sameAlternative(current, choice.alternative)) { 508 return null; 509 } 510 continue; 511 } 512 if (chosen == null) { 513 chosen = new TreeMap<Object, Object>(REF_ORDER); 514 for (int i = 0; i < refs.length; i++) { 515 chosen.put(refs[i], alternatives[i]); 516 } 517 } 518 chosen.put(choice.ref, choice.alternative); 519 } 520 return chosen == null ? this : new Sigma(chosen.keySet().toArray(), chosen.values().toArray()); 521 } 522 523 @Override 524 public boolean equals(Object other) { 525 if (!(other instanceof Sigma)) { 526 return false; 527 } 528 Sigma sigma = (Sigma) other; 529 if (sigma.hash != hash || sigma.refs.length != refs.length) { 530 return false; 531 } 532 for (int i = 0; i < refs.length; i++) { 533 if (!same(refs[i], sigma.refs[i]) || !sameAlternative(alternatives[i], sigma.alternatives[i])) { 534 return false; 535 } 536 } 537 return true; 538 } 539 540 @Override 541 public int hashCode() { 542 return hash; 543 } 544 } 545 546 static boolean sameAlternative(Object a, Object b) { 547 if (a == NOT_LISTED || b == NOT_LISTED) { 548 return a == b; 549 } 550 return same(a, b); 551 } 552 553 /** 554 * Section 7.1, item 2 ({@code COVERS} of Section 9): the choice sets hold in every world. Splits the worlds on 555 * the smallest reference at a time, with an explicit stack. 556 */ 557 boolean covers(Set<Sigma> sigmas) { 558 final class Frame { 559 final List<Sigma> sets; 560 Object ref; 561 List<Object> alternatives; 562 int next; 563 564 Frame(List<Sigma> sets) { 565 this.sets = sets; 566 } 567 } 568 Deque<Frame> stack = new ArrayDeque<Frame>(); 569 List<Sigma> worlds = new ArrayList<Sigma>(); 570 for (Sigma sigma : sigmas) { 571 worlds.add(decidedSigma(sigma)); // a decided reference holds in its deciding reference's worlds 572 } 573 stack.push(new Frame(worlds)); 574 Boolean returned = null; 575 long work = 0; 576 while (!stack.isEmpty()) { 577 Frame frame = stack.peek(); 578 if (returned != null) { 579 if (!returned) { 580 stack.pop(); 581 continue; 582 } 583 returned = null; 584 } 585 if (frame.alternatives == null) { 586 if (++work > maxWorlds) { 587 throw new FoldLimitException("deciding a Fact needs more than " + maxWorlds + " worlds"); 588 } 589 boolean anyEmpty = false; 590 Object smallest = null; 591 boolean first = true; 592 for (Sigma sigma : frame.sets) { 593 if (sigma.isEmpty()) { 594 anyEmpty = true; 595 break; 596 } 597 if (first || REF_ORDER.compare(sigma.refs[0], smallest) < 0) { 598 smallest = sigma.refs[0]; 599 first = false; 600 } 601 } 602 if (anyEmpty || frame.sets.isEmpty()) { 603 returned = anyEmpty; 604 stack.pop(); 605 continue; 606 } 607 frame.ref = smallest; 608 frame.alternatives = alternatives(smallest); 609 } 610 if (frame.next < frame.alternatives.size()) { 611 Object alternative = frame.alternatives.get(frame.next++); 612 List<Sigma> rest = new ArrayList<Sigma>(); 613 for (Sigma sigma : frame.sets) { 614 int at = sigma.indexOf(frame.ref); 615 if (at < 0) { 616 rest.add(sigma); 617 } else if (sameAlternative(sigma.alternatives[at], alternative)) { 618 rest.add(without(sigma, at)); 619 } 620 } 621 stack.push(new Frame(rest)); 622 continue; 623 } 624 returned = Boolean.TRUE; 625 stack.pop(); 626 } 627 return returned != null && returned; 628 } 629 630 private static Sigma without(Sigma sigma, int at) { 631 Object[] refs = new Object[sigma.refs.length - 1]; 632 Object[] alternatives = new Object[refs.length]; 633 for (int i = 0, j = 0; i < sigma.refs.length; i++) { 634 if (i != at) { 635 refs[j] = sigma.refs[i]; 636 alternatives[j++] = sigma.alternatives[i]; 637 } 638 } 639 return refs.length == 0 ? Sigma.EMPTY : new Sigma(refs, alternatives); 640 } 641 642 // --------------------------------------------------------------------------------------------- 643 // Section 4.1: micro-edge shapes 644 // --------------------------------------------------------------------------------------------- 645 646 /** {@code SEMANTIC_FOLD_MALFORMED_EDGE} for every micro-edge of the document whose shape is not conformant. */ 647 public List<Finding> malformedEdges() { 648 List<Finding> findings = new ArrayList<Finding>(); 649 for (Map<?, ?> edge : allEdges) { 650 String problem = shapeProblem(edge); 651 if (problem != null) { 652 findings.add(new Finding("SEMANTIC_FOLD_MALFORMED_EDGE", "explainGraph.microEdges ref " + edge.get("ref"), 653 problem)); 654 } 655 } 656 return findings; 657 } 658 659 String shapeProblem(Map<?, ?> edge) { 660 Object source = edge.get("sourceRef"); 661 Object target = edge.get("targetRef"); 662 String sourceClass = endClass(source); 663 String targetClass = endClass(target); 664 Object statement = edge.get("statementRef"); 665 String problem = null; 666 if (sourceClass == null || targetClass == null) { 667 problem = "an end is neither field-class nor row-set-class"; 668 } else if ("VALUE".equals(edge.get("kind")) && ("ROWSET".equals(sourceClass) || "ROWSET".equals(targetClass))) { 669 problem = "a VALUE micro-edge touches a row set"; 670 } else if ("ROW".equals(edge.get("kind")) && "FIELD".equals(sourceClass) && "FIELD".equals(targetClass) 671 && "ROW_PROPAGATION".equals(edge.get("role"))) { 672 problem = "a field-to-field ROW micro-edge with role ROW_PROPAGATION"; 673 } else if (terminal(target)) { 674 problem = "a micro-edge into a TERMINAL node"; 675 } else if (graphCset(source) && !(edge.get("sourceBinding") instanceof Map<?, ?> 676 && "UNRESOLVED".equals(((Map<?, ?>) edge.get("sourceBinding")).get("status")))) { 677 problem = "a micro-edge from a candidate set without an UNRESOLVED source binding"; 678 } else { 679 for (Object end : new Object[] {source, target}) { 680 Map<?, ?> node = node(end); 681 if (node != null && !same(node.get("statementRef"), statement)) { 682 problem = "an end belongs to another statement"; 683 } 684 } 685 for (Choice choice : rawChoices(edge)) { 686 if (!(choice.ref instanceof Long) || !graphCsets.containsKey(choice.ref)) { 687 problem = "an ambiguous binding names no candidate set of the graph"; 688 } else if (choice.alternative != NOT_LISTED && !containsAlternative(members(choice.ref), 689 choice.alternative)) { 690 problem = "an ambiguous binding's edge reads or writes no member of its candidate set"; 691 } else { 692 Choice decided = decide(choice); 693 if (decided.alternative == UNPAIRED || !(decided.ref instanceof Long) 694 || !graphCsets.containsKey(decided.ref)) { 695 problem = "a decided reference's candidate has no alternative of a deciding set"; 696 } 697 } 698 } 699 } 700 return problem; 701 } 702 703 private static boolean containsAlternative(List<Object> alternatives, Object alternative) { 704 for (Object a : alternatives) { 705 if (sameAlternative(a, alternative)) { 706 return true; 707 } 708 } 709 return false; 710 } 711 712 // --------------------------------------------------------------------------------------------- 713 // Section 9: exploration 714 // --------------------------------------------------------------------------------------------- 715 716 static FoldLabel edgeLabel(Map<?, ?> edge) { 717 return FoldLabel.of(edge.get("kind"), edge.get("role")); 718 } 719 720 /** Section 7.2, item 1, for one micro-edge; observed is Section 7.3, item 2. */ 721 static boolean assumed(Map<?, ?> edge, boolean observed) { 722 if (observed) { 723 return true; 724 } 725 for (String member : new String[] {"sourceBinding", "targetBinding"}) { 726 Object binding = edge.get(member); 727 if (binding instanceof Map<?, ?> && ASSUMED.equals(((Map<?, ?>) binding).get("status"))) { 728 return true; 729 } 730 } 731 return ASSUMED.equals(edge.get("proofStatus")); 732 } 733 734 private static final class State { 735 final Object node; 736 final FoldLabel label; 737 final Sigma sigma; 738 final boolean assumed; 739 private final int hash; 740 741 State(Object node, FoldLabel label, Sigma sigma, boolean assumed) { 742 this.node = norm(node); 743 this.label = label; 744 this.sigma = sigma; 745 this.assumed = assumed; 746 this.hash = ((hash(this.node) * 31 + label.hashCode()) * 31 + sigma.hashCode()) * 2 + (assumed ? 1 : 0); 747 } 748 749 @Override 750 public boolean equals(Object other) { 751 if (!(other instanceof State)) { 752 return false; 753 } 754 State state = (State) other; 755 return state.hash == hash && state.assumed == assumed && same(node, state.node) 756 && label.equals(state.label) && sigma.equals(state.sigma); 757 } 758 759 @Override 760 public int hashCode() { 761 return hash; 762 } 763 } 764 765 /** One fold walk, or several with the same first edge, target, label, choices and proof. */ 766 static final class Walk { 767 final Map<?, ?> first; 768 final Object source; 769 final Object target; 770 final FoldLabel label; 771 final Sigma sigma; 772 final boolean assumed; 773 final Set<Long> support = new TreeSet<Long>(); 774 final List<Map<?, ?>> lastEdges = new ArrayList<Map<?, ?>>(); 775 /** Fold walks (micro-edge refs in walk order) that together cover {@link #support}. */ 776 final List<List<Long>> covering = new ArrayList<List<Long>>(); 777 778 Walk(Map<?, ?> first, State end) { 779 this.first = first; 780 this.source = norm(first.get("sourceRef")); 781 this.target = end.node; 782 this.label = end.label; 783 this.sigma = end.sigma; 784 this.assumed = end.assumed; 785 } 786 } 787 788 private static final class Predecessor { 789 final State state; 790 final Long edge; 791 792 Predecessor(State state, Long edge) { 793 this.state = state; 794 this.edge = edge; 795 } 796 } 797 798 /** Every fold walk of a statement, in the order the reference emits them, merged as {@link Walk} says. */ 799 List<Walk> walks(long statement) { 800 List<Map<?, ?>> edges = edgesOf(statement); 801 boolean observed = observed(statement); 802 Map<Object, List<Map<?, ?>>> out = new HashMap<Object, List<Map<?, ?>>>(); 803 for (Map<?, ?> edge : edges) { 804 Object source = norm(edge.get("sourceRef")); 805 List<Map<?, ?>> list = out.get(source); 806 if (list == null) { 807 list = new ArrayList<Map<?, ?>>(); 808 out.put(source, list); 809 } 810 list.add(edge); 811 } 812 List<Walk> walks = new ArrayList<Walk>(); 813 long[] states = {0}; 814 for (Map<?, ?> first : edges) { 815 Object source = first.get("sourceRef"); 816 if (!(kept(source) || graphCset(source))) { 817 continue; 818 } 819 Sigma sigma0 = extend(Sigma.EMPTY, choices(first)); 820 if (sigma0 == null) { 821 continue; 822 } 823 State x0 = new State(first.get("targetRef"), edgeLabel(first), sigma0, assumed(first, observed)); 824 count(states, statement); 825 Long firstRef = intRef(first.get("ref")); 826 LinkedHashMap<State, Walk> emitted = new LinkedHashMap<State, Walk>(); 827 Map<State, List<State>> befores = new HashMap<State, List<State>>(); 828 Map<State, List<Predecessor>> pred = new HashMap<State, List<Predecessor>>(); 829 if (kept(x0.node)) { 830 Walk walk = new Walk(first, x0); 831 walk.lastEdges.add(first); 832 walk.support.add(firstRef); 833 walk.covering.add(Collections.singletonList(firstRef)); 834 emitted.put(x0, walk); 835 } else { 836 List<Predecessor> start = new ArrayList<Predecessor>(1); 837 start.add(new Predecessor(null, firstRef)); 838 pred.put(x0, start); 839 Set<State> visited = new HashSet<State>(); 840 visited.add(x0); 841 ArrayDeque<State> queue = new ArrayDeque<State>(); 842 queue.add(x0); 843 while (!queue.isEmpty()) { 844 State state = queue.poll(); 845 List<Map<?, ?>> next = out.get(state.node); 846 if (next == null) { 847 continue; 848 } 849 for (Map<?, ?> edge : next) { 850 Sigma chosen = extend(state.sigma, choices(edge)); 851 if (chosen == null) { 852 continue; 853 } 854 State y = new State(edge.get("targetRef"), state.label.compose(edgeLabel(edge)), chosen, 855 state.assumed || assumed(edge, observed)); 856 Long edgeRef = intRef(edge.get("ref")); 857 if (kept(y.node)) { 858 Walk walk = emitted.get(y); 859 if (walk == null) { 860 count(states, statement); 861 walk = new Walk(first, y); 862 emitted.put(y, walk); 863 befores.put(y, new ArrayList<State>()); 864 } 865 walk.lastEdges.add(edge); 866 walk.support.add(edgeRef); 867 befores.get(y).add(state); 868 } else { 869 List<Predecessor> list = pred.get(y); 870 if (list == null) { 871 list = new ArrayList<Predecessor>(2); 872 pred.put(y, list); 873 } 874 list.add(new Predecessor(state, edgeRef)); 875 if (visited.add(y)) { 876 count(states, statement); 877 queue.add(y); 878 } 879 } 880 } 881 } 882 } 883 for (Map.Entry<State, Walk> entry : emitted.entrySet()) { 884 Walk walk = entry.getValue(); 885 List<State> from = befores.get(entry.getKey()); 886 if (from != null) { 887 // insertion order, so that the covering walks are the same on every JVM 888 Set<State> reaching = new LinkedHashSet<State>(from); 889 Deque<State> stack = new ArrayDeque<State>(from); 890 while (!stack.isEmpty()) { 891 State s = stack.pop(); 892 List<Predecessor> list = pred.get(s); 893 if (list == null) { 894 continue; 895 } 896 for (Predecessor p : list) { 897 walk.support.add(p.edge); 898 if (p.state != null && reaching.add(p.state)) { 899 stack.push(p.state); 900 } 901 } 902 } 903 cover(walk, x0, from, reaching, pred); 904 } 905 walks.add(walk); 906 } 907 } 908 return walks; 909 } 910 911 private void count(long[] states, long statement) { 912 if (++states[0] > maxStates) { 913 throw new FoldLimitException("statement ref " + statement + " needs more than " + maxStates 914 + " exploration states"); 915 } 916 } 917 918 /** 919 * Section 8.2, items 3 and 4: fold walks that each yield the record and that together contain every micro-edge 920 * of its support subgraph. Each walk is a path of the explored state graph from the first edge to an emission of 921 * the record, so it holds in the record's worlds; one walk is added for each micro-edge not yet covered. 922 */ 923 private static void cover(Walk walk, State x0, List<State> befores, Set<State> reaching, 924 Map<State, List<Predecessor>> pred) { 925 // the emissions: the state before the last edge, and that edge, in the order they were found 926 List<Object[]> emissions = new ArrayList<Object[]>(); 927 for (int k = 0; k < befores.size(); k++) { 928 emissions.add(new Object[] {befores.get(k), intRef(walk.lastEdges.get(k).get("ref"))}); 929 } 930 Map<State, List<Object[]>> successors = new LinkedHashMap<State, List<Object[]>>(); 931 for (State s : reaching) { 932 List<Predecessor> list = pred.get(s); 933 if (list == null) { 934 continue; 935 } 936 for (Predecessor p : list) { 937 if (p.state != null && reaching.contains(p.state)) { 938 List<Object[]> next = successors.get(p.state); 939 if (next == null) { 940 next = new ArrayList<Object[]>(); 941 successors.put(p.state, next); 942 } 943 next.add(new Object[] {s, p.edge}); 944 } 945 } 946 } 947 Set<Long> uncovered = new TreeSet<Long>(walk.support); 948 while (!uncovered.isEmpty()) { 949 Long edge = uncovered.iterator().next(); 950 List<Long> found = null; 951 for (Object[] emission : emissions) { 952 if (edge.equals(emission[1])) { 953 found = concat(prefix(x0, (State) emission[0], pred, reaching), (Long) emission[1]); 954 break; 955 } 956 } 957 for (Iterator<State> it = reaching.iterator(); found == null && it.hasNext(); ) { 958 State s = it.next(); 959 for (Predecessor p : pred.get(s)) { 960 if (!edge.equals(p.edge)) { 961 continue; 962 } 963 List<Long> head = p.state == null ? new ArrayList<Long>() : prefix(x0, p.state, pred, reaching); 964 head.add(p.edge); 965 found = suffix(head, s, successors, emissions); 966 break; 967 } 968 } 969 if (found == null) { 970 throw new IllegalStateException("micro-edge " + edge + " of a support subgraph lies on no walk"); 971 } 972 uncovered.removeAll(found); 973 walk.covering.add(Collections.unmodifiableList(found)); 974 } 975 } 976 977 private static List<Long> concat(List<Long> head, Long edge) { 978 head.add(edge); 979 return head; 980 } 981 982 /** The micro-edges of a shortest walk from the first edge to the given state, the first edge included. */ 983 private static List<Long> prefix(State x0, State to, Map<State, List<Predecessor>> pred, Set<State> reaching) { 984 Map<State, Predecessor> parent = new HashMap<State, Predecessor>(); 985 Deque<State> queue = new ArrayDeque<State>(); 986 queue.add(to); 987 parent.put(to, null); 988 State start = null; 989 while (!queue.isEmpty()) { 990 State s = queue.poll(); 991 for (Predecessor p : pred.get(s)) { 992 if (p.state == null) { 993 start = s; 994 break; 995 } 996 if (!parent.containsKey(p.state)) { 997 parent.put(p.state, new Predecessor(s, p.edge)); 998 queue.add(p.state); 999 } 1000 } 1001 if (start != null) { 1002 break; 1003 } 1004 } 1005 // start is x0; follow the parents (each names the state after it and the edge between) back to `to` 1006 List<Long> out = new ArrayList<Long>(); 1007 out.add(pred.get(start).get(0).edge); 1008 for (State s = start; parent.get(s) != null; s = parent.get(s).state) { 1009 out.add(parent.get(s).edge); 1010 } 1011 return out; 1012 } 1013 1014 /** The walk extended from a state to an emission along the explored successors. */ 1015 private static List<Long> suffix(List<Long> head, State from, Map<State, List<Object[]>> successors, 1016 List<Object[]> emissions) { 1017 Map<State, Object[]> parent = new HashMap<State, Object[]>(); 1018 Deque<State> queue = new ArrayDeque<State>(); 1019 queue.add(from); 1020 parent.put(from, null); 1021 while (!queue.isEmpty()) { 1022 State s = queue.poll(); 1023 for (Object[] emission : emissions) { 1024 if (emission[0].equals(s)) { 1025 List<Long> tail = new ArrayList<Long>(); 1026 for (State t = s; parent.get(t) != null; t = (State) parent.get(t)[0]) { 1027 tail.add(0, (Long) parent.get(t)[1]); 1028 } 1029 List<Long> out = new ArrayList<Long>(head); 1030 out.addAll(tail); 1031 out.add((Long) emission[1]); 1032 return out; 1033 } 1034 } 1035 List<Object[]> next = successors.get(s); 1036 if (next == null) { 1037 continue; 1038 } 1039 for (Object[] step : next) { 1040 State t = (State) step[0]; 1041 if (!parent.containsKey(t)) { 1042 parent.put(t, new Object[] {s, step[1]}); 1043 queue.add(t); 1044 } 1045 } 1046 } 1047 return null; 1048 } 1049 1050 // --------------------------------------------------------------------------------------------- 1051 // Section 7.1: Facts and the remainder 1052 // --------------------------------------------------------------------------------------------- 1053 1054 private static final class PropositionKey { 1055 final Object source; 1056 final Object target; 1057 final FoldLabel label; 1058 1059 PropositionKey(Object source, Object target, FoldLabel label) { 1060 this.source = source; 1061 this.target = target; 1062 this.label = label; 1063 } 1064 1065 @Override 1066 public boolean equals(Object other) { 1067 if (!(other instanceof PropositionKey)) { 1068 return false; 1069 } 1070 PropositionKey key = (PropositionKey) other; 1071 return same(source, key.source) && same(target, key.target) && label.equals(key.label); 1072 } 1073 1074 @Override 1075 public int hashCode() { 1076 return (hash(source) * 31 + hash(target)) * 31 + label.hashCode(); 1077 } 1078 } 1079 1080 /** One Fact Occurrence before its ordinal is known: (source, target, label, path, offset). */ 1081 private static final class FactKey { 1082 final PropositionKey proposition; 1083 final Object path; 1084 final Long offset; 1085 1086 FactKey(PropositionKey proposition, Object path, Long offset) { 1087 this.proposition = proposition; 1088 this.path = path; 1089 this.offset = offset; 1090 } 1091 1092 @Override 1093 public boolean equals(Object other) { 1094 if (!(other instanceof FactKey)) { 1095 return false; 1096 } 1097 FactKey key = (FactKey) other; 1098 return proposition.equals(key.proposition) && same(path, key.path) && same(offset, key.offset); 1099 } 1100 1101 @Override 1102 public int hashCode() { 1103 return (proposition.hashCode() * 31 + hash(path)) * 31 + hash(offset); 1104 } 1105 } 1106 1107 private static final class FactEntry { 1108 boolean proven; 1109 final Set<Long> support = new TreeSet<Long>(); 1110 final List<Walk> walks = new ArrayList<Walk>(); 1111 } 1112 1113 static Long offsetOf(Object position) { 1114 return position instanceof Map<?, ?> ? intRef(((Map<?, ?>) position).get("utf8ByteOffset0")) : null; 1115 } 1116 1117 /** Folds one statement (Section 9). */ 1118 public StatementFold fold(long statement) { 1119 List<Walk> walks = walks(statement); 1120 LinkedHashMap<PropositionKey, List<Walk>> propositions = new LinkedHashMap<PropositionKey, List<Walk>>(); 1121 for (Walk walk : walks) { 1122 PropositionKey key = new PropositionKey(walk.source, walk.target, walk.label); 1123 List<Walk> list = propositions.get(key); 1124 if (list == null) { 1125 list = new ArrayList<Walk>(); 1126 propositions.put(key, list); 1127 } 1128 list.add(walk); 1129 } 1130 LinkedHashMap<FactKey, FactEntry> facts = new LinkedHashMap<FactKey, FactEntry>(); 1131 List<Walk> remainder = new ArrayList<Walk>(); 1132 for (Map.Entry<PropositionKey, List<Walk>> entry : propositions.entrySet()) { 1133 PropositionKey proposition = entry.getKey(); 1134 Set<Sigma> sigmas = new LinkedHashSet<Sigma>(); 1135 for (Walk walk : entry.getValue()) { 1136 sigmas.add(walk.sigma); 1137 } 1138 if (kept(proposition.source) && covers(sigmas)) { 1139 for (Walk walk : entry.getValue()) { 1140 FactKey key = new FactKey(proposition, walk.first.get("referencePath"), 1141 offsetOf(walk.first.get("referenceStart"))); 1142 FactEntry fact = facts.get(key); 1143 if (fact == null) { 1144 fact = new FactEntry(); 1145 facts.put(key, fact); 1146 } 1147 if (walk.sigma.isEmpty() && !walk.assumed) { 1148 fact.proven = true; 1149 } 1150 fact.support.addAll(walk.support); 1151 fact.walks.add(walk); 1152 } 1153 } else { 1154 for (Walk walk : entry.getValue()) { 1155 if (!walk.sigma.isEmpty()) { 1156 remainder.add(walk); 1157 } 1158 } 1159 } 1160 } 1161 List<StatementFold.FactOccurrence> occurrences = factOccurrences(facts); 1162 List<StatementFold.Candidate> candidates = candidates(remainder); 1163 assignCandidateOrdinals(candidates); 1164 return new StatementFold(statement, occurrences, candidates, writes(statement, occurrences, candidates)); 1165 } 1166 1167 private static final Comparator<Long> OFFSET_ORDER = new Comparator<Long>() { 1168 @Override 1169 public int compare(Long a, Long b) { 1170 if (a == null || b == null) { 1171 return a == null ? (b == null ? 0 : 1) : -1; 1172 } 1173 return a.compareTo(b); 1174 } 1175 }; 1176 1177 private List<StatementFold.FactOccurrence> factOccurrences(LinkedHashMap<FactKey, FactEntry> facts) { 1178 // Section 8.1: the ordinal ranks the Occurrences of one Fact and path by position, missing positions last. 1179 LinkedHashMap<List<Object>, List<FactKey>> groups = new LinkedHashMap<List<Object>, List<FactKey>>(); 1180 for (FactKey key : facts.keySet()) { 1181 List<Object> group = Arrays.<Object>asList(subjectKey(key.proposition.source), 1182 subjectKey(key.proposition.target), key.proposition.label, new PathKey(key.path)); 1183 List<FactKey> list = groups.get(group); 1184 if (list == null) { 1185 list = new ArrayList<FactKey>(); 1186 groups.put(group, list); 1187 } 1188 list.add(key); 1189 } 1190 List<StatementFold.FactOccurrence> out = new ArrayList<StatementFold.FactOccurrence>(); 1191 for (List<FactKey> list : groups.values()) { 1192 Collections.sort(list, new Comparator<FactKey>() { 1193 @Override 1194 public int compare(FactKey a, FactKey b) { 1195 int c = OFFSET_ORDER.compare(a.offset, b.offset); 1196 if (c == 0) { 1197 c = REF_ORDER.compare(a.proposition.source, b.proposition.source); 1198 } 1199 return c != 0 ? c : REF_ORDER.compare(a.proposition.target, b.proposition.target); 1200 } 1201 }); 1202 for (int ordinal = 0; ordinal < list.size(); ordinal++) { 1203 FactKey key = list.get(ordinal); 1204 FactEntry entry = facts.get(key); 1205 List<List<Long>> covering = new ArrayList<List<Long>>(); 1206 for (Walk walk : entry.walks) { 1207 covering.addAll(walk.covering); 1208 } 1209 List<Walk> deciding = decidingWalks(entry); 1210 List<Map<?, ?>> firsts = new ArrayList<Map<?, ?>>(); 1211 List<Map<?, ?>> lasts = new ArrayList<Map<?, ?>>(); 1212 for (Walk walk : deciding) { 1213 firsts.add(walk.first); 1214 lasts.addAll(walk.lastEdges); 1215 } 1216 out.add(new StatementFold.FactOccurrence(key.proposition.source, key.proposition.target, 1217 key.proposition.label, key.path, key.offset, ordinal, entry.proven ? PROVEN : ASSUMED, 1218 entry.support, earliestBinding(firsts, "sourceBinding"), earliestBinding(lasts, "targetBinding"), 1219 minimalCover(covering, entry.support))); 1220 } 1221 } 1222 Collections.sort(out, new Comparator<StatementFold.FactOccurrence>() { 1223 @Override 1224 public int compare(StatementFold.FactOccurrence a, StatementFold.FactOccurrence b) { 1225 int c = REF_ORDER.compare(a.getSourceRef(), b.getSourceRef()); 1226 if (c == 0) { 1227 c = REF_ORDER.compare(a.getTargetRef(), b.getTargetRef()); 1228 } 1229 if (c == 0) { 1230 c = a.getLabel().sortText().compareTo(b.getLabel().sortText()); 1231 } 1232 if (c == 0) { 1233 c = PathKey.ORDER.compare(new PathKey(a.getPath()), new PathKey(b.getPath())); 1234 } 1235 return c != 0 ? c : a.getOrdinal() - b.getOrdinal(); 1236 } 1237 }); 1238 return out; 1239 } 1240 1241 /** The walks whose bindings an Occurrence reports: those that decide its proof (Section 7.2, item 2). */ 1242 private static List<Walk> decidingWalks(FactEntry entry) { 1243 List<Walk> unconditional = new ArrayList<Walk>(); 1244 for (Walk walk : entry.walks) { 1245 if (walk.sigma.isEmpty() && (!entry.proven || !walk.assumed)) { 1246 unconditional.add(walk); 1247 } 1248 } 1249 return unconditional.isEmpty() ? entry.walks : unconditional; 1250 } 1251 1252 /** 1253 * The identity an endpoint stands for in an Occurrence subject (identity-v1, Section 5.4): its portable id when 1254 * it has one, so that two refs of one endpoint rank as one subject; otherwise its ref. 1255 */ 1256 Object subjectKey(Object ref) { 1257 Map<?, ?> endpoint = index.get("endpoint", ref); 1258 Object identity = endpoint == null ? null : endpoint.get("identity"); 1259 if (identity instanceof Map<?, ?> && "PORTABLE".equals(((Map<?, ?>) identity).get("status")) 1260 && ((Map<?, ?>) identity).get("id") instanceof String) { 1261 return "id:" + ((Map<?, ?>) identity).get("id"); 1262 } 1263 return "ref:" + norm(ref); 1264 } 1265 1266 /** The walks of a cover, dropping each walk whose edges the others already cover, in order. */ 1267 static List<List<Long>> minimalCover(List<List<Long>> walks, Set<Long> support) { 1268 List<List<Long>> distinct = new ArrayList<List<Long>>(new LinkedHashSet<List<Long>>(walks)); 1269 Set<Long> covered = new HashSet<Long>(); 1270 List<List<Long>> out = new ArrayList<List<Long>>(); 1271 for (List<Long> walk : distinct) { 1272 if (!covered.containsAll(walk)) { 1273 out.add(walk); 1274 covered.addAll(walk); 1275 } 1276 } 1277 if (!covered.containsAll(support)) { 1278 throw new IllegalStateException("covering walks miss " + support); 1279 } 1280 return out; 1281 } 1282 1283 /** A path value with a total order: absent first, then strings in code point order. */ 1284 static final class PathKey { 1285 static final Comparator<PathKey> ORDER = new Comparator<PathKey>() { 1286 @Override 1287 public int compare(PathKey a, PathKey b) { 1288 if (a.path == null || b.path == null) { 1289 return a.path == null ? (b.path == null ? 0 : -1) : 1; 1290 } 1291 return compareCodePoints(String.valueOf(a.path), String.valueOf(b.path)); 1292 } 1293 }; 1294 final Object path; 1295 1296 PathKey(Object path) { 1297 this.path = path; 1298 } 1299 1300 @Override 1301 public boolean equals(Object other) { 1302 return other instanceof PathKey && same(path, ((PathKey) other).path); 1303 } 1304 1305 @Override 1306 public int hashCode() { 1307 return hash(path); 1308 } 1309 } 1310 1311 /** The position of a reference: its offset (absent last), then its path in code point order. */ 1312 static final class Position implements Comparable<Position> { 1313 final Long offset; 1314 final String path; 1315 1316 Position(Long offset, String path) { 1317 this.offset = offset; 1318 this.path = path; 1319 } 1320 1321 static Position of(Map<?, ?> edge) { 1322 Object path = edge.get("referencePath"); 1323 return new Position(offsetOf(edge.get("referenceStart")), 1324 path instanceof String && !((String) path).isEmpty() ? (String) path : ""); 1325 } 1326 1327 @Override 1328 public int compareTo(Position other) { 1329 int c = OFFSET_ORDER.compare(offset, other.offset); 1330 return c != 0 ? c : compareCodePoints(path, other.path); 1331 } 1332 } 1333 1334 /** 1335 * The binding on the given side of the micro-edge earliest in the text (position, then the canonical JSON of 1336 * the binding), so that equal inputs give equal bindings whatever their refs. 1337 */ 1338 static Map<?, ?> earliestBinding(List<Map<?, ?>> edges, String member) { 1339 Map<?, ?> best = null; 1340 Position bestPosition = null; 1341 byte[] bestBytes = null; 1342 for (Map<?, ?> edge : edges) { 1343 Object binding = edge.get(member); 1344 Map<?, ?> map = binding instanceof Map<?, ?> ? (Map<?, ?>) binding : null; 1345 Position position = Position.of(edge); 1346 byte[] bytes = map == null ? new byte[0] : CanonicalJson.jcsBytes(map); 1347 if (bestPosition == null) { 1348 best = map; 1349 bestPosition = position; 1350 bestBytes = bytes; 1351 continue; 1352 } 1353 int c = position.compareTo(bestPosition); 1354 if (c < 0 || (c == 0 && CanonicalJson.compareUnsigned(bytes, bestBytes) < 0)) { 1355 best = map; 1356 bestPosition = position; 1357 bestBytes = bytes; 1358 } 1359 } 1360 return best; 1361 } 1362 1363 // --------------------------------------------------------------------------------------------- 1364 // Section 7.1, items 4 to 6: FactCandidates 1365 // --------------------------------------------------------------------------------------------- 1366 1367 private List<StatementFold.Candidate> candidates(List<Walk> remainder) { 1368 TreeMap<String, List<Walk>> byLabel = new TreeMap<String, List<Walk>>(); 1369 for (Walk walk : remainder) { 1370 String key = walk.label.sortText(); 1371 List<Walk> list = byLabel.get(key); 1372 if (list == null) { 1373 list = new ArrayList<Walk>(); 1374 byLabel.put(key, list); 1375 } 1376 list.add(walk); 1377 } 1378 List<StatementFold.Candidate> results = new ArrayList<StatementFold.Candidate>(); 1379 for (List<Walk> group : byLabel.values()) { 1380 Map<Object, Object> parent = new HashMap<Object, Object>(); 1381 for (Walk walk : group) { 1382 Object[] refs = walk.sigma.refs; 1383 for (Object r : refs) { 1384 if (!parent.containsKey(r)) { 1385 parent.put(r, r); 1386 } 1387 } 1388 for (int i = 1; i < refs.length; i++) { 1389 parent.put(find(parent, refs[i]), find(parent, refs[0])); 1390 } 1391 } 1392 TreeMap<Object, List<Walk>> components = new TreeMap<Object, List<Walk>>(REF_ORDER); 1393 for (Walk walk : group) { 1394 Object root = find(parent, walk.sigma.refs[0]); 1395 List<Walk> list = components.get(root); 1396 if (list == null) { 1397 list = new ArrayList<Walk>(); 1398 components.put(root, list); 1399 } 1400 list.add(walk); 1401 } 1402 for (List<Walk> component : components.values()) { 1403 results.add(candidate(component.get(0).label, component)); 1404 } 1405 } 1406 return results; 1407 } 1408 1409 private static Object find(Map<Object, Object> parent, Object x) { 1410 Object current = x; 1411 while (!same(parent.get(current), current)) { 1412 current = parent.get(current); 1413 } 1414 return current; 1415 } 1416 1417 private StatementFold.Candidate candidate(FoldLabel label, List<Walk> walks) { 1418 // the references of the group as written (sides, deciding reference), and those whose alternatives make the 1419 // worlds: a decided reference's deciding reference (Section 7.1, items 1 and 5) 1420 TreeSet<Object> rawSet = new TreeSet<Object>(REF_ORDER); 1421 TreeSet<Object> refSet = new TreeSet<Object>(REF_ORDER); 1422 List<Sigma> worldSigmas = new ArrayList<Sigma>(); 1423 for (Walk walk : walks) { 1424 rawSet.addAll(Arrays.asList(walk.sigma.refs)); 1425 Sigma decided = decidedSigma(walk.sigma); 1426 worldSigmas.add(decided); 1427 refSet.addAll(Arrays.asList(decided.refs)); 1428 } 1429 List<Object> rawRefs = new ArrayList<Object>(rawSet); 1430 List<Object> refs = new ArrayList<Object>(refSet); 1431 List<List<Object>> alternatives = new ArrayList<List<Object>>(); 1432 long worlds = 1; 1433 for (Object r : refs) { 1434 List<Object> list = alternatives(r); 1435 alternatives.add(list); 1436 worlds *= list.size(); 1437 if (worlds > maxWorlds) { 1438 throw new FoldLimitException("a group of " + refs.size() + " linked references has more than " 1439 + maxWorlds + " worlds"); 1440 } 1441 } 1442 List<Set<StatementFold.Pair>> outcomes = new ArrayList<Set<StatementFold.Pair>>(); 1443 boolean emptyWorld = false; 1444 String proof = PROVEN; 1445 int n = walks.size(); 1446 int[][] at = new int[n][]; 1447 int[][] alt = new int[n][]; 1448 StatementFold.Pair[] pairOf = new StatementFold.Pair[n]; 1449 for (int k = 0; k < n; k++) { 1450 Sigma sigma = worldSigmas.get(k); 1451 at[k] = new int[sigma.refs.length]; 1452 alt[k] = new int[sigma.refs.length]; 1453 for (int j = 0; j < sigma.refs.length; j++) { 1454 at[k][j] = refs.indexOf(sigma.refs[j]); 1455 alt[k][j] = -1; 1456 List<Object> options = alternatives.get(at[k][j]); 1457 for (int a = 0; a < options.size(); a++) { 1458 if (sameAlternative(options.get(a), sigma.alternatives[j])) { 1459 alt[k][j] = a; 1460 break; 1461 } 1462 } 1463 } 1464 pairOf[k] = new StatementFold.Pair(walks.get(k).source, walks.get(k).target); 1465 } 1466 // an alternative listed twice is one value: compare slots by the first slot holding their value 1467 int[][] canonical = new int[refs.size()][]; 1468 for (int r = 0; r < refs.size(); r++) { 1469 List<Object> options = alternatives.get(r); 1470 canonical[r] = new int[options.size()]; 1471 for (int a = 0; a < options.size(); a++) { 1472 canonical[r][a] = a; 1473 for (int b = 0; b < a; b++) { 1474 if (sameAlternative(options.get(a), options.get(b))) { 1475 canonical[r][a] = b; 1476 break; 1477 } 1478 } 1479 } 1480 } 1481 int[] choice = new int[refs.size()]; 1482 // per world of a single deciding reference: its alternative and its pairs (the decision of an end) 1483 boolean decides = refs.size() == 1 && !rawRefs.equals(refs); 1484 List<Object> worldAlternative = new ArrayList<Object>(); 1485 List<Set<StatementFold.Pair>> worldPairs = new ArrayList<Set<StatementFold.Pair>>(); 1486 for (long w = 0; w < worlds; w++) { 1487 Set<StatementFold.Pair> pairs = new LinkedHashSet<StatementFold.Pair>(); 1488 Set<StatementFold.Pair> proven = new HashSet<StatementFold.Pair>(); 1489 for (int k = 0; k < n; k++) { 1490 boolean holds = true; 1491 for (int j = 0; j < at[k].length && holds; j++) { 1492 holds = alt[k][j] >= 0 && canonical[at[k][j]][choice[at[k][j]]] == alt[k][j]; 1493 } 1494 if (holds) { 1495 pairs.add(pairOf[k]); 1496 if (!walks.get(k).assumed) { 1497 proven.add(pairOf[k]); 1498 } 1499 } 1500 } 1501 if (!proven.containsAll(pairs)) { 1502 proof = ASSUMED; 1503 } 1504 if (pairs.isEmpty()) { 1505 emptyWorld = true; 1506 } else { 1507 outcomes.add(pairs); 1508 } 1509 if (decides) { 1510 worldAlternative.add(alternatives.get(0).get(choice[0])); 1511 worldPairs.add(pairs); 1512 } 1513 for (int i = refs.size() - 1; i >= 0; i--) { 1514 if (++choice[i] < alternatives.get(i).size()) { 1515 break; 1516 } 1517 choice[i] = 0; 1518 } 1519 } 1520 boolean unresolved = false; 1521 TreeSet<Object> sourceSet = new TreeSet<Object>(REF_ORDER); 1522 TreeSet<Object> targetSet = new TreeSet<Object>(REF_ORDER); 1523 for (Walk walk : walks) { 1524 if (graphCset(walk.source)) { 1525 unresolved = true; 1526 } else { 1527 sourceSet.add(walk.source); 1528 } 1529 targetSet.add(walk.target); 1530 } 1531 List<Object> sources = new ArrayList<Object>(sourceSet); 1532 List<Object> targets = new ArrayList<Object>(targetSet); 1533 Set<String> sides = new HashSet<String>(); 1534 boolean complete = !emptyWorld && !unresolved; 1535 for (Object r : rawRefs) { 1536 sides.addAll(sidesOf(r)); 1537 complete &= referenceComplete(r); 1538 } 1539 for (Object r : refs) { 1540 complete &= referenceComplete(r); 1541 } 1542 StatementFold.End targetEnd = !sides.contains(TARGET) && targets.size() == 1 1543 ? StatementFold.End.fixed(targets.get(0)) 1544 : StatementFold.End.set(targets, complete && targets.size() >= 2 ? "COMPLETE" : "INCOMPLETE", 1545 decides ? decision(refs.get(0), targets, false, worldAlternative, worldPairs) : null); 1546 StatementFold.End sourceEnd = !targetEnd.isFixed() && !sides.contains(SOURCE) && sources.size() == 1 && !unresolved 1547 ? StatementFold.End.fixed(sources.get(0)) 1548 : StatementFold.End.set(sources, complete && sources.size() >= 2 ? "COMPLETE" : "INCOMPLETE", 1549 decides ? decision(refs.get(0), sources, true, worldAlternative, worldPairs) : null); 1550 Set<Set<StatementFold.Pair>> effective = new LinkedHashSet<Set<StatementFold.Pair>>(); 1551 for (Set<StatementFold.Pair> outcome : outcomes) { 1552 Set<StatementFold.Pair> kept = new LinkedHashSet<StatementFold.Pair>(); 1553 for (StatementFold.Pair pair : outcome) { 1554 if (!graphCset(pair.getSource())) { 1555 kept.add(pair); 1556 } 1557 } 1558 if (!kept.isEmpty()) { 1559 effective.add(kept); 1560 } 1561 } 1562 Set<Set<StatementFold.Pair>> implied = impliedOutcomes(sourceEnd, targetEnd); 1563 Object deciding = null; 1564 Position decidingPosition = null; 1565 for (Object r : rawRefs) { 1566 Position position = refPosition.get(r); 1567 if (position == null) { 1568 position = new Position(null, ""); 1569 } 1570 if (decidingPosition == null || position.compareTo(decidingPosition) < 0) { 1571 deciding = r; 1572 decidingPosition = position; 1573 } 1574 } 1575 Set<Long> support = new TreeSet<Long>(); 1576 for (Walk walk : walks) { 1577 support.addAll(walk.support); 1578 } 1579 Position known = refPosition.get(deciding); 1580 Object path = known == null || known.path.isEmpty() ? null : known.path; 1581 List<List<Long>> covering = new ArrayList<List<Long>>(); 1582 for (Walk walk : walks) { 1583 covering.addAll(walk.covering); 1584 } 1585 StatementFold.Candidate result = new StatementFold.Candidate(label, rawRefs, sourceEnd, targetEnd, 1586 effective.equals(implied) ? null : effective, effective.isEmpty() ? implied : effective, proof, support, 1587 path, known == null ? null : known.offset, minimalCover(covering, support)); 1588 result.sourceBinding = candidateBinding(SOURCE, sourceEnd, rawRefs, walks, support); 1589 result.targetBinding = candidateBinding(TARGET, targetEnd, rawRefs, walks, support); 1590 return result; 1591 } 1592 1593 /** 1594 * Section 7.1, item 5: the decision of a candidate-set end whose group's worlds are those of one deciding reference: 1595 * each member is paired with the one alternative in whose world it is an end of a pair; null when a member is one in 1596 * several worlds or none, or an alternative would be paired twice (then the end is not decided). 1597 */ 1598 private StatementFold.Decision decision(Object deciding, List<Object> members, boolean source, 1599 List<Object> worldAlternative, List<Set<StatementFold.Pair>> worldPairs) { 1600 if (members.isEmpty()) { 1601 return null; 1602 } 1603 List<StatementFold.Pair> alignment = new ArrayList<StatementFold.Pair>(); 1604 List<Object> used = new ArrayList<Object>(); 1605 for (Object member : members) { 1606 Object found = null; 1607 int count = 0; 1608 for (int w = 0; w < worldPairs.size(); w++) { 1609 if (worldAlternative.get(w) == NOT_LISTED) { 1610 continue; 1611 } 1612 for (StatementFold.Pair pair : worldPairs.get(w)) { 1613 if (same(source ? pair.getSource() : pair.getTarget(), member)) { 1614 found = worldAlternative.get(w); 1615 count++; 1616 break; 1617 } 1618 } 1619 } 1620 if (count != 1 || containsAlternative(used, found)) { 1621 return null; 1622 } 1623 used.add(found); 1624 alignment.add(new StatementFold.Pair(found, member)); 1625 } 1626 Map<?, ?> cset = graphCsets.get(intRef(deciding)); 1627 return new StatementFold.Decision(deciding, members(deciding), cset == null ? null : cset.get("membership"), 1628 alignment); 1629 } 1630 1631 static Set<Set<StatementFold.Pair>> impliedOutcomes(StatementFold.End source, StatementFold.End target) { 1632 List<Object> sources = source.isFixed() ? Collections.singletonList(source.getEndpointRef()) : source.getMembers(); 1633 List<Object> targets = target.isFixed() ? Collections.singletonList(target.getEndpointRef()) : target.getMembers(); 1634 Set<Set<StatementFold.Pair>> out = new LinkedHashSet<Set<StatementFold.Pair>>(); 1635 for (Object s : sources) { 1636 for (Object t : targets) { 1637 out.add(Collections.singleton(new StatementFold.Pair(s, t))); 1638 } 1639 } 1640 return out; 1641 } 1642 1643 /** Section 7.1, item 6. */ 1644 private StatementFold.CandidateBinding candidateBinding(String side, StatementFold.End end, List<Object> refs, 1645 List<Walk> walks, Set<Long> support) { 1646 String member = SOURCE.equals(side) ? "sourceBinding" : "targetBinding"; 1647 if (!end.isFixed()) { 1648 Object deciding = null; 1649 Position decidingPosition = null; 1650 for (Object r : refs) { 1651 if (!sidesOf(r).contains(side)) { 1652 continue; 1653 } 1654 Position position = refPosition.get(r); 1655 if (position == null) { 1656 position = new Position(null, ""); 1657 } 1658 if (decidingPosition == null || position.compareTo(decidingPosition) < 0) { 1659 deciding = r; 1660 decidingPosition = position; 1661 } 1662 } 1663 if (decidingPosition != null) { 1664 List<Map<?, ?>> edges = new ArrayList<Map<?, ?>>(); 1665 for (Long ref : support) { 1666 Map<?, ?> edge = index.get("microEdge", ref); 1667 Object binding = edge == null ? null : edge.get(member); 1668 if (ambiguous(binding) && same(((Map<?, ?>) binding).get("candidateSetRef"), deciding)) { 1669 edges.add(edge); 1670 } 1671 } 1672 if (!edges.isEmpty()) { 1673 return new StatementFold.CandidateBinding(earliestBinding(edges, member), true); 1674 } 1675 } 1676 } 1677 List<Map<?, ?>> edges = new ArrayList<Map<?, ?>>(); 1678 for (Walk walk : walks) { 1679 if (SOURCE.equals(side)) { 1680 edges.add(walk.first); 1681 } else { 1682 edges.addAll(walk.lastEdges); 1683 } 1684 } 1685 return new StatementFold.CandidateBinding(earliestBinding(edges, member), false); 1686 } 1687 1688 /** identity-v1, Section 5.4: the rank among FactCandidates of the same subject and path, by position. */ 1689 private void assignCandidateOrdinals(List<StatementFold.Candidate> candidates) { 1690 LinkedHashMap<List<Object>, List<StatementFold.Candidate>> groups = 1691 new LinkedHashMap<List<Object>, List<StatementFold.Candidate>>(); 1692 for (StatementFold.Candidate candidate : candidates) { 1693 List<Object> subject = Arrays.<Object>asList(candidate.getLabel(), endKey(candidate.getSource()), 1694 endKey(candidate.getTarget()), new PathKey(candidate.getPath())); 1695 List<StatementFold.Candidate> list = groups.get(subject); 1696 if (list == null) { 1697 list = new ArrayList<StatementFold.Candidate>(); 1698 groups.put(subject, list); 1699 } 1700 list.add(candidate); 1701 } 1702 for (List<StatementFold.Candidate> list : groups.values()) { 1703 List<StatementFold.Candidate> sorted = new ArrayList<StatementFold.Candidate>(list); 1704 Collections.sort(sorted, new Comparator<StatementFold.Candidate>() { 1705 @Override 1706 public int compare(StatementFold.Candidate a, StatementFold.Candidate b) { 1707 int c = OFFSET_ORDER.compare(a.getOffset(), b.getOffset()); 1708 if (c != 0) { 1709 return c; 1710 } 1711 List<Object> x = a.getReferences(); 1712 List<Object> y = b.getReferences(); 1713 for (int i = 0; i < Math.min(x.size(), y.size()); i++) { 1714 c = REF_ORDER.compare(x.get(i), y.get(i)); 1715 if (c != 0) { 1716 return c; 1717 } 1718 } 1719 return x.size() - y.size(); 1720 } 1721 }); 1722 for (int i = 0; i < sorted.size(); i++) { 1723 sorted.get(i).ordinal = i; 1724 } 1725 } 1726 } 1727 1728 /** A FactCandidate side as identity-v1 frames it: one endpoint identity, or a membership and sorted identities. */ 1729 private List<Object> endKey(StatementFold.End end) { 1730 if (end.isFixed()) { 1731 return Arrays.<Object>asList("ENDPOINT", subjectKey(end.getEndpointRef())); 1732 } 1733 TreeSet<String> members = new TreeSet<String>(); 1734 for (Object member : end.getMembers()) { 1735 members.add(String.valueOf(subjectKey(member))); 1736 } 1737 return Arrays.<Object>asList("CANDIDATE_SET", end.getMembership(), new ArrayList<String>(members)); 1738 } 1739 1740 // --------------------------------------------------------------------------------------------- 1741 // Sections 7.4 and 7.5: what each write needs 1742 // --------------------------------------------------------------------------------------------- 1743 1744 /** The micro-edges of one statement by source. */ 1745 Map<Object, List<Map<?, ?>>> outOf(long statement) { 1746 Map<Object, List<Map<?, ?>>> out = new LinkedHashMap<Object, List<Map<?, ?>>>(); 1747 for (Map<?, ?> edge : edgesOf(statement)) { 1748 Object source = norm(edge.get("sourceRef")); 1749 List<Map<?, ?>> list = out.get(source); 1750 if (list == null) { 1751 list = new ArrayList<Map<?, ?>>(); 1752 out.put(source, list); 1753 } 1754 list.add(edge); 1755 } 1756 return out; 1757 } 1758 1759 /** 1760 * The kept nodes a node reaches (fold-v1, Section 1): along walks, so a sequence that takes two alternatives of 1761 * one reference does not count (Section 7.1, item 1), through folded interior nodes; VALUE micro-edges only when 1762 * asked. 1763 */ 1764 Set<Object> reachFrom(Map<Object, List<Map<?, ?>>> out, Object start, boolean valueOnly, long statement) { 1765 Set<Object> reached = new TreeSet<Object>(REF_ORDER); 1766 Set<List<Object>> seen = new HashSet<List<Object>>(); 1767 Deque<Object[]> stack = new ArrayDeque<Object[]>(); 1768 stack.push(new Object[] {norm(start), Sigma.EMPTY}); 1769 long[] states = {0}; 1770 while (!stack.isEmpty()) { 1771 Object[] state = stack.pop(); 1772 List<Map<?, ?>> next = out.get(state[0]); 1773 if (next == null) { 1774 continue; 1775 } 1776 for (Map<?, ?> edge : next) { 1777 if (valueOnly && !"VALUE".equals(edge.get("kind"))) { 1778 continue; 1779 } 1780 Sigma chosen = extend((Sigma) state[1], choices(edge)); 1781 if (chosen == null) { 1782 continue; 1783 } 1784 Object target = norm(edge.get("targetRef")); 1785 if (kept(target)) { 1786 reached.add(target); 1787 } else if (folded(target) && seen.add(Arrays.<Object>asList(target, chosen))) { 1788 count(states, statement); 1789 stack.push(new Object[] {target, chosen}); 1790 } 1791 } 1792 } 1793 return reached; 1794 } 1795 1796 /** 1797 * Section 7.4: for each kept node that an UNKNOWN_INPUT terminal or a folded node whose upstreamCompleteness is 1798 * not COMPLETE reaches, the lowest such completeness and the diagnostics of those nodes. 1799 */ 1800 Map<Object, Object[]> lowering(long statement, Map<Object, List<Map<?, ?>>> out) { 1801 Map<Object, Object[]> lowered = new HashMap<Object, Object[]>(); 1802 for (Object source : out.keySet()) { 1803 Map<?, ?> node = node(source); 1804 Object completeness; 1805 if (terminal(source) && "UNKNOWN_INPUT".equals(node.get("inputKind"))) { 1806 completeness = "PARTIAL"; 1807 } else if (folded(source) && node.containsKey("upstreamCompleteness") 1808 && !"COMPLETE".equals(node.get("upstreamCompleteness"))) { 1809 completeness = node.get("upstreamCompleteness"); 1810 } else { 1811 continue; 1812 } 1813 for (Object target : reachFrom(out, source, false, statement)) { 1814 Object[] entry = lowered.get(target); 1815 if (entry == null) { 1816 entry = new Object[] {"COMPLETE", new TreeSet<Long>()}; 1817 lowered.put(target, entry); 1818 } 1819 entry[0] = lower((String) entry[0], completeness); 1820 @SuppressWarnings("unchecked") 1821 Set<Long> diagnostics = (Set<Long>) entry[1]; 1822 addRefs(diagnostics, node.get("diagnosticRefs")); 1823 } 1824 } 1825 return lowered; 1826 } 1827 1828 /** Section 7.5: the kept fields each terminal of the statement reaches through VALUE micro-edges, as inputs. */ 1829 Map<Object, Set<StatementFold.TerminalInput>> terminalInputs(long statement, Map<Object, List<Map<?, ?>>> out) { 1830 Map<Object, Set<StatementFold.TerminalInput>> inputs = 1831 new TreeMap<Object, Set<StatementFold.TerminalInput>>(REF_ORDER); 1832 for (Map<?, ?> node : index.all("node")) { 1833 if ("TERMINAL".equals(node.get("kind")) && same(node.get("statementRef"), statement)) { 1834 for (Object target : reachFrom(out, node.get("ref"), true, statement)) { 1835 if (isField(target)) { 1836 Set<StatementFold.TerminalInput> set = inputs.get(target); 1837 if (set == null) { 1838 set = new LinkedHashSet<StatementFold.TerminalInput>(); 1839 inputs.put(target, set); 1840 } 1841 set.add(terminalInput(node.get("inputKind"), node)); 1842 } 1843 } 1844 } 1845 } 1846 return inputs; 1847 } 1848 1849 /** A terminal input as Section 7.5 compares it; two REDACTED literals of one write are indistinguishable. */ 1850 static StatementFold.TerminalInput terminalInput(Object kind, Map<?, ?> obj) { 1851 Object disclosure = obj.get("disclosure"); 1852 Object value = "PRESENT".equals(disclosure) ? obj.get("literal") 1853 : "HASHED".equals(disclosure) ? CanonicalJson.jcsText(obj.get("token")) : null; 1854 boolean parameter = "PARAMETER".equals(kind); 1855 return new StatementFold.TerminalInput(kind, disclosure, value, parameter ? obj.get("parameterText") : null, 1856 parameter ? obj.get("parameterOrdinal") : null); 1857 } 1858 1859 static String lower(String bound, Object completeness) { 1860 Integer rank = completeness instanceof String ? RANK.get(completeness) : null; 1861 return rank != null && rank < RANK.get(bound) ? (String) completeness : bound; 1862 } 1863 1864 static int rank(Object completeness) { 1865 Integer rank = completeness instanceof String ? RANK.get(completeness) : null; 1866 return rank == null ? 0 : rank; 1867 } 1868 1869 /** The written fields of a statement: those a micro-edge enters, and the targets of its write records. */ 1870 Set<Object> writtenFields(long statement) { 1871 TreeSet<Object> out = new TreeSet<Object>(REF_ORDER); 1872 for (Map<?, ?> edge : edgesOf(statement)) { 1873 if (isField(edge.get("targetRef"))) { 1874 out.add(norm(edge.get("targetRef"))); 1875 } 1876 } 1877 for (Map<?, ?> write : index.all("tfo")) { 1878 if (same(write.get("statementRef"), statement) && isField(write.get("targetRef"))) { 1879 out.add(norm(write.get("targetRef"))); 1880 } 1881 } 1882 return out; 1883 } 1884 1885 private List<StatementFold.WriteRequirement> writes(long statement, List<StatementFold.FactOccurrence> facts, 1886 List<StatementFold.Candidate> candidates) { 1887 boolean observed = observed(statement); 1888 Map<Object, List<Map<?, ?>>> out = outOf(statement); 1889 Map<Object, Set<StatementFold.TerminalInput>> terminals = terminalInputs(statement, out); 1890 Map<Object, Object[]> lowered = lowering(statement, out); 1891 TreeSet<Object> targets = new TreeSet<Object>(REF_ORDER); 1892 targets.addAll(writtenFields(statement)); 1893 targets.addAll(terminals.keySet()); 1894 List<StatementFold.WriteRequirement> requirements = new ArrayList<StatementFold.WriteRequirement>(); 1895 for (Object target : targets) { 1896 String bound = observed ? "PARTIAL" : "COMPLETE"; 1897 Set<Long> diagnostics = new TreeSet<Long>(); 1898 List<StatementFold.FactOccurrence> inputs = new ArrayList<StatementFold.FactOccurrence>(); 1899 Set<PropositionKey> seen = new HashSet<PropositionKey>(); 1900 for (StatementFold.FactOccurrence fact : facts) { 1901 if (same(fact.getTargetRef(), target) 1902 && seen.add(new PropositionKey(fact.getSourceRef(), fact.getTargetRef(), fact.getLabel()))) { 1903 inputs.add(fact); 1904 } 1905 } 1906 List<StatementFold.Candidate> candidateInputs = new ArrayList<StatementFold.Candidate>(); 1907 for (StatementFold.Candidate candidate : candidates) { 1908 StatementFold.End end = candidate.getTarget(); 1909 boolean reaches = end.isFixed() ? same(end.getEndpointRef(), target) : containsRef(end.getMembers(), target); 1910 if (reaches) { 1911 candidateInputs.add(candidate); 1912 bound = lower(bound, "PARTIAL"); 1913 } 1914 } 1915 if (enteredByChoice(statement, target)) { 1916 bound = lower(bound, "PARTIAL"); // written only in some worlds (Section 7.4) 1917 } 1918 Object[] lowering = lowered.get(target); 1919 if (lowering != null) { 1920 bound = lower(bound, lowering[0]); 1921 @SuppressWarnings("unchecked") 1922 Set<Long> lowering1 = (Set<Long>) lowering[1]; 1923 diagnostics.addAll(lowering1); 1924 } 1925 Set<StatementFold.TerminalInput> inputsOfKind = terminals.get(target); 1926 requirements.add(new StatementFold.WriteRequirement(target, bound, inputs, candidateInputs, 1927 inputsOfKind == null ? Collections.<StatementFold.TerminalInput>emptySet() : inputsOfKind, 1928 diagnostics)); 1929 } 1930 return requirements; 1931 } 1932 1933 /** 1934 * Section 7.4: whether every micro-edge of the statement that enters the target has a CANDIDATE or UNRESOLVED 1935 * target-side binding: the statement writes it only when one of those references denotes it. 1936 */ 1937 boolean enteredByChoice(long statement, Object target) { 1938 boolean entered = false; 1939 for (Map<?, ?> edge : edgesOf(statement)) { 1940 if (same(edge.get("targetRef"), target)) { 1941 if (!ambiguous(edge.get("targetBinding"))) { 1942 return false; 1943 } 1944 entered = true; 1945 } 1946 } 1947 return entered; 1948 } 1949 1950 static boolean containsRef(List<Object> refs, Object ref) { 1951 for (Object r : refs) { 1952 if (same(r, ref)) { 1953 return true; 1954 } 1955 } 1956 return false; 1957 } 1958 1959 static void addRefs(Set<Long> out, Object refs) { 1960 if (refs instanceof List<?>) { 1961 for (Object r : (List<?>) refs) { 1962 if (intRef(r) != null) { 1963 out.add(intRef(r)); 1964 } 1965 } 1966 } 1967 } 1968}