1 | // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. |
2 | // |
3 | // SPDX-License-Identifier: GPL-3.0-or-later |
4 | |
5 | #include "javadebugger.h" |
6 | #include "javaparam.h" |
7 | |
8 | #include <QProcess> |
9 | #include <QDebug> |
10 | #include <QRegularExpression> |
11 | #include <QDBusConnection> |
12 | #include <QDBusMessage> |
13 | #include <QApplication> |
14 | #include <QDir> |
15 | #include <QJsonDocument> |
16 | #include <QJsonObject> |
17 | #include <QJsonArray> |
18 | #include <QDateTime> |
19 | |
20 | class JavaDebuggerPrivate { |
21 | friend class JavaDebugger; |
22 | QProcess process; |
23 | QString tempBuffer; |
24 | JavaParam javaparam; |
25 | int dapRequestId = 0; |
26 | int mainClassRequestId = 0; |
27 | int classPathRequestId = 0; |
28 | |
29 | int port = 0; |
30 | QString mainClass; |
31 | QString projectName; |
32 | QStringList classPaths; |
33 | QString uuid; |
34 | QString workspace; |
35 | QString kit; |
36 | |
37 | int requestId = 1; |
38 | bool initialized = false; |
39 | }; |
40 | |
41 | JavaDebugger::JavaDebugger(QObject *parent) |
42 | : QObject(parent) |
43 | , d(new JavaDebuggerPrivate()) |
44 | { |
45 | registerLaunchDAPConnect(); |
46 | connect(this, &JavaDebugger::sigResolveClassPath, this, &JavaDebugger::slotResolveClassPath); |
47 | connect(this, &JavaDebugger::sigCheckInfo, this, &JavaDebugger::slotCheckInfo); |
48 | |
49 | connect(this, &JavaDebugger::sigSendToClient, [](const QString &uuid, int port, const QString &kit, |
50 | const QMap<QString, QVariant> ¶m) { |
51 | QDBusMessage msg = QDBusMessage::createSignal("/path" , |
52 | "com.deepin.unioncode.interface" , |
53 | "dapport" ); |
54 | msg << uuid |
55 | << port |
56 | << kit |
57 | << param; |
58 | QDBusConnection::sessionBus().send(msg); |
59 | }); |
60 | |
61 | connect(&d->process, &QProcess::readyReadStandardOutput, [this]() { |
62 | QByteArray data = d->process.readAllStandardOutput(); |
63 | |
64 | qInfo() << "message:" << qPrintable(data); |
65 | outputMsg("stdOut" , data); |
66 | parseResult(data); |
67 | }); |
68 | |
69 | connect(&d->process, &QProcess::readyReadStandardError, [this]() { |
70 | QByteArray data = d->process.readAllStandardError(); |
71 | qInfo() << "error:" << qPrintable(data); |
72 | outputMsg("stdErr" , data); |
73 | }); |
74 | |
75 | |
76 | } |
77 | |
78 | JavaDebugger::~JavaDebugger() |
79 | { |
80 | if (d) |
81 | delete d; |
82 | } |
83 | |
84 | void JavaDebugger::registerLaunchDAPConnect() |
85 | { |
86 | QDBusConnection sessionBus = QDBusConnection::sessionBus(); |
87 | sessionBus.disconnect(QString("" ), |
88 | "/path" , |
89 | "com.deepin.unioncode.interface" , |
90 | "launch_java_dap" , |
91 | this, SLOT(slotReceivePojectInfo(QString, QString, QString, QString, QString, |
92 | QString, QString, QString, QString, QString))); |
93 | sessionBus.connect(QString("" ), |
94 | "/path" , |
95 | "com.deepin.unioncode.interface" , |
96 | "launch_java_dap" , |
97 | this, SLOT(slotReceivePojectInfo(QString, QString, QString, QString, QString, |
98 | QString, QString, QString, QString, QString))); |
99 | } |
100 | |
101 | void JavaDebugger::initialize(const QString &configHomePath, |
102 | const QString &jreExecute, |
103 | const QString &launchPackageFile, |
104 | const QString &launchConfigPath, |
105 | const QString &workspace) |
106 | { |
107 | if (d->initialized) |
108 | return; |
109 | |
110 | int startPort = 6000; |
111 | |
112 | auto checkPortFree = [](int port) { |
113 | QProcess process; |
114 | QString cmd = QString("fuser %1/tcp" ).arg(port); |
115 | process.start(cmd); |
116 | process.waitForFinished(); |
117 | QString ret = process.readAll(); |
118 | if (ret.isEmpty()) |
119 | return true; |
120 | return false; |
121 | }; |
122 | |
123 | while (startPort) { |
124 | if (checkPortFree(startPort)) { |
125 | break; |
126 | } |
127 | startPort--; |
128 | } |
129 | |
130 | QString validPort = QString::number(startPort); |
131 | QString logFolder = configHomePath + "/dap/javalog/" + QFileInfo(workspace).fileName() + |
132 | "_" + QDateTime::currentDateTime().toString("yyyyMMddHHmmss" ); |
133 | QString heapDumpPath = logFolder + "/heapdump/headdump.java" ; |
134 | QString dataPath = logFolder + "/jdt_ws" ; |
135 | |
136 | QString param = d->javaparam.getInitBackendParam(validPort, |
137 | jreExecute, |
138 | launchPackageFile, |
139 | heapDumpPath, |
140 | launchConfigPath, |
141 | dataPath); |
142 | qInfo() << validPort; |
143 | QStringList options; |
144 | options << "-c" << param; |
145 | outputMsg("normal" , options.join(";" )); |
146 | d->process.start("/bin/bash" , options); |
147 | d->process.waitForStarted(); |
148 | |
149 | d->initialized = true; |
150 | } |
151 | |
152 | void JavaDebugger::slotReceivePojectInfo(const QString &uuid, |
153 | const QString &kit, |
154 | const QString &workspace, |
155 | const QString &configHomePath, |
156 | const QString &jrePath, |
157 | const QString &jreExecute, |
158 | const QString &launchPackageFile, |
159 | const QString &launchConfigPath, |
160 | const QString &dapPackageFile, |
161 | const QString &projectCachePath) |
162 | { |
163 | d->port = 0; |
164 | d->mainClass.clear(); |
165 | d->projectName.clear(); |
166 | d->classPaths.clear(); |
167 | d->requestId = 1; |
168 | d->uuid = uuid; |
169 | d->workspace = workspace; |
170 | d->kit = kit; |
171 | |
172 | Q_UNUSED(projectCachePath) |
173 | initialize(configHomePath, jreExecute, launchPackageFile, launchConfigPath, workspace); |
174 | |
175 | int pid = static_cast<int>(QApplication::applicationPid()); |
176 | |
177 | QStringList commandQueue; |
178 | commandQueue << d->javaparam.getLSPInitParam(d->requestId++, pid, workspace, jrePath, dapPackageFile); |
179 | commandQueue << d->javaparam.getLSPInitilizedParam(d->requestId++); |
180 | |
181 | d->dapRequestId = d->requestId++; |
182 | commandQueue << d->javaparam.getLaunchJavaDAPParam(d->dapRequestId); |
183 | |
184 | d->mainClassRequestId = d->requestId++; |
185 | commandQueue << d->javaparam.getResolveMainClassParam(d->mainClassRequestId, workspace); |
186 | |
187 | foreach (auto command, commandQueue) { |
188 | executeCommand(command); |
189 | } |
190 | } |
191 | |
192 | void JavaDebugger::executeCommand(const QString &command) |
193 | { |
194 | int length = command.length(); |
195 | QString writeStr = QString("Content-Length:%1\r\n\r\n%2" ).arg(length).arg(command); |
196 | qInfo() << writeStr; |
197 | outputMsg("normal" , writeStr.toUtf8()); |
198 | d->process.write(writeStr.toUtf8()); |
199 | d->process.waitForBytesWritten(); |
200 | } |
201 | |
202 | void JavaDebugger::slotResolveClassPath(const QString &mainClass, const QString &projectName) |
203 | { |
204 | d->classPathRequestId = d->requestId++; |
205 | QString command = d->javaparam.getResolveClassPathParam(d->classPathRequestId, mainClass, projectName); |
206 | executeCommand(command); |
207 | } |
208 | |
209 | void JavaDebugger::parseResult(const QString &content) |
210 | { |
211 | // ex: {"jsonrpc":"2.0","id":3,"result":33097} |
212 | const auto PORT_REG = QRegularExpression(R"({"jsonrpc":"2.0","id":([0-9]+),"result":([0-9]+)})" , |
213 | QRegularExpression::NoPatternOption); |
214 | |
215 | // ex: {"jsonrpc":"2.0","id":3,"result":[{"mainClass":"com.uniontech.App","projectName":"maven_demo", |
216 | // "filePath":"/home/zhouyi/Desktop/debugJava/new/maven_demo/src/main/java/com/uniontech/App.java"}]} |
217 | |
218 | // ex: {"jsonrpc":"2.0","id":3,"result":[[],["/home/zhouyi/Desktop/debugJava/new/maven_demo/target/classes"]]} |
219 | const auto CONTENT_REG = QRegularExpression(R"({"jsonrpc":"2.0","id":([0-9]+),"result":\[(.+)\]})" , |
220 | QRegularExpression::NoPatternOption); |
221 | //qInfo() << content << endl; |
222 | QRegularExpressionMatch regMatch; |
223 | if ((regMatch = PORT_REG.match(content)).hasMatch()) { |
224 | //qInfo() << regMatch; |
225 | |
226 | int requestId = regMatch.captured(1).trimmed().toInt(); |
227 | if (d->dapRequestId == requestId) { |
228 | d->port = regMatch.captured(2).trimmed().toInt(); |
229 | emit sigCheckInfo(); |
230 | } |
231 | } else if ((regMatch = CONTENT_REG.match(content)).hasMatch()) { |
232 | //qInfo() << regMatch; |
233 | int requestId = regMatch.captured(1).trimmed().toInt(); |
234 | if (d->mainClassRequestId == requestId) { |
235 | QString content = regMatch.captured(2).trimmed(); |
236 | if (parseMainClass(content, d->mainClass, d->projectName)) |
237 | emit sigResolveClassPath(d->mainClass, d->projectName); |
238 | } else if (d->classPathRequestId == requestId) { |
239 | QString content = regMatch.captured(2).trimmed(); |
240 | if (parseClassPath(content, d->classPaths)) |
241 | emit sigCheckInfo(); |
242 | } |
243 | } |
244 | } |
245 | |
246 | bool JavaDebugger::parseMainClass(const QString &content, QString &mainClass, QString &projectName) |
247 | { |
248 | if (content.isEmpty()) |
249 | return false; |
250 | |
251 | QString newContent = "{\"result\":[" + content + "]}" ; |
252 | //qInfo() << newContent; |
253 | |
254 | //ex : "{\"result\":[{\"mainClass\":\"com.uniontech.App\",\"projectName\":\"maven_demo\",\"filePath\":\"/App.java\"}]}" |
255 | QByteArray data = newContent.toUtf8(); |
256 | QJsonParseError parseError; |
257 | QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); |
258 | if (QJsonParseError::NoError != parseError.error) { |
259 | return false; |
260 | } |
261 | |
262 | if (!doc.isObject()) |
263 | return false; |
264 | |
265 | QJsonObject rootObject = doc.object(); |
266 | QJsonArray array = rootObject.value("result" ).toArray(); |
267 | foreach (auto value, array) { |
268 | QJsonObject object = value.toObject(); |
269 | mainClass = object.value("mainClass" ).toString(); |
270 | projectName = object.value("projectName" ).toString(); |
271 | |
272 | if (!mainClass.isEmpty() && !projectName.isEmpty()) |
273 | return true; |
274 | } |
275 | |
276 | return false; |
277 | } |
278 | |
279 | bool JavaDebugger::parseClassPath(const QString &content, QStringList &classPaths) |
280 | { |
281 | if (content.isEmpty()) |
282 | return false; |
283 | |
284 | QString newContent = "{\"result\":[" + content + "]}" ; |
285 | //qInfo() << newContent; |
286 | |
287 | //ex : "{\"result\":[[],[\"/home/maven_demo/maven_demo/target/classes\"]]}" |
288 | QByteArray data = newContent.toUtf8(); |
289 | QJsonParseError parseError; |
290 | QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); |
291 | if (QJsonParseError::NoError != parseError.error) { |
292 | return false; |
293 | } |
294 | |
295 | if (!doc.isObject()) |
296 | return false; |
297 | |
298 | QJsonObject rootObject = doc.object(); |
299 | QJsonArray array = rootObject.value("result" ).toArray(); |
300 | foreach (auto value, array) { |
301 | QJsonArray subArray = value.toArray(); |
302 | foreach (auto subValue, subArray) { |
303 | QString classPath = subValue.toString(); |
304 | if (!classPath.isEmpty()) |
305 | classPaths.append(classPath); |
306 | } |
307 | } |
308 | |
309 | if (!classPaths.isEmpty()) |
310 | return true; |
311 | |
312 | return false; |
313 | } |
314 | |
315 | void JavaDebugger::outputMsg(const QString &title, const QString &msg) |
316 | { |
317 | |
318 | auto dbusMsg = QDBusMessage::createSignal("/path" , |
319 | "com.deepin.unioncode.interface" , |
320 | "output" ); |
321 | |
322 | dbusMsg << title; |
323 | dbusMsg << (msg + "\n" ); |
324 | QDBusConnection::sessionBus().send(dbusMsg); |
325 | } |
326 | |
327 | void JavaDebugger::slotCheckInfo() |
328 | { |
329 | if (d->port > 0 |
330 | /* && !d->mainClass.isEmpty() |
331 | && !d->projectName.isEmpty() |
332 | && !d->classPaths.isEmpty()*/) { |
333 | QMap<QString, QVariant> param; |
334 | param.insert("workspace" , d->workspace); |
335 | param.insert("mainClass" , d->mainClass); |
336 | param.insert("projectName" , d->projectName); |
337 | param.insert("classPaths" , d->classPaths); |
338 | emit sigSendToClient(d->uuid, d->port, d->kit, param); |
339 | } |
340 | } |
341 | |
342 | |