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}