001package gudusoft.gsqlparser.lineage.v2.validation; 002 003import java.math.BigInteger; 004import java.util.ArrayList; 005import java.util.Arrays; 006import java.util.Collections; 007import java.util.HashMap; 008import java.util.List; 009import java.util.Map; 010import java.util.TreeMap; 011 012/** 013 * The objects of a lineage document by kind and document-local reference, tolerant of any shape: the first 014 * object with a reference wins and nothing throws ({@code conformance/run.py}, {@code Index}). Stage 015 * {@code REFERENCE} reports duplicate and dangling references itself. 016 * 017 * @since 4.2.17 018 */ 019public final class DocumentIndex { 020 021 /** The module collections of the document root. */ 022 public static final List<String> MODULE_MEMBERS = Collections.unmodifiableList(Arrays.asList("evidence", 023 "dynamicSqlSites", "expressionSpans", "typedSteps", "routineCalls", "wildcardSelections", 024 "structuralSignatures", "refinements", "naryFacts")); 025 026 private final Map<?, ?> doc; 027 private final Map<?, ?> graph; 028 private final Map<Long, String> kindOf = new HashMap<Long, String>(); 029 private final Map<String, TreeMap<Long, Map<?, ?>>> objects = new HashMap<String, TreeMap<Long, Map<?, ?>>>(); 030 031 public DocumentIndex(Map<?, ?> doc) { 032 this.doc = doc; 033 this.graph = doc.get("explainGraph") instanceof Map<?, ?> ? (Map<?, ?>) doc.get("explainGraph") 034 : Collections.emptyMap(); 035 Object[][] collections = { 036 {"sourceUnit", doc.get("sourceUnits")}, {"statement", doc.get("statements")}, 037 {"scope", doc.get("scopes")}, {"endpoint", doc.get("endpoints")}, {"fact", doc.get("facts")}, 038 {"factCandidate", doc.get("factCandidates")}, {"candidateSet", doc.get("candidateSets")}, 039 {"occurrence", doc.get("occurrences")}, 040 {"tfo", doc.get("targetFieldOccurrences")}, {"diagnostic", doc.get("diagnostics")}, 041 {"node", graph.get("nodes")}, {"microEdge", graph.get("microEdges")}, 042 {"candidateSet", graph.get("candidateSets")}, {"supportPath", graph.get("supportPaths")}}; 043 for (Object[] collection : collections) { 044 for (Map<?, ?> obj : items(collection[1])) { 045 add((String) collection[0], obj); 046 } 047 } 048 for (Map<?, ?> candidate : items(doc.get("factCandidates"))) { 049 for (String side : new String[] {"source", "target"}) { 050 Object end = candidate.get(side); 051 if (end instanceof Map<?, ?> && ((Map<?, ?>) end).get("candidateSet") instanceof Map<?, ?>) { 052 add("candidateSet", (Map<?, ?>) ((Map<?, ?>) end).get("candidateSet")); 053 } 054 } 055 } 056 for (String member : MODULE_MEMBERS) { 057 for (Map<?, ?> obj : items(doc.get(member))) { 058 add("module:" + member, obj); 059 } 060 } 061 } 062 063 /** The objects of a JSON array; anything else, and every non-object element, is skipped. */ 064 public static List<Map<?, ?>> items(Object value) { 065 List<Map<?, ?>> out = new ArrayList<Map<?, ?>>(); 066 if (value instanceof List<?>) { 067 for (Object item : (List<?>) value) { 068 if (item instanceof Map<?, ?>) { 069 out.add((Map<?, ?>) item); 070 } 071 } 072 } 073 return out; 074 } 075 076 /** A document-local reference: an integer (not a boolean), as a Long; null for anything else. */ 077 public static Long intRef(Object value) { 078 if (value instanceof Long || value instanceof Integer || value instanceof Short || value instanceof Byte) { 079 return Long.valueOf(((Number) value).longValue()); 080 } 081 if (value instanceof BigInteger && ((BigInteger) value).bitLength() < 64) { 082 return Long.valueOf(((BigInteger) value).longValue()); 083 } 084 return null; 085 } 086 087 private void add(String kind, Map<?, ?> obj) { 088 Long ref = intRef(obj.get("ref")); 089 if (ref == null || kindOf.containsKey(ref)) { 090 return; 091 } 092 kindOf.put(ref, kind); 093 TreeMap<Long, Map<?, ?>> byRef = objects.get(kind); 094 if (byRef == null) { 095 byRef = new TreeMap<Long, Map<?, ?>>(); 096 objects.put(kind, byRef); 097 } 098 byRef.put(ref, obj); 099 } 100 101 /** The object of this kind with this reference, or null. */ 102 public Map<?, ?> get(String kind, Object ref) { 103 Long key = intRef(ref); 104 if (key == null) { 105 return null; 106 } 107 TreeMap<Long, Map<?, ?>> byRef = objects.get(kind); 108 return byRef == null ? null : byRef.get(key); 109 } 110 111 /** Every object of a kind, ordered by reference. */ 112 public List<Map<?, ?>> all(String kind) { 113 TreeMap<Long, Map<?, ?>> byRef = objects.get(kind); 114 return byRef == null ? Collections.<Map<?, ?>>emptyList() : new ArrayList<Map<?, ?>>(byRef.values()); 115 } 116 117 public Map<?, ?> document() { 118 return doc; 119 } 120 121 /** The ExplainGraph object, or an empty map. */ 122 public Map<?, ?> graph() { 123 return graph; 124 } 125 126 /** True when the document has a non-empty ExplainGraph object. */ 127 public boolean hasGraph() { 128 return !graph.isEmpty(); 129 } 130}