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
20class 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
41JavaDebugger::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> &param) {
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
78JavaDebugger::~JavaDebugger()
79{
80 if (d)
81 delete d;
82}
83
84void 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
101void 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
152void 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
192void 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
202void 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
209void 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
246bool 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
279bool 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
315void 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
327void 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