java-class-analyzer-mcp 0.1.0__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.
- java_class_analyzer_mcp/__init__.py +3 -0
- java_class_analyzer_mcp/analyzer/__init__.py +3 -0
- java_class_analyzer_mcp/analyzer/java_class_analyzer.py +384 -0
- java_class_analyzer_mcp/cfr-0.152.jar +0 -0
- java_class_analyzer_mcp/cli/__init__.py +3 -0
- java_class_analyzer_mcp/cli.py +145 -0
- java_class_analyzer_mcp/decompiler/__init__.py +3 -0
- java_class_analyzer_mcp/decompiler/decompiler_service.py +218 -0
- java_class_analyzer_mcp/main.py +102 -0
- java_class_analyzer_mcp/scanner/__init__.py +3 -0
- java_class_analyzer_mcp/scanner/dependency_scanner.py +342 -0
- java_class_analyzer_mcp-0.1.0.dist-info/METADATA +200 -0
- java_class_analyzer_mcp-0.1.0.dist-info/RECORD +16 -0
- java_class_analyzer_mcp-0.1.0.dist-info/WHEEL +5 -0
- java_class_analyzer_mcp-0.1.0.dist-info/entry_points.txt +2 -0
- java_class_analyzer_mcp-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import subprocess
|
|
4
|
+
import re
|
|
5
|
+
from typing import List, Optional, Dict
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from ..scanner.dependency_scanner import DependencyScanner
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _debug(msg: str) -> None:
|
|
11
|
+
"""仅在 DEBUG 模式下输出到 stderr(不污染 MCP stdout JSON-RPC)"""
|
|
12
|
+
if os.environ.get("LOG_LEVEL", "DEBUG").upper() == "DEBUG":
|
|
13
|
+
print(msg, file=sys.stderr)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class ClassField:
|
|
18
|
+
"""类字段"""
|
|
19
|
+
|
|
20
|
+
name: str
|
|
21
|
+
type_: str
|
|
22
|
+
modifiers: List[str]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class ClassMethod:
|
|
27
|
+
"""类方法"""
|
|
28
|
+
|
|
29
|
+
name: str
|
|
30
|
+
return_type: str
|
|
31
|
+
parameters: List[str]
|
|
32
|
+
modifiers: List[str]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class ClassAnalysis:
|
|
37
|
+
"""类分析结果"""
|
|
38
|
+
|
|
39
|
+
class_name: str
|
|
40
|
+
package_name: str
|
|
41
|
+
modifiers: List[str]
|
|
42
|
+
super_class: Optional[str]
|
|
43
|
+
interfaces: List[str]
|
|
44
|
+
fields: List[ClassField]
|
|
45
|
+
methods: List[ClassMethod]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class JavaClassAnalyzer:
|
|
49
|
+
"""Java类分析器"""
|
|
50
|
+
|
|
51
|
+
def __init__(self):
|
|
52
|
+
self.scanner = DependencyScanner()
|
|
53
|
+
|
|
54
|
+
def analyze_class(self, class_name: str, project_path: str) -> ClassAnalysis:
|
|
55
|
+
"""
|
|
56
|
+
分析Java类的结构信息
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
# 1. 获取类文件路径
|
|
60
|
+
jar_path = self.scanner.find_jar_for_class(class_name, project_path)
|
|
61
|
+
if not jar_path:
|
|
62
|
+
raise Exception(f"未找到类 {class_name} 对应的JAR包")
|
|
63
|
+
|
|
64
|
+
# 2. 直接使用 javap 分析JAR包中的类
|
|
65
|
+
analysis = self.analyze_class_with_javap(jar_path, class_name)
|
|
66
|
+
|
|
67
|
+
return analysis
|
|
68
|
+
except Exception as e:
|
|
69
|
+
_debug(f"分析类 {class_name} 失败: {e}")
|
|
70
|
+
raise e
|
|
71
|
+
|
|
72
|
+
def analyze_class_with_javap(self, jar_path: str, class_name: str) -> ClassAnalysis:
|
|
73
|
+
"""
|
|
74
|
+
使用 javap 工具分析JAR包中的类结构
|
|
75
|
+
"""
|
|
76
|
+
try:
|
|
77
|
+
javap_cmd = self.get_javap_command()
|
|
78
|
+
|
|
79
|
+
# 使用 javap -v 获取详细信息(包括参数名称)
|
|
80
|
+
result = subprocess.run(
|
|
81
|
+
[javap_cmd, "-v", "-cp", jar_path, class_name],
|
|
82
|
+
capture_output=True,
|
|
83
|
+
text=True,
|
|
84
|
+
timeout=10,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if result.returncode != 0:
|
|
88
|
+
raise Exception(f"javap执行失败: {result.stderr}")
|
|
89
|
+
|
|
90
|
+
return self.parse_javap_output(result.stdout, class_name)
|
|
91
|
+
except subprocess.TimeoutExpired:
|
|
92
|
+
raise Exception("javap分析超时")
|
|
93
|
+
except Exception as e:
|
|
94
|
+
_debug(f"javap 分析失败: {e}")
|
|
95
|
+
raise Exception(f"javap 分析失败: {str(e)}")
|
|
96
|
+
|
|
97
|
+
def parse_javap_output(self, output: str, class_name: str) -> ClassAnalysis:
|
|
98
|
+
"""
|
|
99
|
+
解析 javap 输出
|
|
100
|
+
"""
|
|
101
|
+
lines = output.split("\n")
|
|
102
|
+
# 从类全名中提取简单类名
|
|
103
|
+
simple_class_name = class_name.split(".")[-1]
|
|
104
|
+
package_name = ".".join(class_name.split(".")[:-1]) if "." in class_name else ""
|
|
105
|
+
|
|
106
|
+
analysis = ClassAnalysis(
|
|
107
|
+
class_name=simple_class_name,
|
|
108
|
+
package_name=package_name,
|
|
109
|
+
modifiers=[],
|
|
110
|
+
super_class=None,
|
|
111
|
+
interfaces=[],
|
|
112
|
+
fields=[],
|
|
113
|
+
methods=[],
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
current_method = None
|
|
117
|
+
in_local_variable_table = False
|
|
118
|
+
method_parameters = {}
|
|
119
|
+
|
|
120
|
+
for i, line in enumerate(lines):
|
|
121
|
+
trimmed_line = line.strip()
|
|
122
|
+
|
|
123
|
+
# 解析类声明
|
|
124
|
+
if any(
|
|
125
|
+
trimmed_line.startswith(prefix)
|
|
126
|
+
for prefix in ["public class", "public interface", "public enum"]
|
|
127
|
+
):
|
|
128
|
+
self.parse_class_declaration(trimmed_line, analysis)
|
|
129
|
+
continue
|
|
130
|
+
|
|
131
|
+
# 解析方法声明
|
|
132
|
+
if (
|
|
133
|
+
(
|
|
134
|
+
"public " in trimmed_line
|
|
135
|
+
or "private " in trimmed_line
|
|
136
|
+
or "protected " in trimmed_line
|
|
137
|
+
)
|
|
138
|
+
and "(" in trimmed_line
|
|
139
|
+
and ")" in trimmed_line
|
|
140
|
+
):
|
|
141
|
+
current_method = self.parse_method_from_javap(trimmed_line)
|
|
142
|
+
if current_method:
|
|
143
|
+
analysis.methods.append(current_method)
|
|
144
|
+
method_parameters = {}
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
# 检测 LocalVariableTable 开始
|
|
148
|
+
if trimmed_line == "LocalVariableTable:":
|
|
149
|
+
in_local_variable_table = True
|
|
150
|
+
continue
|
|
151
|
+
|
|
152
|
+
# 解析 LocalVariableTable 中的参数名称
|
|
153
|
+
if in_local_variable_table and current_method:
|
|
154
|
+
if trimmed_line.startswith("Start") or trimmed_line.startswith("Slot"):
|
|
155
|
+
continue # 跳过表头
|
|
156
|
+
|
|
157
|
+
if trimmed_line == "":
|
|
158
|
+
# LocalVariableTable 结束,立即更新当前方法的参数名称
|
|
159
|
+
if method_parameters:
|
|
160
|
+
updated_params = []
|
|
161
|
+
for j, param_type in enumerate(current_method.parameters):
|
|
162
|
+
param_name = method_parameters.get(j, f"param{j + 1}")
|
|
163
|
+
updated_params.append(f"{param_type} {param_name}")
|
|
164
|
+
current_method.parameters = updated_params
|
|
165
|
+
in_local_variable_table = False
|
|
166
|
+
method_parameters = {}
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
# 解析参数行: "0 6 0 file Ljava/io/File;"
|
|
170
|
+
param_match = re.match(
|
|
171
|
+
r"^\s*(\d+)\s+\d+\s+(\d+)\s+(\w+)\s+(.+)$", trimmed_line
|
|
172
|
+
)
|
|
173
|
+
if param_match:
|
|
174
|
+
slot = int(param_match.group(2))
|
|
175
|
+
param_name = param_match.group(3)
|
|
176
|
+
param_type = param_match.group(4)
|
|
177
|
+
|
|
178
|
+
# 只处理参数(slot >= 0,但排除局部变量)
|
|
179
|
+
if slot >= 0 and slot < len(current_method.parameters):
|
|
180
|
+
method_parameters[slot] = param_name
|
|
181
|
+
|
|
182
|
+
# 检测方法结束 - 当遇到下一个方法或类结束时
|
|
183
|
+
if current_method and (
|
|
184
|
+
(
|
|
185
|
+
(
|
|
186
|
+
"public " in trimmed_line
|
|
187
|
+
or "private " in trimmed_line
|
|
188
|
+
or "protected " in trimmed_line
|
|
189
|
+
)
|
|
190
|
+
and "(" in trimmed_line
|
|
191
|
+
and ")" in trimmed_line
|
|
192
|
+
)
|
|
193
|
+
or trimmed_line.startswith("}")
|
|
194
|
+
or trimmed_line.startswith("SourceFile:")
|
|
195
|
+
):
|
|
196
|
+
# 更新方法的参数名称
|
|
197
|
+
if method_parameters:
|
|
198
|
+
updated_params = []
|
|
199
|
+
for j, param_type in enumerate(current_method.parameters):
|
|
200
|
+
param_name = method_parameters.get(j, f"param{j + 1}")
|
|
201
|
+
updated_params.append(f"{param_type} {param_name}")
|
|
202
|
+
current_method.parameters = updated_params
|
|
203
|
+
current_method = None
|
|
204
|
+
in_local_variable_table = False
|
|
205
|
+
method_parameters = {}
|
|
206
|
+
|
|
207
|
+
return analysis
|
|
208
|
+
|
|
209
|
+
def parse_class_declaration(self, line: str, analysis: ClassAnalysis) -> None:
|
|
210
|
+
"""
|
|
211
|
+
解析类声明
|
|
212
|
+
"""
|
|
213
|
+
# 提取修饰符
|
|
214
|
+
modifiers = re.findall(
|
|
215
|
+
r"\b(public|private|protected|static|final|abstract|strictfp)\b", line
|
|
216
|
+
)
|
|
217
|
+
analysis.modifiers = modifiers
|
|
218
|
+
|
|
219
|
+
# 提取父类
|
|
220
|
+
extends_match = re.search(r"extends\s+([a-zA-Z_$][a-zA-Z0-9_$.]*)", line)
|
|
221
|
+
if extends_match:
|
|
222
|
+
analysis.super_class = extends_match.group(1)
|
|
223
|
+
|
|
224
|
+
# 提取接口
|
|
225
|
+
implements_match = re.search(r"implements\s+([^{]+)", line)
|
|
226
|
+
if implements_match:
|
|
227
|
+
interfaces = [
|
|
228
|
+
iface.strip()
|
|
229
|
+
for iface in implements_match.group(1).split(",")
|
|
230
|
+
if iface.strip()
|
|
231
|
+
]
|
|
232
|
+
analysis.interfaces = interfaces
|
|
233
|
+
|
|
234
|
+
def parse_method_from_javap(self, line: str) -> Optional[ClassMethod]:
|
|
235
|
+
"""
|
|
236
|
+
从 javap 输出解析方法
|
|
237
|
+
"""
|
|
238
|
+
try:
|
|
239
|
+
trimmed_line = line.strip()
|
|
240
|
+
|
|
241
|
+
# 提取修饰符
|
|
242
|
+
modifiers = []
|
|
243
|
+
modifier_words = [
|
|
244
|
+
"public",
|
|
245
|
+
"private",
|
|
246
|
+
"protected",
|
|
247
|
+
"static",
|
|
248
|
+
"final",
|
|
249
|
+
"abstract",
|
|
250
|
+
"synchronized",
|
|
251
|
+
"native",
|
|
252
|
+
]
|
|
253
|
+
|
|
254
|
+
# 处理多个修饰符
|
|
255
|
+
remaining_line = trimmed_line
|
|
256
|
+
while True:
|
|
257
|
+
found_modifier = False
|
|
258
|
+
for modifier in modifier_words:
|
|
259
|
+
if remaining_line.startswith(modifier + " "):
|
|
260
|
+
modifiers.append(modifier)
|
|
261
|
+
remaining_line = remaining_line[len(modifier) + 1 :]
|
|
262
|
+
found_modifier = True
|
|
263
|
+
break
|
|
264
|
+
if not found_modifier:
|
|
265
|
+
break
|
|
266
|
+
|
|
267
|
+
# 查找方法名和参数部分
|
|
268
|
+
paren_index = trimmed_line.find("(")
|
|
269
|
+
if paren_index == -1:
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
close_paren_index = trimmed_line.find(")", paren_index)
|
|
273
|
+
if close_paren_index == -1:
|
|
274
|
+
return None
|
|
275
|
+
|
|
276
|
+
# 提取返回类型和方法名
|
|
277
|
+
before_paren = (
|
|
278
|
+
trimmed_line[
|
|
279
|
+
trimmed_line.find(modifiers[-1])
|
|
280
|
+
+ len(modifiers[-1])
|
|
281
|
+
+ 1 : paren_index
|
|
282
|
+
].strip()
|
|
283
|
+
if modifiers
|
|
284
|
+
else trimmed_line[:paren_index].strip()
|
|
285
|
+
)
|
|
286
|
+
last_space_index = before_paren.rfind(" ")
|
|
287
|
+
if last_space_index == -1:
|
|
288
|
+
return None
|
|
289
|
+
|
|
290
|
+
return_type = before_paren[:last_space_index].strip()
|
|
291
|
+
method_name = before_paren[last_space_index + 1 :].strip()
|
|
292
|
+
|
|
293
|
+
# 提取参数
|
|
294
|
+
params_str = trimmed_line[paren_index + 1 : close_paren_index].strip()
|
|
295
|
+
parameters = []
|
|
296
|
+
|
|
297
|
+
if params_str:
|
|
298
|
+
# 处理参数,需要考虑泛型和嵌套类型
|
|
299
|
+
param_parts = self.split_parameters(params_str)
|
|
300
|
+
parameters = [param.strip() for param in param_parts if param.strip()]
|
|
301
|
+
|
|
302
|
+
return ClassMethod(
|
|
303
|
+
name=method_name,
|
|
304
|
+
return_type=return_type,
|
|
305
|
+
parameters=parameters,
|
|
306
|
+
modifiers=modifiers,
|
|
307
|
+
)
|
|
308
|
+
except Exception as e:
|
|
309
|
+
_debug(f"解析方法失败: {line}, 错误: {e}")
|
|
310
|
+
return None
|
|
311
|
+
|
|
312
|
+
def split_parameters(self, params_str: str) -> List[str]:
|
|
313
|
+
"""
|
|
314
|
+
智能分割参数,处理泛型和嵌套类型
|
|
315
|
+
"""
|
|
316
|
+
params = []
|
|
317
|
+
current = ""
|
|
318
|
+
angle_bracket_count = 0
|
|
319
|
+
|
|
320
|
+
for char in params_str:
|
|
321
|
+
if char == "<":
|
|
322
|
+
angle_bracket_count += 1
|
|
323
|
+
elif char == ">":
|
|
324
|
+
angle_bracket_count -= 1
|
|
325
|
+
elif char == "," and angle_bracket_count == 0:
|
|
326
|
+
params.append(current)
|
|
327
|
+
current = ""
|
|
328
|
+
continue
|
|
329
|
+
|
|
330
|
+
current += char
|
|
331
|
+
|
|
332
|
+
if current.strip():
|
|
333
|
+
params.append(current)
|
|
334
|
+
|
|
335
|
+
return params
|
|
336
|
+
|
|
337
|
+
def get_javap_command(self) -> str:
|
|
338
|
+
"""
|
|
339
|
+
获取javap命令路径
|
|
340
|
+
"""
|
|
341
|
+
java_home = os.environ.get("JAVA_HOME")
|
|
342
|
+
if java_home:
|
|
343
|
+
javap_cmd = "javap.exe" if os.name == "nt" else "javap"
|
|
344
|
+
return os.path.join(java_home, "bin", javap_cmd)
|
|
345
|
+
return "javap"
|
|
346
|
+
|
|
347
|
+
def get_inheritance_hierarchy(
|
|
348
|
+
self, class_name: str, project_path: str
|
|
349
|
+
) -> List[str]:
|
|
350
|
+
"""
|
|
351
|
+
获取类的继承层次结构
|
|
352
|
+
"""
|
|
353
|
+
analysis = self.analyze_class(class_name, project_path)
|
|
354
|
+
hierarchy = [class_name]
|
|
355
|
+
|
|
356
|
+
if analysis.super_class:
|
|
357
|
+
try:
|
|
358
|
+
super_hierarchy = self.get_inheritance_hierarchy(
|
|
359
|
+
analysis.super_class, project_path
|
|
360
|
+
)
|
|
361
|
+
hierarchy = super_hierarchy + hierarchy
|
|
362
|
+
except Exception:
|
|
363
|
+
# 如果父类不在当前项目中,直接添加
|
|
364
|
+
hierarchy.insert(0, analysis.super_class)
|
|
365
|
+
|
|
366
|
+
return hierarchy
|
|
367
|
+
|
|
368
|
+
def find_sub_classes(self, class_name: str, project_path: str) -> List[str]:
|
|
369
|
+
"""
|
|
370
|
+
查找类的所有子类
|
|
371
|
+
"""
|
|
372
|
+
all_classes = self.scanner.get_all_class_names(project_path)
|
|
373
|
+
sub_classes = []
|
|
374
|
+
|
|
375
|
+
for cls in all_classes:
|
|
376
|
+
try:
|
|
377
|
+
analysis = self.analyze_class(cls, project_path)
|
|
378
|
+
if analysis.super_class == class_name:
|
|
379
|
+
sub_classes.append(cls)
|
|
380
|
+
except Exception:
|
|
381
|
+
# 忽略分析失败的类型
|
|
382
|
+
pass
|
|
383
|
+
|
|
384
|
+
return sub_classes
|
|
Binary file
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from .scanner.dependency_scanner import DependencyScanner
|
|
6
|
+
from .decompiler.decompiler_service import DecompilerService
|
|
7
|
+
from .analyzer.java_class_analyzer import JavaClassAnalyzer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@click.group()
|
|
11
|
+
@click.version_option(version="1.0.0")
|
|
12
|
+
def cli():
|
|
13
|
+
"""Java Class Analyzer MCP Server CLI"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@cli.command()
|
|
18
|
+
def start():
|
|
19
|
+
"""启动MCP服务器"""
|
|
20
|
+
try:
|
|
21
|
+
from .main import mcp
|
|
22
|
+
|
|
23
|
+
mcp.run(transport="stdio")
|
|
24
|
+
except Exception as e:
|
|
25
|
+
print(f"启动服务器失败: {e}", file=sys.stderr)
|
|
26
|
+
sys.exit(1)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@cli.command()
|
|
30
|
+
@click.option("-o", "--output", required=True, help="输出配置文件路径")
|
|
31
|
+
def config(output):
|
|
32
|
+
"""生成MCP客户端配置模板"""
|
|
33
|
+
config_template = {
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"java-class-analyzer": {
|
|
36
|
+
"command": "java-class-analyzer-mcp",
|
|
37
|
+
"args": [],
|
|
38
|
+
"env": {
|
|
39
|
+
"LOG_LEVEL": "INFO",
|
|
40
|
+
"MAVEN_REPO": os.path.expanduser("~/.m2/repository"),
|
|
41
|
+
"JAVA_HOME": os.environ.get("JAVA_HOME", ""),
|
|
42
|
+
"CFR_PATH": "",
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
with open(output, "w", encoding="utf-8") as f:
|
|
49
|
+
json.dump(config_template, f, indent=2, ensure_ascii=False)
|
|
50
|
+
|
|
51
|
+
print(f"配置文件已生成: {output}")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@cli.command()
|
|
55
|
+
@click.option(
|
|
56
|
+
"-t",
|
|
57
|
+
"--tool",
|
|
58
|
+
type=click.Choice(["scan", "decompile", "analyze", "all"]),
|
|
59
|
+
required=True,
|
|
60
|
+
help="要测试的工具",
|
|
61
|
+
)
|
|
62
|
+
@click.option("-p", "--project", required=True, help="项目路径")
|
|
63
|
+
@click.option("-c", "--class-name", help="要分析的类名")
|
|
64
|
+
@click.option("--no-refresh", is_flag=True, help="不强制刷新依赖索引")
|
|
65
|
+
@click.option("--no-cache", is_flag=True, help="不使用反编译缓存")
|
|
66
|
+
def test(tool, project, class_name, no_refresh, no_cache):
|
|
67
|
+
"""测试工具使用"""
|
|
68
|
+
project_path = os.path.abspath(project)
|
|
69
|
+
|
|
70
|
+
if not os.path.exists(project_path):
|
|
71
|
+
print(f"项目路径不存在: {project_path}")
|
|
72
|
+
sys.exit(1)
|
|
73
|
+
|
|
74
|
+
# 初始化服务
|
|
75
|
+
scanner = DependencyScanner()
|
|
76
|
+
decompiler = DecompilerService()
|
|
77
|
+
analyzer = JavaClassAnalyzer()
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
if tool == "scan" or tool == "all":
|
|
81
|
+
print("=== 测试依赖扫描 ===")
|
|
82
|
+
result = scanner.scan_project(project_path, not no_refresh)
|
|
83
|
+
print(
|
|
84
|
+
f"扫描完成!处理了 {result.jar_count} 个JAR包,索引了 {result.class_count} 个类"
|
|
85
|
+
)
|
|
86
|
+
print(f"索引文件路径: {result.index_path}")
|
|
87
|
+
print("示例索引条目:")
|
|
88
|
+
for entry in result.sample_entries[:5]:
|
|
89
|
+
print(f" {entry}")
|
|
90
|
+
print()
|
|
91
|
+
|
|
92
|
+
if tool in ["decompile", "analyze", "all"]:
|
|
93
|
+
if not class_name:
|
|
94
|
+
print("请指定要分析的类名")
|
|
95
|
+
sys.exit(1)
|
|
96
|
+
|
|
97
|
+
# 确保索引存在
|
|
98
|
+
index_path = os.path.join(project_path, ".mcp-class-index.json")
|
|
99
|
+
if not os.path.exists(index_path):
|
|
100
|
+
print("正在创建类索引...")
|
|
101
|
+
scanner.scan_project(project_path, False)
|
|
102
|
+
|
|
103
|
+
if tool == "decompile" or tool == "all":
|
|
104
|
+
print("=== 测试类反编译 ===")
|
|
105
|
+
use_cache = not no_cache
|
|
106
|
+
source_code = decompiler.decompile_class(
|
|
107
|
+
class_name, project_path, use_cache
|
|
108
|
+
)
|
|
109
|
+
print(f"类 {class_name} 的反编译源码:")
|
|
110
|
+
print("=" * 50)
|
|
111
|
+
print(source_code)
|
|
112
|
+
print("=" * 50)
|
|
113
|
+
print()
|
|
114
|
+
|
|
115
|
+
if tool == "analyze" or tool == "all":
|
|
116
|
+
print("=== 测试类分析 ===")
|
|
117
|
+
analysis = analyzer.analyze_class(class_name, project_path)
|
|
118
|
+
|
|
119
|
+
print(f"类 {class_name} 的分析结果:")
|
|
120
|
+
print(f"包名: {analysis.package_name}")
|
|
121
|
+
print(f"类名: {analysis.class_name}")
|
|
122
|
+
print(f"修饰符: {' '.join(analysis.modifiers)}")
|
|
123
|
+
print(f"父类: {analysis.super_class or '无'}")
|
|
124
|
+
print(f"实现的接口: {', '.join(analysis.interfaces) or '无'}")
|
|
125
|
+
|
|
126
|
+
if analysis.fields:
|
|
127
|
+
print(f"\n字段 ({len(analysis.fields)}个):")
|
|
128
|
+
for field in analysis.fields:
|
|
129
|
+
print(f" - {' '.join(field.modifiers)} {field.type_} {field.name}")
|
|
130
|
+
|
|
131
|
+
if analysis.methods:
|
|
132
|
+
print(f"\n方法 ({len(analysis.methods)}个):")
|
|
133
|
+
for method in analysis.methods:
|
|
134
|
+
print(
|
|
135
|
+
f" - {' '.join(method.modifiers)} {method.return_type} {method.name}({', '.join(method.parameters)})"
|
|
136
|
+
)
|
|
137
|
+
print()
|
|
138
|
+
|
|
139
|
+
except Exception as e:
|
|
140
|
+
print(f"测试失败: {e}")
|
|
141
|
+
sys.exit(1)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
if __name__ == "__main__":
|
|
145
|
+
cli()
|