001package gudusoft.gsqlparser.dlineage.dynamicsql;
002
003import gudusoft.gsqlparser.EDbVendor;
004
005import java.io.File;
006import java.io.IOException;
007import java.nio.charset.StandardCharsets;
008import java.nio.file.Files;
009import java.util.ArrayList;
010import java.util.Arrays;
011import java.util.Collections;
012import java.util.List;
013
014/**
015 * Public opt-in entry point for cross-file Oracle PL/SQL and SQL Server
016 * T-SQL routine-summary lineage.
017 *
018 * <p>The normal {@code DataFlowAnalyzer} remains the authoritative legacy
019 * analysis. This API computes an observation-only interprocedural result over
020 * the same unit texts. Its default options are LEGACY and therefore perform no
021 * summary extraction; callers must choose SHADOW or ENHANCED explicitly.
022 */
023public final class ProceduralLineageAnalyzer {
024
025    private final EDbVendor vendor;
026    private final ProceduralLineageOptions options;
027
028    public ProceduralLineageAnalyzer(EDbVendor vendor) {
029        this(vendor, ProceduralLineageOptions.legacy());
030    }
031
032    public ProceduralLineageAnalyzer(EDbVendor vendor,
033            ProceduralLineageOptions options) {
034        if (vendor == null) {
035            throw new IllegalArgumentException("vendor must not be null");
036        }
037        if (vendor != EDbVendor.dbvmssql && vendor != EDbVendor.dbvoracle) {
038            throw new IllegalArgumentException(
039                    "procedural summary lineage supports only dbvmssql and dbvoracle");
040        }
041        this.vendor = vendor;
042        this.options = options == null
043                ? ProceduralLineageOptions.legacy() : options;
044    }
045
046    public ProceduralLineageResult analyze(String... unitTexts) {
047        return analyze(unitTexts == null
048                ? Collections.<String>emptyList()
049                : Arrays.asList(unitTexts));
050    }
051
052    public ProceduralLineageResult analyze(List<String> unitTexts) {
053        List<String> units = unitTexts == null
054                ? Collections.<String>emptyList()
055                : new ArrayList<String>(unitTexts);
056        ShadowSummaryApplier.ShadowResult shadow =
057                new ShadowSummaryApplier(vendor, options).apply(units);
058        return new ProceduralLineageResult(options.getMode(), shadow);
059    }
060
061    /** Analyze files in the supplied order, BOM-sniffing UTF-8/UTF-16. */
062    public ProceduralLineageResult analyzeFiles(File... unitFiles) throws IOException {
063        List<String> units = new ArrayList<String>();
064        if (unitFiles != null) {
065            for (File file : unitFiles) {
066                if (file == null) {
067                    throw new IllegalArgumentException("unit file must not be null");
068                }
069                units.add(readSql(file));
070            }
071        }
072        return analyze(units);
073    }
074
075    private static String readSql(File file) throws IOException {
076        byte[] bytes = Files.readAllBytes(file.toPath());
077        if (bytes.length >= 2 && (bytes[0] & 0xff) == 0xff
078                && (bytes[1] & 0xff) == 0xfe) {
079            return new String(bytes, 2, bytes.length - 2,
080                    StandardCharsets.UTF_16LE);
081        }
082        if (bytes.length >= 2 && (bytes[0] & 0xff) == 0xfe
083                && (bytes[1] & 0xff) == 0xff) {
084            return new String(bytes, 2, bytes.length - 2,
085                    StandardCharsets.UTF_16BE);
086        }
087        int offset = bytes.length >= 3 && (bytes[0] & 0xff) == 0xef
088                && (bytes[1] & 0xff) == 0xbb && (bytes[2] & 0xff) == 0xbf
089                ? 3 : 0;
090        return new String(bytes, offset, bytes.length - offset,
091                StandardCharsets.UTF_8);
092    }
093}