001package gudusoft.gsqlparser.dlineage.dataflow.model;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.LinkedHashMap;
006import java.util.List;
007import java.util.Map;
008
009/**
010 * Immutable semantic evidence captured while the parser AST and catalog
011 * bindings are still available. The legacy XML/JSON contract deliberately
012 * does not serialize this object; consumers correlate it to the completed
013 * legacy POJO through document-local relationship, routine, and field IDs.
014 */
015public final class AuthoritativeLineageEvidence {
016
017    private static final AuthoritativeLineageEvidence EMPTY = new AuthoritativeLineageEvidence(
018            Collections.<TypedTransform>emptyList(), Collections.<RoutineBinding>emptyList(),
019            Collections.<CompositeForeignKey>emptyList());
020
021    private final Map<String, TypedTransform> typedTransformsByRelationshipId;
022    private final Map<String, RoutineBinding> routineBindingsByRoutineId;
023    private final List<CompositeForeignKey> compositeForeignKeys;
024
025    public AuthoritativeLineageEvidence(List<TypedTransform> typedTransforms,
026            List<RoutineBinding> routineBindings, List<CompositeForeignKey> compositeForeignKeys) {
027        this.typedTransformsByRelationshipId = indexTypedTransforms(typedTransforms);
028        this.routineBindingsByRoutineId = indexRoutineBindings(routineBindings);
029        this.compositeForeignKeys = immutable(compositeForeignKeys);
030    }
031
032    public static AuthoritativeLineageEvidence empty() { return EMPTY; }
033
034    public TypedTransform typedTransform(String relationshipId) {
035        return typedTransformsByRelationshipId.get(relationshipId);
036    }
037
038    public RoutineBinding routineBinding(String routineId) {
039        return routineBindingsByRoutineId.get(routineId);
040    }
041
042    public List<TypedTransform> typedTransforms() {
043        return Collections.unmodifiableList(new ArrayList<TypedTransform>(
044                typedTransformsByRelationshipId.values()));
045    }
046
047    public List<RoutineBinding> routineBindings() {
048        return Collections.unmodifiableList(new ArrayList<RoutineBinding>(
049                routineBindingsByRoutineId.values()));
050    }
051
052    public List<CompositeForeignKey> compositeForeignKeys() { return compositeForeignKeys; }
053
054    public boolean isEmpty() {
055        return typedTransformsByRelationshipId.isEmpty() && routineBindingsByRoutineId.isEmpty()
056                && compositeForeignKeys.isEmpty();
057    }
058
059    public static final class TypedTransform {
060        private final String relationshipId;
061        private final String operationFamily;
062        private final String operationCode;
063        private final List<TypedOperand> operands;
064
065        public TypedTransform(String relationshipId, String operationFamily, String operationCode,
066                List<TypedOperand> operands) {
067            this.relationshipId = required(relationshipId, "relationshipId");
068            this.operationFamily = required(operationFamily, "operationFamily");
069            this.operationCode = required(operationCode, "operationCode");
070            this.operands = immutableRequired(operands, "operands");
071        }
072
073        public String relationshipId() { return relationshipId; }
074        public String operationFamily() { return operationFamily; }
075        public String operationCode() { return operationCode; }
076        public List<TypedOperand> operands() { return operands; }
077    }
078
079    public static final class TypedOperand {
080        private final String sourceLegacyId;
081        private final String roleCode;
082
083        public TypedOperand(String sourceLegacyId, String roleCode) {
084            this.sourceLegacyId = required(sourceLegacyId, "sourceLegacyId");
085            this.roleCode = required(roleCode, "roleCode");
086        }
087
088        public String sourceLegacyId() { return sourceLegacyId; }
089        public String roleCode() { return roleCode; }
090    }
091
092    public static final class RoutineBinding {
093        private final String routineLegacyId;
094        private final String opaqueIdentity;
095        private final List<RoutineParameter> parameters;
096
097        public RoutineBinding(String routineLegacyId, String opaqueIdentity,
098                List<RoutineParameter> parameters) {
099            this.routineLegacyId = required(routineLegacyId, "routineLegacyId");
100            this.opaqueIdentity = required(opaqueIdentity, "opaqueIdentity");
101            this.parameters = immutable(parameters);
102        }
103
104        public String routineLegacyId() { return routineLegacyId; }
105        public String opaqueIdentity() { return opaqueIdentity; }
106        public List<RoutineParameter> parameters() { return parameters; }
107    }
108
109    public static final class RoutineParameter {
110        private final String bindingKey;
111        private final String legacyArgumentId;
112        private final String boundLegacyEndpointId;
113        private final String displayName;
114        private final String role;
115        private final int ordinal;
116        private final String declaredType;
117        private final String opaqueIdentity;
118
119        public RoutineParameter(String bindingKey, String legacyArgumentId,
120                String boundLegacyEndpointId, String displayName, String role, int ordinal,
121                String declaredType, String opaqueIdentity) {
122            this.bindingKey = required(bindingKey, "bindingKey");
123            this.legacyArgumentId = optional(legacyArgumentId);
124            this.boundLegacyEndpointId = optional(boundLegacyEndpointId);
125            this.displayName = required(displayName, "displayName");
126            this.role = required(role, "role");
127            if (ordinal < 1) throw new IllegalArgumentException("ordinal must be positive");
128            this.ordinal = ordinal;
129            this.declaredType = optional(declaredType);
130            this.opaqueIdentity = required(opaqueIdentity, "opaqueIdentity");
131        }
132
133        public String bindingKey() { return bindingKey; }
134        public String legacyArgumentId() { return legacyArgumentId; }
135        public String boundLegacyEndpointId() { return boundLegacyEndpointId; }
136        public String displayName() { return displayName; }
137        public String role() { return role; }
138        public int ordinal() { return ordinal; }
139        public String declaredType() { return declaredType; }
140        public String opaqueIdentity() { return opaqueIdentity; }
141    }
142
143    public static final class CompositeForeignKey {
144        private final List<String> evidenceRelationshipIds;
145        private final List<ForeignKeyPair> keyPairs;
146
147        public CompositeForeignKey(List<String> evidenceRelationshipIds,
148                List<ForeignKeyPair> keyPairs) {
149            this.evidenceRelationshipIds = immutableRequired(evidenceRelationshipIds,
150                    "evidenceRelationshipIds");
151            this.keyPairs = immutableRequired(keyPairs, "keyPairs");
152        }
153
154        public List<String> evidenceRelationshipIds() { return evidenceRelationshipIds; }
155        public List<ForeignKeyPair> keyPairs() { return keyPairs; }
156    }
157
158    public static final class ForeignKeyPair {
159        private final String foreignKeyFieldLegacyId;
160        private final String referencedFieldLegacyId;
161
162        public ForeignKeyPair(String foreignKeyFieldLegacyId, String referencedFieldLegacyId) {
163            this.foreignKeyFieldLegacyId = required(foreignKeyFieldLegacyId,
164                    "foreignKeyFieldLegacyId");
165            this.referencedFieldLegacyId = required(referencedFieldLegacyId,
166                    "referencedFieldLegacyId");
167        }
168
169        public String foreignKeyFieldLegacyId() { return foreignKeyFieldLegacyId; }
170        public String referencedFieldLegacyId() { return referencedFieldLegacyId; }
171    }
172
173    private static Map<String, TypedTransform> indexTypedTransforms(List<TypedTransform> values) {
174        Map<String, TypedTransform> result = new LinkedHashMap<String, TypedTransform>();
175        if (values != null) for (TypedTransform value : values) {
176            if (value == null) throw new IllegalArgumentException("typedTransforms must not contain null");
177            if (result.put(value.relationshipId(), value) != null) {
178                throw new IllegalArgumentException("duplicate typed transform relationship ID: "
179                        + value.relationshipId());
180            }
181        }
182        return Collections.unmodifiableMap(result);
183    }
184
185    private static Map<String, RoutineBinding> indexRoutineBindings(List<RoutineBinding> values) {
186        Map<String, RoutineBinding> result = new LinkedHashMap<String, RoutineBinding>();
187        if (values != null) for (RoutineBinding value : values) {
188            if (value == null) throw new IllegalArgumentException("routineBindings must not contain null");
189            if (result.put(value.routineLegacyId(), value) != null) {
190                throw new IllegalArgumentException("duplicate routine binding ID: "
191                        + value.routineLegacyId());
192            }
193        }
194        return Collections.unmodifiableMap(result);
195    }
196
197    private static <T> List<T> immutable(List<T> values) {
198        if (values == null || values.isEmpty()) return Collections.emptyList();
199        List<T> copy = new ArrayList<T>(values.size());
200        for (T value : values) {
201            if (value == null) throw new IllegalArgumentException("evidence list must not contain null");
202            copy.add(value);
203        }
204        return Collections.unmodifiableList(copy);
205    }
206
207    private static <T> List<T> immutableRequired(List<T> values, String field) {
208        List<T> result = immutable(values);
209        if (result.isEmpty()) throw new IllegalArgumentException(field + " must not be empty");
210        return result;
211    }
212
213    private static String optional(String value) {
214        return value == null || value.length() == 0 ? null : value;
215    }
216
217    private static String required(String value, String field) {
218        if (value == null || value.length() == 0) {
219            throw new IllegalArgumentException(field + " must not be empty");
220        }
221        return value;
222    }
223}