001package gudusoft.gsqlparser.lineage.v2.shape; 002 003import java.lang.reflect.Field; 004import java.util.ArrayList; 005import java.util.Collection; 006import java.util.EnumSet; 007import java.util.HashMap; 008import java.util.IdentityHashMap; 009import java.util.LinkedHashMap; 010import java.util.List; 011import java.util.Map; 012import java.util.function.IntFunction; 013import java.util.function.IntSupplier; 014 015import gudusoft.gsqlparser.EExpressionType; 016 017/** 018 * Builds the {@link Shape} of one analysis (plan 09, slice 1b) from the live recorder and catalog, on the analysis 019 * thread: when the analysis freezes ({@link #freeze}) it copies every member the producer reads, records the 020 * traversals its visitors replay and makes the mirrors; when the analysis has finished and before it is disposed 021 * ({@link #complete}) it takes the text renderings and seals the shape. It then drops every live object. 022 * 023 * <p>It calls each copied getter once per object, never a getter that creates what it returns (the field is read and 024 * a detached empty list stands in, as the getter would have made), never {@code toString()} before 025 * {@link #complete}. A getter that throws is recorded and throws the same kind of exception when the mirror's getter 026 * is called.</p> 027 * 028 * <p>Internal API of the SQL-LR producer.</p> 029 * 030 * @since 4.2.17 031 */ 032public final class ShapeBuilder { 033 034 /** A copied getter call. */ 035 public interface Call { 036 Object call(); 037 } 038 039 private final Map<Object, Integer> handles = new IdentityHashMap<Object, Integer>(); 040 private final List<Object> lives = new ArrayList<Object>(); 041 private final List<Integer> kinds = new ArrayList<Integer>(); 042 private final List<Object> values = new ArrayList<Object>(); 043 private final Map<String, Integer> kindByName = new HashMap<String, Integer>(); 044 private final Map<Class<?>, Integer> kindCache = new HashMap<Class<?>, Integer>(); 045 private final int kRecorder; 046 private final int kElement; 047 private final int kReference; 048 private final int kStatementRecord; 049 private final int kUnit; 050 private final int kDerived; 051 private final int kFrozen; 052 private final int kParseTreeNode; 053 private final int kExpression; 054 private int projected; 055 056 // traversals 057 private final List<int[]> references = new ArrayList<int[]>(); 058 private final Map<Integer, int[]> traversal = new HashMap<Integer, int[]>(); 059 private final Map<Integer, String> traversalThrow = new HashMap<Integer, String>(); 060 private final Map<Integer, int[]> sliceCandidate = new HashMap<Integer, int[]>(); 061 private final List<Integer> traversalOrder = new ArrayList<Integer>(); 062 063 // the recorder's precomputed answers 064 private final Map<Integer, Object> frozenColumns = new LinkedHashMap<Integer, Object>(); 065 private final Map<Integer, Integer> structItems = new LinkedHashMap<Integer, Integer>(); 066 067 private Shape shape; 068 private int root; 069 /** Traversals: slices taken, slices refused (the node's own traversal differs), own without a slice, nodes visited. */ 070 private final long[] traversalStats = new long[11]; 071 private int catalogRoot = -1; 072 073 private ShapeBuilder() { 074 for (int k = 0; k < ShapeKinds.COUNT; k++) { 075 kindByName.put(ShapeKinds.LIVE[k], Integer.valueOf(k)); 076 } 077 String model = "gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations"; 078 kRecorder = kind(model); 079 kElement = kind(model + "$ElementRecord"); 080 kReference = kind(model + "$ReferenceRecord"); 081 kStatementRecord = kind(model + "$StatementRecord"); 082 kUnit = kind(model + "$UnitRecord"); 083 kDerived = kind(model + "$DerivedRecord"); 084 kFrozen = kind(model + "$FrozenRelation"); 085 kParseTreeNode = kind("gudusoft.gsqlparser.nodes.TParseTreeNode"); 086 kExpression = kind("gudusoft.gsqlparser.nodes.TExpression"); 087 } 088 089 private int kind(String liveName) { 090 Integer k = kindByName.get(liveName); 091 if (k == null) { 092 throw new IllegalStateException("not in the shape spec: " + liveName); 093 } 094 return k.intValue(); 095 } 096 097 /** 098 * Projects what the producer reads, when the analysis freezes. {@code recorder} is the live 099 * {@code SqllrObservations}; {@code catalog} the live {@code TSQLEnv} the producer reads, or null. 100 */ 101 public static ShapeBuilder freeze(gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations recorder, 102 Object catalog) { 103 long started = System.nanoTime(); 104 ShapeBuilder b = new ShapeBuilder(); 105 b.root = b.handle(recorder); 106 b.catalogRoot = b.handle(catalog); 107 b.drain(); 108 // the statements of the units are the roots of the traversals: their recordings are the references others 109 // are slices of 110 for (gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.UnitRecord u : recorder.getUnits()) { 111 for (gudusoft.gsqlparser.TCustomSqlStatement s : u.getStatements()) { 112 b.traversalOrder.add(Integer.valueOf(b.handle(s))); 113 } 114 } 115 b.close(); 116 b.shape.setFreezeNanos(System.nanoTime() - started); 117 long stored = 0; 118 for (int[] r : b.references) { 119 stored += r.length / 2; 120 } 121 b.traversalStats[10] = stored; 122 b.shape.setTraversalStats(b.traversalStats.clone()); 123 return b; 124 } 125 126 /** 127 * The completed shape of one statement the producer parses itself (a catalog relation's definition): projected, 128 * rendered and released at once, as nothing analyses it. 129 */ 130 public static Shape project(gudusoft.gsqlparser.TCustomSqlStatement statement) { 131 ShapeBuilder b = new ShapeBuilder(); 132 b.root = b.handle(statement); 133 b.traversalOrder.add(Integer.valueOf(b.root)); 134 b.close(); 135 return b.complete(); 136 } 137 138 /** 139 * {@link #project}, keeping the live objects: a caller holding the live tree finds the mirrors of its nodes with 140 * {@link #mirrorOf}. 141 */ 142 public static ShapeBuilder projectKeepingLive(gudusoft.gsqlparser.TCustomSqlStatement statement) { 143 ShapeBuilder b = new ShapeBuilder(); 144 b.root = b.handle(statement); 145 b.traversalOrder.add(Integer.valueOf(b.root)); 146 b.close(); 147 b.complete(true); 148 return b; 149 } 150 151 /** The mirror of a projected live object, until {@link #release}; null when it was not projected. */ 152 public ShapeObject mirrorOf(Object live) { 153 int h = handleOf(live); 154 return h < 0 ? null : shape.at(h); 155 } 156 157 /** Projects and records traversals until nothing new is reached, then makes the mirrors. */ 158 private void close() { 159 while (true) { 160 long t0 = System.nanoTime(); 161 drain(); 162 long t1 = System.nanoTime(); 163 int before = traversal.size(); 164 traverseAll(); 165 traversalStats[4]++; 166 traversalStats[5] += t1 - t0; 167 traversalStats[6] += System.nanoTime() - t1; 168 if (traversal.size() == before && projected == lives.size()) { 169 break; 170 } 171 } 172 long t2 = System.nanoTime(); 173 build(); 174 traversalStats[7] += System.nanoTime() - t2; 175 } 176 177 /** The handle of a live object, projected later; -1 for null. */ 178 int handle(Object live) { 179 if (live == null) { 180 return -1; 181 } 182 Integer h = handles.get(live); 183 if (h != null) { 184 return h.intValue(); 185 } 186 int k = kindOf(live.getClass()); 187 if (k < 0) { 188 throw new IllegalArgumentException("no mirror for " + live.getClass().getName()); 189 } 190 h = Integer.valueOf(lives.size()); 191 handles.put(live, h); 192 lives.add(live); 193 kinds.add(Integer.valueOf(k)); 194 values.add(null); 195 return h.intValue(); 196 } 197 198 /** The kind of a live object no mirror class stands for: an opaque {@link LiveObject}. */ 199 static final int OPAQUE = Integer.MAX_VALUE; 200 201 /** 202 * The kind of the nearest mirrored class of a live class, else of a mirrored interface it implements, else 203 * {@link #OPAQUE} for another object of the analysis (a parse tree node, a model object, a resolver or catalog 204 * object); -1 for anything else. 205 */ 206 int kindOf(Class<?> c) { 207 Integer cached = kindCache.get(c); 208 if (cached != null) { 209 return cached.intValue(); 210 } 211 int found = -1; 212 for (Class<?> x = c; x != null && found < 0; x = x.getSuperclass()) { 213 Integer k = kindByName.get(x.getName()); 214 if (k != null && !ShapeKinds.INTERFACE[k.intValue()]) { 215 found = k.intValue(); 216 } 217 } 218 if (found < 0) { 219 for (int k = 0; k < ShapeKinds.COUNT && found < 0; k++) { 220 if (!ShapeKinds.INTERFACE[k]) { 221 continue; 222 } 223 try { 224 if (Class.forName(ShapeKinds.LIVE[k]).isAssignableFrom(c)) { 225 found = k; 226 } 227 } catch (ClassNotFoundException e) { 228 throw new IllegalStateException(e); 229 } 230 } 231 } 232 if (found < 0 && isAnalysisObject(c)) { 233 found = OPAQUE; 234 } 235 kindCache.put(c, Integer.valueOf(found)); 236 return found; 237 } 238 239 private static boolean isAnalysisObject(Class<?> c) { 240 String n = c.getName(); 241 return n.startsWith("gudusoft.gsqlparser.nodes.") || n.startsWith("gudusoft.gsqlparser.stmt.") 242 || n.startsWith("gudusoft.gsqlparser.dlineage.dataflow.model.") 243 || n.startsWith("gudusoft.gsqlparser.resolver2.") || n.startsWith("gudusoft.gsqlparser.sqlenv.") 244 || n.startsWith("gudusoft.gsqlparser.T"); 245 } 246 247 /** Projects every handed-out live object not yet projected. */ 248 private void drain() { 249 while (projected < lives.size()) { 250 int h = projected++; 251 values.set(h, extract(kinds.get(h).intValue(), lives.get(h))); 252 } 253 } 254 255 private Object extract(int k, Object live) { 256 if (k == OPAQUE) { 257 return null; 258 } 259 if (k == kRecorder) { 260 gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations r = 261 (gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations) live; 262 Object[] v = {refs(r::getUnits), refs(r::getElements), refs(r::getReferences), refs(r::getFrozenRelations), 263 refs(r::getFrozenResultSets), refs(r::getDerived), refs(r::getStatements)}; 264 for (gudusoft.gsqlparser.dlineage.dataflow.model.ResultSet rs : r.getFrozenResultSets()) { 265 frozenColumns.put(Integer.valueOf(handle(rs)), refs(() -> r.getFrozenColumns(rs))); 266 } 267 for (Object column : r.getStructFieldColumns()) { 268 structItems.put(Integer.valueOf(handle(column)), Integer.valueOf(handle(r.getStructItem(column)))); 269 } 270 return v; 271 } 272 if (k == kElement) { 273 gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.ElementRecord e = 274 (gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.ElementRecord) live; 275 return new Object[] {ref(e::getRelation), ref(e::getElement), Boolean.valueOf(e.isTarget()), 276 ref(e::getReference), ref(e::getStatement), Long.valueOf(e.getSequence())}; 277 } 278 if (k == kReference) { 279 gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.ReferenceRecord e = 280 (gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.ReferenceRecord) live; 281 return new Object[] {ref(e::getReference), dynamic(e::getTarget), ref(e::getStatement), 282 Long.valueOf(e.getSequence())}; 283 } 284 if (k == kStatementRecord) { 285 gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.StatementRecord e = 286 (gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.StatementRecord) live; 287 Throwable f = e.getFailure(); 288 return new Object[] {ref(e::getStatement), f == null ? null : f.getClass().getName(), 289 f == null ? null : f.getMessage(), Boolean.valueOf(e.isFinished()), 290 f == null ? null : f.getClass().getSimpleName()}; 291 } 292 if (k == kUnit) { 293 gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.UnitRecord e = 294 (gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.UnitRecord) live; 295 return new Object[] {Integer.valueOf(e.getUnitIndex()), Integer.valueOf(e.getParseResult()), 296 refs(e::getStatements), refs(e::getSyntaxErrors), refs(e::getRecoveredSyntaxErrors)}; 297 } 298 if (k == kDerived) { 299 gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.DerivedRecord e = 300 (gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.DerivedRecord) live; 301 return new Object[] {ref(e::getStatement), ref(e::getSite)}; 302 } 303 if (k == kFrozen) { 304 gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.FrozenRelation e = 305 (gudusoft.gsqlparser.dlineage.dataflow.model.SqllrObservations.FrozenRelation) live; 306 return new Object[] {ref(e::getRelation), ref(e::getTarget)}; 307 } 308 return ShapeKinds.extract(k, live, this); 309 } 310 311 // --------------------------------------------------------------------------------------------- 312 // Member copies (called by ShapeKinds) 313 // --------------------------------------------------------------------------------------------- 314 315 static boolean isValue(Object v) { 316 return v instanceof String || v instanceof Number || v instanceof Boolean || v instanceof Character 317 || v instanceof Enum || v instanceof EnumSet || v instanceof gudusoft.gsqlparser.dlineage.util.Pair3 318 || v instanceof gudusoft.gsqlparser.sqlenv.IdentifierService 319 || v instanceof gudusoft.gsqlparser.sqlenv.CanonKey; 320 } 321 322 private static Shape.Throw thrown(Throwable t) { 323 return new Shape.Throw(t.getClass().getName(), t.getMessage()); 324 } 325 326 @SuppressWarnings({"unchecked", "rawtypes"}) 327 Object value(Call c) { 328 try { 329 Object v = c.call(); 330 if (v == null) { 331 return null; 332 } 333 if (v instanceof EnumSet) { 334 return EnumSet.copyOf((EnumSet) v); 335 } 336 return isValue(v) ? valueSlot(v) : new Shape.Unsupported(v.getClass().getName()); 337 } catch (Throwable t) { 338 return thrown(t); 339 } 340 } 341 342 /** A value as its slot holds it: an identifier service by its profile ({@link Shape.Service}). */ 343 private static Object valueSlot(Object v) { 344 return v instanceof gudusoft.gsqlparser.sqlenv.IdentifierService 345 ? new Shape.Service((gudusoft.gsqlparser.sqlenv.IdentifierService) v) : v; 346 } 347 348 Object values(Call c) { 349 try { 350 Object v = c.call(); 351 if (v == null) { 352 return null; 353 } 354 if (v instanceof EnumSet) { 355 return value(c); 356 } 357 List<Object> copy = new ArrayList<Object>(); 358 for (Object o : (Collection<?>) v) { 359 if (o != null && (!isValue(o) || o instanceof gudusoft.gsqlparser.sqlenv.IdentifierService)) { 360 return new Shape.Unsupported("a collection of " + o.getClass().getName()); 361 } 362 copy.add(o); 363 } 364 return java.util.Collections.unmodifiableList(copy); 365 } catch (Throwable t) { 366 return thrown(t); 367 } 368 } 369 370 Object ref(Call c) { 371 try { 372 Object v = c.call(); 373 if (v == null) { 374 return null; 375 } 376 if (kindOf(v.getClass()) < 0) { 377 return new Shape.Unsupported(v.getClass().getName()); 378 } 379 return new Shape.Ref(handle(v)); 380 } catch (Throwable t) { 381 return thrown(t); 382 } 383 } 384 385 Object refs(Call c) { 386 try { 387 Object v = c.call(); 388 if (v == null) { 389 return null; 390 } 391 Collection<?> list = (Collection<?>) v; 392 int[] hs = new int[list.size()]; 393 int i = 0; 394 for (Object o : list) { 395 if (o != null && kindOf(o.getClass()) < 0) { 396 return new Shape.Unsupported("a collection of " + o.getClass().getName()); 397 } 398 hs[i++] = handle(o); 399 } 400 return new Shape.Refs(hs); 401 } catch (Throwable t) { 402 return thrown(t); 403 } 404 } 405 406 @SuppressWarnings({"unchecked", "rawtypes"}) 407 Object dynamic(Call c) { 408 try { 409 Object v = c.call(); 410 if (v instanceof EnumSet) { 411 return EnumSet.copyOf((EnumSet) v); 412 } 413 if (v == null || isValue(v)) { 414 return valueSlot(v); 415 } 416 if (kindOf(v.getClass()) >= 0) { 417 return new Shape.Ref(handle(v)); 418 } 419 return new Shape.Unsupported(v.getClass().getName()); 420 } catch (Throwable t) { 421 return thrown(t); 422 } 423 } 424 425 /** 426 * A getter that creates an empty list when its field is null: the field is read; when null, a detached empty list 427 * of the kind's class stands in, as the getter would have made, and the tree is not changed. 428 */ 429 Object lazy(Object owner, String fieldName, int kind) { 430 try { 431 Class<?> type = Class.forName(ShapeKinds.LIVE[kind]); 432 Field f = lazyField(owner.getClass(), fieldName, type); 433 Object v = f.get(owner); 434 if (v == null) { 435 v = type.getConstructor().newInstance(); 436 } 437 return new Shape.Ref(handle(v)); 438 } catch (IllegalStateException e) { 439 throw e; 440 } catch (Throwable t) { 441 return thrown(t); 442 } 443 } 444 445 private static final java.util.concurrent.ConcurrentHashMap<List<Object>, Field> LAZY_FIELDS = 446 new java.util.concurrent.ConcurrentHashMap<List<Object>, Field>(); 447 448 /** 449 * The field a lazy getter reads, by name: the fields are private, and every shipping ProGuard policy keeps these 450 * names (proguard-gsp-api.cfg; ShapeLazyFieldsKeptTest). Its type must be the kind's list class, so a drift of the 451 * live class fails here, not silently. 452 */ 453 static Field lazyField(Class<?> ownerClass, String fieldName, Class<?> type) { 454 List<Object> key = java.util.Arrays.<Object>asList(ownerClass, fieldName); 455 Field cached = LAZY_FIELDS.get(key); 456 if (cached != null) { 457 return cached; 458 } 459 Field f = null; 460 for (Class<?> c = ownerClass; c != null; c = c.getSuperclass()) { 461 try { 462 Field declared = c.getDeclaredField(fieldName); 463 if (f != null) { 464 // a subclass shadows the field: which one the getter reads is not known here 465 throw new IllegalStateException("two fields " + fieldName + " in " + ownerClass.getName()); 466 } 467 f = declared; 468 } catch (NoSuchFieldException e) { 469 // the superclass declares it 470 } 471 } 472 if (f == null || f.getType() != type) { 473 throw new IllegalStateException("no field " + fieldName + " of " + type.getName() + " in " 474 + ownerClass.getName() + " (a renamed field: keep it in proguard-gsp-api.cfg)"); 475 } 476 f.setAccessible(true); 477 LAZY_FIELDS.put(key, f); 478 return f; 479 } 480 481 /** The elements of a list class, by its own size and element getter. */ 482 Object elements(IntSupplier size, IntFunction<Object> get) { 483 try { 484 int n = size.getAsInt(); 485 int[] hs = new int[n]; 486 for (int i = 0; i < n; i++) { 487 Object o = get.apply(i); 488 if (o != null && kindOf(o.getClass()) < 0) { 489 return new Shape.Unsupported("an element " + o.getClass().getName()); 490 } 491 hs[i] = handle(o); 492 } 493 return new Shape.Refs(hs); 494 } catch (Throwable t) { 495 return thrown(t); 496 } 497 } 498 499 // --------------------------------------------------------------------------------------------- 500 // Traversals 501 // --------------------------------------------------------------------------------------------- 502 503 /** 504 * One recorded live traversal: the relevant events ({overload, handle} pairs), each node's first place, every 505 * closed place of at least {@link #MIN_SHARED} events of a node with a handle ({handle, start, end}), and how it 506 * ended. Primitive arrays: a recording can hold millions of events (a long UNION chain's statement list). 507 */ 508 private final class Recording implements RecordingVisitor.Sink { 509 int[] events = new int[64]; 510 int size; 511 final Map<Object, int[]> places = new IdentityHashMap<Object, int[]>(); 512 int[] occurrences = new int[48]; 513 int occurrenceCount; 514 Object[] openNodes = new Object[32]; 515 int[] openStarts = new int[32]; 516 boolean[] openDirect = new boolean[32]; 517 int depth; 518 final Object root; 519 String threw; 520 long visited; 521 522 Recording(Object root) { 523 this.root = root; 524 } 525 526 @Override 527 public void event(boolean pre, int overload, Object node) { 528 if (pre) { 529 visited++; 530 if (overload >= 0) { 531 if (2 * size + 2 > events.length) { 532 events = java.util.Arrays.copyOf(events, events.length * 2); 533 } 534 events[2 * size] = overload; 535 events[2 * size + 1] = handle(node); 536 size++; 537 } 538 if (depth == openNodes.length) { 539 openNodes = java.util.Arrays.copyOf(openNodes, depth * 2); 540 openStarts = java.util.Arrays.copyOf(openStarts, depth * 2); 541 openDirect = java.util.Arrays.copyOf(openDirect, depth * 2); 542 } 543 // the start counts the node's own relevant event, which its own traversal begins with 544 openNodes[depth] = node; 545 openStarts[depth] = size - (overload >= 0 ? 1 : 0); 546 // the recording's root is its own acceptChildren; a node inside a node whose region is its own 547 // acceptChildren, of a class whose acceptChildren reaches every child through the child's own 548 // acceptChildren, has its own acceptChildren as its region too (DirectTraversals) 549 openDirect[depth] = depth == 0 ? node == root 550 : openDirect[depth - 1] && DirectTraversals.directChild(openNodes[depth - 1], node); 551 depth++; 552 } else if (depth > 0 && openNodes[depth - 1] == node) { 553 depth--; 554 openNodes[depth] = null; 555 int start = openStarts[depth]; 556 if (!places.containsKey(node)) { 557 places.put(node, new int[] {start, size, openDirect[depth] ? 1 : 0}); 558 } 559 if (size - start >= MIN_SHARED) { 560 Integer h = handles.get(node); 561 if (h != null) { 562 if (3 * occurrenceCount + 3 > occurrences.length) { 563 occurrences = java.util.Arrays.copyOf(occurrences, occurrences.length * 2); 564 } 565 occurrences[3 * occurrenceCount] = h.intValue(); 566 occurrences[3 * occurrenceCount + 1] = start; 567 occurrences[3 * occurrenceCount + 2] = size; 568 occurrenceCount++; 569 } 570 } 571 } 572 } 573 } 574 575 /** Stops a comparing traversal at its first difference (no stack trace: it is control flow). */ 576 private static final class Differs extends RuntimeException { 577 private static final long serialVersionUID = 1L; 578 579 Differs() { 580 super(null, null, false, false); 581 } 582 } 583 584 /** 585 * Runs a node's own traversal against a region of a recording, event by event, and stops at the first difference: 586 * the node's traversal is that region exactly when it reaches the region's end with nothing left over. No map is 587 * built, so checking a slice costs the node's own traversal and nothing more. 588 */ 589 private final class Comparing implements RecordingVisitor.Sink { 590 final int ref; 591 final int[] events; 592 final int end; 593 final Object root; 594 int at; 595 long visited; 596 // the open nodes of this run of the node's own acceptChildren, and whether each one's region is its own 597 Object[] openNodes = new Object[32]; 598 int[] openStarts = new int[32]; 599 boolean[] openDirect = new boolean[32]; 600 int depth; 601 /** The regions this run proves: {node, start, end} of every node reached through its own acceptChildren. */ 602 final List<Object[]> proven = new ArrayList<Object[]>(); 603 604 Comparing(Object root, int[] slice) { 605 this.root = root; 606 this.ref = slice[0]; 607 this.events = references.get(slice[0]); 608 this.at = slice[1]; 609 this.end = slice[2]; 610 } 611 612 @Override 613 public void event(boolean pre, int overload, Object node) { 614 if (!pre) { 615 if (depth > 0 && openNodes[depth - 1] == node) { 616 depth--; 617 if (openDirect[depth] && node != root) { 618 proven.add(new Object[] {node, Integer.valueOf(openStarts[depth]), Integer.valueOf(at)}); 619 } 620 openNodes[depth] = null; 621 } 622 return; 623 } 624 visited++; 625 if (depth == openNodes.length) { 626 openNodes = java.util.Arrays.copyOf(openNodes, depth * 2); 627 openStarts = java.util.Arrays.copyOf(openStarts, depth * 2); 628 openDirect = java.util.Arrays.copyOf(openDirect, depth * 2); 629 } 630 openNodes[depth] = node; 631 openStarts[depth] = at; 632 openDirect[depth] = depth == 0 ? node == root 633 : openDirect[depth - 1] && DirectTraversals.directChild(openNodes[depth - 1], node); 634 depth++; 635 if (overload < 0) { 636 return; 637 } 638 if (at >= end || events[2 * at] != overload || lives.get(events[2 * at + 1]) != node) { 639 throw new Differs(); 640 } 641 at++; 642 } 643 } 644 645 /** 646 * Whether the node's own traversal is the slice: it ends normally, with exactly the slice's events. When it is, 647 * the run also proves the regions of the nodes it reached through their own acceptChildren (DirectTraversals): 648 * they become exact candidates in the same reference, so their own traversals need not run again. 649 */ 650 private boolean isSlice(Object live, int[] slice) { 651 Comparing c = new Comparing(live, slice); 652 boolean same; 653 try { 654 ((gudusoft.gsqlparser.nodes.TParseTreeNode) live).acceptChildren(new RecordingVisitor(c)); 655 same = c.at == c.end; 656 } catch (Throwable t) { 657 same = false; // a difference, or a traversal that throws: its own recording keeps how it ends 658 } 659 traversalStats[3] += c.visited; 660 if (same) { 661 for (Object[] p : c.proven) { 662 Integer inner = handles.get(p[0]); 663 if (inner != null && !traversal.containsKey(inner)) { 664 sliceCandidate.put(inner, new int[] {c.ref, ((Integer) p[1]).intValue(), 665 ((Integer) p[2]).intValue(), 1}); 666 } 667 } 668 } 669 return same; 670 } 671 672 private Recording record(Object live) { 673 Recording r = new Recording(live); 674 try { 675 ((gudusoft.gsqlparser.nodes.TParseTreeNode) live).acceptChildren(new RecordingVisitor(r)); 676 } catch (Throwable t) { 677 r.threw = t.getClass().getName(); 678 } 679 return r; 680 } 681 682 /** Records the traversal of every projected parse tree node not yet recorded, roots first. */ 683 private void traverseAll() { 684 List<Integer> order = new ArrayList<Integer>(traversalOrder); 685 for (int h = 0; h < lives.size(); h++) { 686 order.add(Integer.valueOf(h)); 687 } 688 // the roots first: their recordings offer every node inside them its place 689 for (Integer h : traversalOrder) { 690 traverse(h, false); 691 } 692 // then every slice, outermost first: checking a node proves the places of the nodes inside it 693 List<Integer> sliced = new ArrayList<Integer>(); 694 for (Integer h : order) { 695 if (sliceCandidate.containsKey(h) && !traversal.containsKey(h)) { 696 sliced.add(h); 697 } 698 } 699 java.util.Collections.sort(sliced, (x, y) -> { 700 int[] a = sliceCandidate.get(x); 701 int[] b = sliceCandidate.get(y); 702 return a[0] != b[0] ? Integer.compare(a[0], b[0]) : a[1] != b[1] ? Integer.compare(a[1], b[1]) 703 : Integer.compare(b[2], a[2]); 704 }); 705 for (Integer h : sliced) { 706 traverse(h, true); 707 } 708 // the rest on their own, the latest found (the deepest) first, for the same reason 709 for (int i = order.size() - 1; i >= 0; i--) { 710 traverse(order.get(i), false); 711 } 712 } 713 714 /** The traversal of one node: a slice when its own traversal is one, else (unless {@code slicesOnly}) its own. */ 715 private void traverse(Integer h, boolean slicesOnly) { 716 { 717 Object live = lives.get(h.intValue()); 718 if (traversal.containsKey(h) || !(live instanceof gudusoft.gsqlparser.nodes.TParseTreeNode)) { 719 return; 720 } 721 int[] slice = sliceCandidate.get(h); 722 if (slice != null && slice[3] == 1) { 723 // its place is its own acceptChildren (Recording): exact without running it again 724 traversal.put(h, new int[] {slice[0], slice[1], slice[2]}); 725 traversalStats[0]++; 726 traversalStats[8]++; 727 return; 728 } 729 if (slice != null && !refused.containsKey(h) && isSlice(live, slice)) { 730 traversal.put(h, new int[] {slice[0], slice[1], slice[2]}); 731 traversalStats[0]++; 732 return; 733 } 734 if (slice != null) { 735 refused.put(h, Boolean.TRUE); 736 } 737 if (slicesOnly) { 738 return; 739 } 740 int[] composed = composedList(live); 741 if (composed != null) { 742 traversal.put(h, composed); 743 traversalStats[9]++; 744 return; 745 } 746 traversalStats[slice != null ? 1 : 2]++; 747 Recording own = record(live); 748 traversalStats[3] += own.visited; 749 traversal.put(h, own(h.intValue(), own)); 750 if (own.threw != null) { 751 traversalThrow.put(h, own.threw); 752 } 753 } 754 } 755 756 /** Nodes whose own traversal is not their slice: never checked twice. */ 757 private final Map<Integer, Boolean> refused = new HashMap<Integer, Boolean>(); 758 759 /** The shortest run of a recording kept as another node's traversal rather than copied. */ 760 private static final int MIN_SHARED = 16; 761 762 /** 763 * The pieces ({ref, start, end}...) of a node's own recording. A run of it that is, event for event, the traversal 764 * another node already has (the statement list of a long UNION chain repeats the traversal of each statement in 765 * it) is kept as that traversal's pieces, not copied: what replays is the same, and the shape does not grow with 766 * the square of the chain. The other events become one new reference; a node inside the recording with no 767 * traversal yet is offered its place, where its place is a single piece. 768 */ 769 private int[] own(int h, Recording own) { 770 int[] events = own.events; 771 int n = own.size; 772 List<int[]> ranges = new ArrayList<int[]>(); 773 for (int o = 0; o < own.occurrenceCount; o++) { 774 Integer inner = Integer.valueOf(own.occurrences[3 * o]); 775 if (inner.intValue() == h || !traversal.containsKey(inner) || traversalThrow.containsKey(inner)) { 776 continue; 777 } 778 ranges.add(new int[] {own.occurrences[3 * o + 1], own.occurrences[3 * o + 2], inner.intValue()}); 779 } 780 // outermost first; a range inside one already kept is part of it 781 java.util.Collections.sort(ranges, (x, y) -> x[0] != y[0] ? Integer.compare(x[0], y[0]) 782 : Integer.compare(y[1], x[1])); 783 List<int[]> kept = new ArrayList<int[]>(); 784 int keptEnd = 0; 785 for (int[] r : ranges) { 786 if (r[0] >= keptEnd && sameEvents(events, r[0], r[1], traversal.get(Integer.valueOf(r[2])))) { 787 kept.add(r); 788 keptEnd = r[1]; 789 } 790 } 791 int rawCount = n; 792 for (int[] r : kept) { 793 rawCount -= r[1] - r[0]; 794 } 795 int ref = -1; 796 int[] flat = new int[rawCount * 2]; 797 if (rawCount > 0) { 798 ref = references.size(); 799 references.add(flat); 800 } 801 // the pieces, in order: raw runs of the new reference and the kept traversals 802 List<Integer> pieces = new ArrayList<Integer>(); 803 int raw = 0; 804 int at = 0; 805 for (int k = 0; k <= kept.size(); k++) { 806 int runEnd = k < kept.size() ? kept.get(k)[0] : n; 807 if (runEnd > at) { 808 System.arraycopy(events, 2 * at, flat, 2 * raw, 2 * (runEnd - at)); 809 addPiece(pieces, ref, raw, raw + runEnd - at); 810 raw += runEnd - at; 811 } 812 if (k < kept.size()) { 813 int[] t = traversal.get(Integer.valueOf(kept.get(k)[2])); 814 for (int i = 0; i < t.length; i += 3) { 815 addPiece(pieces, t[i], t[i + 1], t[i + 2]); 816 } 817 at = kept.get(k)[1]; 818 } 819 } 820 // the places of nodes with no traversal yet: in a raw run, or inside a kept traversal that is one piece 821 int[] keptStarts = new int[kept.size()]; 822 long[] keptBefore = new long[kept.size() + 1]; 823 for (int k = 0; k < kept.size(); k++) { 824 keptStarts[k] = kept.get(k)[0]; 825 keptBefore[k + 1] = keptBefore[k] + kept.get(k)[1] - kept.get(k)[0]; 826 } 827 for (Map.Entry<Object, int[]> e : own.places.entrySet()) { 828 Integer inner = handles.get(e.getKey()); 829 if (inner == null || inner.intValue() == h || sliceCandidate.containsKey(inner) 830 || traversal.containsKey(inner)) { 831 continue; 832 } 833 int[] place = e.getValue(); 834 // the last kept range starting at or before the place, and the first one after it 835 int k = java.util.Arrays.binarySearch(keptStarts, place[0]); 836 k = k >= 0 ? k : -k - 2; 837 if (k >= 0 && kept.get(k)[1] > place[0]) { 838 int[] within = kept.get(k); 839 if (place[1] > within[1]) { 840 continue; // starts inside a kept range and ends past it 841 } 842 int[] t = traversal.get(Integer.valueOf(within[2])); 843 if (t.length == 3) { 844 sliceCandidate.put(inner, new int[] {t[0], t[1] + place[0] - within[0], 845 t[1] + place[1] - within[0], place[2]}); 846 } 847 continue; 848 } 849 if (k + 1 < kept.size() && kept.get(k + 1)[0] < place[1]) { 850 continue; // a kept range lies inside it: not one run of the new reference 851 } 852 if (ref >= 0) { // with no raw events the place is empty and the node is recorded on its own 853 int before = (int) keptBefore[k + 1]; 854 sliceCandidate.put(inner, new int[] {ref, place[0] - before, place[1] - before, place[2]}); 855 } 856 } 857 int[] out = new int[pieces.size()]; 858 for (int i = 0; i < out.length; i++) { 859 out[i] = pieces.get(i).intValue(); 860 } 861 return out; 862 } 863 864 /** 865 * The traversal of a list whose acceptChildren is the list classes' own (DirectTraversals.isListTraversal): its 866 * elements' traversals in order, made without running it; null when an element has no traversal yet, its traversal 867 * throws, or (a statement list) an element is null, or the list's own preVisit is one a producer visitor overrides. 868 */ 869 private int[] composedList(Object live) { 870 if (!DirectTraversals.isListTraversal(live) || DirectTraversals.LIST_OVERLOAD_RELEVANT) { 871 return null; 872 } 873 List<Object> elements = DirectTraversals.elements(live); 874 if (elements == null) { 875 return null; 876 } 877 List<Integer> pieces = new ArrayList<Integer>(); 878 for (Object element : elements) { 879 Integer e = handles.get(element); 880 int[] t = e == null ? null : traversal.get(e); 881 if (t == null || traversalThrow.containsKey(e)) { 882 return null; 883 } 884 for (int i = 0; i < t.length; i += 3) { 885 addPiece(pieces, t[i], t[i + 1], t[i + 2]); 886 } 887 } 888 int[] out = new int[pieces.size()]; 889 for (int i = 0; i < out.length; i++) { 890 out[i] = pieces.get(i).intValue(); 891 } 892 return out; 893 } 894 895 /** Appends a piece, joined to the last one when it continues it in the same reference. */ 896 private static void addPiece(List<Integer> pieces, int ref, int start, int end) { 897 if (end <= start) { 898 return; 899 } 900 int size = pieces.size(); 901 if (size >= 3 && pieces.get(size - 3).intValue() == ref && pieces.get(size - 1).intValue() == start) { 902 pieces.set(size - 1, Integer.valueOf(end)); 903 return; 904 } 905 pieces.add(Integer.valueOf(ref)); 906 pieces.add(Integer.valueOf(start)); 907 pieces.add(Integer.valueOf(end)); 908 } 909 910 /** Whether events [start, end) of a recording are exactly the events of a traversal's pieces. */ 911 private boolean sameEvents(int[] events, int start, int end, int[] pieces) { 912 int length = 0; 913 for (int i = 0; i < pieces.length; i += 3) { 914 length += pieces[i + 2] - pieces[i + 1]; 915 } 916 if (length != end - start) { 917 return false; 918 } 919 int at = start; 920 for (int i = 0; i < pieces.length; i += 3) { 921 int[] ref = references.get(pieces[i]); 922 for (int j = pieces[i + 1]; j < pieces[i + 2]; j++, at++) { 923 if (events[2 * at] != ref[2 * j] || events[2 * at + 1] != ref[2 * j + 1]) { 924 return false; 925 } 926 } 927 } 928 return true; 929 } 930 931 // --------------------------------------------------------------------------------------------- 932 // The mirrors 933 // --------------------------------------------------------------------------------------------- 934 935 private void build() { 936 int n = lives.size(); 937 String[] names = new String[n]; 938 String[] simple = new String[n]; 939 int[][] tPieces = new int[n][]; 940 String[] tThrow = new String[n]; 941 for (int h = 0; h < n; h++) { 942 names[h] = lives.get(h).getClass().getName(); 943 simple[h] = lives.get(h).getClass().getSimpleName(); 944 int[] t = traversal.get(Integer.valueOf(h)); 945 tPieces[h] = t; 946 tThrow[h] = traversalThrow.get(Integer.valueOf(h)); 947 if (t == null && lives.get(h) instanceof gudusoft.gsqlparser.nodes.TParseTreeNode) { 948 throw new IllegalStateException("no traversal for " + names[h]); 949 } 950 } 951 shape = new Shape(n, names, simple, references.toArray(new int[0][]), tPieces, tThrow, root, catalogRoot); 952 // mirrors a public field refers to are made first 953 for (int pass = 0; pass < 2; pass++) { 954 for (int h = 0; h < n; h++) { 955 int k = kinds.get(h).intValue(); 956 if ((k != OPAQUE && ShapeKinds.FIELD_TARGET[k]) == (pass == 0)) { 957 shape.objects[h] = make(k, h); 958 } 959 } 960 } 961 } 962 963 private ShapeObject make(int k, int h) { 964 Object v = values.get(h); 965 if (k == OPAQUE) { 966 return new LiveObject(shape, h); 967 } 968 if (k == kRecorder) { 969 return new SqllrObservations(shape, h, (Object[]) v, frozenColumns, structItems); 970 } 971 if (k == kElement) { 972 return new SqllrObservations.ElementRecord(shape, h, (Object[]) v); 973 } 974 if (k == kReference) { 975 return new SqllrObservations.ReferenceRecord(shape, h, (Object[]) v); 976 } 977 if (k == kStatementRecord) { 978 return new SqllrObservations.StatementRecord(shape, h, (Object[]) v); 979 } 980 if (k == kUnit) { 981 return new SqllrObservations.UnitRecord(shape, h, (Object[]) v); 982 } 983 if (k == kDerived) { 984 return new SqllrObservations.DerivedRecord(shape, h, (Object[]) v); 985 } 986 if (k == kFrozen) { 987 return new SqllrObservations.FrozenRelation(shape, h, (Object[]) v); 988 } 989 return ShapeKinds.create(k, shape, h, (Object[][]) v); 990 } 991 992 /** 993 * Takes the text renderings, after the analysis has finished and before it is disposed (when the producer took 994 * them before slice 1b), seals the shape and drops every live object. 995 */ 996 public Shape complete() { 997 return complete(false); 998 } 999 1000 /** 1001 * {@link #complete()}; with {@code keepLive} the live objects stay until {@link #release} (tests compare the 1002 * mirrors with them). 1003 */ 1004 public Shape complete(boolean keepLive) { 1005 long started = System.nanoTime(); 1006 for (int h = 0; h < lives.size(); h++) { 1007 Object live = lives.get(h); 1008 int k = kinds.get(h).intValue(); 1009 if (k == OPAQUE) { 1010 continue; 1011 } 1012 if (rendered(live)) { 1013 try { 1014 shape.putRendering(h, live.toString()); 1015 } catch (RuntimeException e) { 1016 shape.putRendering(h, "\u0000" + e.getClass().getName()); 1017 } 1018 } 1019 for (String member : ShapeKinds.renderMembers(k)) { 1020 try { 1021 shape.putMemberRendering(h, member, ShapeKinds.renderMember(live, member)); 1022 } catch (RuntimeException e) { 1023 shape.putMemberRendering(h, member, "\u0000" + e.getClass().getName()); 1024 } 1025 } 1026 } 1027 shape.setCompleteNanos(System.nanoTime() - started); 1028 shape.seal(); 1029 if (!keepLive) { 1030 release(); 1031 } 1032 return shape; 1033 } 1034 1035 /** Drops every live object. */ 1036 public void release() { 1037 lives.clear(); 1038 handles.clear(); 1039 } 1040 1041 /** Tests only: the live object of handle {@code h}, until {@link #release}. */ 1042 Object live(int h) { 1043 return lives.get(h); 1044 } 1045 1046 /** Tests only: the handle of a live object, or -1. */ 1047 int handleOf(Object live) { 1048 Integer h = handles.get(live); 1049 return h == null ? -1 : h.intValue(); 1050 } 1051 1052 /** Names, constants, type names and the leaf expressions the producer shows (a literal, a variable). */ 1053 private static boolean rendered(Object live) { 1054 if (live instanceof gudusoft.gsqlparser.nodes.TObjectName || live instanceof gudusoft.gsqlparser.nodes.TConstant 1055 || live instanceof gudusoft.gsqlparser.nodes.TTypeName) { 1056 return true; 1057 } 1058 if (live instanceof gudusoft.gsqlparser.nodes.TExpression) { 1059 EExpressionType t = ((gudusoft.gsqlparser.nodes.TExpression) live).getExpressionType(); 1060 return t == EExpressionType.simple_constant_t || t == EExpressionType.simple_object_name_t; 1061 } 1062 return false; 1063 } 1064 1065 /** The shape made at the freeze (renderings come with {@link #complete}). */ 1066 public Shape shape() { 1067 return shape; 1068 } 1069}