00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00027 #include "inspectorj/client/inspectorj.h"
00028 #include <iostream>
00029
00030 using namespace inspectorj::jdwp;
00031 using namespace inspectorj::toolset;
00032 using inspectorj::model::BytecodeHighlighter;
00033 using inspectorj::toolset::ProfileToolSet;
00034
00040 InspectorJ::InspectorJ(QWidget *parent, Qt::WindowFlags flags)
00041 : QMainWindow(parent, flags), profile(0), REFERENCE_TAB(0), BYTECODE_TAB(1)
00042 {
00043 setupUi(this);
00044
00045 connectionManager = new ConnectionManager(this, profiles, javaAppProcess);
00046 classToolSet = new ClassToolSet(*connectionManager, &profile);
00047 loadSettings();
00048 initWindow();
00049 createActions();
00050 createMenus();
00051
00052 setupConnections();
00053
00054 QFont currentFont = font();
00055 QList<QWidget *> widgets = this->findChildren<QWidget *>("");
00056 for (int i = 0; i < widgets.size(); i++) {
00057 QWidget *widget = widgets[i];
00058 widget->setFont(currentFont);
00059 }
00060 }
00061
00065 void InspectorJ::initWindow()
00066 {
00067 setWindowTitle("inspectorJ");
00068 setWindowIcon(QIcon(":/images/ij-16.png"));
00069 ctbFilterGroupBox->setVisible(false);
00070 allClassesBtn->setEnabled(false);
00071 bytecodeBtn->setEnabled(false);
00072 classTreeView->setAutoScroll(true);
00073 classTreeDockWidget->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable);
00074
00075 connectedStateBtn = new QToolButton(this);
00076 connectedStateBtn->setIcon(QIcon(":/images/redled.png"));
00077 connectedStateBtn->setMaximumSize(QSize(16,16));
00078 connectedStateBtn->setToolTip("Disconnected");
00079 this->statusbar->addPermanentWidget(dynamic_cast<QWidget*>(connectedStateBtn));
00080
00081 QFont codeFont;
00082 codeFont.setFamily("Monospace");
00083 codeFont.setFixedPitch(true);
00084 bytecodeTextEdit->setFont(codeFont);
00085
00086 bytecodeHighlighter = new BytecodeHighlighter(bytecodeTextEdit->document());
00087 bytecodeGroupBox->setMaximumHeight(bytecodeGroupBox->sizeHint().height());
00088 }
00089
00093 void InspectorJ::setupConnections()
00094 {
00095 connect(allClassesBtn, SIGNAL(clicked()), classToolSet, SLOT(getAllLoadedClasses()));
00096 connect(classToolSet, SIGNAL(tableModelReady()), this, SLOT(updateClassTable()));
00097 connect(classToolSet, SIGNAL(treeModelReady()), this, SLOT(updateClassTree()));
00098 connect(disconnectAction, SIGNAL (triggered()), connectionManager, SLOT (closeConnection()));
00099 connect(stopJavaAction, SIGNAL (triggered()), connectionManager, SLOT (stopJavaApp()));
00100 connect(fontAction, SIGNAL(triggered()), this, SLOT(showFontDlg()));
00101 connect(exitAction, SIGNAL(triggered()), this, SLOT(close()));
00102 connect(profileDirAction, SIGNAL(triggered()), this, SLOT (setProfileDirectory()));
00103 connect(connectedStateBtn, SIGNAL (clicked()), this, SLOT (getJvmInfo()));
00104 connect(jvmInfoResponse, SIGNAL (triggered(JDWPPacket&)), this, SLOT (showJvmInfo(JDWPPacket&)));
00105 connect(newProfileAction, SIGNAL(triggered()), this, SLOT(showSessionDlg()));
00106 connect(connectionManager, SIGNAL(connected(bool, QString&)), this, SLOT(connected(bool,QString&)));
00107 connect(ctbRemoveFilterBtn, SIGNAL(clicked()), this, SLOT(removeClassFilter()));
00108 connect(ctbAddFilterBtn, SIGNAL(clicked()), this, SLOT(addClassFilter()));
00109 connect(classTableView, SIGNAL(clicked(const QModelIndex&)), classToolSet, SLOT(getClassDetails(const QModelIndex&)));
00110 connect(bytecodeBtn, SIGNAL(clicked()), this, SLOT(disassembleBytecodes()));
00111 connect(classTableView, SIGNAL(clicked(const QModelIndex&)), this, SLOT(updateBytecodeBtn()));
00112 connect(classToolSet, SIGNAL(bytecodesReady()), this, SLOT(updateBytecodeTextEdit()));
00113 connect(&javaAppProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT (javaAppErr(QProcess::ProcessError)));
00114 connect(&javaAppProcess, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT (javaAppFinished(int, QProcess::ExitStatus)));
00115 connect(&javaAppProcess, SIGNAL(readyReadStandardError()), this, SLOT (javaAppStdErr()));
00116 connect(&javaAppProcess, SIGNAL(readyReadStandardOutput()), this, SLOT (javaAppStdOut()));
00117 connect(&javaAppProcess, SIGNAL(started()), this, SLOT (javaAppStarted()));
00118 }
00119
00126 void InspectorJ::connected(bool attached, QString &msg)
00127 {
00128 disconnectAction->setEnabled(attached);
00129 allClassesBtn->setEnabled(attached);
00130 updateBytecodeBtn();
00131 updateClassToolBox(attached);
00132 this->statusbar->showMessage(msg);
00133 if (attached) {
00134 connectedStateBtn->setIcon(QIcon(":/images/greenled.png"));
00135 connectedStateBtn->setToolTip("Connected");
00136 QList<QAction*> actions = editProfileMenuItem->actions();
00137 for (int i = 0; i < actions.size(); ++i) {
00138 if (actions[i]->data() == profile->getName()) {
00139 actions[i]->setEnabled(false);
00140 }
00141 }
00142 allClassesBtn->click();
00143 } else {
00144 connectedStateBtn->setIcon(QIcon(":/images/redled.png"));
00145 connectedStateBtn->setToolTip("Disconnected");
00146 QList<QAction*> actions = editProfileMenuItem->actions();
00147 for (int i = 0; i < actions.size(); ++i) {
00148 actions[i]->setEnabled(true);
00149 }
00150 profile = 0;
00151 }
00152 }
00153
00157 void InspectorJ::updateClassToolBox(bool attached)
00158 {
00159 ctbFilterGroupBox->setVisible(attached);
00160 if (attached) {
00161 ctbClassFilterList->clear();
00162 QStringList filters = profile->getClassFilters();
00163 QStringListIterator iter(filters);
00164 while (iter.hasNext()) {
00165 ctbClassFilterList->addItem(iter.next());
00166 }
00167 }
00168 }
00169
00173 void InspectorJ::addClassFilter()
00174 {
00175 if (connectionManager->isAttached()) {
00176 if (! ctbFilterEdit->text().isEmpty()) {
00177 profile->addFilter(ctbFilterEdit->text());
00178 ctbClassFilterList->addItem(ctbFilterEdit->text());
00179 ctbFilterEdit->clear();
00180 }
00181 }
00182 }
00183
00187 void InspectorJ::removeClassFilter()
00188 {
00189 if (connectionManager->isAttached()) {
00190 QList<QListWidgetItem *> itemList = ctbClassFilterList->selectedItems();
00191 QList<QListWidgetItem *>::iterator iter;
00192 for (iter = itemList.begin(); iter != itemList.end(); ++iter) {
00193 QListWidgetItem *item = ctbClassFilterList->takeItem(ctbClassFilterList->row(*iter));
00194 profile->removeFilter(item->text());
00195 }
00196 }
00197 }
00198
00199
00203 void InspectorJ::showSessionDlg()
00204 {
00205 if (profileDir.isEmpty()) {
00206 profileDir = browseProfileDirectory();
00207 if (profileDir.isEmpty()) {
00208 return;
00209 }
00210 }
00211
00212 QAction *action = qobject_cast<QAction *>(sender());
00213 if (action) {
00214 SessionProfileDialog dlg(profiles, action->data().toString());
00215 dlg.setFont(font());
00216 dlg.setProfileDir(profileDir);
00217 dlg.exec();
00218 updateSessionsMenu();
00219 updateClassToolBox(connectionManager->isAttached());
00220 }
00221 }
00222
00226 void InspectorJ::showFontDlg()
00227 {
00228 bool ok;
00229 QFont currentFont = font();
00230 QFont font = QFontDialog::getFont(&ok, currentFont, this );
00231 if ( ok ) {
00232 setFont(font);
00233 QList<QWidget *> widgets = this->findChildren<QWidget *>("");
00234 for (int i = 0; i < widgets.size(); i++) {
00235 QWidget *widget = widgets[i];
00236 widget->setFont(font);
00237 }
00238 QFont treeFont(classTreeView->font());
00239 treeFont.setPointSize(font.pointSize());
00240 classTreeView->setFont(treeFont);
00241 }
00242 }
00243
00248 void InspectorJ::showJvmInfo(JDWPPacket& reply)
00249 {
00250 if(ClientPacketHandler::handleJDWPError(reply)) {
00251 return;
00252 } else {
00253 QString msg;
00254 reply >> msg;
00255 QString title = "JVM Information";
00256 QMessageBox::information( this, title, msg,
00257 QMessageBox::Ok | QMessageBox::Default);
00258 }
00259 }
00260
00264 void InspectorJ::getJvmInfo()
00265 {
00266 if(connectionManager->isAttached()) {
00267 JDWPCommand *cmd = JDWPCommand::newCommand(VM_CMD_VERSION);
00268 connectionManager->sendRequest(cmd, jvmInfoResponse);
00269 }
00270 }
00271
00275 void InspectorJ::disassembleBytecodes()
00276 {
00277 bytecodeBtn->setEnabled(false);
00278 bytecodeTextEdit->document()->clear();
00279 bytecodeTextEdit->document()->setPlainText(
00280 "inspectorJ: Getting bytecodes. Please wait...");
00281 classToolSet->disassembleBytecodes(
00282 classTableView->selectionModel()->currentIndex(),
00283 bytecode_cb_disassemble->isChecked(),
00284 bytecode_cb_lineNumber->isChecked(),
00285 bytecode_cb_private->isChecked(),
00286 bytecode_cb_internal->isChecked(),
00287 bytecode_cb_verbose->isChecked());
00288 }
00289
00294 void InspectorJ::updateBytecodeTextEdit()
00295 {
00296 classToolSet->prepareBytecodeOutput(bytecodeTextEdit->document());
00297 updateBytecodeBtn();
00298 }
00299
00304 void InspectorJ::updateBytecodeBtn()
00305 {
00306 if (!connectionManager->isAttached()) {
00307 bytecodeBtn->setEnabled(false);
00308 return;
00309 }
00310 bool enabled = false;
00311 QItemSelectionModel *selectionModel;
00312 selectionModel = classTableView->selectionModel();
00313 if (selectionModel) {
00314 enabled = selectionModel->hasSelection();
00315 }
00316 bytecodeBtn->setEnabled(enabled);
00317 }
00318
00319
00324 void InspectorJ::loadSettings()
00325 {
00326
00327 QSettings settings("inspectorJ", "inspectorJ");
00328 QRect rect = settings.value("geometry", QRect(0,0,800,600)).toRect();
00329 move(rect.topLeft());
00330 resize(rect.size());
00331
00332 consoleTabSplitter->restoreState(settings.value("consoleTabSplitterSizes").toByteArray());
00333 splitter1->restoreState(settings.value("splitter1Sizes").toByteArray());
00334 splitter2->restoreState(settings.value("splitter2Sizes").toByteArray());
00335 splitter3->restoreState(settings.value("splitter3Sizes").toByteArray());
00336 splitter4->restoreState(settings.value("splitter4Sizes").toByteArray());
00337 splitter5->restoreState(settings.value("splitter5Sizes").toByteArray());
00338
00339
00340 profileDir = settings.value("profileDirectory").toString();
00341
00342 loadProfiles();
00343
00344
00345 QFont font = settings.value("font", QFont("Sans Serif", 8)).value<QFont>();
00346 setFont(font);
00347 }
00348
00354 void InspectorJ::saveSettings()
00355 {
00356
00357 QSettings settings("inspectorJ", "inspectorJ");
00358 settings.setValue("geometry", geometry());
00359
00360 settings.setValue("consoleTabSplitterSizes", consoleTabSplitter->saveState());
00361 settings.setValue("splitter1Sizes", splitter1->saveState());
00362 settings.setValue("splitter2Sizes", splitter2->saveState());
00363 settings.setValue("splitter3Sizes", splitter3->saveState());
00364 settings.setValue("splitter4Sizes", splitter4->saveState());
00365 settings.setValue("splitter5Sizes", splitter5->saveState());
00366
00367 settings.setValue("profileDirectory", profileDir);
00368
00369
00370 QFont currentFont = font();
00371 settings.setValue("font", currentFont);
00372 }
00373
00374 void InspectorJ::loadProfiles()
00375 {
00376
00377 if (profileDir.isEmpty()) {
00378 QString tmp = QDir::homePath().append(QDir::separator());
00379 tmp.append("inspectorj").append(QDir::separator());
00380 tmp.append("profiles");
00381
00382 QDir tmpDir(tmp);
00383 if (tmpDir.exists() || tmpDir.mkpath(tmpDir.path())) {
00384 profileDir = tmp;
00385 }
00386 }
00387
00388
00389 QDir dir(profileDir);
00390 if (dir.exists()) {
00391 QStringList filters;
00392 filters << "*.xml";
00393 QStringList files = dir.entryList(filters);
00394 for (int i = 0; i < files.size(); ++i) {
00395 SessionProfile profile;
00396 if (ProfileToolSet::loadProfile(profile, dir.absoluteFilePath(files.at(i)))) {
00397 profiles[profile.getName()] = profile;
00398 }
00399 }
00400 }
00401 }
00402
00406 void InspectorJ::setProfileDirectory()
00407 {
00408 QString newDir = browseProfileDirectory();
00409 if (profileDir != newDir) {
00410 profileDir = newDir;
00411 profiles.clear();
00412 loadProfiles();
00413 updateSessionsMenu();
00414 }
00415 }
00416
00423 QString InspectorJ::browseProfileDirectory()
00424 {
00425 QFileDialog fileDialog(this, QString("Select a profile directory"), profileDir);
00426 fileDialog.setFileMode(QFileDialog::DirectoryOnly);
00427 QStringList fileNames;
00428 if (fileDialog.exec()) {
00429 fileNames = fileDialog.selectedFiles();
00430 return fileNames.at(0);
00431 }
00432 return QString("");
00433 }
00434
00439 void InspectorJ::closeEvent(QCloseEvent *event)
00440 {
00441 QString msg = "Are you sure you want to exit inspectorJ ?";
00442 int result = QMessageBox::information(this,
00443 " inspectorJ - Exit ?", msg,
00444 QMessageBox::Yes | QMessageBox::Default,
00445 QMessageBox::No);
00446
00447 if (result == QMessageBox::Yes) {
00448 connectionManager->setExiting(true);
00449 connectionManager->closeConnection();
00450
00451
00452
00453 QProcess::ProcessState state = javaAppProcess.state();
00454 if (state == QProcess::Starting || state == QProcess::Running) {
00455 QString msg("Please wait while the launched process is terminated.");
00456
00457 javaAppProcess.kill();
00458 javaAppProcess.waitForFinished();
00459 }
00460
00461 saveSettings();
00462 event->accept();
00463 } else {
00464 event->ignore();
00465 }
00466 }
00467
00471 void InspectorJ::createActions()
00472 {
00473 stopJavaAction = new QAction("&Stop Java App", this);
00474 stopJavaAction->setEnabled(false);
00475 disconnectAction = new QAction("&Disconnect", this);
00476 disconnectAction->setEnabled(false);
00477 jvmInfoResponse = new JDWPAction(this);
00478 }
00479
00483 void InspectorJ::createMenus()
00484 {
00485
00486 attachProfileMenuItem = menuSessions->addMenu("&Attach");
00487 attachProfileMenuItem->setEnabled(false);
00488 launchProfileMenuItem = menuSessions->addMenu("&Launch");
00489 launchProfileMenuItem->setEnabled(false);
00490 editProfileMenuItem = menuSessions->addMenu("&Edit");
00491 editProfileMenuItem->setEnabled(false);
00492 menuSessions->addSeparator();
00493 menuSessions->addAction(disconnectAction);
00494 menuSessions->addAction(stopJavaAction);
00495 updateSessionsMenu();
00496 }
00497
00502 void InspectorJ::updateSessionsMenu()
00503 {
00504 QMapIterator<QString, SessionProfile> iter(profiles);
00505
00506
00507 editProfileMenuItem->setEnabled(iter.hasNext());
00508 editProfileMenuItem->clear();
00509 attachProfileMenuItem->setEnabled(false);
00510 attachProfileMenuItem->clear();
00511 launchProfileMenuItem->setEnabled(false);
00512 launchProfileMenuItem->clear();
00513
00514
00515 while (iter.hasNext()) {
00516 iter.next();
00517 SessionProfile sProfile = iter.value();
00518
00519 if (sProfile.isExternalLaunch()) {
00520 attachProfileMenuItem->setEnabled(true);
00521 QAction *startAction = new QAction(iter.key(), this);
00522 QStringList actionData;
00523 ijConnection conn = sProfile.getConnection();
00524 actionData << sProfile.getName() << conn.host << conn.port;
00525 startAction->setData(actionData);
00526 attachProfileMenuItem->addAction(startAction);
00527 connect(startAction, SIGNAL(triggered()), this, SLOT(setCurrentProfile()));
00528 connect(startAction, SIGNAL(triggered()), connectionManager, SLOT(attachToServer()));
00529 } else {
00530 launchProfileMenuItem->setEnabled(true);
00531 QAction *launchAction = new QAction(iter.key(), this);
00532 QStringList actionData;
00533 actionData << sProfile.getName() << profileDir;
00534 launchAction->setData(actionData);
00535 launchProfileMenuItem->addAction(launchAction);
00536 connect(launchAction, SIGNAL(triggered()), this, SLOT(setCurrentProfile()));
00537 connect(launchAction, SIGNAL(triggered()), connectionManager, SLOT(launchJavaApp()));
00538 }
00539
00540 QAction *editAction = new QAction(iter.key(), this);
00541 editAction->setData(iter.key());
00542 editProfileMenuItem->addAction(editAction);
00543 connect(editAction, SIGNAL(triggered()), this, SLOT(showSessionDlg()));
00544 }
00545 }
00546
00547
00551 void InspectorJ::setCurrentProfile()
00552 {
00553 QAction *action = qobject_cast<QAction *>(sender());
00554 if (action) {
00555 QStringList actionData = action->data().toStringList();
00556 QString profileName;
00557 profileName = actionData[0];
00558 profile = &(profiles[profileName]);
00559 }
00560 }
00561
00568 void InspectorJ::updateClassTree()
00569 {
00570 this->classTreeView->reset();
00571 ClassTreeModel *classTreeModel = classToolSet->getClassTreeModel();
00572 classTreeModel->initModel();
00573 this->classTreeView->setModel(classTreeModel);
00574 this->classTreeView->expandAll();
00575 int columnCount = classTreeModel->columnCount();
00576 for (int i = 0; i < columnCount; i++) {
00577 this->classTreeView->resizeColumnToContents(i);
00578 }
00579 }
00580
00587 void InspectorJ::updateClassTable()
00588 {
00589 ClassTableModel *classTableModel = classToolSet->getClassTableModel();
00590 this->classTableView->setModel(classTableModel);
00591 this->classTableView->reset();
00592 classTableView->setColumnWidth(0,classTableView->viewport()->width());
00593 classTableView->resizeRowsToContents();
00594 QString totalClassesNum;
00595 QString filteredClassesNum;
00596 totalClassesNum.setNum(classTableModel->getTotalClassesLoaded());
00597 filteredClassesNum.setNum(classTableModel->getFilteredClassesLoaded());
00598 totalClassesLoadedVal->setText(totalClassesNum);
00599 filteredClassesLoadedVal->setText(filteredClassesNum);
00600 }
00601
00605 void InspectorJ::javaAppErr(QProcess::ProcessError error)
00606 {
00607 QString javaOutput;
00608 if (error == QProcess::FailedToStart) {
00609 javaOutput.append("java executable could not be started.");
00610 javaOutput.append("Make sure the profile's jre/jdk home is correct");
00611 } else {
00612 javaOutput.append("An unknown error has occurred.");
00613 }
00614
00615 consoleTextEdit->append(javaOutput);
00616 }
00617
00621 void InspectorJ::javaAppFinished(int exitCode, QProcess::ExitStatus exitStatus)
00622 {
00623 stopJavaAction->setEnabled(false);
00624 QString javaOutput;
00625 javaOutput = QString::fromLocal8Bit(javaAppProcess.readAllStandardOutput());
00626 javaOutput.append("\n");
00627
00628 if (exitStatus == QProcess::CrashExit) {
00629 javaOutput.append("The application exited unexpectedly.");
00630 } else if (exitCode != 0) {
00631 javaOutput.append("The application exited with code: ");
00632 javaOutput.append(QString::number(exitCode));
00633 }
00634 consoleTextEdit->append(javaOutput);
00635 }
00636
00640 void InspectorJ::javaAppStdErr()
00641 {
00642 if (javaAppProcess.state() == QProcess::NotRunning) {
00643 stopJavaAction->setEnabled(false);
00644 }
00645
00646 statusbar->clearMessage();
00647 QString msg = QString::fromLocal8Bit(javaAppProcess.
00648 readAllStandardError());
00649 if (!msg.isEmpty()) {
00650 consoleTextEdit->append(cleanConsoleText(msg));
00651 }
00652 }
00653
00657 void InspectorJ::javaAppStdOut()
00658 {
00659 QString msg = QString::fromLocal8Bit(javaAppProcess.
00660 readAllStandardOutput());
00661 if (!msg.isEmpty()) {
00662 consoleTextEdit->append(cleanConsoleText(msg));
00663 }
00664 }
00665
00669 void InspectorJ::javaAppStarted()
00670 {
00671 stopJavaAction->setEnabled(true);
00672 if (connectionManager->startAttachTimer(2000, profile->getConnection())) {
00673 QString msg("Attaching to ");
00674 msg.append(profile->getName());
00675 msg.append(" profile launched process. Please wait...");
00676 statusbar->showMessage(msg);
00677 }
00678 }
00679
00680 QString& InspectorJ::cleanConsoleText(QString& msg)
00681 {
00682 int len = msg.length();
00683 if (len > 0 && msg[len-1] == QChar('\n')) {
00684 msg.remove(len-1, 1);
00685 }
00686 len = msg.length();
00687 if (len > 0 && msg[len-1] == QChar('\r')) {
00688 msg.remove(len-1, 1);
00689 }
00690 return msg;
00691 }