|
| 1 | +################################################################################ |
| 2 | +# Copyright IBM Corporation 2026 |
| 3 | +# |
| 4 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | +# you may not use this file except in compliance with the License. |
| 6 | +# You may obtain a copy of the License at |
| 7 | +# |
| 8 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | +# |
| 10 | +# Unless required by applicable law or agreed to in writing, software |
| 11 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | +# See the License for the specific language governing permissions and |
| 14 | +# limitations under the License. |
| 15 | +################################################################################ |
| 16 | + |
| 17 | +"""The Java analysis backend contract. |
| 18 | +
|
| 19 | +:class:`JavaAnalysis` is a (mostly) thin façade that delegates its static-analysis queries to a |
| 20 | +*backend*. Today the only backend is :class:`~cldk.analysis.java.codeanalyzer.JCodeanalyzer` |
| 21 | +(in-memory pydantic / NetworkX over the codeanalyzer JSON); this ABC formalizes the surface the |
| 22 | +façade depends on so an alternative backend (e.g. a forthcoming Neo4j/Cypher backend, mirroring |
| 23 | +the TypeScript :class:`~cldk.analysis.typescript.neo4j.TSNeo4jBackend`) can be dropped in and |
| 24 | +selected without touching the façade. |
| 25 | +
|
| 26 | +The contract is enforced by the type system and at instantiation time rather than matching only by |
| 27 | +convention. Note the façade also calls Tree-sitter directly for a few parsing/sanitization helpers |
| 28 | +(e.g. ``is_parsable``, ``get_raw_ast``); those are not part of the backend contract — only the |
| 29 | +analysis queries the façade routes through ``self.backend`` are. |
| 30 | +""" |
| 31 | + |
| 32 | +from __future__ import annotations |
| 33 | + |
| 34 | +from abc import ABC, abstractmethod |
| 35 | +from typing import Dict, List, Tuple, Union |
| 36 | + |
| 37 | +import networkx as nx |
| 38 | + |
| 39 | +from cldk.models.java.models import ( |
| 40 | + JApplication, |
| 41 | + JCallable, |
| 42 | + JCallableParameter, |
| 43 | + JComment, |
| 44 | + JCompilationUnit, |
| 45 | + JCRUDOperation, |
| 46 | + JField, |
| 47 | + JMethodDetail, |
| 48 | + JType, |
| 49 | +) |
| 50 | + |
| 51 | +# A CRUD query row: the owning type + callable and the operations found within it. |
| 52 | +CRUDRow = Dict[str, Union[JType, JCallable, List[JCRUDOperation]]] |
| 53 | + |
| 54 | + |
| 55 | +class JavaAnalysisBackend(ABC): |
| 56 | + """Abstract base every Java analysis backend implements. |
| 57 | +
|
| 58 | + A backend owns all indexing and query logic for a Java application (symbol table, call graph, |
| 59 | + class/method/field navigation, entry points, CRUD operations, comments/docstrings); the |
| 60 | + :class:`JavaAnalysis` façade delegates to it. Implementations must return the canonical |
| 61 | + ``cldk.models.java`` pydantic objects (or the documented NetworkX / dict / list shapes) so |
| 62 | + backends are behaviorally interchangeable. |
| 63 | + """ |
| 64 | + |
| 65 | + # -----[ application / whole-program ]----- |
| 66 | + @abstractmethod |
| 67 | + def get_application_view(self) -> JApplication: |
| 68 | + """The whole application view.""" |
| 69 | + |
| 70 | + @abstractmethod |
| 71 | + def get_symbol_table(self) -> Dict[str, JCompilationUnit]: |
| 72 | + """The per-file symbol table, keyed by file path.""" |
| 73 | + |
| 74 | + @abstractmethod |
| 75 | + def get_compilation_units(self) -> List[JCompilationUnit]: |
| 76 | + """All compilation units.""" |
| 77 | + |
| 78 | + @abstractmethod |
| 79 | + def get_java_file(self, qualified_class_name: str) -> str: |
| 80 | + """The file path declaring a class.""" |
| 81 | + |
| 82 | + @abstractmethod |
| 83 | + def get_java_compilation_unit(self, file_path: str) -> JCompilationUnit: |
| 84 | + """The compilation unit for a file path.""" |
| 85 | + |
| 86 | + # -----[ call graph ]----- |
| 87 | + @abstractmethod |
| 88 | + def get_call_graph(self) -> nx.DiGraph: |
| 89 | + """NetworkX DiGraph of the application's call edges.""" |
| 90 | + |
| 91 | + @abstractmethod |
| 92 | + def get_call_graph_json(self) -> str: |
| 93 | + """The call graph serialized as JSON.""" |
| 94 | + |
| 95 | + @abstractmethod |
| 96 | + def get_all_callers(self, target_class_name: str, target_method_signature: str, using_symbol_table: bool) -> Dict: |
| 97 | + """Callers of a method.""" |
| 98 | + |
| 99 | + @abstractmethod |
| 100 | + def get_all_callees(self, source_class_name: str, source_method_signature: str, using_symbol_table: bool) -> Dict: |
| 101 | + """Callees of a method.""" |
| 102 | + |
| 103 | + @abstractmethod |
| 104 | + def get_class_call_graph(self, qualified_class_name: str, method_name: str | None = None) -> List[Tuple[JMethodDetail, JMethodDetail]]: |
| 105 | + """Call-graph edges reachable from a class (or one of its methods).""" |
| 106 | + |
| 107 | + @abstractmethod |
| 108 | + def get_class_call_graph_using_symbol_table(self, qualified_class_name: str, method_signature: str | None = None) -> List[Tuple[JMethodDetail, JMethodDetail]]: |
| 109 | + """Call-graph edges reachable from a class, computed from the symbol table only.""" |
| 110 | + |
| 111 | + # -----[ classes / methods / fields ]----- |
| 112 | + @abstractmethod |
| 113 | + def get_all_classes(self) -> Dict[str, JType]: |
| 114 | + """Every class, keyed by qualified name.""" |
| 115 | + |
| 116 | + @abstractmethod |
| 117 | + def get_class(self, qualified_class_name: str) -> JType: |
| 118 | + """A single class by qualified name.""" |
| 119 | + |
| 120 | + @abstractmethod |
| 121 | + def get_all_sub_classes(self, qualified_class_name: str) -> Dict[str, JType]: |
| 122 | + """Classes that extend/implement the given class.""" |
| 123 | + |
| 124 | + @abstractmethod |
| 125 | + def get_all_nested_classes(self, qualified_class_name: str) -> List[JType]: |
| 126 | + """The classes declared inside a class.""" |
| 127 | + |
| 128 | + @abstractmethod |
| 129 | + def get_extended_classes(self, qualified_class_name: str) -> List[str]: |
| 130 | + """The base classes a class extends.""" |
| 131 | + |
| 132 | + @abstractmethod |
| 133 | + def get_implemented_interfaces(self, qualified_class_name: str) -> List[str]: |
| 134 | + """The interfaces a class implements.""" |
| 135 | + |
| 136 | + @abstractmethod |
| 137 | + def get_all_methods_in_application(self) -> Dict[str, Dict[str, JCallable]]: |
| 138 | + """All methods grouped by their owning class qualified name.""" |
| 139 | + |
| 140 | + @abstractmethod |
| 141 | + def get_all_methods_in_class(self, qualified_class_name: str) -> Dict[str, JCallable]: |
| 142 | + """The methods of a class.""" |
| 143 | + |
| 144 | + @abstractmethod |
| 145 | + def get_method(self, qualified_class_name: str, method_signature: str) -> JCallable: |
| 146 | + """A single method of a class.""" |
| 147 | + |
| 148 | + @abstractmethod |
| 149 | + def get_method_parameters(self, qualified_class_name: str, method_signature: str) -> List[JCallableParameter]: |
| 150 | + """The parameters of a method.""" |
| 151 | + |
| 152 | + @abstractmethod |
| 153 | + def get_all_constructors(self, qualified_class_name: str) -> Dict[str, JCallable]: |
| 154 | + """The constructors of a class.""" |
| 155 | + |
| 156 | + @abstractmethod |
| 157 | + def get_all_fields(self, qualified_class_name: str) -> List[JField]: |
| 158 | + """The fields of a class.""" |
| 159 | + |
| 160 | + # -----[ entry points ]----- |
| 161 | + @abstractmethod |
| 162 | + def get_all_entry_point_methods(self) -> Dict[str, Dict[str, JCallable]]: |
| 163 | + """Methods identified as application entry points.""" |
| 164 | + |
| 165 | + @abstractmethod |
| 166 | + def get_all_entry_point_classes(self) -> Dict[str, JType]: |
| 167 | + """Classes identified as application entry points.""" |
| 168 | + |
| 169 | + # -----[ CRUD operations ]----- |
| 170 | + @abstractmethod |
| 171 | + def get_all_crud_operations(self) -> List[CRUDRow]: |
| 172 | + """All CRUD operations across the application.""" |
| 173 | + |
| 174 | + @abstractmethod |
| 175 | + def get_all_create_operations(self) -> List[CRUDRow]: |
| 176 | + """All create operations.""" |
| 177 | + |
| 178 | + @abstractmethod |
| 179 | + def get_all_read_operations(self) -> List[CRUDRow]: |
| 180 | + """All read operations.""" |
| 181 | + |
| 182 | + @abstractmethod |
| 183 | + def get_all_update_operations(self) -> List[CRUDRow]: |
| 184 | + """All update operations.""" |
| 185 | + |
| 186 | + @abstractmethod |
| 187 | + def get_all_delete_operations(self) -> List[CRUDRow]: |
| 188 | + """All delete operations.""" |
| 189 | + |
| 190 | + # -----[ comments / docstrings ]----- |
| 191 | + @abstractmethod |
| 192 | + def get_all_comments(self) -> Dict[str, List[JComment]]: |
| 193 | + """All comments across the application, keyed by file.""" |
| 194 | + |
| 195 | + @abstractmethod |
| 196 | + def get_comment_in_file(self, file_path: str) -> List[JComment]: |
| 197 | + """The comments in a file.""" |
| 198 | + |
| 199 | + @abstractmethod |
| 200 | + def get_comments_in_a_class(self, qualified_class_name: str) -> List[JComment]: |
| 201 | + """The comments in a class.""" |
| 202 | + |
| 203 | + @abstractmethod |
| 204 | + def get_comments_in_a_method(self, qualified_class_name: str, method_signature: str) -> List[JComment]: |
| 205 | + """The comments in a method.""" |
| 206 | + |
| 207 | + @abstractmethod |
| 208 | + def get_all_docstrings(self) -> List[Tuple[str, JComment]]: |
| 209 | + """All docstring-style comments across the application.""" |
| 210 | + |
| 211 | + @abstractmethod |
| 212 | + def remove_all_comments(self, src_code: str) -> str: |
| 213 | + """Strip all comments from the given source code.""" |
0 commit comments