alita-sdk 0.3.457__py3-none-any.whl → 0.3.486__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of alita-sdk might be problematic. Click here for more details.
- alita_sdk/cli/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +155 -0
- alita_sdk/cli/agent_loader.py +194 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3592 -0
- alita_sdk/cli/callbacks.py +647 -0
- alita_sdk/cli/cli.py +168 -0
- alita_sdk/cli/config.py +306 -0
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/formatting.py +182 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1256 -0
- alita_sdk/cli/mcp_loader.py +315 -0
- alita_sdk/cli/toolkit.py +327 -0
- alita_sdk/cli/toolkit_loader.py +85 -0
- alita_sdk/cli/tools/__init__.py +43 -0
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +1665 -0
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +64 -8
- alita_sdk/community/inventory/__init__.py +224 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +169 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/bitbucket.py +0 -3
- alita_sdk/runtime/clients/client.py +99 -26
- alita_sdk/runtime/langchain/assistant.py +4 -2
- alita_sdk/runtime/langchain/constants.py +2 -1
- alita_sdk/runtime/langchain/langraph_agent.py +134 -31
- alita_sdk/runtime/langchain/utils.py +1 -1
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/toolkits/__init__.py +2 -0
- alita_sdk/runtime/toolkits/application.py +1 -1
- alita_sdk/runtime/toolkits/mcp.py +46 -36
- alita_sdk/runtime/toolkits/planning.py +171 -0
- alita_sdk/runtime/toolkits/tools.py +39 -6
- alita_sdk/runtime/tools/function.py +17 -5
- alita_sdk/runtime/tools/llm.py +249 -14
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/vectorstore_base.py +41 -6
- alita_sdk/runtime/utils/mcp_oauth.py +80 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +19 -4
- alita_sdk/tools/__init__.py +54 -27
- alita_sdk/tools/ado/repos/repos_wrapper.py +1 -2
- alita_sdk/tools/base_indexer_toolkit.py +150 -19
- alita_sdk/tools/bitbucket/__init__.py +2 -2
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +95 -6
- alita_sdk/tools/chunkers/universal_chunker.py +269 -0
- alita_sdk/tools/code_indexer_toolkit.py +55 -22
- alita_sdk/tools/elitea_base.py +86 -21
- alita_sdk/tools/jira/__init__.py +1 -1
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/qtest/__init__.py +1 -1
- alita_sdk/tools/qtest/api_wrapper.py +871 -32
- alita_sdk/tools/sharepoint/api_wrapper.py +22 -2
- alita_sdk/tools/sharepoint/authorization_helper.py +17 -1
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +8 -2
- alita_sdk/tools/zephyr_essential/api_wrapper.py +12 -13
- {alita_sdk-0.3.457.dist-info → alita_sdk-0.3.486.dist-info}/METADATA +146 -2
- {alita_sdk-0.3.457.dist-info → alita_sdk-0.3.486.dist-info}/RECORD +102 -40
- alita_sdk-0.3.486.dist-info/entry_points.txt +2 -0
- {alita_sdk-0.3.457.dist-info → alita_sdk-0.3.486.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.457.dist-info → alita_sdk-0.3.486.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.457.dist-info → alita_sdk-0.3.486.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Java Parser - Regex-based parsing.
|
|
3
|
+
|
|
4
|
+
This parser uses regex patterns for Java analysis:
|
|
5
|
+
- Import/package detection
|
|
6
|
+
- Class, interface, enum extraction
|
|
7
|
+
- Inheritance and implementation patterns
|
|
8
|
+
- Method and field detection
|
|
9
|
+
- Annotation support
|
|
10
|
+
|
|
11
|
+
Works without external dependencies like tree-sitter.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import re
|
|
15
|
+
import hashlib
|
|
16
|
+
import logging
|
|
17
|
+
import time
|
|
18
|
+
import concurrent.futures
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Dict, List, Optional, Set, Union, Tuple
|
|
21
|
+
|
|
22
|
+
from .base import (
|
|
23
|
+
BaseParser, ParseResult, Symbol, Relationship,
|
|
24
|
+
SymbolType, RelationshipType, Scope, Position, Range,
|
|
25
|
+
parser_registry
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
logger = logging.getLogger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# Comprehensive regex patterns for Java
|
|
32
|
+
PATTERNS = {
|
|
33
|
+
# Package
|
|
34
|
+
'package': re.compile(
|
|
35
|
+
r'package\s+([\w.]+)\s*;',
|
|
36
|
+
re.MULTILINE
|
|
37
|
+
),
|
|
38
|
+
|
|
39
|
+
# Imports
|
|
40
|
+
'import': re.compile(
|
|
41
|
+
r'import\s+(?:static\s+)?([\w.]+(?:\.\*)?)\s*;',
|
|
42
|
+
re.MULTILINE
|
|
43
|
+
),
|
|
44
|
+
|
|
45
|
+
# Classes
|
|
46
|
+
'class_def': re.compile(
|
|
47
|
+
r'(?:public\s+|private\s+|protected\s+)?(?:abstract\s+|final\s+)?class\s+(\w+)'
|
|
48
|
+
r'(?:<[^>]+>)?' # Generics
|
|
49
|
+
r'(?:\s+extends\s+(\w+)(?:<[^>]+>)?)?'
|
|
50
|
+
r'(?:\s+implements\s+([\w,\s<>]+))?',
|
|
51
|
+
re.MULTILINE
|
|
52
|
+
),
|
|
53
|
+
|
|
54
|
+
# Interfaces
|
|
55
|
+
'interface_def': re.compile(
|
|
56
|
+
r'(?:public\s+|private\s+|protected\s+)?interface\s+(\w+)'
|
|
57
|
+
r'(?:<[^>]+>)?'
|
|
58
|
+
r'(?:\s+extends\s+([\w,\s<>]+))?',
|
|
59
|
+
re.MULTILINE
|
|
60
|
+
),
|
|
61
|
+
|
|
62
|
+
# Enums
|
|
63
|
+
'enum_def': re.compile(
|
|
64
|
+
r'(?:public\s+|private\s+|protected\s+)?enum\s+(\w+)'
|
|
65
|
+
r'(?:\s+implements\s+([\w,\s<>]+))?',
|
|
66
|
+
re.MULTILINE
|
|
67
|
+
),
|
|
68
|
+
|
|
69
|
+
# Records (Java 14+)
|
|
70
|
+
'record_def': re.compile(
|
|
71
|
+
r'(?:public\s+|private\s+|protected\s+)?record\s+(\w+)\s*\([^)]*\)'
|
|
72
|
+
r'(?:\s+implements\s+([\w,\s<>]+))?',
|
|
73
|
+
re.MULTILINE
|
|
74
|
+
),
|
|
75
|
+
|
|
76
|
+
# Methods
|
|
77
|
+
'method_def': re.compile(
|
|
78
|
+
r'(?:public\s+|private\s+|protected\s+)?'
|
|
79
|
+
r'(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:abstract\s+)?'
|
|
80
|
+
r'(?:<[^>]+>\s+)?' # Generic type params
|
|
81
|
+
r'([\w<>\[\],\s]+)\s+' # Return type
|
|
82
|
+
r'(\w+)\s*' # Method name
|
|
83
|
+
r'\([^)]*\)', # Parameters
|
|
84
|
+
re.MULTILINE
|
|
85
|
+
),
|
|
86
|
+
|
|
87
|
+
# Constructors
|
|
88
|
+
'constructor_def': re.compile(
|
|
89
|
+
r'(?:public\s+|private\s+|protected\s+)?(\w+)\s*\([^)]*\)\s*(?:throws\s+[\w,\s]+)?\s*\{',
|
|
90
|
+
re.MULTILINE
|
|
91
|
+
),
|
|
92
|
+
|
|
93
|
+
# Fields
|
|
94
|
+
'field_def': re.compile(
|
|
95
|
+
r'(?:public\s+|private\s+|protected\s+)?'
|
|
96
|
+
r'(?:static\s+)?(?:final\s+)?'
|
|
97
|
+
r'([\w<>\[\],\s]+)\s+' # Type
|
|
98
|
+
r'(\w+)\s*' # Name
|
|
99
|
+
r'(?:=|;)',
|
|
100
|
+
re.MULTILINE
|
|
101
|
+
),
|
|
102
|
+
|
|
103
|
+
# Annotations
|
|
104
|
+
'annotation': re.compile(
|
|
105
|
+
r'@(\w+)(?:\([^)]*\))?',
|
|
106
|
+
re.MULTILINE
|
|
107
|
+
),
|
|
108
|
+
|
|
109
|
+
# Method calls
|
|
110
|
+
'method_call': re.compile(
|
|
111
|
+
r'(?:(\w+)\.)?(\w+)\s*\(',
|
|
112
|
+
re.MULTILINE
|
|
113
|
+
),
|
|
114
|
+
|
|
115
|
+
# Object instantiation
|
|
116
|
+
'new_instance': re.compile(
|
|
117
|
+
r'new\s+(\w+)\s*(?:<[^>]*>)?\s*\(',
|
|
118
|
+
re.MULTILINE
|
|
119
|
+
),
|
|
120
|
+
|
|
121
|
+
# JavaDoc @see
|
|
122
|
+
'javadoc_see': re.compile(
|
|
123
|
+
r'@see\s+(?:#|{@link\s+)?(\w+(?:\.\w+)*)',
|
|
124
|
+
re.MULTILINE
|
|
125
|
+
),
|
|
126
|
+
|
|
127
|
+
# Type references in generics
|
|
128
|
+
'generic_type': re.compile(
|
|
129
|
+
r'<(\w+)(?:\s*,\s*(\w+))*>',
|
|
130
|
+
re.MULTILINE
|
|
131
|
+
),
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _parse_single_file(file_path: str) -> Tuple[str, ParseResult]:
|
|
136
|
+
"""Parse a single Java file."""
|
|
137
|
+
try:
|
|
138
|
+
parser = JavaParser()
|
|
139
|
+
result = parser.parse_file(file_path)
|
|
140
|
+
return file_path, result
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.warning(f"Failed to parse {file_path}: {e}")
|
|
143
|
+
return file_path, ParseResult(
|
|
144
|
+
file_path=file_path,
|
|
145
|
+
language="java",
|
|
146
|
+
symbols=[],
|
|
147
|
+
relationships=[]
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class JavaParser(BaseParser):
|
|
152
|
+
"""
|
|
153
|
+
Java parser using regex patterns.
|
|
154
|
+
|
|
155
|
+
Extracts classes, interfaces, methods, and relationships.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
def __init__(self):
|
|
159
|
+
super().__init__("java")
|
|
160
|
+
self._current_file = ""
|
|
161
|
+
self._current_content = ""
|
|
162
|
+
self._current_package = ""
|
|
163
|
+
|
|
164
|
+
# Cross-file resolution
|
|
165
|
+
self._global_classes: Dict[str, str] = {} # class_name -> file_path
|
|
166
|
+
self._global_interfaces: Dict[str, str] = {}
|
|
167
|
+
|
|
168
|
+
def _get_supported_extensions(self) -> Set[str]:
|
|
169
|
+
return {'.java'}
|
|
170
|
+
|
|
171
|
+
def parse_file(self, file_path: Union[str, Path], content: Optional[str] = None) -> ParseResult:
|
|
172
|
+
"""Parse a Java file."""
|
|
173
|
+
start_time = time.time()
|
|
174
|
+
file_path = str(file_path)
|
|
175
|
+
self._current_file = file_path
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
if content is None:
|
|
179
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
180
|
+
content = f.read()
|
|
181
|
+
|
|
182
|
+
self._current_content = content
|
|
183
|
+
|
|
184
|
+
# Extract package
|
|
185
|
+
pkg_match = PATTERNS['package'].search(content)
|
|
186
|
+
self._current_package = pkg_match.group(1) if pkg_match else ""
|
|
187
|
+
|
|
188
|
+
symbols = self._extract_symbols(content, file_path)
|
|
189
|
+
relationships = self._extract_relationships(content, symbols, file_path)
|
|
190
|
+
imports = self._extract_imports(content)
|
|
191
|
+
|
|
192
|
+
result = ParseResult(
|
|
193
|
+
file_path=file_path,
|
|
194
|
+
language="java",
|
|
195
|
+
symbols=symbols,
|
|
196
|
+
relationships=relationships,
|
|
197
|
+
imports=imports,
|
|
198
|
+
parse_time=time.time() - start_time
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
return self.validate_result(result)
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.error(f"Failed to parse Java file {file_path}: {e}")
|
|
205
|
+
return ParseResult(
|
|
206
|
+
file_path=file_path,
|
|
207
|
+
language="java",
|
|
208
|
+
symbols=[],
|
|
209
|
+
relationships=[],
|
|
210
|
+
parse_time=time.time() - start_time,
|
|
211
|
+
errors=[str(e)]
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def _extract_symbols(self, content: str, file_path: str) -> List[Symbol]:
|
|
215
|
+
"""Extract symbols from Java content."""
|
|
216
|
+
symbols = []
|
|
217
|
+
lines = content.split('\n')
|
|
218
|
+
class_name = Path(file_path).stem
|
|
219
|
+
|
|
220
|
+
# Module/file symbol
|
|
221
|
+
full_name = f"{self._current_package}.{class_name}" if self._current_package else class_name
|
|
222
|
+
symbols.append(Symbol(
|
|
223
|
+
name=class_name,
|
|
224
|
+
symbol_type=SymbolType.MODULE,
|
|
225
|
+
scope=Scope.GLOBAL,
|
|
226
|
+
range=Range(Position(1, 0), Position(len(lines), 0)),
|
|
227
|
+
file_path=file_path,
|
|
228
|
+
full_name=full_name
|
|
229
|
+
))
|
|
230
|
+
|
|
231
|
+
# Classes
|
|
232
|
+
for match in PATTERNS['class_def'].finditer(content):
|
|
233
|
+
line = content[:match.start()].count('\n') + 1
|
|
234
|
+
name = match.group(1)
|
|
235
|
+
extends = match.group(2)
|
|
236
|
+
|
|
237
|
+
symbols.append(Symbol(
|
|
238
|
+
name=name,
|
|
239
|
+
symbol_type=SymbolType.CLASS,
|
|
240
|
+
scope=Scope.GLOBAL,
|
|
241
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
242
|
+
file_path=file_path,
|
|
243
|
+
full_name=f"{self._current_package}.{name}" if self._current_package else name,
|
|
244
|
+
visibility='public' if 'public' in match.group(0) else 'package',
|
|
245
|
+
metadata={'extends': extends} if extends else {}
|
|
246
|
+
))
|
|
247
|
+
|
|
248
|
+
# Interfaces
|
|
249
|
+
for match in PATTERNS['interface_def'].finditer(content):
|
|
250
|
+
line = content[:match.start()].count('\n') + 1
|
|
251
|
+
name = match.group(1)
|
|
252
|
+
|
|
253
|
+
symbols.append(Symbol(
|
|
254
|
+
name=name,
|
|
255
|
+
symbol_type=SymbolType.INTERFACE,
|
|
256
|
+
scope=Scope.GLOBAL,
|
|
257
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
258
|
+
file_path=file_path,
|
|
259
|
+
full_name=f"{self._current_package}.{name}" if self._current_package else name,
|
|
260
|
+
visibility='public' if 'public' in match.group(0) else 'package'
|
|
261
|
+
))
|
|
262
|
+
|
|
263
|
+
# Enums
|
|
264
|
+
for match in PATTERNS['enum_def'].finditer(content):
|
|
265
|
+
line = content[:match.start()].count('\n') + 1
|
|
266
|
+
name = match.group(1)
|
|
267
|
+
|
|
268
|
+
symbols.append(Symbol(
|
|
269
|
+
name=name,
|
|
270
|
+
symbol_type=SymbolType.ENUM,
|
|
271
|
+
scope=Scope.GLOBAL,
|
|
272
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
273
|
+
file_path=file_path,
|
|
274
|
+
full_name=f"{self._current_package}.{name}" if self._current_package else name,
|
|
275
|
+
visibility='public' if 'public' in match.group(0) else 'package'
|
|
276
|
+
))
|
|
277
|
+
|
|
278
|
+
# Records
|
|
279
|
+
for match in PATTERNS['record_def'].finditer(content):
|
|
280
|
+
line = content[:match.start()].count('\n') + 1
|
|
281
|
+
name = match.group(1)
|
|
282
|
+
|
|
283
|
+
symbols.append(Symbol(
|
|
284
|
+
name=name,
|
|
285
|
+
symbol_type=SymbolType.CLASS,
|
|
286
|
+
scope=Scope.GLOBAL,
|
|
287
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
288
|
+
file_path=file_path,
|
|
289
|
+
full_name=f"{self._current_package}.{name}" if self._current_package else name,
|
|
290
|
+
visibility='public' if 'public' in match.group(0) else 'package',
|
|
291
|
+
metadata={'is_record': True}
|
|
292
|
+
))
|
|
293
|
+
|
|
294
|
+
# Methods
|
|
295
|
+
for match in PATTERNS['method_def'].finditer(content):
|
|
296
|
+
line = content[:match.start()].count('\n') + 1
|
|
297
|
+
return_type = match.group(1).strip()
|
|
298
|
+
name = match.group(2)
|
|
299
|
+
|
|
300
|
+
# Skip if it looks like a constructor (return type == class name)
|
|
301
|
+
if name in [s.name for s in symbols if s.symbol_type in (SymbolType.CLASS, SymbolType.INTERFACE)]:
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
symbols.append(Symbol(
|
|
305
|
+
name=name,
|
|
306
|
+
symbol_type=SymbolType.METHOD,
|
|
307
|
+
scope=Scope.CLASS,
|
|
308
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
309
|
+
file_path=file_path,
|
|
310
|
+
return_type=return_type,
|
|
311
|
+
visibility='public' if 'public' in match.group(0) else ('private' if 'private' in match.group(0) else 'protected'),
|
|
312
|
+
is_static='static' in match.group(0),
|
|
313
|
+
is_async='synchronized' in match.group(0) # Using is_async for synchronized
|
|
314
|
+
))
|
|
315
|
+
|
|
316
|
+
# Fields
|
|
317
|
+
for match in PATTERNS['field_def'].finditer(content):
|
|
318
|
+
line = content[:match.start()].count('\n') + 1
|
|
319
|
+
field_type = match.group(1).strip()
|
|
320
|
+
name = match.group(2)
|
|
321
|
+
|
|
322
|
+
# Skip common false positives (method returns, etc.)
|
|
323
|
+
if field_type in ('return', 'throw', 'if', 'else', 'for', 'while', 'switch', 'case'):
|
|
324
|
+
continue
|
|
325
|
+
|
|
326
|
+
symbols.append(Symbol(
|
|
327
|
+
name=name,
|
|
328
|
+
symbol_type=SymbolType.FIELD,
|
|
329
|
+
scope=Scope.CLASS,
|
|
330
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
331
|
+
file_path=file_path,
|
|
332
|
+
return_type=field_type,
|
|
333
|
+
visibility='public' if 'public' in match.group(0) else ('private' if 'private' in match.group(0) else 'protected'),
|
|
334
|
+
is_static='static' in match.group(0)
|
|
335
|
+
))
|
|
336
|
+
|
|
337
|
+
# Imports as symbols
|
|
338
|
+
for match in PATTERNS['import'].finditer(content):
|
|
339
|
+
line = content[:match.start()].count('\n') + 1
|
|
340
|
+
import_path = match.group(1)
|
|
341
|
+
name = import_path.split('.')[-1]
|
|
342
|
+
|
|
343
|
+
symbols.append(Symbol(
|
|
344
|
+
name=name,
|
|
345
|
+
symbol_type=SymbolType.IMPORT,
|
|
346
|
+
scope=Scope.GLOBAL,
|
|
347
|
+
range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
348
|
+
file_path=file_path,
|
|
349
|
+
metadata={'full_path': import_path, 'is_static': 'static' in match.group(0)}
|
|
350
|
+
))
|
|
351
|
+
|
|
352
|
+
return symbols
|
|
353
|
+
|
|
354
|
+
def _extract_relationships(self, content: str, symbols: List[Symbol], file_path: str) -> List[Relationship]:
|
|
355
|
+
"""Extract relationships from Java content."""
|
|
356
|
+
relationships = []
|
|
357
|
+
module_name = Path(file_path).stem
|
|
358
|
+
|
|
359
|
+
# Import relationships
|
|
360
|
+
for match in PATTERNS['import'].finditer(content):
|
|
361
|
+
line = content[:match.start()].count('\n') + 1
|
|
362
|
+
import_path = match.group(1)
|
|
363
|
+
|
|
364
|
+
relationships.append(Relationship(
|
|
365
|
+
source_symbol=module_name,
|
|
366
|
+
target_symbol=import_path,
|
|
367
|
+
relationship_type=RelationshipType.IMPORTS,
|
|
368
|
+
source_file=file_path,
|
|
369
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
370
|
+
confidence=1.0
|
|
371
|
+
))
|
|
372
|
+
|
|
373
|
+
# Inheritance (extends)
|
|
374
|
+
for match in PATTERNS['class_def'].finditer(content):
|
|
375
|
+
class_name = match.group(1)
|
|
376
|
+
extends = match.group(2)
|
|
377
|
+
implements = match.group(3)
|
|
378
|
+
line = content[:match.start()].count('\n') + 1
|
|
379
|
+
|
|
380
|
+
if extends:
|
|
381
|
+
relationships.append(Relationship(
|
|
382
|
+
source_symbol=class_name,
|
|
383
|
+
target_symbol=extends,
|
|
384
|
+
relationship_type=RelationshipType.INHERITANCE,
|
|
385
|
+
source_file=file_path,
|
|
386
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
387
|
+
confidence=0.95
|
|
388
|
+
))
|
|
389
|
+
|
|
390
|
+
if implements:
|
|
391
|
+
for iface in implements.split(','):
|
|
392
|
+
iface = re.sub(r'<[^>]*>', '', iface).strip() # Remove generics
|
|
393
|
+
if iface:
|
|
394
|
+
relationships.append(Relationship(
|
|
395
|
+
source_symbol=class_name,
|
|
396
|
+
target_symbol=iface,
|
|
397
|
+
relationship_type=RelationshipType.IMPLEMENTATION,
|
|
398
|
+
source_file=file_path,
|
|
399
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
400
|
+
confidence=0.95
|
|
401
|
+
))
|
|
402
|
+
|
|
403
|
+
# Interface extension
|
|
404
|
+
for match in PATTERNS['interface_def'].finditer(content):
|
|
405
|
+
iface_name = match.group(1)
|
|
406
|
+
extends = match.group(2)
|
|
407
|
+
if extends:
|
|
408
|
+
line = content[:match.start()].count('\n') + 1
|
|
409
|
+
for parent in extends.split(','):
|
|
410
|
+
parent = re.sub(r'<[^>]*>', '', parent).strip()
|
|
411
|
+
if parent:
|
|
412
|
+
relationships.append(Relationship(
|
|
413
|
+
source_symbol=iface_name,
|
|
414
|
+
target_symbol=parent,
|
|
415
|
+
relationship_type=RelationshipType.INHERITANCE,
|
|
416
|
+
source_file=file_path,
|
|
417
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
418
|
+
confidence=0.95
|
|
419
|
+
))
|
|
420
|
+
|
|
421
|
+
# Enum implementation
|
|
422
|
+
for match in PATTERNS['enum_def'].finditer(content):
|
|
423
|
+
enum_name = match.group(1)
|
|
424
|
+
implements = match.group(2)
|
|
425
|
+
if implements:
|
|
426
|
+
line = content[:match.start()].count('\n') + 1
|
|
427
|
+
for iface in implements.split(','):
|
|
428
|
+
iface = re.sub(r'<[^>]*>', '', iface).strip()
|
|
429
|
+
if iface:
|
|
430
|
+
relationships.append(Relationship(
|
|
431
|
+
source_symbol=enum_name,
|
|
432
|
+
target_symbol=iface,
|
|
433
|
+
relationship_type=RelationshipType.IMPLEMENTATION,
|
|
434
|
+
source_file=file_path,
|
|
435
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
436
|
+
confidence=0.95
|
|
437
|
+
))
|
|
438
|
+
|
|
439
|
+
# Object instantiation (new)
|
|
440
|
+
for match in PATTERNS['new_instance'].finditer(content):
|
|
441
|
+
type_name = match.group(1)
|
|
442
|
+
line = content[:match.start()].count('\n') + 1
|
|
443
|
+
|
|
444
|
+
relationships.append(Relationship(
|
|
445
|
+
source_symbol=module_name,
|
|
446
|
+
target_symbol=type_name,
|
|
447
|
+
relationship_type=RelationshipType.USES,
|
|
448
|
+
source_file=file_path,
|
|
449
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
450
|
+
confidence=0.85
|
|
451
|
+
))
|
|
452
|
+
|
|
453
|
+
# Annotation relationships
|
|
454
|
+
annotations_found = list(PATTERNS['annotation'].finditer(content))
|
|
455
|
+
for i, match in enumerate(annotations_found):
|
|
456
|
+
ann_name = match.group(1)
|
|
457
|
+
line = content[:match.start()].count('\n') + 1
|
|
458
|
+
|
|
459
|
+
# Find the next class, interface, method, or field
|
|
460
|
+
remaining = content[match.end():]
|
|
461
|
+
target = None
|
|
462
|
+
|
|
463
|
+
class_match = PATTERNS['class_def'].search(remaining)
|
|
464
|
+
iface_match = PATTERNS['interface_def'].search(remaining)
|
|
465
|
+
method_match = PATTERNS['method_def'].search(remaining)
|
|
466
|
+
|
|
467
|
+
if class_match and (not method_match or class_match.start() < method_match.start()):
|
|
468
|
+
target = class_match.group(1)
|
|
469
|
+
elif iface_match and (not method_match or iface_match.start() < method_match.start()):
|
|
470
|
+
target = iface_match.group(1)
|
|
471
|
+
elif method_match:
|
|
472
|
+
target = method_match.group(2)
|
|
473
|
+
|
|
474
|
+
if target:
|
|
475
|
+
relationships.append(Relationship(
|
|
476
|
+
source_symbol=ann_name,
|
|
477
|
+
target_symbol=target,
|
|
478
|
+
relationship_type=RelationshipType.DECORATES,
|
|
479
|
+
source_file=file_path,
|
|
480
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
481
|
+
confidence=0.90
|
|
482
|
+
))
|
|
483
|
+
|
|
484
|
+
# JavaDoc @see references
|
|
485
|
+
for match in PATTERNS['javadoc_see'].finditer(content):
|
|
486
|
+
ref = match.group(1)
|
|
487
|
+
line = content[:match.start()].count('\n') + 1
|
|
488
|
+
|
|
489
|
+
relationships.append(Relationship(
|
|
490
|
+
source_symbol=module_name,
|
|
491
|
+
target_symbol=ref,
|
|
492
|
+
relationship_type=RelationshipType.REFERENCES,
|
|
493
|
+
source_file=file_path,
|
|
494
|
+
source_range=Range(Position(line, 0), Position(line, len(match.group(0)))),
|
|
495
|
+
confidence=0.80
|
|
496
|
+
))
|
|
497
|
+
|
|
498
|
+
return relationships
|
|
499
|
+
|
|
500
|
+
def _extract_imports(self, content: str) -> List[str]:
|
|
501
|
+
"""Extract import paths."""
|
|
502
|
+
imports = []
|
|
503
|
+
for match in PATTERNS['import'].finditer(content):
|
|
504
|
+
imports.append(match.group(1))
|
|
505
|
+
return list(set(imports))
|
|
506
|
+
|
|
507
|
+
def parse_multiple_files(self, file_paths: List[str], max_workers: int = 4) -> Dict[str, ParseResult]:
|
|
508
|
+
"""Parse multiple Java files with cross-file resolution."""
|
|
509
|
+
results: Dict[str, ParseResult] = {}
|
|
510
|
+
total = len(file_paths)
|
|
511
|
+
|
|
512
|
+
self._global_classes = {}
|
|
513
|
+
self._global_interfaces = {}
|
|
514
|
+
|
|
515
|
+
logger.info(f"Parsing {total} Java files")
|
|
516
|
+
start_time = time.time()
|
|
517
|
+
|
|
518
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
519
|
+
futures = {executor.submit(_parse_single_file, fp): fp for fp in file_paths}
|
|
520
|
+
|
|
521
|
+
for future in concurrent.futures.as_completed(futures):
|
|
522
|
+
try:
|
|
523
|
+
fp, result = future.result(timeout=60)
|
|
524
|
+
results[fp] = result
|
|
525
|
+
except Exception as e:
|
|
526
|
+
fp = futures[future]
|
|
527
|
+
logger.error(f"Failed to parse {fp}: {e}")
|
|
528
|
+
results[fp] = ParseResult(
|
|
529
|
+
file_path=fp,
|
|
530
|
+
language="java",
|
|
531
|
+
symbols=[],
|
|
532
|
+
relationships=[],
|
|
533
|
+
errors=[str(e)]
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Build global registry
|
|
537
|
+
for fp, result in results.items():
|
|
538
|
+
self._build_global_registry(fp, result)
|
|
539
|
+
|
|
540
|
+
# Enhance relationships
|
|
541
|
+
for fp, result in results.items():
|
|
542
|
+
self._enhance_relationships(fp, result)
|
|
543
|
+
|
|
544
|
+
elapsed = time.time() - start_time
|
|
545
|
+
logger.info(f"Parsed {len(results)} Java files in {elapsed:.2f}s")
|
|
546
|
+
|
|
547
|
+
return results
|
|
548
|
+
|
|
549
|
+
def _build_global_registry(self, file_path: str, result: ParseResult):
|
|
550
|
+
"""Build global class/interface registry."""
|
|
551
|
+
for symbol in result.symbols:
|
|
552
|
+
if symbol.symbol_type == SymbolType.CLASS:
|
|
553
|
+
self._global_classes[symbol.name] = file_path
|
|
554
|
+
if symbol.full_name:
|
|
555
|
+
self._global_classes[symbol.full_name] = file_path
|
|
556
|
+
elif symbol.symbol_type == SymbolType.INTERFACE:
|
|
557
|
+
self._global_interfaces[symbol.name] = file_path
|
|
558
|
+
if symbol.full_name:
|
|
559
|
+
self._global_interfaces[symbol.full_name] = file_path
|
|
560
|
+
|
|
561
|
+
def _enhance_relationships(self, file_path: str, result: ParseResult):
|
|
562
|
+
"""Enhance relationships with cross-file info."""
|
|
563
|
+
for rel in result.relationships:
|
|
564
|
+
target = rel.target_symbol
|
|
565
|
+
|
|
566
|
+
# Extract simple name from qualified
|
|
567
|
+
simple_name = target.split('.')[-1] if '.' in target else target
|
|
568
|
+
|
|
569
|
+
# Check global registries
|
|
570
|
+
if target in self._global_classes:
|
|
571
|
+
target_file = self._global_classes[target]
|
|
572
|
+
if target_file != file_path:
|
|
573
|
+
rel.target_file = target_file
|
|
574
|
+
rel.is_cross_file = True
|
|
575
|
+
elif simple_name in self._global_classes:
|
|
576
|
+
target_file = self._global_classes[simple_name]
|
|
577
|
+
if target_file != file_path:
|
|
578
|
+
rel.target_file = target_file
|
|
579
|
+
rel.is_cross_file = True
|
|
580
|
+
elif target in self._global_interfaces:
|
|
581
|
+
target_file = self._global_interfaces[target]
|
|
582
|
+
if target_file != file_path:
|
|
583
|
+
rel.target_file = target_file
|
|
584
|
+
rel.is_cross_file = True
|
|
585
|
+
elif simple_name in self._global_interfaces:
|
|
586
|
+
target_file = self._global_interfaces[simple_name]
|
|
587
|
+
if target_file != file_path:
|
|
588
|
+
rel.target_file = target_file
|
|
589
|
+
rel.is_cross_file = True
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
# Register the parser
|
|
593
|
+
parser_registry.register_parser(JavaParser())
|