| 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 | |