@@ -18,11 +18,12 @@ import path from "path";
1818import fg from "fast-glob" ;
1919import fs from "fs" ;
2020import log from "loglevel" ;
21- import { spawn , spawnSync } from "node:child_process" ;
22- import { JApplication } from "../../models/java" ;
23- import { JApplicationType , JCompilationUnitType } from "../../models/java/types" ;
21+ import { spawnSync } from "node:child_process" ;
22+ import { JApplication } from "../../models/java" ;
23+ import * as types from "../../models/java/types" ;
2424import os from "os" ;
2525import JSONStream from "JSONStream" ;
26+ import crypto from "crypto" ;
2627
2728enum AnalysisLevel {
2829 SYMBOL_TABLE = "1" ,
@@ -38,8 +39,8 @@ const analysisLevelMap: Record<string, AnalysisLevel> = {
3839
3940export class JavaAnalysis {
4041 private readonly projectDir : string | null ;
41- private readonly analysisLevel : AnalysisLevel ;
42- application ?: JApplicationType ;
42+ private analysisLevel : AnalysisLevel ;
43+ application ?: types . JApplicationType ;
4344
4445 constructor ( options : { projectDir : string | null ; analysisLevel : string } ) {
4546 this . projectDir = options . projectDir ;
@@ -64,26 +65,24 @@ export class JavaAnalysis {
6465 /**
6566 * Initialize the application by running the codeanalyzer and parsing the output.
6667 * @private
67- * @returns {Promise<JApplicationType> } A promise that resolves to the parsed application data
68+ * @returns {Promise<types. JApplicationType> } A promise that resolves to the parsed application data
6869 * @throws {Error } If the project directory is not specified or if codeanalyzer fails
6970 */
70- private async _initialize_application ( ) : Promise < JApplicationType > {
71- return new Promise < JApplicationType > ( ( resolve , reject ) => {
71+ private async _initialize_application ( ) : Promise < types . JApplicationType > {
72+ return new Promise < types . JApplicationType > ( ( resolve , reject ) => {
7273 if ( ! this . projectDir ) {
7374 return reject ( new Error ( "Project directory not specified" ) ) ;
7475 }
7576
7677 const projectPath = path . resolve ( this . projectDir ) ;
77- /**
78- * I kept running into OOM issues when running the codeanalyzer output is piped to stream.
79- * So I decided to write the output to a temporary file and then read the file.
80- */
81- // Create a temporary file to store the codeanalyzer output
82- const crypto = require ( 'crypto' ) ;
78+ // Create a temporary file to store the codeanalyzer output
8379 const tmpFilePath = path . join ( os . tmpdir ( ) , `${ Date . now ( ) } -${ crypto . randomUUID ( ) } ` ) ;
8480 const command = [ ...this . getCodeAnalyzerExec ( ) , "-i" , projectPath , '-o' , tmpFilePath , `--analysis-level=${ this . analysisLevel } ` , '--verbose' ] ;
81+ // Check if command is valid
82+ if ( ! command [ 0 ] ) {
83+ return reject ( new Error ( "Codeanalyzer command not found" ) ) ;
84+ }
8585 log . debug ( command . join ( " " ) ) ;
86-
8786 const result = spawnSync ( command [ 0 ] , command . slice ( 1 ) , {
8887 stdio : [ "ignore" , "pipe" , "inherit" ] ,
8988 } ) ;
@@ -99,9 +98,9 @@ export class JavaAnalysis {
9998 // Read the analysis result from the temporary file
10099 try {
101100 const stream = fs . createReadStream ( path . join ( tmpFilePath , 'analysis.json' ) ) . pipe ( JSONStream . parse ( ) ) ;
102- const result = { } as JApplicationType ;
101+ const result = { } as types . JApplicationType ;
103102
104- stream . on ( 'data' , ( data ) => {
103+ stream . on ( 'data' , ( data : unknown ) => {
105104 Object . assign ( result , JApplication . parse ( data ) ) ;
106105 } ) ;
107106
@@ -110,10 +109,10 @@ export class JavaAnalysis {
110109 fs . rm ( tmpFilePath , { recursive : true , force : true } , ( err ) => {
111110 if ( err ) log . warn ( `Failed to delete temporary file: ${ tmpFilePath } ` , err ) ;
112111 } ) ;
113- resolve ( result as JApplicationType ) ;
112+ resolve ( result as types . JApplicationType ) ;
114113 } ) ;
115114
116- stream . on ( 'error' , ( err ) => {
115+ stream . on ( 'error' , ( err : any ) => {
117116 reject ( err ) ;
118117 } ) ;
119118 } catch ( error ) {
@@ -135,17 +134,28 @@ export class JavaAnalysis {
135134 *
136135 * The application view denoted by this application structure is crucial for further fine-grained analysis APIs.
137136 * If the application is not already initialized, it will be initialized first.
138- * @returns {Promise<JApplicationType> } A promise that resolves to the application data
137+ * @returns {Promise<types. JApplicationType> } A promise that resolves to the application data
139138 */
140- public async getApplication ( ) : Promise < JApplicationType > {
139+ public async getApplication ( ) : Promise < types . JApplicationType > {
141140 if ( ! this . application ) {
142141 this . application = await this . _initialize_application ( ) ;
143142 }
144143 return this . application ;
145144 }
146145
147- public async getSymbolTable ( ) : Promise < Record < string , JCompilationUnitType > > {
146+ public async getSymbolTable ( ) : Promise < Record < string , types . JCompilationUnitType > > {
148147 return ( await this . getApplication ( ) ) . symbol_table ;
149148 }
149+
150+ public async getCallGraph ( ) : Promise < JCallGraph > {
151+ const application = await this . getApplication ( ) ;
152+ if ( application . call_graph === undefined || application . call_graph === null ) {
153+ log . debug ( "Re-initializing application with call graph" ) ;
154+ this . analysisLevel = AnalysisLevel . CALL_GRAPH ;
155+ this . application = await this . _initialize_application ( ) ;
156+ }
157+
158+ }
159+
150160}
151161
0 commit comments