001package gudusoft.gsqlparser.lineage.v2.json; 002 003import gudusoft.gsqlparser.lineage.v2.SqllrException; 004 005import java.math.BigInteger; 006import java.nio.ByteBuffer; 007import java.nio.charset.CharacterCodingException; 008import java.nio.charset.CodingErrorAction; 009import java.nio.charset.StandardCharsets; 010import java.util.ArrayDeque; 011import java.util.ArrayList; 012import java.util.Arrays; 013import java.util.Collections; 014import java.util.Comparator; 015import java.util.Deque; 016import java.util.Iterator; 017import java.util.LinkedHashMap; 018import java.util.List; 019import java.util.Map; 020 021/** 022 * {@code canonical-json-v1} of SQL-LR ({@code lineage-spec/algorithms/canonical-json-v1.md}): the strict 023 * reader of Section 3, the RFC 8785 writer restricted to integers of Section 4, and the array ordering of 024 * the {@code DOCUMENT} mode (Sections 5 and 6). 025 * 026 * <p>A JSON value is one of these Java objects: {@code Map<String, Object>} (members in input order), 027 * {@code List<Object>}, {@code String}, {@code Long}, {@code Boolean}, or {@code null} for JSON null. The 028 * reader returns exactly these types; the writer also accepts {@code Integer}, {@code Short}, {@code Byte} 029 * and {@code BigInteger} within the integer range.</p> 030 * 031 * <p>The reference implementation is {@code lineage-spec/algorithms/reference/sqllr_ids.py} 032 * ({@code parse_json_strict}, {@code jcs_bytes}, {@code canonicalize_document}). Where the two can report 033 * different codes for one input, this class reports the code the reference reports: the parse runs left to 034 * right, a duplicate member name is detected when its object closes, and an unpaired surrogate is reported 035 * only after the whole text parsed.</p> 036 * 037 * <p>Every traversal uses an explicit stack, so nesting is limited by {@link #MAX_DEPTH} alone, not by the 038 * thread's stack size. A value nested deeper is {@code SCHEMA_INVALID_JSON} for the reader, the writer and 039 * the canonicalizer alike, so the writer never produces bytes the reader rejects. (The reference raises an 040 * uncaught {@code RecursionError} from about 500 levels on; no fixture comes near either limit.)</p> 041 * 042 * @since 4.2.17 043 */ 044public final class CanonicalJson { 045 046 /** The two modes of Section 2, and the {@code DOCUMENT} mode that rejects unregistered arrays. */ 047 public enum Mode { 048 /** Every array keeps its order. */ 049 JCS, 050 /** Arrays are ordered as registered; an unregistered array keeps its order. */ 051 DOCUMENT, 052 /** As {@link #DOCUMENT}, and an unregistered array outside open content is {@code SCHEMA_VIOLATION}. */ 053 DOCUMENT_STRICT 054 } 055 056 /** 2^53 - 1: the largest integer the format admits (Section 3, rule 7). */ 057 public static final long MAX_SAFE_INTEGER = 9007199254740991L; 058 059 /** 060 * The most objects and arrays that may enclose one another; the outermost container is level 1. A 061 * deeper value is {@code SCHEMA_INVALID_JSON}. 062 */ 063 public static final int MAX_DEPTH = 1000; 064 065 public static final String SCHEMA_INVALID_JSON = "SCHEMA_INVALID_JSON"; 066 public static final String SCHEMA_DUPLICATE_KEY = "SCHEMA_DUPLICATE_KEY"; 067 public static final String SCHEMA_NON_INTEGER_NUMBER = "SCHEMA_NON_INTEGER_NUMBER"; 068 public static final String SCHEMA_VIOLATION = "SCHEMA_VIOLATION"; 069 public static final String SEMANTIC_ARRAY_KIND_VIOLATION = "SEMANTIC_ARRAY_KIND_VIOLATION"; 070 071 private static final BigInteger MAX_BIG = BigInteger.valueOf(MAX_SAFE_INTEGER); 072 073 private CanonicalJson() { 074 } 075 076 // --------------------------------------------------------------------------------------------- 077 // Reading (Section 3) 078 // --------------------------------------------------------------------------------------------- 079 080 /** 081 * Parses JSON bytes under the eight input rules of Section 3. 082 * 083 * @throws SqllrException {@code SCHEMA_INVALID_JSON}, {@code SCHEMA_DUPLICATE_KEY} or 084 * {@code SCHEMA_NON_INTEGER_NUMBER} 085 */ 086 public static Object parse(byte[] data) { 087 if (data.length >= 3 && (data[0] & 0xff) == 0xef && (data[1] & 0xff) == 0xbb && (data[2] & 0xff) == 0xbf) { 088 throw new SqllrException(SCHEMA_INVALID_JSON, "a byte order mark is not allowed"); 089 } 090 String text; 091 try { 092 text = StandardCharsets.UTF_8.newDecoder() 093 .onMalformedInput(CodingErrorAction.REPORT) 094 .onUnmappableCharacter(CodingErrorAction.REPORT) 095 .decode(ByteBuffer.wrap(data)).toString(); 096 } catch (CharacterCodingException e) { 097 throw new SqllrException(SCHEMA_INVALID_JSON, "input is not well-formed UTF-8"); 098 } 099 return new Parser(text).parseText(); 100 } 101 102 /** The canonical bytes of JSON input bytes in the given mode. */ 103 public static byte[] canonicalBytes(byte[] data, Mode mode) { 104 Object value = parse(data); 105 if (mode == Mode.JCS) { 106 return jcsBytes(value); 107 } 108 return jcsBytes(canonicalizeDocument(value, mode == Mode.DOCUMENT_STRICT, ArrayRegistry.standard(), null)); 109 } 110 111 // --------------------------------------------------------------------------------------------- 112 // Writing (Section 4) 113 // --------------------------------------------------------------------------------------------- 114 115 /** The RFC 8785 bytes of a value, every array in its current order ({@code JCS} mode). */ 116 public static byte[] jcsBytes(Object value) { 117 return jcsText(value).getBytes(StandardCharsets.UTF_8); 118 } 119 120 /** 121 * The RFC 8785 text of a value, every array in its current order. Its UTF-8 encoding is 122 * {@link #jcsBytes(Object)}. 123 */ 124 public static String jcsText(Object value) { 125 StringBuilder out = new StringBuilder(); 126 if (!isContainer(value)) { 127 writeScalar(value, out); 128 return out.toString(); 129 } 130 Deque<WriteFrame> stack = new ArrayDeque<WriteFrame>(); 131 stack.push(WriteFrame.open(value, out, 1)); 132 while (!stack.isEmpty()) { 133 WriteFrame frame = stack.peek(); 134 if (!frame.hasNext()) { 135 out.append(frame.close); 136 stack.pop(); 137 continue; 138 } 139 Object child = frame.next(out); 140 if (isContainer(child)) { 141 stack.push(WriteFrame.open(child, out, stack.size() + 1)); 142 } else { 143 writeScalar(child, out); 144 } 145 } 146 return out.toString(); 147 } 148 149 /** {@code jcsBytes(canonicalizeDocument(value, true))}: the canonical wire form of a lineage document. */ 150 public static byte[] documentBytes(Object value) { 151 return jcsBytes(canonicalizeDocument(value, true)); 152 } 153 154 private static boolean isContainer(Object value) { 155 return value instanceof Map<?, ?> || value instanceof List<?>; 156 } 157 158 /** One object or array being written: its members in JCS order, or its elements in order. */ 159 private static final class WriteFrame { 160 final char close; 161 private final Map<?, ?> map; 162 private final List<String> names; 163 private final Iterator<?> elements; 164 private int index; 165 166 private WriteFrame(char close, Map<?, ?> map, List<String> names, Iterator<?> elements) { 167 this.close = close; 168 this.map = map; 169 this.names = names; 170 this.elements = elements; 171 } 172 173 static WriteFrame open(Object value, StringBuilder out, int level) { 174 if (level > MAX_DEPTH) { 175 throw new SqllrException(SCHEMA_INVALID_JSON, "nesting deeper than " + MAX_DEPTH); 176 } 177 if (value instanceof Map<?, ?>) { 178 Map<?, ?> map = (Map<?, ?>) value; 179 List<String> names = new ArrayList<String>(map.size()); 180 for (Object name : map.keySet()) { 181 if (!(name instanceof String)) { 182 throw new IllegalArgumentException("JSON member name is not a String: " + name); 183 } 184 names.add((String) name); 185 } 186 // String.compareTo compares UTF-16 code units, unsigned: RFC 8785, Section 3.2.3. 187 Collections.sort(names); 188 out.append('{'); 189 return new WriteFrame('}', map, names, null); 190 } 191 out.append('['); 192 return new WriteFrame(']', null, null, ((List<?>) value).iterator()); 193 } 194 195 boolean hasNext() { 196 return map != null ? index < names.size() : elements.hasNext(); 197 } 198 199 /** Writes the separator (and the member name) before the next child and returns the child. */ 200 Object next(StringBuilder out) { 201 if (index > 0) { 202 out.append(','); 203 } 204 if (map != null) { 205 String name = names.get(index++); 206 writeString(name, out); 207 out.append(':'); 208 return map.get(name); 209 } 210 index++; 211 return elements.next(); 212 } 213 } 214 215 private static void writeScalar(Object value, StringBuilder out) { 216 if (value == null) { 217 out.append("null"); 218 } else if (value instanceof Boolean) { 219 out.append(((Boolean) value).booleanValue() ? "true" : "false"); 220 } else if (value instanceof String) { 221 writeString((String) value, out); 222 } else if (value instanceof Number) { 223 out.append(integerValue((Number) value)); 224 } else { 225 throw new IllegalArgumentException("not a JSON value: " + value.getClass().getName()); 226 } 227 } 228 229 /** The value of an integer {@link Number} within the integer range of Section 3, rule 7. */ 230 static long integerValue(Number number) { 231 if (number instanceof Long || number instanceof Integer || number instanceof Short || number instanceof Byte) { 232 long value = number.longValue(); 233 if (value > MAX_SAFE_INTEGER || value < -MAX_SAFE_INTEGER) { 234 throw new SqllrException(SCHEMA_NON_INTEGER_NUMBER, "integer out of range: " + value); 235 } 236 return value; 237 } 238 if (number instanceof BigInteger) { 239 BigInteger big = (BigInteger) number; 240 if (big.abs().compareTo(MAX_BIG) > 0) { 241 throw new SqllrException(SCHEMA_NON_INTEGER_NUMBER, "integer out of range: " + big); 242 } 243 return big.longValue(); 244 } 245 throw new SqllrException(SCHEMA_NON_INTEGER_NUMBER, String.valueOf(number)); 246 } 247 248 private static void writeString(String value, StringBuilder out) { 249 if (firstUnpairedSurrogate(value) >= 0) { 250 throw new SqllrException(SCHEMA_INVALID_JSON, "unpaired surrogate in a string"); 251 } 252 out.append('"'); 253 for (int i = 0; i < value.length(); i++) { 254 char c = value.charAt(i); 255 switch (c) { 256 case '"': out.append("\\\""); break; 257 case '\\': out.append("\\\\"); break; 258 case '\b': out.append("\\b"); break; 259 case '\t': out.append("\\t"); break; 260 case '\n': out.append("\\n"); break; 261 case '\f': out.append("\\f"); break; 262 case '\r': out.append("\\r"); break; 263 default: 264 if (c < 0x20) { 265 out.append("\\u00").append(HEX[c >> 4]).append(HEX[c & 0xf]); 266 } else { 267 out.append(c); 268 } 269 } 270 } 271 out.append('"'); 272 } 273 274 private static final char[] HEX = "0123456789abcdef".toCharArray(); 275 276 /** The index of the first unpaired surrogate code unit of {@code value}, or -1. */ 277 static int firstUnpairedSurrogate(String value) { 278 for (int i = 0; i < value.length(); i++) { 279 char c = value.charAt(i); 280 if (Character.isHighSurrogate(c)) { 281 if (i + 1 < value.length() && Character.isLowSurrogate(value.charAt(i + 1))) { 282 i++; 283 } else { 284 return i; 285 } 286 } else if (Character.isLowSurrogate(c)) { 287 return i; 288 } 289 } 290 return -1; 291 } 292 293 // --------------------------------------------------------------------------------------------- 294 // Ordering (Sections 5 and 6) 295 // --------------------------------------------------------------------------------------------- 296 297 /** {@code canonicalizeDocument(value, strict, ArrayRegistry.standard(), null)}. */ 298 public static Object canonicalizeDocument(Object value, boolean strict) { 299 return canonicalizeDocument(value, strict, ArrayRegistry.standard(), null); 300 } 301 302 /** 303 * A copy of a lineage document with every {@code SET} array sorted by its registered key; every 304 * {@code SEQUENCE} and {@code DISPLAY_ORDER} array, and every array in open content, keeps its order. 305 * Members keep their order (the writer orders them). Children are visited in input order and an array 306 * is checked after its elements, as the reference does, so the first rejection is the reference's. 307 * 308 * @param strict reject an unregistered array outside open content with {@code SCHEMA_VIOLATION} 309 * (Section 5, rule 5); otherwise keep it in order 310 * @param notes when not null and not strict, receives one line per unregistered array kept in order 311 * @throws SqllrException {@code SEMANTIC_ARRAY_KIND_VIOLATION} for a {@code SET} holding two equal elements 312 * or a {@code SEQUENCE} whose ordinals are not 0..n-1 (Section 5, rules 3 and 4); 313 * {@code SCHEMA_VIOLATION} as above 314 */ 315 public static Object canonicalizeDocument(Object value, boolean strict, ArrayRegistry registry, List<String> notes) { 316 ArrayRegistry.Position root = registry.root(); 317 if (!isContainer(value)) { 318 return value; 319 } 320 Deque<WalkFrame> stack = new ArrayDeque<WalkFrame>(); 321 stack.push(new WalkFrame(value, root, "$", root != null && root.isOpenContent(), 1)); 322 Object result = null; 323 while (!stack.isEmpty()) { 324 WalkFrame frame = stack.peek(); 325 if (frame.hasNext()) { 326 WalkFrame child = frame.nextChild(); 327 if (child != null) { 328 stack.push(child); 329 } 330 continue; 331 } 332 stack.pop(); 333 Object done = frame.finish(strict, notes); 334 if (stack.isEmpty()) { 335 result = done; 336 } else { 337 stack.peek().accept(done); 338 } 339 } 340 return result; 341 } 342 343 /** One object or array being canonicalized. */ 344 private static final class WalkFrame { 345 private final ArrayRegistry.Position position; 346 private final String path; 347 private final boolean inOpen; 348 private final int level; 349 private final Iterator<? extends Map.Entry<?, ?>> members; 350 private final Map<String, Object> outMap; 351 private final Iterator<?> elements; 352 private final List<Object> outList; 353 private String pendingName; 354 355 WalkFrame(Object node, ArrayRegistry.Position position, String path, boolean inOpen, int level) { 356 if (level > MAX_DEPTH) { 357 throw new SqllrException(SCHEMA_INVALID_JSON, "nesting deeper than " + MAX_DEPTH); 358 } 359 this.position = position; 360 this.path = path; 361 this.inOpen = inOpen; 362 this.level = level; 363 if (node instanceof Map<?, ?>) { 364 this.members = ((Map<?, ?>) node).entrySet().iterator(); 365 this.outMap = new LinkedHashMap<String, Object>(); 366 this.elements = null; 367 this.outList = null; 368 } else { 369 this.members = null; 370 this.outMap = null; 371 this.elements = ((List<?>) node).iterator(); 372 this.outList = new ArrayList<Object>(((List<?>) node).size()); 373 } 374 } 375 376 boolean hasNext() { 377 return members != null ? members.hasNext() : elements.hasNext(); 378 } 379 380 /** Takes the next child: a scalar is copied at once (null returned), a container gets a frame. */ 381 WalkFrame nextChild() { 382 Object child; 383 ArrayRegistry.Position childPosition; 384 String childPath; 385 boolean childOpen; 386 if (members != null) { 387 Map.Entry<?, ?> entry = members.next(); 388 String name = (String) entry.getKey(); 389 child = entry.getValue(); 390 childPosition = position == null ? null : position.member(name); 391 childPath = path + "." + name; 392 childOpen = inOpen || "extensions".equals(name); 393 pendingName = name; 394 } else { 395 child = elements.next(); 396 childPosition = position == null ? null : position.element(); 397 childPath = path + "[]"; 398 childOpen = inOpen; 399 } 400 if (!isContainer(child)) { 401 accept(child); 402 return null; 403 } 404 childOpen = childOpen || (childPosition != null && childPosition.isOpenContent()); 405 return new WalkFrame(child, childPosition, childPath, childOpen, level + 1); 406 } 407 408 void accept(Object value) { 409 if (outMap != null) { 410 outMap.put(pendingName, value); 411 } else { 412 outList.add(value); 413 } 414 } 415 416 Object finish(boolean strict, List<String> notes) { 417 if (outMap != null) { 418 return outMap; 419 } 420 ArrayRegistry.Registration registration = inOpen ? ArrayRegistry.Registration.OPEN_SEQUENCE 421 : position == null ? null : position.registration(); 422 if (registration == null) { 423 if (strict) { 424 throw new SqllrException(SCHEMA_VIOLATION, "unregistered array " + path); 425 } 426 if (notes != null) { 427 notes.add("unregistered array " + path + " kept in order"); 428 } 429 return outList; 430 } 431 if (registration.getOrder() == ArrayRegistry.Order.SET) { 432 return sortSet(outList, registration.getSortKey(), path); 433 } 434 checkOrdinals(outList, path); 435 return outList; 436 } 437 } 438 439 private static List<Object> sortSet(List<Object> items, ArrayRegistry.SortKey key, String path) { 440 List<Keyed> keyed = new ArrayList<Keyed>(items.size()); 441 for (Object item : items) { 442 keyed.add(new Keyed(item, key)); 443 } 444 Collections.sort(keyed, KEYED_ORDER); 445 List<Object> out = new ArrayList<Object>(items.size()); 446 for (int i = 0; i < keyed.size(); i++) { 447 if (i > 0 && Arrays.equals(keyed.get(i - 1).bytes, keyed.get(i).bytes)) { 448 throw new SqllrException(SEMANTIC_ARRAY_KIND_VIOLATION, path + " holds equal elements"); 449 } 450 out.add(keyed.get(i).element); 451 } 452 return out; 453 } 454 455 /** Section 5, rule 3: when every element carries an integer ordinal, they are 0..n-1 in array order. */ 456 private static void checkOrdinals(List<Object> items, String path) { 457 if (items.isEmpty()) { 458 return; 459 } 460 for (Object item : items) { 461 if (!(item instanceof Map<?, ?>) || !isInteger(((Map<?, ?>) item).get("ordinal"))) { 462 return; 463 } 464 } 465 for (int i = 0; i < items.size(); i++) { 466 long ordinal = integerValue((Number) ((Map<?, ?>) items.get(i)).get("ordinal")); 467 if (ordinal != i) { 468 throw new SqllrException(SEMANTIC_ARRAY_KIND_VIOLATION, path + " ordinals are not 0..n-1 in array order"); 469 } 470 } 471 } 472 473 /** A deep copy of a JSON value in which every object and array is unmodifiable. */ 474 public static Object immutableCopy(Object value) { 475 if (!isContainer(value)) { 476 return value; 477 } 478 Deque<CopyFrame> stack = new ArrayDeque<CopyFrame>(); 479 stack.push(new CopyFrame(value, 1)); 480 Object result = null; 481 while (!stack.isEmpty()) { 482 CopyFrame frame = stack.peek(); 483 if (frame.hasNext()) { 484 Object child = frame.next(); 485 if (isContainer(child)) { 486 stack.push(new CopyFrame(child, stack.size() + 1)); 487 } else { 488 frame.accept(child); 489 } 490 continue; 491 } 492 stack.pop(); 493 Object done = frame.finish(); 494 if (stack.isEmpty()) { 495 result = done; 496 } else { 497 stack.peek().accept(done); 498 } 499 } 500 return result; 501 } 502 503 /** One object or array being copied. */ 504 private static final class CopyFrame { 505 private final Iterator<? extends Map.Entry<?, ?>> members; 506 private final Map<String, Object> outMap; 507 private final Iterator<?> elements; 508 private final List<Object> outList; 509 private String pendingName; 510 511 CopyFrame(Object node, int level) { 512 if (level > MAX_DEPTH) { 513 throw new SqllrException(SCHEMA_INVALID_JSON, "nesting deeper than " + MAX_DEPTH); 514 } 515 if (node instanceof Map<?, ?>) { 516 members = ((Map<?, ?>) node).entrySet().iterator(); 517 outMap = new LinkedHashMap<String, Object>(); 518 elements = null; 519 outList = null; 520 } else { 521 members = null; 522 outMap = null; 523 elements = ((List<?>) node).iterator(); 524 outList = new ArrayList<Object>(((List<?>) node).size()); 525 } 526 } 527 528 boolean hasNext() { 529 return members != null ? members.hasNext() : elements.hasNext(); 530 } 531 532 Object next() { 533 if (members != null) { 534 Map.Entry<?, ?> entry = members.next(); 535 pendingName = (String) entry.getKey(); 536 return entry.getValue(); 537 } 538 return elements.next(); 539 } 540 541 void accept(Object value) { 542 if (outMap != null) { 543 outMap.put(pendingName, value); 544 } else { 545 outList.add(value); 546 } 547 } 548 549 Object finish() { 550 return outMap != null ? Collections.unmodifiableMap(outMap) : Collections.unmodifiableList(outList); 551 } 552 } 553 554 /** True for a JSON integer (not a boolean). */ 555 public static boolean isInteger(Object value) { 556 return value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte 557 || value instanceof BigInteger; 558 } 559 560 /** An element with its precomputed sort key and canonical bytes. */ 561 private static final class Keyed { 562 final Object element; 563 final TypedKey[] key; 564 final byte[] bytes; 565 566 Keyed(Object element, ArrayRegistry.SortKey sortKey) { 567 this.element = element; 568 this.bytes = jcsBytes(element); 569 switch (sortKey.getKind()) { 570 case SELF: 571 key = new TypedKey[] {new TypedKey(element, bytes)}; 572 break; 573 case REF: 574 key = new TypedKey[] {new TypedKey(member(element, "ref"), null)}; 575 break; 576 case BYTES: 577 key = new TypedKey[0]; 578 break; 579 default: 580 List<String> names = sortKey.getMembers(); 581 key = new TypedKey[names.size()]; 582 for (int i = 0; i < key.length; i++) { 583 key[i] = new TypedKey(member(element, names.get(i)), null); 584 } 585 } 586 } 587 588 private static Object member(Object element, String name) { 589 return element instanceof Map<?, ?> ? ((Map<?, ?>) element).get(name) : null; 590 } 591 } 592 593 private static final Comparator<Keyed> KEYED_ORDER = new Comparator<Keyed>() { 594 @Override 595 public int compare(Keyed a, Keyed b) { 596 for (int i = 0; i < a.key.length; i++) { 597 int c = a.key[i].compareTo(b.key[i]); 598 if (c != 0) { 599 return c; 600 } 601 } 602 return compareUnsigned(a.bytes, b.bytes); 603 } 604 }; 605 606 /** 607 * The total order of Section 6.2 over JSON values: absent or {@code null} < {@code false} < 608 * {@code true} < integers (numerically) < strings (unsigned UTF-8 bytes) < arrays and objects 609 * (their canonical bytes, unsigned). 610 */ 611 public static int compareTyped(Object a, Object b) { 612 return new TypedKey(a, null).compareTo(new TypedKey(b, null)); 613 } 614 615 /** Unsigned lexicographic order of byte arrays. */ 616 public static int compareUnsigned(byte[] a, byte[] b) { 617 int common = Math.min(a.length, b.length); 618 for (int i = 0; i < common; i++) { 619 int c = (a[i] & 0xff) - (b[i] & 0xff); 620 if (c != 0) { 621 return c; 622 } 623 } 624 return a.length - b.length; 625 } 626 627 /** One component of a sort key, ranked as in Section 6.2. */ 628 private static final class TypedKey implements Comparable<TypedKey> { 629 final int rank; 630 final long number; 631 final byte[] bytes; 632 633 TypedKey(Object value, byte[] canonical) { 634 if (value == null) { 635 rank = 0; 636 number = 0; 637 bytes = null; 638 } else if (value instanceof Boolean) { 639 rank = 1; 640 number = ((Boolean) value).booleanValue() ? 1 : 0; 641 bytes = null; 642 } else if (value instanceof Number) { 643 rank = 2; 644 number = integerValue((Number) value); 645 bytes = null; 646 } else if (value instanceof String) { 647 if (firstUnpairedSurrogate((String) value) >= 0) { 648 throw new SqllrException(SCHEMA_INVALID_JSON, "unpaired surrogate in a string"); 649 } 650 rank = 3; 651 number = 0; 652 bytes = ((String) value).getBytes(StandardCharsets.UTF_8); 653 } else { 654 rank = 4; 655 number = 0; 656 bytes = canonical != null ? canonical : jcsBytes(value); 657 } 658 } 659 660 @Override 661 public int compareTo(TypedKey other) { 662 if (rank != other.rank) { 663 return rank < other.rank ? -1 : 1; 664 } 665 if (rank == 1 || rank == 2) { 666 return number < other.number ? -1 : number == other.number ? 0 : 1; 667 } 668 if (rank == 0) { 669 return 0; 670 } 671 return compareUnsigned(bytes, other.bytes); 672 } 673 } 674 675 // --------------------------------------------------------------------------------------------- 676 // The parser 677 // --------------------------------------------------------------------------------------------- 678 679 /** 680 * An RFC 8259 parser that follows the decisions of CPython's {@code json} scanner as the reference 681 * configures it (strict strings, integers through {@code parse_int}, fractions and exponents rejected, 682 * duplicate names detected by the object hook when the object closes), with an explicit stack. 683 */ 684 private static final class Parser { 685 private final String s; 686 private final int length; 687 private int pos; 688 private boolean unpairedSurrogate; 689 690 /** An object or array whose closing bracket has not been read yet. */ 691 private static final class Open { 692 final List<String> names; 693 final List<Object> values; 694 String pendingName; 695 696 Open(boolean object) { 697 names = object ? new ArrayList<String>() : null; 698 values = new ArrayList<Object>(); 699 } 700 } 701 702 Parser(String s) { 703 this.s = s; 704 this.length = s.length(); 705 } 706 707 Object parseText() { 708 skipWhitespace(); 709 Object value = value(); 710 skipWhitespace(); 711 if (pos != length) { 712 throw invalid("extra data"); 713 } 714 if (unpairedSurrogate) { 715 throw new SqllrException(SCHEMA_INVALID_JSON, "unpaired surrogate in a string or member name"); 716 } 717 return value; 718 } 719 720 /** One JSON value at {@code pos}, the text before it already skipped. */ 721 private Object value() { 722 Deque<Open> stack = new ArrayDeque<Open>(); 723 while (true) { 724 // Read the start of a value: a scalar completes it, a non-empty container opens a level. 725 Object completed; 726 if (pos >= length) { 727 throw invalid("expecting value"); 728 } 729 char c = s.charAt(pos); 730 if (c == '{' || c == '[') { 731 if (stack.size() + 1 > MAX_DEPTH) { 732 throw invalid("nesting deeper than " + MAX_DEPTH); 733 } 734 pos++; 735 skipWhitespace(); 736 boolean object = c == '{'; 737 if (pos < length && s.charAt(pos) == (object ? '}' : ']')) { 738 pos++; 739 completed = object ? new LinkedHashMap<String, Object>() : new ArrayList<Object>(); 740 } else { 741 Open open = new Open(object); 742 if (object) { 743 open.pendingName = memberName(); 744 } 745 stack.push(open); 746 continue; 747 } 748 } else { 749 completed = scalar(c); 750 } 751 // Attach the completed value to the open containers, closing those that end here. 752 while (true) { 753 Open top = stack.peek(); 754 if (top == null) { 755 return completed; 756 } 757 boolean object = top.names != null; 758 if (object) { 759 top.names.add(top.pendingName); 760 } 761 top.values.add(completed); 762 skipWhitespace(); 763 if (pos < length && s.charAt(pos) == (object ? '}' : ']')) { 764 pos++; 765 stack.pop(); 766 completed = object ? closeObject(top) : top.values; 767 continue; 768 } 769 if (pos >= length || s.charAt(pos) != ',') { 770 throw invalid("expecting ',' delimiter"); 771 } 772 pos++; 773 skipWhitespace(); 774 if (object) { 775 top.pendingName = memberName(); 776 } 777 break; 778 } 779 } 780 } 781 782 /** {@code "name"}, whitespace, {@code :}, whitespace; the reader is at the opening quote. */ 783 private String memberName() { 784 if (pos >= length || s.charAt(pos) != '"') { 785 throw invalid("expecting property name enclosed in double quotes"); 786 } 787 pos++; 788 String name = string(); 789 skipWhitespace(); 790 if (pos >= length || s.charAt(pos) != ':') { 791 throw invalid("expecting ':' delimiter"); 792 } 793 pos++; 794 skipWhitespace(); 795 return name; 796 } 797 798 /** Rule 5 is checked when the object closes, as the reference's object_pairs_hook does. */ 799 private Map<String, Object> closeObject(Open open) { 800 Map<String, Object> out = new LinkedHashMap<String, Object>(open.names.size() * 2); 801 for (int i = 0; i < open.names.size(); i++) { 802 if (out.containsKey(open.names.get(i))) { 803 throw new SqllrException(SCHEMA_DUPLICATE_KEY, "duplicate member " + open.names.get(i)); 804 } 805 out.put(open.names.get(i), open.values.get(i)); 806 } 807 return out; 808 } 809 810 private Object scalar(char c) { 811 switch (c) { 812 case '"': 813 pos++; 814 return string(); 815 case 'n': 816 return literal("null", null); 817 case 't': 818 return literal("true", Boolean.TRUE); 819 case 'f': 820 return literal("false", Boolean.FALSE); 821 default: 822 if (c == '-' || (c >= '0' && c <= '9')) { 823 return number(); 824 } 825 // NaN, Infinity and every other token are not JSON. 826 throw invalid("expecting value"); 827 } 828 } 829 830 private Object literal(String word, Object value) { 831 if (!s.startsWith(word, pos)) { 832 throw invalid("expecting value"); 833 } 834 pos += word.length(); 835 return value; 836 } 837 838 /** The string whose opening quote has been consumed. */ 839 private String string() { 840 StringBuilder out = new StringBuilder(); 841 while (true) { 842 if (pos >= length) { 843 throw invalid("unterminated string"); 844 } 845 char c = s.charAt(pos++); 846 if (c == '"') { 847 break; 848 } 849 if (c == '\\') { 850 if (pos >= length) { 851 throw invalid("unterminated string"); 852 } 853 char e = s.charAt(pos++); 854 switch (e) { 855 case '"': out.append('"'); break; 856 case '\\': out.append('\\'); break; 857 case '/': out.append('/'); break; 858 case 'b': out.append('\b'); break; 859 case 'f': out.append('\f'); break; 860 case 'n': out.append('\n'); break; 861 case 'r': out.append('\r'); break; 862 case 't': out.append('\t'); break; 863 case 'u': out.append(hex4()); break; 864 default: throw invalid("invalid escape"); 865 } 866 } else if (c < 0x20) { 867 throw invalid("invalid control character in a string"); 868 } else { 869 out.append(c); 870 } 871 } 872 String value = out.toString(); 873 if (firstUnpairedSurrogate(value) >= 0) { 874 // Reported after the whole text parsed, as the reference checks values after json.loads. 875 unpairedSurrogate = true; 876 } 877 return value; 878 } 879 880 private char hex4() { 881 if (pos + 4 > length) { 882 throw invalid("invalid \\uXXXX escape"); 883 } 884 int value = 0; 885 for (int i = 0; i < 4; i++) { 886 char c = s.charAt(pos++); 887 int digit; 888 if (c >= '0' && c <= '9') { 889 digit = c - '0'; 890 } else if (c >= 'a' && c <= 'f') { 891 digit = c - 'a' + 10; 892 } else if (c >= 'A' && c <= 'F') { 893 digit = c - 'A' + 10; 894 } else { 895 throw invalid("invalid \\uXXXX escape"); 896 } 897 value = (value << 4) | digit; 898 } 899 return (char) value; 900 } 901 902 private Long number() { 903 int start = pos; 904 if (s.charAt(pos) == '-') { 905 pos++; 906 if (pos >= length) { 907 throw invalid("expecting value"); 908 } 909 } 910 char c = s.charAt(pos); 911 if (c >= '1' && c <= '9') { 912 pos++; 913 while (pos < length && isDigit(s.charAt(pos))) { 914 pos++; 915 } 916 } else if (c == '0') { 917 pos++; 918 } else { 919 throw invalid("expecting value"); 920 } 921 boolean fraction = false; 922 if (pos + 1 < length && s.charAt(pos) == '.' && isDigit(s.charAt(pos + 1))) { 923 pos += 2; 924 while (pos < length && isDigit(s.charAt(pos))) { 925 pos++; 926 } 927 fraction = true; 928 } 929 if (pos < length && (s.charAt(pos) == 'e' || s.charAt(pos) == 'E')) { 930 int exponentStart = pos; 931 pos++; 932 if (pos < length && (s.charAt(pos) == '-' || s.charAt(pos) == '+')) { 933 pos++; 934 } 935 int digits = pos; 936 while (pos < length && isDigit(s.charAt(pos))) { 937 pos++; 938 } 939 if (pos > digits) { 940 fraction = true; 941 } else { 942 pos = exponentStart; 943 } 944 } 945 String token = s.substring(start, pos); 946 if (fraction) { 947 throw new SqllrException(SCHEMA_NON_INTEGER_NUMBER, token); 948 } 949 if ("-0".equals(token)) { 950 throw new SqllrException(SCHEMA_NON_INTEGER_NUMBER, "-0 is not a canonical integer"); 951 } 952 int digitCount = token.startsWith("-") ? token.length() - 1 : token.length(); 953 if (digitCount > 16) { 954 throw new SqllrException(SCHEMA_NON_INTEGER_NUMBER, "integer out of range: " + token); 955 } 956 long value = Long.parseLong(token); 957 if (value > MAX_SAFE_INTEGER || value < -MAX_SAFE_INTEGER) { 958 throw new SqllrException(SCHEMA_NON_INTEGER_NUMBER, "integer out of range: " + token); 959 } 960 return Long.valueOf(value); 961 } 962 963 private static boolean isDigit(char c) { 964 return c >= '0' && c <= '9'; 965 } 966 967 private void skipWhitespace() { 968 while (pos < length) { 969 char c = s.charAt(pos); 970 if (c != ' ' && c != '\t' && c != '\n' && c != '\r') { 971 return; 972 } 973 pos++; 974 } 975 } 976 977 private SqllrException invalid(String message) { 978 return new SqllrException(SCHEMA_INVALID_JSON, message + " at character " + pos); 979 } 980 } 981}