001package gudusoft.gsqlparser.analyzer.v2; 002 003import gudusoft.gsqlparser.analyzer.v2.callgraph.CallEdge; 004import gudusoft.gsqlparser.analyzer.v2.callgraph.CallGraph; 005import gudusoft.gsqlparser.analyzer.v2.callgraph.CallGraphNode; 006import gudusoft.gsqlparser.analyzer.v2.callgraph.TableAccess; 007import gudusoft.gsqlparser.ir.bound.BoundProgram; 008import gudusoft.gsqlparser.ir.bound.BoundRoutineRef; 009import gudusoft.gsqlparser.ir.bound.BoundRoutineSymbol; 010import gudusoft.gsqlparser.ir.common.SourceAnchor; 011 012import java.util.Map; 013 014/** 015 * Exports IR analysis results to JSON format. 016 * <p> 017 * Phase A provides basic BoundProgram export. 018 * Phase B adds CallGraph and impact analysis export. 019 */ 020public class AnalyzerJsonExporter { 021 022 private AnalyzerJsonExporter() {} 023 024 /** Whether to include source anchors in output. */ 025 private static boolean includeSourceAnchors = true; 026 /** Whether to include evidence in output. */ 027 private static boolean includeEvidence = true; 028 029 /** 030 * Exports the full IRProgram to JSON (no call graph). 031 */ 032 public static String export(IRProgram program) { 033 return export(program, null); 034 } 035 036 /** 037 * Exports the full IRProgram and optional CallGraph to JSON. 038 */ 039 public static String export(IRProgram program, CallGraph callGraph) { 040 return export(program, callGraph, null); 041 } 042 043 /** 044 * Exports the full IRProgram and optional CallGraph to JSON, with config control. 045 */ 046 public static String export(IRProgram program, CallGraph callGraph, AnalyzerV2Config config) { 047 if (program == null) { 048 return "{}"; 049 } 050 // Apply config 051 if (config != null) { 052 includeSourceAnchors = config.includeSourceAnchors; 053 includeEvidence = config.includeEvidence; 054 } else { 055 includeSourceAnchors = true; 056 includeEvidence = true; 057 } 058 059 StringBuilder sb = new StringBuilder(); 060 sb.append("{\n"); 061 exportBoundProgram(sb, program.getBoundProgram(), " "); 062 if (callGraph != null) { 063 sb.append(",\n"); 064 exportCallGraph(sb, callGraph, " "); 065 } 066 sb.append("}\n"); 067 return sb.toString(); 068 } 069 070 private static void exportBoundProgram(StringBuilder sb, BoundProgram bound, String indent) { 071 if (bound == null) { 072 sb.append(indent).append("\"boundProgram\": null\n"); 073 return; 074 } 075 076 sb.append(indent).append("\"boundProgram\": {\n"); 077 String inner = indent + " "; 078 079 // Routine index 080 sb.append(inner).append("\"routines\": ["); 081 boolean first = true; 082 for (Map.Entry<String, BoundRoutineSymbol> entry : bound.getRoutineIndex().entrySet()) { 083 if (!first) sb.append(","); 084 first = false; 085 sb.append("\n"); 086 exportRoutineSymbol(sb, entry.getValue(), inner + " "); 087 } 088 if (!bound.getRoutineIndex().isEmpty()) { 089 sb.append("\n").append(inner); 090 } 091 sb.append("],\n"); 092 093 // Object refs 094 sb.append(inner).append("\"objectRefs\": ").append(bound.getAllObjectRefs().size()).append(",\n"); 095 096 // Column refs 097 sb.append(inner).append("\"columnRefs\": ").append(bound.getAllColumnRefs().size()).append(",\n"); 098 099 // Routine refs 100 sb.append(inner).append("\"routineRefs\": ["); 101 first = true; 102 for (BoundRoutineRef ref : bound.getAllRoutineRefs()) { 103 if (!first) sb.append(","); 104 first = false; 105 sb.append("\n"); 106 exportRoutineRef(sb, ref, inner + " "); 107 } 108 if (!bound.getAllRoutineRefs().isEmpty()) { 109 sb.append("\n").append(inner); 110 } 111 sb.append("]\n"); 112 113 sb.append(indent).append("}"); 114 } 115 116 private static void exportCallGraph(StringBuilder sb, CallGraph callGraph, String indent) { 117 sb.append(indent).append("\"callGraph\": {\n"); 118 String inner = indent + " "; 119 120 // Nodes 121 sb.append(inner).append("\"nodes\": ["); 122 boolean first = true; 123 for (Map.Entry<String, CallGraphNode> entry : callGraph.getNodes().entrySet()) { 124 if (!first) sb.append(","); 125 first = false; 126 sb.append("\n"); 127 exportCallGraphNode(sb, entry.getValue(), inner + " "); 128 } 129 if (!callGraph.getNodes().isEmpty()) { 130 sb.append("\n").append(inner); 131 } 132 sb.append("],\n"); 133 134 // Edges 135 sb.append(inner).append("\"edges\": ["); 136 first = true; 137 for (CallEdge edge : callGraph.getEdges()) { 138 if (!first) sb.append(","); 139 first = false; 140 sb.append("\n"); 141 exportCallEdge(sb, edge, inner + " "); 142 } 143 if (!callGraph.getEdges().isEmpty()) { 144 sb.append("\n").append(inner); 145 } 146 sb.append("]\n"); 147 148 sb.append(indent).append("}"); 149 } 150 151 private static void exportCallGraphNode(StringBuilder sb, CallGraphNode node, String indent) { 152 sb.append(indent).append("{"); 153 sb.append("\"routineId\": \"").append(escapeJson(node.getRoutineId())).append("\""); 154 sb.append(", \"name\": \"").append(escapeJson(node.getRoutineName())).append("\""); 155 if (node.getPackageName() != null) { 156 sb.append(", \"package\": \"").append(escapeJson(node.getPackageName())).append("\""); 157 } 158 sb.append(", \"kind\": \"").append(node.getRoutineKind()).append("\""); 159 160 if (!node.getTableAccesses().isEmpty()) { 161 sb.append(", \"tableAccesses\": ["); 162 boolean first = true; 163 for (TableAccess ta : node.getTableAccesses()) { 164 if (!first) sb.append(", "); 165 first = false; 166 sb.append("{\"table\": \"").append(escapeJson(ta.getTableName())).append("\""); 167 sb.append(", \"access\": \"").append(ta.getAccessKind()).append("\"}"); 168 } 169 sb.append("]"); 170 } 171 sb.append("}"); 172 } 173 174 private static void exportCallEdge(StringBuilder sb, CallEdge edge, String indent) { 175 sb.append(indent).append("{"); 176 sb.append("\"caller\": \"").append(escapeJson(edge.getCallerRoutineId())).append("\""); 177 sb.append(", \"callee\": \"").append(escapeJson(edge.getCalleeRoutineId())).append("\""); 178 if (includeSourceAnchors && edge.getCallSiteAnchor() != null) { 179 sb.append(", \"callSite\": "); 180 exportSourceAnchor(sb, edge.getCallSiteAnchor()); 181 } 182 sb.append("}"); 183 } 184 185 private static void exportRoutineSymbol(StringBuilder sb, BoundRoutineSymbol symbol, String indent) { 186 sb.append(indent).append("{"); 187 sb.append("\"routineId\": \"").append(escapeJson(symbol.getRoutineId())).append("\""); 188 sb.append(", \"kind\": \"").append(symbol.getRoutineKind()).append("\""); 189 sb.append(", \"name\": \"").append(escapeJson(symbol.getRoutineName())).append("\""); 190 if (symbol.getPackageName() != null) { 191 sb.append(", \"package\": \"").append(escapeJson(symbol.getPackageName())).append("\""); 192 } 193 sb.append(", \"paramCount\": ").append(symbol.getParameters().size()); 194 if (includeSourceAnchors) { 195 SourceAnchor anchor = symbol.getDeclarationAnchor(); 196 if (anchor != null) { 197 sb.append(", \"sourceAnchor\": "); 198 exportSourceAnchor(sb, anchor); 199 } 200 } 201 sb.append("}"); 202 } 203 204 private static void exportRoutineRef(StringBuilder sb, BoundRoutineRef ref, String indent) { 205 sb.append(indent).append("{"); 206 sb.append("\"text\": \"").append(escapeJson(ref.getOriginalText())).append("\""); 207 sb.append(", \"status\": \"").append(ref.getBindingStatus()).append("\""); 208 if (ref.getResolvedRoutine() != null) { 209 sb.append(", \"resolvedTo\": \"").append(escapeJson(ref.getResolvedRoutine().getRoutineId())).append("\""); 210 } 211 if (includeEvidence && ref.getEvidence() != null) { 212 sb.append(", \"evidence\": \"").append(escapeJson(ref.getEvidence().message)).append("\""); 213 } 214 if (includeSourceAnchors) { 215 SourceAnchor anchor = ref.getSourceAnchor(); 216 if (anchor != null) { 217 sb.append(", \"sourceAnchor\": "); 218 exportSourceAnchor(sb, anchor); 219 } 220 } 221 sb.append("}"); 222 } 223 224 private static void exportSourceAnchor(StringBuilder sb, SourceAnchor anchor) { 225 sb.append("{\"startLine\": ").append(anchor.startLine); 226 sb.append(", \"startCol\": ").append(anchor.startCol); 227 sb.append(", \"endLine\": ").append(anchor.endLine); 228 sb.append(", \"endCol\": ").append(anchor.endCol); 229 if (anchor.fileId != null && !anchor.fileId.isEmpty()) { 230 sb.append(", \"fileId\": \"").append(escapeJson(anchor.fileId)).append("\""); 231 } 232 sb.append("}"); 233 } 234 235 private static String escapeJson(String s) { 236 if (s == null) return ""; 237 return s.replace("\\", "\\\\") 238 .replace("\"", "\\\"") 239 .replace("\n", "\\n") 240 .replace("\r", "\\r") 241 .replace("\t", "\\t"); 242 } 243}