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}