001package gudusoft.gsqlparser.lineage.v2.model; 002 003import gudusoft.gsqlparser.lineage.v2.json.CanonicalJson; 004import gudusoft.gsqlparser.lineage.v2.validation.DocumentIndex; 005 006import java.math.BigInteger; 007import java.util.AbstractList; 008import java.util.AbstractMap; 009import java.util.ArrayDeque; 010import java.util.ArrayList; 011import java.util.Collections; 012import java.util.Deque; 013import java.util.Iterator; 014import java.util.LinkedHashMap; 015import java.util.List; 016import java.util.Map; 017import java.util.RandomAccess; 018import java.util.Set; 019import java.util.concurrent.ConcurrentHashMap; 020 021/** 022 * An object of a lineage document: its members as JSON values, with typed accessors in each generated subclass. 023 * 024 * <p><b>Values.</b> A member holds a JSON value: a {@code Map} with string keys, a {@code List}, a {@code String}, a 025 * {@code Long} (or a {@code BigInteger} outside the range of {@code long}), a {@code Boolean}, or JSON null. Integers 026 * given as {@code Integer}, {@code Short}, {@code Byte} or a {@code BigInteger} that fits are stored as {@code Long}, 027 * so two objects with the same JSON are equal however they were built. Every member is kept, including members the 028 * model does not type (extensions, module content, members of a later minor version): reading a document and 029 * writing it again loses nothing.</p> 030 * 031 * <p><b>Absence and null.</b> A typed getter returns null when the member is absent and throws 032 * {@link IllegalStateException} when the member holds JSON of another type, JSON null included. A getter typed 033 * {@code Object} (any JSON value) returns {@link #JSON_NULL} for a member that is present with JSON null. A builder 034 * setter given null removes the member; given {@link #JSON_NULL} it sets JSON null.</p> 035 * 036 * <p><b>Immutability and cost.</b> Objects are immutable and thread-safe; a {@link Builder} makes a changed copy. 037 * Values are frozen once, without recursion, so any depth the strict reader accepts is supported. A typed getter 038 * computes its view once and returns the same object afterwards. Equality compares the canonical JSON 039 * (RFC 8785) of the members.</p> 040 * 041 * <p>The model checks shapes only when a typed accessor reads a member; it does not validate. Use 042 * {@link gudusoft.gsqlparser.lineage.v2.validation.LineageValidator} for that.</p> 043 * 044 * @since 4.2.17 045 */ 046public abstract class SqllrObject { 047 048 /** JSON null as a member value, distinct from an absent member; see the class description. */ 049 public static final Object JSON_NULL = new Object() { 050 @Override 051 public String toString() { 052 return "null"; 053 } 054 }; 055 056 /** Converts one JSON value to a typed value. */ 057 public interface Reader<T> { 058 T read(Object json); 059 } 060 061 private static final Object NO_VALUE = new Object(); 062 063 private final Map<String, Object> members; 064 private final ConcurrentHashMap<String, Object> views = new ConcurrentHashMap<String, Object>(); 065 private volatile String canonical; 066 067 protected SqllrObject(Map<String, Object> members) { 068 this.members = members; 069 } 070 071 /** The members of a JSON object as frozen JSON values (a frozen tree is used as it is, not copied). */ 072 @SuppressWarnings("unchecked") 073 protected static Map<String, Object> members(Object json, String type) { 074 if (json instanceof SqllrObject) { 075 return ((SqllrObject) json).members; 076 } 077 if (!(json instanceof Map<?, ?>)) { 078 throw new IllegalArgumentException(type + ": expected a JSON object, got " + describe(json)); 079 } 080 return (Map<String, Object>) freeze(json); 081 } 082 083 private static String describe(Object json) { 084 return json == null || json == JSON_NULL ? "null" : json.getClass().getSimpleName(); 085 } 086 087 /** The raw JSON value of a member: null when absent or JSON null (see {@link #has}). */ 088 public final Object get(String member) { 089 return members.get(member); 090 } 091 092 public final boolean has(String member) { 093 return members.containsKey(member); 094 } 095 096 /** The members as a frozen JSON object, in the order they were set. */ 097 public final Map<String, Object> toJson() { 098 return members; 099 } 100 101 private IllegalStateException wrongType(String member, String expected, Throwable cause) { 102 IllegalStateException e = new IllegalStateException(getClass().getSimpleName() + "." + member + " is not " 103 + expected + ": " + describe(members.get(member)) + (cause == null ? "" : " (" + cause.getMessage() + ")")); 104 if (cause != null) { 105 e.initCause(cause); 106 } 107 return e; 108 } 109 110 @SuppressWarnings("unchecked") 111 private <T> T view(String member, String expected, Reader<T> reader) { 112 Object cached = views.get(member); 113 if (cached != null) { 114 return cached == NO_VALUE ? null : (T) cached; 115 } 116 T value; 117 if (!members.containsKey(member)) { 118 value = null; 119 } else if (members.get(member) == null) { 120 throw wrongType(member, expected, null); 121 } else { 122 try { 123 value = reader.read(members.get(member)); 124 } catch (IllegalArgumentException e) { 125 throw wrongType(member, expected, e); 126 } catch (IllegalStateException e) { 127 throw wrongType(member, expected, e); 128 } 129 } 130 Object previous = views.putIfAbsent(member, value == null ? NO_VALUE : value); 131 return previous == null || previous == NO_VALUE ? value : (T) previous; 132 } 133 134 protected final String string(String member) { 135 return view(member, "a string", STRING); 136 } 137 138 protected final Long integer(String member) { 139 return view(member, "an integer", INTEGER); 140 } 141 142 protected final Boolean bool(String member) { 143 return view(member, "a boolean", BOOLEAN); 144 } 145 146 protected final Map<String, Object> object(String member) { 147 return view(member, "an object", OBJECT); 148 } 149 150 /** Any JSON value: null when absent, {@link #JSON_NULL} when present with JSON null. */ 151 protected final Object anyMember(String member) { 152 if (!members.containsKey(member)) { 153 return null; 154 } 155 Object value = members.get(member); 156 return value == null ? JSON_NULL : value; 157 } 158 159 protected final <T> T typed(String member, Reader<T> reader) { 160 return view(member, "an object of the expected shape", reader); 161 } 162 163 protected final <T> List<T> list(String member, Reader<T> reader) { 164 return view(member, "an array of the expected items", listOf(reader)); 165 } 166 167 protected final <T> Map<String, T> map(String member, Reader<T> reader) { 168 return view(member, "an object of the expected members", mapOf(reader)); 169 } 170 171 /** Reads a JSON array, each item with a reader, as an unmodifiable list. */ 172 protected static <T> Reader<List<T>> listOf(final Reader<T> item) { 173 return new Reader<List<T>>() { 174 @Override 175 public List<T> read(Object json) { 176 if (!(json instanceof List<?>)) { 177 throw new IllegalStateException("not an array: " + describe(json)); 178 } 179 List<T> out = new ArrayList<T>(); 180 int index = 0; 181 for (Object element : (List<?>) json) { 182 if (element == null && item != ANY) { 183 throw new IllegalStateException("item " + index + " is null"); 184 } 185 try { 186 out.add(item.read(element)); 187 } catch (IllegalArgumentException e) { 188 throw new IllegalStateException("item " + index + ": " + e.getMessage(), e); 189 } 190 index++; 191 } 192 return Collections.unmodifiableList(out); 193 } 194 }; 195 } 196 197 /** Reads a JSON object, each member with a reader, as an unmodifiable map. */ 198 protected static <T> Reader<Map<String, T>> mapOf(final Reader<T> item) { 199 return new Reader<Map<String, T>>() { 200 @Override 201 public Map<String, T> read(Object json) { 202 if (!(json instanceof Map<?, ?>)) { 203 throw new IllegalStateException("not an object: " + describe(json)); 204 } 205 Map<String, T> out = new LinkedHashMap<String, T>(); 206 for (Map.Entry<?, ?> entry : ((Map<?, ?>) json).entrySet()) { 207 if (entry.getValue() == null && item != ANY) { 208 throw new IllegalStateException("member " + entry.getKey() + " is null"); 209 } 210 try { 211 out.put((String) entry.getKey(), item.read(entry.getValue())); 212 } catch (IllegalArgumentException e) { 213 throw new IllegalStateException("member " + entry.getKey() + ": " + e.getMessage(), e); 214 } 215 } 216 return Collections.unmodifiableMap(out); 217 } 218 }; 219 } 220 221 /** Reads a JSON string. */ 222 protected static final Reader<String> STRING = new Reader<String>() { 223 @Override 224 public String read(Object json) { 225 if (json instanceof String) { 226 return (String) json; 227 } 228 throw new IllegalStateException("not a string: " + describe(json)); 229 } 230 }; 231 232 /** Reads a JSON integer. */ 233 protected static final Reader<Long> INTEGER = new Reader<Long>() { 234 @Override 235 public Long read(Object json) { 236 Long value = DocumentIndex.intRef(json); 237 if (value == null) { 238 throw new IllegalStateException("not an integer in the range of long: " + describe(json)); 239 } 240 return value; 241 } 242 }; 243 244 /** Reads a JSON boolean. */ 245 protected static final Reader<Boolean> BOOLEAN = new Reader<Boolean>() { 246 @Override 247 public Boolean read(Object json) { 248 if (json instanceof Boolean) { 249 return (Boolean) json; 250 } 251 throw new IllegalStateException("not a boolean: " + describe(json)); 252 } 253 }; 254 255 /** Reads any JSON value as it is (JSON null as null). */ 256 protected static final Reader<Object> ANY = new Reader<Object>() { 257 @Override 258 public Object read(Object json) { 259 return json; 260 } 261 }; 262 263 /** Reads a JSON object as a map. */ 264 protected static final Reader<Map<String, Object>> OBJECT = new Reader<Map<String, Object>>() { 265 @Override 266 @SuppressWarnings("unchecked") 267 public Map<String, Object> read(Object json) { 268 if (json instanceof Map<?, ?>) { 269 return (Map<String, Object>) json; 270 } 271 throw new IllegalStateException("not an object: " + describe(json)); 272 } 273 }; 274 275 // --------------------------------------------------------------------------------------------- 276 // Frozen JSON trees 277 // --------------------------------------------------------------------------------------------- 278 279 /** A frozen JSON object. */ 280 private static final class FrozenMap extends AbstractMap<String, Object> { 281 private final Map<String, Object> map; 282 283 FrozenMap(LinkedHashMap<String, Object> map) { 284 this.map = Collections.unmodifiableMap(map); 285 } 286 287 @Override 288 public Set<Map.Entry<String, Object>> entrySet() { 289 return map.entrySet(); 290 } 291 292 @Override 293 public Object get(Object key) { 294 return map.get(key); 295 } 296 297 @Override 298 public boolean containsKey(Object key) { 299 return map.containsKey(key); 300 } 301 302 @Override 303 public int size() { 304 return map.size(); 305 } 306 } 307 308 /** A frozen JSON array. */ 309 private static final class FrozenList extends AbstractList<Object> implements RandomAccess { 310 private final Object[] items; 311 312 FrozenList(List<Object> items) { 313 this.items = items.toArray(); 314 } 315 316 @Override 317 public Object get(int index) { 318 return items[index]; 319 } 320 321 @Override 322 public int size() { 323 return items.length; 324 } 325 } 326 327 private static boolean unfrozen(Object value) { 328 return (value instanceof Map<?, ?> && !(value instanceof FrozenMap)) 329 || (value instanceof List<?> && !(value instanceof FrozenList)); 330 } 331 332 /** A JSON scalar in its stored form, or a container to freeze, or a frozen tree to use as it is. */ 333 private static Object normalize(Object value) { 334 if (value instanceof SqllrObject) { 335 return ((SqllrObject) value).members; 336 } 337 if (value == null || value == JSON_NULL) { 338 return null; 339 } 340 if (value instanceof Long || value instanceof String || value instanceof Boolean || value instanceof Map<?, ?> 341 || value instanceof List<?>) { 342 return value; 343 } 344 if (value instanceof Integer || value instanceof Short || value instanceof Byte) { 345 return Long.valueOf(((Number) value).longValue()); 346 } 347 if (value instanceof BigInteger) { 348 return ((BigInteger) value).bitLength() < 64 ? Long.valueOf(((BigInteger) value).longValue()) : value; 349 } 350 throw new IllegalArgumentException("not a JSON value of the model: " + value.getClass().getName()); 351 } 352 353 /** One container being frozen. */ 354 private static final class Frame { 355 final Iterator<?> items; 356 final LinkedHashMap<String, Object> map; 357 final List<Object> list; 358 String key; 359 360 Frame(Object container) { 361 if (container instanceof Map<?, ?>) { 362 items = ((Map<?, ?>) container).entrySet().iterator(); 363 map = new LinkedHashMap<String, Object>(); 364 list = null; 365 } else { 366 items = ((List<?>) container).iterator(); 367 map = null; 368 list = new ArrayList<Object>(); 369 } 370 } 371 372 /** The next value to freeze; for an object, its member name is kept until the value is added. */ 373 Object next() { 374 Object item = items.next(); 375 if (map != null) { 376 Map.Entry<?, ?> entry = (Map.Entry<?, ?>) item; 377 if (!(entry.getKey() instanceof String)) { 378 throw new IllegalArgumentException("member name " + entry.getKey() + " is not a string"); 379 } 380 key = (String) entry.getKey(); 381 return entry.getValue(); 382 } 383 return item; 384 } 385 386 void add(Object value) { 387 if (map != null) { 388 map.put(key, value); 389 } else { 390 list.add(value); 391 } 392 } 393 394 Object build() { 395 return map != null ? new FrozenMap(map) : new FrozenList(list); 396 } 397 } 398 399 /** A frozen copy of a JSON value, made with an explicit stack; frozen subtrees are shared, not copied. */ 400 static Object freeze(Object value) { 401 Object root = normalize(value); 402 if (!unfrozen(root)) { 403 return root; 404 } 405 Deque<Frame> stack = new ArrayDeque<Frame>(); 406 stack.push(new Frame(root)); 407 Object result = null; 408 while (!stack.isEmpty()) { 409 Frame frame = stack.peek(); 410 if (frame.items.hasNext()) { 411 Object child = normalize(frame.next()); 412 if (unfrozen(child)) { 413 stack.push(new Frame(child)); 414 } else { 415 frame.add(child); 416 } 417 } else { 418 stack.pop(); 419 Object frozen = frame.build(); 420 if (stack.isEmpty()) { 421 result = frozen; 422 } else { 423 stack.peek().add(frozen); 424 } 425 } 426 } 427 return result; 428 } 429 430 /** The RFC 8785 text of the members, computed once. */ 431 private String canonical() { 432 String text = canonical; 433 if (text == null) { 434 text = CanonicalJson.jcsText(members); 435 canonical = text; 436 } 437 return text; 438 } 439 440 @Override 441 public final boolean equals(Object other) { 442 return other != null && other.getClass() == getClass() && canonical().equals(((SqllrObject) other).canonical()); 443 } 444 445 @Override 446 public final int hashCode() { 447 return canonical().hashCode(); 448 } 449 450 /** The RFC 8785 text of the object. */ 451 @Override 452 public final String toString() { 453 return canonical(); 454 } 455 456 /** 457 * Collects the members of a new object. A setter given null removes the member; given {@link #JSON_NULL} it sets 458 * JSON null; a typed value, list or map of typed values is stored as its JSON. 459 */ 460 public abstract static class Builder<T extends SqllrObject, B extends Builder<T, B>> { 461 private final LinkedHashMap<String, Object> members = new LinkedHashMap<String, Object>(); 462 463 protected Builder(Map<String, Object> from) { 464 if (from != null) { 465 members.putAll(from); 466 } 467 } 468 469 @SuppressWarnings("unchecked") 470 private B self() { 471 return (B) this; 472 } 473 474 /** Sets a member the model may not type, such as module content; null removes it. */ 475 public final B set(String member, Object value) { 476 if (value == null) { 477 members.remove(member); 478 } else { 479 members.put(member, value); 480 } 481 return self(); 482 } 483 484 public final B remove(String member) { 485 members.remove(member); 486 return self(); 487 } 488 489 /** The frozen members collected so far. */ 490 @SuppressWarnings("unchecked") 491 protected final Map<String, Object> frozenMembers() { 492 return (Map<String, Object>) freeze(members); 493 } 494 495 public abstract T build(); 496 } 497}