ij-16.png   inspectorJ -- JavaTM Profiler
sf project site browse source checkout source
SourceForge.net Logo



src/inspectorj/client/inspectorj.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002 *   inspectorJ - java profiler                                            *
00003 *   Copyright (C) 2006 by James May                                       *
00004 *                                                                         *
00005 *   This program is free software; you can redistribute it and/or modify  *
00006 *   it under the terms of the GNU General Public License as published by  *
00007 *   the Free Software Foundation; either version 2 of the License, or     *
00008 *   (at your option) any later version.                                   *
00009 *                                                                         *
00010 *   This program is distributed in the hope that it will be useful,       *
00011 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00012 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00013 *   GNU General Public License for more details.                          *
00014 *                                                                         *
00015 *   You should have received a copy of the GNU General Public License     *
00016 *   along with this program; if not, write to the                         *
00017 *   Free Software Foundation, Inc.,                                       *
00018 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
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     // Restore geometry
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     // Restore profile directory
00340     profileDir = settings.value("profileDirectory").toString();
00341     
00342     loadProfiles();   
00343     
00344     // Restore font settings
00345     QFont font = settings.value("font", QFont("Sans Serif", 8)).value<QFont>();
00346     setFont(font);
00347 }
00348 
00354 void InspectorJ::saveSettings()
00355 {
00356     // Save geometry
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     // Save font preference
00370     QFont currentFont = font();
00371     settings.setValue("font", currentFont);
00372 }
00373 
00374 void InspectorJ::loadProfiles()
00375 {
00376         // If profileDir is not set, try to create a default one
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     // load profiles stored on disk...
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         // If we've started a java or other application process, try to                                             
00452         // bring it down gracefully before exiting.
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     // Connections menu
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     // clear the SessionProfile menu items
00507     editProfileMenuItem->setEnabled(iter.hasNext());
00508     editProfileMenuItem->clear();
00509     attachProfileMenuItem->setEnabled(false);
00510     attachProfileMenuItem->clear();
00511     launchProfileMenuItem->setEnabled(false);
00512     launchProfileMenuItem->clear();
00513      
00514     // rebuild the menu items
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 }

Generated on Sun Aug 19 17:07:53 2007 for inspectorJ by  doxygen 1.5.1