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}