2626
2727from cldk .analysis import AnalysisLevel
2828from cldk .analysis .java .codeanalyzer import JCodeanalyzer
29- from cldk .models .java .models import JApplication , JCRUDOperation , JType , JCallable , JCompilationUnit , JMethodDetail
29+ from cldk .models .java .models import JApplication , JCRUDOperation , JType , JCallable , JCompilationUnit , JImport , JMethodDetail
3030from cldk .models .java import JGraphEdges
3131
3232
33+ def _build_analysis_json_payload (version : str , imports : list [dict [str , object ] | str ], include_call_graph : bool = False ) -> dict :
34+ payload = {
35+ "symbol_table" : {
36+ "/tmp/T.java" : {
37+ "file_path" : "/tmp/T.java" ,
38+ "package_name" : "" ,
39+ "comments" : [],
40+ "imports" : imports ,
41+ "type_declarations" : {},
42+ "is_modified" : False ,
43+ }
44+ },
45+ "version" : version ,
46+ }
47+ if include_call_graph :
48+ payload ["call_graph" ] = []
49+ return payload
50+
51+
3352def test_init_japplication (test_fixture , codeanalyzer_jar_path , analysis_json ):
3453 """Should return the initialized JApplication"""
3554
@@ -102,6 +121,85 @@ def test_init_codeanalyzer_with_json_path(test_fixture, analysis_json, analysis_
102121 assert isinstance (app , JApplication )
103122
104123
124+ def test_init_japplication_supports_legacy_import_schema () -> None :
125+ """Should parse legacy string-based imports and expose both import fields."""
126+ payload = _build_analysis_json_payload (version = "2.3.6" , imports = ["java.util.List" ])
127+ application = JCodeanalyzer ._init_japplication (json .dumps (payload ))
128+ compilation_unit = next (iter (application .symbol_table .values ()))
129+ assert compilation_unit .imports == ["java.util.List" ]
130+ assert len (compilation_unit .import_declarations ) == 1
131+ assert isinstance (compilation_unit .import_declarations [0 ], JImport )
132+ assert compilation_unit .import_declarations [0 ].path == "java.util.List"
133+ assert compilation_unit .import_declarations [0 ].is_static is False
134+ assert compilation_unit .import_declarations [0 ].is_wildcard is False
135+
136+
137+ def test_init_japplication_supports_structured_import_schema () -> None :
138+ """Should parse structured imports and keep legacy imports list populated."""
139+ payload = _build_analysis_json_payload (
140+ version = "2.3.7" ,
141+ imports = [{"path" : "java.util.List" , "is_static" : True , "is_wildcard" : False }],
142+ )
143+ application = JCodeanalyzer ._init_japplication (json .dumps (payload ))
144+ compilation_unit = next (iter (application .symbol_table .values ()))
145+ assert compilation_unit .imports == ["java.util.List" ]
146+ assert len (compilation_unit .import_declarations ) == 1
147+ assert isinstance (compilation_unit .import_declarations [0 ], JImport )
148+ assert compilation_unit .import_declarations [0 ].path == "java.util.List"
149+ assert compilation_unit .import_declarations [0 ].is_static is True
150+ assert compilation_unit .import_declarations [0 ].is_wildcard is False
151+
152+
153+ def test_check_existing_analysis_file_level_accepts_legacy_import_schema (tmp_path ) -> None :
154+ """Should accept cached analysis files that use the legacy imports schema."""
155+ analysis_file = tmp_path / "analysis.json"
156+ payload = _build_analysis_json_payload (version = "2.3.6" , imports = ["java.util.List" ])
157+ analysis_file .write_text (json .dumps (payload ), encoding = "utf-8" )
158+ assert JCodeanalyzer .check_exisiting_analysis_file_level (analysis_file , analysis_level = 1 )
159+
160+
161+ def test_check_existing_analysis_file_level_accepts_structured_import_schema (tmp_path ) -> None :
162+ """Should accept cached analysis files that use the structured imports schema."""
163+ analysis_file = tmp_path / "analysis.json"
164+ payload = _build_analysis_json_payload (version = "2.3.7" , imports = [{"path" : "java.util.List" , "is_static" : False , "is_wildcard" : False }])
165+ analysis_file .write_text (json .dumps (payload ), encoding = "utf-8" )
166+ assert JCodeanalyzer .check_exisiting_analysis_file_level (analysis_file , analysis_level = 1 )
167+
168+
169+ def test_check_existing_analysis_file_level_rejects_invalid_json (tmp_path ) -> None :
170+ """Should reject invalid analysis.json payloads and force regeneration."""
171+ analysis_file = tmp_path / "analysis.json"
172+ analysis_file .write_text ("{not-valid-json" , encoding = "utf-8" )
173+ assert not JCodeanalyzer .check_exisiting_analysis_file_level (analysis_file , analysis_level = 1 )
174+
175+
176+ def test_init_codeanalyzer_reuses_legacy_cache_when_compatible (test_fixture , codeanalyzer_jar_path , tmp_path ) -> None :
177+ """Should reuse cached analysis.json when legacy imports are still compatible."""
178+ analysis_json_dir = tmp_path / "analysis-cache"
179+ analysis_json_dir .mkdir ()
180+ analysis_json_file = analysis_json_dir / "analysis.json"
181+ legacy_payload = _build_analysis_json_payload (version = "2.3.6" , imports = ["java.util.List" ])
182+ analysis_json_file .write_text (json .dumps (legacy_payload ), encoding = "utf-8" )
183+
184+ with patch ("cldk.analysis.java.codeanalyzer.codeanalyzer.subprocess.run" ) as run_mock :
185+ code_analyzer = JCodeanalyzer (
186+ project_dir = test_fixture ,
187+ source_code = None ,
188+ analysis_backend_path = codeanalyzer_jar_path ,
189+ analysis_json_path = analysis_json_dir ,
190+ analysis_level = AnalysisLevel .symbol_table ,
191+ eager_analysis = False ,
192+ target_files = None ,
193+ )
194+ assert not run_mock .called
195+ compilation_unit = next (iter (code_analyzer .application .symbol_table .values ()))
196+ assert compilation_unit .imports == ["java.util.List" ]
197+ assert isinstance (compilation_unit .import_declarations [0 ], JImport )
198+ assert compilation_unit .import_declarations [0 ].path == "java.util.List"
199+ assert compilation_unit .import_declarations [0 ].is_static is False
200+ assert compilation_unit .import_declarations [0 ].is_wildcard is False
201+
202+
105203def test_get_codeanalyzer_exec (test_fixture , codeanalyzer_jar_path , analysis_json ):
106204 """Should return the correct codeanalyzer location"""
107205
@@ -128,7 +226,7 @@ def test_get_codeanalyzer_exec(test_fixture, codeanalyzer_jar_path, analysis_jso
128226 code_analyzer .analysis_backend_path = None
129227 jar_file = code_analyzer ._get_codeanalyzer_exec ()[- 1 ]
130228 exec_path = os .path .dirname (jar_file )
131- relative_path = exec_path .split ("/cldk" )[1 ]
229+ relative_path = exec_path .rsplit ("/cldk" , 1 )[1 ]
132230 assert relative_path == "/analysis/java/codeanalyzer/jar"
133231
134232
@@ -800,6 +898,39 @@ def test_get_all_entrypoint_methods_in_application(test_fixture, codeanalyzer_ja
800898 assert callable .is_entrypoint
801899
802900
901+ def test_source_analysis_imports_disambiguate_static_and_wildcard (codeanalyzer_jar_path ) -> None :
902+ """Should preserve static and wildcard import metadata for colliding import paths."""
903+ source_code = "import static Foo.bar;\n import Foo.bar.*;\n class T {}"
904+ code_analyzer = JCodeanalyzer (
905+ project_dir = "." ,
906+ source_code = source_code ,
907+ analysis_backend_path = codeanalyzer_jar_path ,
908+ analysis_json_path = None ,
909+ analysis_level = AnalysisLevel .symbol_table ,
910+ eager_analysis = False ,
911+ target_files = None ,
912+ )
913+ symbol_table = code_analyzer .get_symbol_table ()
914+ assert len (symbol_table ) == 1
915+
916+ compilation_unit = next (iter (symbol_table .values ()))
917+ assert compilation_unit .imports == ["Foo.bar" , "Foo.bar" ]
918+
919+ import_declarations = compilation_unit .import_declarations
920+ assert len (import_declarations ) == 2
921+ assert all (isinstance (import_decl , JImport ) for import_decl in import_declarations )
922+ assert [import_decl .path for import_decl in import_declarations ].count ("Foo.bar" ) == 2
923+
924+ static_import = next ((import_decl for import_decl in import_declarations if import_decl .is_static ), None )
925+ wildcard_import = next ((import_decl for import_decl in import_declarations if import_decl .is_wildcard ), None )
926+ assert static_import is not None
927+ assert wildcard_import is not None
928+ assert static_import .path == "Foo.bar"
929+ assert static_import .is_wildcard is False
930+ assert wildcard_import .path == "Foo.bar"
931+ assert wildcard_import .is_static is False
932+
933+
803934def test_get_all_entrypoint_classes_in_the_application (test_fixture , codeanalyzer_jar_path ):
804935 """Should return all of the entrypoint classes in an application"""
805936 code_analyzer = JCodeanalyzer (
0 commit comments