|
40 | 40 | import java.nio.file.Files;
|
41 | 41 | import java.nio.file.Path;
|
42 | 42 | import java.text.SimpleDateFormat;
|
43 |
| -import java.util.Arrays; |
44 |
| -import java.util.Collection; |
45 |
| -import java.util.Collections; |
46 |
| -import java.util.Date; |
47 |
| -import java.util.HashMap; |
48 |
| -import java.util.HashSet; |
49 |
| -import java.util.List; |
50 |
| -import java.util.Map; |
51 |
| -import java.util.Properties; |
52 |
| -import java.util.Set; |
53 |
| -import java.util.TimeZone; |
| 43 | +import java.util.*; |
54 | 44 | import java.util.regex.Pattern;
|
55 | 45 |
|
56 | 46 | import static java.util.Arrays.asList;
|
@@ -526,89 +516,72 @@ public void shouldGenerateJsonWithCorrectObjectStructure(boolean useNativeGit) t
|
526 | 516 | try (InputStreamReader reader = new InputStreamReader(fis, StandardCharsets.UTF_8)) {
|
527 | 517 | try (JsonReader jsonReader = Json.createReader(reader)) {
|
528 | 518 | JsonObject jsonObject = jsonReader.readObject();
|
529 |
| - buildTree(jsonObject.keySet()); |
| 519 | + validateNoPrefixConflicts(jsonObject.keySet()); |
530 | 520 | }
|
531 | 521 | }
|
532 | 522 | }
|
533 | 523 | }
|
534 | 524 |
|
535 |
| - class TreeNode { |
536 |
| - String value; |
537 |
| - private Set<TreeNode> children; |
| 525 | + private static void validateNoPrefixConflicts(Set<String> keys) { |
| 526 | + TreeNode root = new TreeNode(); |
| 527 | + Map<TreeNode, String> pathMap = new HashMap<>(); |
538 | 528 |
|
539 |
| - public TreeNode(String value) { |
540 |
| - this.value = value; |
541 |
| - this.children = new HashSet<>(); |
542 |
| - } |
| 529 | + for (String key : keys) { |
| 530 | + String[] parts = key.split("\\."); |
| 531 | + TreeNode current = root; |
| 532 | + StringBuilder pathBuilder = new StringBuilder(); |
543 | 533 |
|
544 |
| - public void addChild(TreeNode t) { |
545 |
| - this.children.add(t); |
546 |
| - } |
| 534 | + for (int i = 0; i < parts.length; i++) { |
| 535 | + String part = parts[i]; |
| 536 | + if (pathBuilder.length() > 0) { |
| 537 | + pathBuilder.append("."); |
| 538 | + } |
| 539 | + pathBuilder.append(part); |
547 | 540 |
|
548 |
| - @Override |
549 |
| - public String toString() { |
550 |
| - return "TreeNode{" + |
551 |
| - "value='" + value + "'," + |
552 |
| - "children=" + children + |
553 |
| - "}"; |
554 |
| - } |
555 |
| - } |
| 541 | + if (!current.children.containsKey(part)) { |
| 542 | + current.children.put(part, new TreeNode()); |
| 543 | + } |
556 | 544 |
|
557 |
| - class TreeLeave extends TreeNode { |
558 |
| - public TreeLeave(String value) { |
559 |
| - super(value); |
560 |
| - } |
| 545 | + current = current.children.get(part); |
| 546 | + String currentPath = pathBuilder.toString(); |
| 547 | + pathMap.put(current, currentPath); |
561 | 548 |
|
562 |
| - @Override |
563 |
| - public void addChild(TreeNode t) { |
564 |
| - throw new IllegalStateException( |
565 |
| - "Unexpected TreeLeave: Can not nest " + t.value + " under " + this.value); |
566 |
| - } |
| 549 | + if (current.isLeaf && i < parts.length - 1) { |
| 550 | + throw new IllegalArgumentException( |
| 551 | + "Key '" + key + "' attempts to nest under existing key '" + currentPath + "', which is already a value." |
| 552 | + ); |
| 553 | + } |
| 554 | + } |
| 555 | + |
| 556 | + if (!current.children.isEmpty()) { |
| 557 | + String conflictingPath = findDeepestChildPath(current, pathMap); |
| 558 | + throw new IllegalArgumentException( |
| 559 | + "Key '" + key + "' is a value but conflicts with existing nested key '" + conflictingPath + "'." |
| 560 | + ); |
| 561 | + } |
567 | 562 |
|
568 |
| - @Override |
569 |
| - public String toString() { |
570 |
| - return "TreeLeave{" + |
571 |
| - "value='" + value + "'}"; |
| 563 | + current.isLeaf = true; |
572 | 564 | }
|
573 | 565 | }
|
574 | 566 |
|
575 |
| - private TreeNode buildTree(Set<String> nodes) { |
576 |
| - TreeNode root = new TreeNode(""); |
577 |
| - |
578 |
| - for (String node : nodes) { |
579 |
| - TreeNode currentNode = root; |
580 |
| - String[] keys = node.split("\\."); |
581 |
| - |
582 |
| - for (int i = 0; i < keys.length; i++) { |
583 |
| - String key = keys[i]; |
584 |
| - |
585 |
| - TreeNode child = findChild(currentNode, key); |
586 |
| - if (i == keys.length - 1) { |
587 |
| - if (child == null) { |
588 |
| - child = new TreeLeave(key); |
589 |
| - } else if (child instanceof TreeNode) { |
590 |
| - throw new IllegalStateException( |
591 |
| - "Unexpected TreeNode: Can not nest " + key + " under " + child.value); |
592 |
| - } |
593 |
| - } else { |
594 |
| - if (child == null) { |
595 |
| - child = new TreeNode(key); |
596 |
| - } |
597 |
| - } |
598 |
| - currentNode.addChild(child); |
599 |
| - currentNode = child; |
| 567 | + private static String findDeepestChildPath(TreeNode node, Map<TreeNode, String> pathMap) { |
| 568 | + Queue<TreeNode> queue = new LinkedList<>(); |
| 569 | + queue.add(node); |
| 570 | + |
| 571 | + while (!queue.isEmpty()) { |
| 572 | + TreeNode current = queue.poll(); |
| 573 | + if (current.isLeaf) { |
| 574 | + return pathMap.get(current); |
600 | 575 | }
|
| 576 | + queue.addAll(current.children.values()); |
601 | 577 | }
|
602 |
| - return root; |
| 578 | + |
| 579 | + return pathMap.getOrDefault(node, "<unknown>"); |
603 | 580 | }
|
604 | 581 |
|
605 |
| - private TreeNode findChild(TreeNode node, String value) { |
606 |
| - for (TreeNode child : node.children) { |
607 |
| - if (child.value.equals(value)) { |
608 |
| - return child; |
609 |
| - } |
610 |
| - } |
611 |
| - return null; |
| 582 | + static class TreeNode { |
| 583 | + Map<String, TreeNode> children = new HashMap<>(); |
| 584 | + boolean isLeaf = false; |
612 | 585 | }
|
613 | 586 |
|
614 | 587 | @ParameterizedTest
|
|
0 commit comments