001package gudusoft.gsqlparser.lineage.v2.shape;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.EnumSet;
006import java.util.HashMap;
007import java.util.List;
008import java.util.Map;
009
010/**
011 * The value projection of what the SQL-LR producer reads from one analysis (plan 09, Section 3.1, slice 1b): one
012 * immutable mirror ({@link ShapeObject}) per live object it reads, addressed by handle, the traversals its visitors
013 * replay, and the text renderings it shows in diagnostics. Built by {@link ShapeBuilder} when the analysis freezes;
014 * holds no live object, so the document can be built from it after the analyzer is disposed, on any thread.
015 *
016 * <p>Two values are taken later than the freeze, when the analysis has finished and before it is disposed, at the
017 * moment the producer took them before slice 1b: the text renderings of names, literals and type names
018 * ({@code toString()} can mark tokens deleted, so it never runs inside the analysis). {@link #seal()} ends that phase;
019 * after it nothing changes.</p>
020 *
021 * <p>Internal API of the SQL-LR producer.</p>
022 *
023 * @since 4.2.17
024 */
025public final class Shape {
026
027    /** A slot holding a handle of another mirror. */
028    static final class Ref {
029        final int handle;
030
031        Ref(int handle) {
032            this.handle = handle;
033        }
034    }
035
036    /** A slot holding the handles of a list of mirrors, in order. */
037    static final class Refs {
038        final int[] handles;
039        /** The list of mirrors, made at the first read and shared (unmodifiable) by every later one. */
040        volatile List<Object> list;
041
042        Refs(int[] handles) {
043            this.handles = handles;
044        }
045    }
046
047    /** A slot whose getter threw when the analysis froze: reading it throws the same kind of exception again. */
048    static final class Throw {
049        final String className;
050        final String message;
051
052        Throw(String className, String message) {
053            this.className = className;
054            this.message = message;
055        }
056    }
057
058    /** A slot holding a value the projection does not copy: reading it is an error, never a silent default. */
059    static final class Unsupported {
060        final String what;
061
062        Unsupported(String what) {
063            this.what = what;
064        }
065    }
066
067    /**
068     * A slot holding an identifier service, kept as its profile and whether it collates: each read makes the service
069     * again, so the shape holds no collator cache (a {@code ThreadLocal}, plan 09, Section 3.1). The service has no
070     * other state.
071     */
072    static final class Service {
073        final gudusoft.gsqlparser.sqlenv.IdentifierProfile profile;
074        final boolean collating;
075
076        Service(gudusoft.gsqlparser.sqlenv.IdentifierService service) {
077            this.profile = service.getProfile();
078            this.collating = service.getCollatorProvider() != null;
079        }
080
081        gudusoft.gsqlparser.sqlenv.IdentifierService make() {
082            return new gudusoft.gsqlparser.sqlenv.IdentifierService(profile,
083                    collating ? new gudusoft.gsqlparser.sqlenv.CollatorProvider() : null);
084        }
085    }
086
087    final ShapeObject[] objects;
088    private final String[] liveClassNames;
089    private final String[] liveSimpleNames;
090    private final int[][] references;
091    /** Per mirror, its traversal as pieces {reference, start, end}... of the recorded references; null for none. */
092    private final int[][] traversalPieces;
093    private final String[] traversalThrow;
094    private final Map<Integer, String> renderings = new HashMap<Integer, String>();
095    private final Map<String, String> memberRenderings = new HashMap<String, String>();
096    private final int root;
097    private final int catalog;
098    private volatile boolean sealed;
099    private long freezeNanos = -1;
100    private long completeNanos = -1;
101
102    Shape(int size, String[] liveClassNames, String[] liveSimpleNames, int[][] references, int[][] traversalPieces,
103            String[] traversalThrow, int root, int catalog) {
104        this.objects = new ShapeObject[size];
105        this.liveClassNames = liveClassNames;
106        this.liveSimpleNames = liveSimpleNames;
107        this.references = references;
108        this.traversalPieces = traversalPieces;
109        this.traversalThrow = traversalThrow;
110        this.root = root;
111        this.catalog = catalog;
112    }
113
114    /** The mirror of the recorder, the root of the projection of an analysis. */
115    public SqllrObservations observations() {
116        return (SqllrObservations) objects[root];
117    }
118
119    /** The root of the projection: the recorder of an analysis, or the statement {@link ShapeBuilder#project} took. */
120    public ShapeObject root() {
121        return objects[root];
122    }
123
124    /** The mirror of the catalog the producer reads (its own copy of the supplied one), or null when none. */
125    public TSQLEnv catalog() {
126        return catalog < 0 ? null : (TSQLEnv) objects[catalog];
127    }
128
129    /** The number of mirrors. */
130    public int size() {
131        return objects.length;
132    }
133
134    /** The mirror with handle {@code h}. */
135    public ShapeObject at(int h) {
136        return objects[h];
137    }
138
139    String liveSimpleName(int h) {
140        return liveSimpleNames[h];
141    }
142
143    String liveClassName(int h) {
144        return liveClassNames[h];
145    }
146
147    @SuppressWarnings("unchecked")
148    <T> T resolve(Object slot) {
149        if (slot == null) {
150            return null;
151        }
152        if (slot instanceof Ref) {
153            return (T) objects[((Ref) slot).handle];
154        }
155        if (slot instanceof Refs) {
156            Refs refs = (Refs) slot;
157            List<Object> made = refs.list;
158            if (made == null) {
159                List<Object> list = new ArrayList<Object>(refs.handles.length);
160                for (int h : refs.handles) {
161                    list.add(h < 0 ? null : objects[h]);
162                }
163                made = Collections.unmodifiableList(list);
164                refs.list = made; // a race makes two equal lists; either is right
165            }
166            return (T) made;
167        }
168        if (slot instanceof Throw) {
169            throw rethrow(((Throw) slot).className, ((Throw) slot).message);
170        }
171        if (slot instanceof Unsupported) {
172            throw new IllegalStateException("not in the shape: " + ((Unsupported) slot).what);
173        }
174        if (slot instanceof Service) {
175            return (T) ((Service) slot).make();
176        }
177        if (slot instanceof EnumSet) {
178            return (T) ((EnumSet<?>) slot).clone();
179        }
180        return (T) slot;
181    }
182
183    /**
184     * The exception a live call threw, thrown again: the same class when it can be made, so a caller that catches it
185     * or names its class sees what it saw before.
186     */
187    static RuntimeException rethrow(String className, String message) {
188        Throwable t;
189        try {
190            Class<?> c = Class.forName(className);
191            try {
192                t = (Throwable) c.getConstructor(String.class).newInstance(message);
193            } catch (NoSuchMethodException e) {
194                t = (Throwable) c.getConstructor().newInstance();
195            }
196        } catch (Exception e) {
197            t = new IllegalStateException(className + ": " + message);
198        }
199        if (t instanceof RuntimeException) {
200            return (RuntimeException) t;
201        }
202        if (t instanceof Error) {
203            throw (Error) t;
204        }
205        return new IllegalStateException(className + ": " + message, t);
206    }
207
208    /** Replays the live {@code acceptChildren} of mirror {@code h} on a mirror visitor. */
209    void replay(int h, TParseTreeVisitor visitor) {
210        int[] pieces = traversalPieces[h];
211        for (int p = 0; pieces != null && p < pieces.length; p += 3) {
212            int[] events = references[pieces[p]];
213            for (int i = pieces[p + 1]; i < pieces[p + 2]; i++) {
214                visitor.dispatch(events[2 * i], objects[events[2 * i + 1]]);
215            }
216        }
217        if (traversalThrow[h] != null) {
218            throw rethrow(traversalThrow[h], null);
219        }
220    }
221
222    /** The text the live object rendered ({@code toString()}), taken after the analysis finished. */
223    String render(int h) {
224        String text = renderings.get(Integer.valueOf(h));
225        if (text == null && !renderings.containsKey(Integer.valueOf(h))) {
226            throw new IllegalStateException("no rendering of " + liveClassNames[h] + " #" + h);
227        }
228        if (text != null && text.startsWith("\u0000")) {
229            throw rethrow(text.substring(1), null);
230        }
231        return text;
232    }
233
234    /** The value a rendering getter returned, taken after the analysis finished. */
235    String renderMember(int h, String member) {
236        String key = h + "." + member;
237        if (!memberRenderings.containsKey(key)) {
238            throw new IllegalStateException("no rendering of " + liveClassNames[h] + " #" + h + "." + member);
239        }
240        String text = memberRenderings.get(key);
241        if (text != null && text.startsWith("\u0000")) {
242            throw rethrow(text.substring(1), null);
243        }
244        return text;
245    }
246
247    void putRendering(int h, String text) {
248        if (sealed) {
249            throw new IllegalStateException("the shape is sealed");
250        }
251        renderings.put(Integer.valueOf(h), text);
252    }
253
254    void putMemberRendering(int h, String member, String value) {
255        if (sealed) {
256            throw new IllegalStateException("the shape is sealed");
257        }
258        memberRenderings.put(h + "." + member, value);
259    }
260
261    /** Ends the rendering phase: from now on the shape never changes. */
262    void seal() {
263        sealed = true;
264    }
265
266    public boolean isSealed() {
267        return sealed;
268    }
269
270    /** The time the projection took when the analysis froze, in nanoseconds (cost measurement, plan 09 ยง3.1). */
271    public long getFreezeNanos() {
272        return freezeNanos;
273    }
274
275    /** The time taking the renderings took after the analysis, in nanoseconds; -1 before {@link #seal()}. */
276    public long getCompleteNanos() {
277        return completeNanos;
278    }
279
280    private long[] traversalStats = new long[8];
281
282    /**
283     * How the traversals were taken (cost measurement): slices of an enclosing recording taken, slices refused because
284     * the node's own traversal differs, own recordings without a slice, and nodes visited while recording.
285     */
286    public long[] getTraversalStats() {
287        return traversalStats.clone();
288    }
289
290    void setTraversalStats(long[] stats) {
291        traversalStats = stats;
292    }
293
294    void setFreezeNanos(long nanos) {
295        freezeNanos = nanos;
296    }
297
298    void setCompleteNanos(long nanos) {
299        completeNanos = nanos;
300    }
301}