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}