|
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