<commit_before>/**
 * @file
 * @author  Mohammad S. Babaei <info@babaei.net>
 * @version 0.1.0
 *
 * @section LICENSE
 *
 * (The MIT License)
 *
 * Copyright (c) 2016 Mohammad S. Babaei
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @section DESCRIPTION
 *
 * The CMS start page.
 */

#include <ctime>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/format.hpp>
#include <pqxx/pqxx>
#include <Wt/WApplication>
#include <Wt/WContainerWidget>
#include <Wt/WSignalMapper>
#include <Wt/WStackedWidget>
#include <Wt/WTemplate>
#include <Wt/WText>
#include <Wt/WTimer>
#include <Wt/WWidget>
#include <CoreLib/CDate.hpp>
#include <CoreLib/Database.hpp>
#include <CoreLib/FileSystem.hpp>
#include <CoreLib/Log.hpp>
#include <CoreLib/Random.hpp>
#include <CoreLib/System.hpp>
#include "CgiEnv.hpp"
#include "CgiRoot.hpp"
#include "Cms.hpp"
#include "CmsChangeEmail.hpp"
#include "CmsChangePassword.hpp"
#include "CmsContacts.hpp"
#include "CmsDashboard.hpp"
#include "CmsNewsletter.hpp"
#include "CmsSettings.hpp"
#include "CmsSubscribers.hpp"
#include "Div.hpp"
#include "Pool.hpp"
#include "SysMon.hpp"

using namespace std;
using namespace boost;
using namespace cppdb;
using namespace Wt;
using namespace CoreLib;
using namespace Service;

struct Cms::Impl : public Wt::WObject
{
public:
    WStackedWidget *Contents;
    WText *LastSelectedMenuItem;

    SysMon *SystemMonitor;

public:
    Impl();
    ~Impl();

public:
    void OnMenuItemPressed(WText *sender);

public:
    void ValidateSession();

private:
    void ForceExit();
};

Cms::Cms()
    : Page(),
    m_pimpl(make_unique<Cms::Impl>())
{
    WApplication *app = WApplication::instance();
    app->setTitle(tr("cms-page-title"));

    this->clear();
    this->setId("CmsPage");
    this->setStyleClass("cms-page container-fluid");
    this->addWidget(this->Layout());

    app->root()->clear();
    app->root()->addWidget(this);

    WTimer *timer = new WTimer(this);
    timer->setInterval(60000);       // every one minute
    timer->timeout().connect(m_pimpl.get(), &Cms::Impl::ValidateSession);
    timer->start();
}

Cms::~Cms() = default;

WWidget *Cms::Layout()
{
    Div *container = new Div("Cms", "cms-layout container-fluid");
    Div *noScript = new Div(container);
    noScript->addWidget(new WText(tr("no-script")));

    CgiRoot *cgiRoot = static_cast<CgiRoot *>(WApplication::instance());
    CgiEnv *cgiEnv = cgiRoot->GetCgiEnvInstance();

    string htmlData;
    string file;
    if (cgiEnv->GetCurrentLanguage() == CgiEnv::Language::Fa) {
        file = "../templates/cms-fa.wtml";
    } else {
        file = "../templates/cms.wtml";
    }

    if (CoreLib::FileSystem::Read(file, htmlData)) {
        WTemplate *tmpl = new WTemplate(container);
        tmpl->setTemplateText(WString::fromUTF8(htmlData), TextFormat::XHTMLUnsafeText);

        tmpl->bindWidget("brand-title", new WText(tr("cms-page-title")));

        WText *dashboard = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard")), TextFormat::XHTMLUnsafeText);
        WText *newsletter = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-newsletter")), TextFormat::XHTMLUnsafeText);
        WText *subscribers = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-subscribers")), TextFormat::XHTMLUnsafeText);
        WText *contacts = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-contacts")), TextFormat::XHTMLUnsafeText);
        WText *settings = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-settings")), TextFormat::XHTMLUnsafeText);
        WText *changeEmail = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-admin-change-email")), TextFormat::XHTMLUnsafeText);
        WText *changePassword = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-admin-change-password")), TextFormat::XHTMLUnsafeText);
        WText *sysmon = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-system-monitor")), TextFormat::XHTMLUnsafeText);
        WText *switchLanguage = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-switch-language")), TextFormat::XHTMLUnsafeText);
        WText *exit = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-exit")), TextFormat::XHTMLUnsafeText);

        dashboard->setId("menu-item-dashboard");
        newsletter->setId("menu-item-newsletter");
        subscribers->setId("menu-item-subscribers");
        contacts->setId("menu-item-contacts");
        settings->setId("menu-item-settings");
        changeEmail->setId("menu-item-change-email");
        changePassword->setId("menu-item-change-password");
        sysmon->setId("menu-item-system-monitor");
        switchLanguage->setId("menu-item-switch-language");
        exit->setId("menu-item-exit");

        tmpl->bindWidget("dashboard", dashboard);
        tmpl->bindWidget("newsletter", newsletter);
        tmpl->bindWidget("subscribers", subscribers);
        tmpl->bindWidget("contacts", contacts);
        tmpl->bindWidget("settings", settings);
        tmpl->bindWidget("admin", new WText(tr("cms-dashboard-admin")));
        tmpl->bindWidget("change-email", changeEmail);
        tmpl->bindWidget("change-password", changePassword);
        tmpl->bindWidget("system-monitor", sysmon);
        tmpl->bindWidget("switch-language", switchLanguage);
        tmpl->bindWidget("exit", exit);

        m_pimpl->Contents = new Wt::WStackedWidget();
        m_pimpl->Contents->addWidget(new CmsDashboard());
        m_pimpl->Contents->addWidget(new CmsNewsletter());
        m_pimpl->Contents->addWidget(new CmsSubscribers());
        m_pimpl->Contents->addWidget(new CmsContacts());
        m_pimpl->Contents->addWidget(new CmsSettings());
        m_pimpl->Contents->addWidget(new CmsChangeEmail());
        m_pimpl->Contents->addWidget(new CmsChangePassword());
        m_pimpl->SystemMonitor = new SysMon();
        m_pimpl->Contents->addWidget(m_pimpl->SystemMonitor);
        tmpl->bindWidget("stcked-widget", m_pimpl->Contents);

        WSignalMapper<WText *, WStackedWidget *> *menuItemSignalMapper = new WSignalMapper<WText *, WStackedWidget *>(m_pimpl.get());
        menuItemSignalMapper->mapped().connect(m_pimpl.get(), &Cms::Impl::OnMenuItemPressed);
        menuItemSignalMapper->mapConnect(dashboard->clicked(), dashboard);
        menuItemSignalMapper->mapConnect(newsletter->clicked(), newsletter);
        menuItemSignalMapper->mapConnect(subscribers->clicked(), subscribers);
        menuItemSignalMapper->mapConnect(contacts->clicked(), contacts);
        menuItemSignalMapper->mapConnect(settings->clicked(), settings);
        menuItemSignalMapper->mapConnect(changeEmail->clicked(), changeEmail);
        menuItemSignalMapper->mapConnect(changePassword->clicked(), changePassword);
        menuItemSignalMapper->mapConnect(sysmon->clicked(), sysmon);
        menuItemSignalMapper->mapConnect(switchLanguage->clicked(), switchLanguage);
        menuItemSignalMapper->mapConnect(exit->clicked(), exit);
    }

    return container;
}

Cms::Impl::Impl()
    : LastSelectedMenuItem(nullptr)
{

}

Cms::Impl::~Impl() = default;

void Cms::Impl::OnMenuItemPressed(WText *sender)
{
    if (LastSelectedMenuItem != nullptr) {
        LastSelectedMenuItem->removeStyleClass("selected");
    }
    LastSelectedMenuItem = sender;
    LastSelectedMenuItem->setStyleClass("selected");

    if (sender->id() == "menu-item-system-monitor") {
        SystemMonitor->Resume();
        Contents->setCurrentIndex(7);
        return;
    } else {
        SystemMonitor->Pause();
    }

    CgiRoot *cgiRoot = static_cast<CgiRoot *>(WApplication::instance());
    CgiEnv *cgiEnv = cgiRoot->GetCgiEnvInstance();

    if (sender->id() == "menu-item-dashboard") {
        Contents->setCurrentIndex(0);
    } else if (sender->id() == "menu-item-newsletter") {
        Contents->setCurrentIndex(1);
    } else if (sender->id() == "menu-item-subscribers") {
        Contents->setCurrentIndex(2);
    } else if (sender->id() == "menu-item-contacts") {
        Contents->setCurrentIndex(3);
    } else if (sender->id() == "menu-item-settings") {
        Contents->setCurrentIndex(4);
    } else if (sender->id() == "menu-item-change-email") {
        Contents->setCurrentIndex(5);
    } else if (sender->id() == "menu-item-change-password") {
        Contents->setCurrentIndex(6);
    } else if (sender->id() == "menu-item-switch-language") {
        switch (cgiEnv->GetCurrentLanguage()) {
        case CgiEnv::Language::None:
        case CgiEnv::Language::Invalid:
            break;
        case CgiEnv::Language::En:
            cgiRoot->redirect("/?root&lang=fa");
            break;
        case CgiEnv::Language::Fa:
            cgiRoot->redirect("/?root&lang=en");
            break;
        }
        return;
    } else if (sender->id() == "menu-item-exit") {
        ForceExit();
        return;
    } else {
        LOG_WARNING("ERROR: INVALID STACKED-WIDGET PAGE ID!");
    }
}

void Cms::Impl::ValidateSession()
{
    CgiRoot *cgiRoot = static_cast<CgiRoot *>(WApplication::instance());
    CgiEnv *cgiEnv = cgiRoot->GetCgiEnvInstance();

    try {
        auto conn = Pool::Database().Connection();
        conn->activate();
        pqxx::work txn(*conn.get());

        string query((boost::format("SELECT EXTRACT ( EPOCH FROM expiry::TIMESTAMPTZ ) as expiry FROM \"%1%\""
                                    " WHERE token = %2%;")
                      % txn.esc(Service::Pool::Database().GetTableName("ROOT_SESSIONS"))
                      % txn.quote(cgiEnv->GetInformation().Client.Session.Token)).str());
        LOG_INFO("Running query...", query, cgiEnv->GetInformation().ToJson());

        result r = txn.exec(query);

        string expiry("0");
        if (!r.empty()) {
            const result::tuple row(r[0]);
            expiry = row["expiry"].c_str();
        }

        time_t rawTime = lexical_cast<time_t>(expiry);
        CDate::Now n(CDate::Timezone::UTC);

        /// 0 means force exit the current session
        /// if you don't know why,
        /// see Service::RootLogin::PreserveSessionData method
        if (rawTime == 0) {
            ForceExit();
        }
    }

    catch (const pqxx::sql_error &ex) {
        LOG_ERROR(ex.what(), ex.query(), cgiEnv->GetInformation().ToJson());
    }

    catch (const boost::exception &ex) {
        LOG_ERROR(boost::diagnostic_information(ex), cgiEnv->GetInformation().ToJson());
    }

    catch (const std::exception &ex) {
        LOG_ERROR(ex.what(), cgiEnv->GetInformation().ToJson());
    }

    catch (...) {
        LOG_ERROR(UNKNOWN_ERROR, cgiEnv->GetInformation().ToJson());
    }
}

void Cms::Impl::ForceExit()
{
    CgiRoot *cgiRoot = static_cast<CgiRoot *>(WApplication::instance());
    CgiEnv *cgiEnv = cgiRoot->GetCgiEnvInstance();

    srand(static_cast<unsigned int>(System::RandSeed()));
    try {
        cgiRoot->removeCookie("cms-session-token");
        LOG_INFO("Root logout succeed!", cgiEnv->GetInformation().ToJson());
    } catch(...) {
        LOG_ERROR("Root logout failed!", cgiEnv->GetInformation().ToJson());
    }
    cgiRoot->Exit("/?root&logout");
}
<commit_msg>fix Service::Cms to reflect new API changes<commit_after>/**
 * @file
 * @author  Mohammad S. Babaei <info@babaei.net>
 * @version 0.1.0
 *
 * @section LICENSE
 *
 * (The MIT License)
 *
 * Copyright (c) 2016 Mohammad S. Babaei
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @section DESCRIPTION
 *
 * The CMS start page.
 */

#include <ctime>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/format.hpp>
#include <pqxx/pqxx>
#include <Wt/WApplication>
#include <Wt/WContainerWidget>
#include <Wt/WSignalMapper>
#include <Wt/WStackedWidget>
#include <Wt/WTemplate>
#include <Wt/WText>
#include <Wt/WTimer>
#include <Wt/WWidget>
#include <CoreLib/CDate.hpp>
#include <CoreLib/Database.hpp>
#include <CoreLib/FileSystem.hpp>
#include <CoreLib/Log.hpp>
#include <CoreLib/Random.hpp>
#include <CoreLib/System.hpp>
#include "CgiEnv.hpp"
#include "CgiRoot.hpp"
#include "Cms.hpp"
#include "CmsChangeEmail.hpp"
#include "CmsChangePassword.hpp"
#include "CmsContacts.hpp"
#include "CmsDashboard.hpp"
#include "CmsNewsletter.hpp"
#include "CmsSettings.hpp"
#include "CmsSubscribers.hpp"
#include "Div.hpp"
#include "Pool.hpp"
#include "SysMon.hpp"

using namespace std;
using namespace boost;
using namespace pqxx;
using namespace Wt;
using namespace CoreLib;
using namespace Service;

struct Cms::Impl : public Wt::WObject
{
public:
    WStackedWidget *Contents;
    WText *LastSelectedMenuItem;

    SysMon *SystemMonitor;

public:
    Impl();
    ~Impl();

public:
    void OnMenuItemPressed(WText *sender);

public:
    void ValidateSession();

private:
    void ForceExit();
};

Cms::Cms()
    : Page(),
    m_pimpl(make_unique<Cms::Impl>())
{
    WApplication *app = WApplication::instance();
    app->setTitle(tr("cms-page-title"));

    this->clear();
    this->setId("CmsPage");
    this->setStyleClass("cms-page container-fluid");
    this->addWidget(this->Layout());

    app->root()->clear();
    app->root()->addWidget(this);

    WTimer *timer = new WTimer(this);
    timer->setInterval(60000);       // every one minute
    timer->timeout().connect(m_pimpl.get(), &Cms::Impl::ValidateSession);
    timer->start();
}

Cms::~Cms() = default;

WWidget *Cms::Layout()
{
    Div *container = new Div("Cms", "cms-layout container-fluid");
    Div *noScript = new Div(container);
    noScript->addWidget(new WText(tr("no-script")));

    CgiRoot *cgiRoot = static_cast<CgiRoot *>(WApplication::instance());
    CgiEnv *cgiEnv = cgiRoot->GetCgiEnvInstance();

    string htmlData;
    string file;
    if (cgiEnv->GetInformation().Client.Language.Code
            == CgiEnv::InformationRecord::ClientRecord::LanguageCode::Fa) {
        file = "../templates/cms-fa.wtml";
    } else {
        file = "../templates/cms.wtml";
    }

    if (CoreLib::FileSystem::Read(file, htmlData)) {
        WTemplate *tmpl = new WTemplate(container);
        tmpl->setTemplateText(WString::fromUTF8(htmlData), TextFormat::XHTMLUnsafeText);

        tmpl->bindWidget("brand-title", new WText(tr("cms-page-title")));

        WText *dashboard = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard")), TextFormat::XHTMLUnsafeText);
        WText *newsletter = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-newsletter")), TextFormat::XHTMLUnsafeText);
        WText *subscribers = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-subscribers")), TextFormat::XHTMLUnsafeText);
        WText *contacts = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-contacts")), TextFormat::XHTMLUnsafeText);
        WText *settings = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-settings")), TextFormat::XHTMLUnsafeText);
        WText *changeEmail = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-admin-change-email")), TextFormat::XHTMLUnsafeText);
        WText *changePassword = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-admin-change-password")), TextFormat::XHTMLUnsafeText);
        WText *sysmon = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-system-monitor")), TextFormat::XHTMLUnsafeText);
        WText *switchLanguage = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-switch-language")), TextFormat::XHTMLUnsafeText);
        WText *exit = new WText(
                    WString("
 {1}
")
                    .arg(tr("cms-dashboard-exit")), TextFormat::XHTMLUnsafeText);

        dashboard->setId("menu-item-dashboard");
        newsletter->setId("menu-item-newsletter");
        subscribers->setId("menu-item-subscribers");
        contacts->setId("menu-item-contacts");
        settings->setId("menu-item-settings");
        changeEmail->setId("menu-item-change-email");
        changePassword->setId("menu-item-change-password");
        sysmon->setId("menu-item-system-monitor");
        switchLanguage->setId("menu-item-switch-language");
        exit->setId("menu-item-exit");

        tmpl->bindWidget("dashboard", dashboard);
        tmpl->bindWidget("newsletter", newsletter);
        tmpl->bindWidget("subscribers", subscribers);
        tmpl->bindWidget("contacts", contacts);
        tmpl->bindWidget("settings", settings);
        tmpl->bindWidget("admin", new WText(tr("cms-dashboard-admin")));
        tmpl->bindWidget("change-email", changeEmail);
        tmpl->bindWidget("change-password", changePassword);
        tmpl->bindWidget("system-monitor", sysmon);
        tmpl->bindWidget("switch-language", switchLanguage);
        tmpl->bindWidget("exit", exit);

        m_pimpl->Contents = new Wt::WStackedWidget();
        m_pimpl->Contents->addWidget(new CmsDashboard());
        m_pimpl->Contents->addWidget(new CmsNewsletter());
        m_pimpl->Contents->addWidget(new CmsSubscribers());
        m_pimpl->Contents->addWidget(new CmsContacts());
        m_pimpl->Contents->addWidget(new CmsSettings());
        m_pimpl->Contents->addWidget(new CmsChangeEmail());
        m_pimpl->Contents->addWidget(new CmsChangePassword());
        m_pimpl->SystemMonitor = new SysMon();
        m_pimpl->Contents->addWidget(m_pimpl->SystemMonitor);
        tmpl->bindWidget("stcked-widget", m_pimpl->Contents);

        WSignalMapper<WText *, WStackedWidget *> *menuItemSignalMapper = new WSignalMapper<WText *, WStackedWidget *>(m_pimpl.get());
        menuItemSignalMapper->mapped().connect(m_pimpl.get(), &Cms::Impl::OnMenuItemPressed);
        menuItemSignalMapper->mapConnect(dashboard->clicked(), dashboard);
        menuItemSignalMapper->mapConnect(newsletter->clicked(), newsletter);
        menuItemSignalMapper->mapConnect(subscribers->clicked(), subscribers);
        menuItemSignalMapper->mapConnect(contacts->clicked(), contacts);
        menuItemSignalMapper->mapConnect(settings->clicked(), settings);
        menuItemSignalMapper->mapConnect(changeEmail->clicked(), changeEmail);
        menuItemSignalMapper->mapConnect(changePassword->clicked(), changePassword);
        menuItemSignalMapper->mapConnect(sysmon->clicked(), sysmon);
        menuItemSignalMapper->mapConnect(switchLanguage->clicked(), switchLanguage);
        menuItemSignalMapper->mapConnect(exit->clicked(), exit);
    }

    return container;
}

Cms::Impl::Impl()
    : LastSelectedMenuItem(nullptr)
{

}

Cms::Impl::~Impl() = default;

void Cms::Impl::OnMenuItemPressed(WText *sender)
{
    if (LastSelectedMenuItem != nullptr) {
        LastSelectedMenuItem->removeStyleClass("selected");
    }
    LastSelectedMenuItem = sender;
    LastSelectedMenuItem->setStyleClass("selected");

    if (sender->id() == "menu-item-system-monitor") {
        SystemMonitor->Resume();
        Contents->setCurrentIndex(7);
        return;
    } else {
        SystemMonitor->Pause();
    }

    CgiRoot *cgiRoot = static_cast<CgiRoot *>(WApplication::instance());
    CgiEnv *cgiEnv = cgiRoot->GetCgiEnvInstance();

    if (sender->id() == "menu-item-dashboard") {
        Contents->setCurrentIndex(0);
    } else if (sender->id() == "menu-item-newsletter") {
        Contents->setCurrentIndex(1);
    } else if (sender->id() == "menu-item-subscribers") {
        Contents->setCurrentIndex(2);
    } else if (sender->id() == "menu-item-contacts") {
        Contents->setCurrentIndex(3);
    } else if (sender->id() == "menu-item-settings") {
        Contents->setCurrentIndex(4);
    } else if (sender->id() == "menu-item-change-email") {
        Contents->setCurrentIndex(5);
    } else if (sender->id() == "menu-item-change-password") {
        Contents->setCurrentIndex(6);
    } else if (sender->id() == "menu-item-switch-language") {
        switch (cgiEnv->GetInformation().Client.Language.Code) {
        case CgiEnv::InformationRecord::ClientRecord::LanguageCode::None:
        case CgiEnv::InformationRecord::ClientRecord::LanguageCode::Invalid:
            break;
        case CgiEnv::InformationRecord::ClientRecord::LanguageCode::En:
            cgiRoot->redirect("/?root&lang=fa");
            break;
        case CgiEnv::InformationRecord::ClientRecord::LanguageCode::Fa:
            cgiRoot->redirect("/?root&lang=en");
            break;
        }
        return;
    } else if (sender->id() == "menu-item-exit") {
        ForceExit();
        return;
    } else {
        LOG_WARNING("ERROR: INVALID STACKED-WIDGET PAGE ID!");
    }
}

void Cms::Impl::ValidateSession()
{
    CgiRoot *cgiRoot = static_cast<CgiRoot *>(WApplication::instance());
    CgiEnv *cgiEnv = cgiRoot->GetCgiEnvInstance();

    try {
        auto conn = Pool::Database().Connection();
        conn->activate();
        pqxx::work txn(*conn.get());

        string query((boost::format("SELECT EXTRACT ( EPOCH FROM expiry::TIMESTAMPTZ ) as expiry FROM \"%1%\""
                                    " WHERE token = %2%;")
                      % txn.esc(Service::Pool::Database().GetTableName("ROOT_SESSIONS"))
                      % txn.quote(cgiEnv->GetInformation().Client.Session.Token)).str());
        LOG_INFO("Running query...", query, cgiEnv->GetInformation().ToJson());

        result r = txn.exec(query);

        string expiry("0");
        if (!r.empty()) {
            const result::tuple row(r[0]);
            expiry = row["expiry"].c_str();
        }

        time_t rawTime = lexical_cast<time_t>(expiry);
        CDate::Now n(CDate::Timezone::UTC);

        /// 0 means force exit the current session
        /// if you don't know why,
        /// see Service::RootLogin::PreserveSessionData method
        if (rawTime == 0) {
            ForceExit();
        }
    }

    catch (const pqxx::sql_error &ex) {
        LOG_ERROR(ex.what(), ex.query(), cgiEnv->GetInformation().ToJson());
    }

    catch (const boost::exception &ex) {
        LOG_ERROR(boost::diagnostic_information(ex), cgiEnv->GetInformation().ToJson());
    }

    catch (const std::exception &ex) {
        LOG_ERROR(ex.what(), cgiEnv->GetInformation().ToJson());
    }

    catch (...) {
        LOG_ERROR(UNKNOWN_ERROR, cgiEnv->GetInformation().ToJson());
    }
}

void Cms::Impl::ForceExit()
{
    CgiRoot *cgiRoot = static_cast<CgiRoot *>(WApplication::instance());
    CgiEnv *cgiEnv = cgiRoot->GetCgiEnvInstance();

    srand(static_cast<unsigned int>(System::RandSeed()));
    try {
        cgiRoot->removeCookie("cms-session-token");
        LOG_INFO("Root logout succeed!", cgiEnv->GetInformation().ToJson());
    } catch(...) {
        LOG_ERROR("Root logout failed!", cgiEnv->GetInformation().ToJson());
    }
    cgiRoot->Exit("/?root&logout");
}
<|endoftext|>


<commit_before>// ==========  This file is under  LGPL, the GNU Lesser General Public Licence
// ==========  Dialing Posmorphological Module (www.aot.ru)
// ==========  Copyright by Dmitry Pankratov, Alexey Sokirko (1999-2002)

#include "MAPostMain.h"
#include "../common/PlmLine.h"

typedef CSmallVector<CHomonym*,5> SmallHomonymsVec;
typedef CSmallVector<CLineIter,5> SmallWordsVec;

enum FioItemTypeEnum {fiName, fiSurname, fiMiddle,fiAbbr,fiStop,fiRoman,fiProbName, fiString, fiOrdinal};
struct CFIOItem {
	FioItemTypeEnum m_fiType;
	string			m_ItemStr;
};

struct CFIOFormat
{
	string m_FormatStr;
	bool   m_GleicheCase;
	vector<CFIOItem> m_Items;
	CFIOFormat () {};

	CFIOFormat (string  FormatStr, bool GleicheCase)
	{
		m_FormatStr = FormatStr;
		m_GleicheCase = GleicheCase;
		StringTokenizer tok(FormatStr.c_str(), " ");
		while (tok())
		{
			string s = tok.val();
			FioItemTypeEnum t;
		    if  (s == "")
				t = fiName;
			else
			if (s == "")
				t = fiSurname;
			else
			if (s == "")
				t = fiMiddle;
		    else
			if (s == "")
				t = fiAbbr;
			else
			if (s == "")
				t = fiStop;
		    else
			if (s == "_")
				t = fiRoman;
			else
		 	if (s == "?")
				t = fiProbName;
			else
		 	if (s == "_")
				t = fiOrdinal;
		    else
			{
		       t = fiString;
			};
			CFIOItem I;
			I.m_fiType = t;
			I.m_ItemStr = s;
			m_Items.push_back(I);
		};
	};
};

static void GetCommonVariants(const vector<SmallHomonymsVec>&  Parents,
									   SmallHomonymsVec&   V,
							  vector<SmallHomonymsVec>&  Variants,
								long       Position)
{
    if (Variants.size() > 1000) return;
	if (Position < V.size())
	for (long i=0; i< Parents[Position].size(); i++)
	{
			V.m_Items[Position] = Parents[Position].m_Items[i];
			GetCommonVariants(Parents, V, Variants, Position+1);
	}
	else
			Variants.push_back(V);

};

static bool IsPartFio(const CMAPost& C, const CFIOItem& I, const CPostLemWord& Word, const CHomonym* pH)
{

    if ( Word.HasDes(OFAM1) || Word.HasDes(OFAM2)  )    return false;

    switch (I.m_fiType)
    {
        case fiName     :  return pH->HasGrammem(rName);
        case fiSurname  :  return pH->HasGrammem(rSurName);
        case fiMiddle   :  return pH->HasGrammem(rPatronymic);

        case fiAbbr     :  return      Word.m_bFirstUpperAlpha
                                    && (Word.m_strWord.size() == 1)
                                    && Word.HasDes(ORLE);

        case fiStop     :  return Word.m_strWord == ".";
        case fiRoman    :  return Word.HasDes(ORoman);
		case fiOrdinal  :  return pH->HasPos(NUMERAL_P) && (unsigned char)Word.m_strWord[0]<224; // " "  " "
        case fiProbName :  return Word.HasDes(ONam);
        default         :  return pH->m_strLemma == I.m_ItemStr;
    }

};

bool IsPartOfNonSingleOborot(const CHomonym* pH)
{
    return pH->m_bOborot1 != pH->m_bOborot2;
}

static bool SetFioFormat (CMAPost& C, const CFIOFormat& Format, CLineIter it)
{
   vector<SmallHomonymsVec> Hypots;
   SmallWordsVec FioWords;

   Hypots.resize(Format.m_Items.size());

   int CountOfVariants = 1;
   for (long ItemNo = 0; ItemNo < Format.m_Items.size() && it != C.m_Words.end(); ItemNo++, it=C.NextNotSpace(it))
   {
        FioWords.Add(it);
		CPostLemWord& W = *it;

        for (int HomNo=0; HomNo < W.GetHomonymsCount();  HomNo++)
		{
            CHomonym* pH = W.GetHomonym(HomNo);
			//  ".. "
            if (IsPartOfNonSingleOborot(pH)) return false;

			if (IsPartFio(C, Format.m_Items[ItemNo], W, pH))
					Hypots[ItemNo].Add(pH);
		};
        if ( Hypots[ItemNo].empty() ) return false;
		CountOfVariants *= Hypots[ItemNo].size();
   };

   if (FioWords.size() != Format.m_Items.size()) return false; //

   SmallHomonymsVec V; //
   vector<SmallHomonymsVec> Variants;
   Variants.reserve(CountOfVariants);
   V.m_ItemsCount = Hypots.size();
   GetCommonVariants(Hypots, V, Variants, 0);

   if (Format.m_GleicheCase)
	for (long VarNo=0; VarNo < Variants.size(); VarNo++)
	{
		QWORD Grammems = rAllCases | rAllNumbers;
		for (long i=0; i < Variants[VarNo].size(); i++)
		{
            Grammems  &=   Variants[VarNo].m_Items[i]->m_iGrammems;
		};
		if ((Grammems & rAllCases) == 0 || (Grammems & rAllNumbers) == 0 )
		{
			Variants.erase(Variants.begin()+VarNo);
			VarNo--;
		};

	};

   if (Variants.empty()) return false;

   for (size_t i=0; i <FioWords.size(); i++)
   {
       CPostLemWord& W = *FioWords[i];
       CHomonym* pH = Variants[0].m_Items[i];
       W.SetHomonymsDel(true);
       pH->m_bDelete = false;
       pH->DeleteOborotMarks(); //    (    )
       W.DeleteMarkedHomonymsBeforeClauses();
       if (W.HasDes(OSentEnd))
       {
           /*
                 ,
	          ,    SENT_END    .
           */
           W.DelDes(OSentEnd);
           FioWords.back()->AddDes(OSentEnd);
       }
   }

   //  .
   FioWords[0]->AddDes(OFAM1);
   FioWords.back()->AddDes(OFAM2);

   return true;

};

/*
  ,
  ,
 ,
 ,
 ,
  ,
  II
.
*/

void CMAPost::Rule_Fio()
{
    vector<CFIOFormat> FioFormats;
    FioFormats.push_back(CFIOFormat("  ", true));
    FioFormats.push_back(CFIOFormat("  ", true));
    FioFormats.push_back(CFIOFormat(" ", true));
    FioFormats.push_back(CFIOFormat(" ", true));
    FioFormats.push_back(CFIOFormat(" ", true));
    FioFormats.push_back(CFIOFormat("  ?", false)); //
    FioFormats.push_back(CFIOFormat("    ", false)); // .
    FioFormats.push_back(CFIOFormat("  ", false)); // .
    FioFormats.push_back(CFIOFormat("  _", false)); //   II
    FioFormats.push_back(CFIOFormat(" _", false)); //  II
    FioFormats.push_back(CFIOFormat(" _", true)); //
    FioFormats.push_back(CFIOFormat("  _", false)); //
    FioFormats.push_back(CFIOFormat(" ", false)); //

    for (CLineIter it=m_Words.begin(); it !=  m_Words.end(); it++)
    {
        for (long FormatNo=0; FormatNo < FioFormats.size(); FormatNo++)
            if (SetFioFormat (*this, FioFormats[FormatNo], it))
            {
				SetFioFormat (*this, FioFormats[FormatNo], it);
                break;
            }
    };

};

<commit_msg>Fix for numbers in FIO <commit_after>// ==========  This file is under  LGPL, the GNU Lesser General Public Licence
// ==========  Dialing Posmorphological Module (www.aot.ru)
// ==========  Copyright by Dmitry Pankratov, Alexey Sokirko (1999-2002)

#include "MAPostMain.h"
#include "../common/PlmLine.h"

typedef CSmallVector<CHomonym*,5> SmallHomonymsVec;
typedef CSmallVector<CLineIter,5> SmallWordsVec;

enum FioItemTypeEnum {fiName, fiSurname, fiMiddle,fiAbbr,fiStop,fiRoman,fiProbName, fiString, fiOrdinal};
struct CFIOItem {
	FioItemTypeEnum m_fiType;
	string			m_ItemStr;
};

struct CFIOFormat
{
	string m_FormatStr;
	bool   m_GleicheCase;
	vector<CFIOItem> m_Items;
	CFIOFormat () {};

	CFIOFormat (string  FormatStr, bool GleicheCase)
	{
		m_FormatStr = FormatStr;
		m_GleicheCase = GleicheCase;
		StringTokenizer tok(FormatStr.c_str(), " ");
		while (tok())
		{
			string s = tok.val();
			FioItemTypeEnum t;
		    if  (s == "ИМЯ")
				t = fiName;
			else
			if (s == "ФАМИЛИЯ")
				t = fiSurname;
			else
			if (s == "ОТЧЕСТВО")
				t = fiMiddle;
		    else
			if (s == "ИНИЦИАЛ")
				t = fiAbbr;
			else
			if (s == "ТОЧКА")
				t = fiStop;
		    else
			if (s == "РИМСК_ЦК")
				t = fiRoman;
			else
		 	if (s == "ИМ?")
				t = fiProbName;
			else
		 	if (s == "ПОРЯД_ЧИСЛ")
				t = fiOrdinal;
		    else
			{
		       t = fiString;
			};
			CFIOItem I;
			I.m_fiType = t;
			I.m_ItemStr = s;
			m_Items.push_back(I);
		};
	};
};

static void GetCommonVariants(const vector<SmallHomonymsVec>&  Parents,
									   SmallHomonymsVec&   V,
							  vector<SmallHomonymsVec>&  Variants,
								long       Position)
{
    if (Variants.size() > 1000) return;
	if (Position < V.size())
	for (long i=0; i< Parents[Position].size(); i++)
	{
			V.m_Items[Position] = Parents[Position].m_Items[i];
			GetCommonVariants(Parents, V, Variants, Position+1);
	}
	else
			Variants.push_back(V);

};

static bool IsPartFio(const CMAPost& C, const CFIOItem& I, const CPostLemWord& Word, const CHomonym* pH)
{

    if ( Word.HasDes(OFAM1) || Word.HasDes(OFAM2)  )    return false;

    switch (I.m_fiType)
    {
        case fiName     :  return pH->HasGrammem(rName);
        case fiSurname  :  return pH->HasGrammem(rSurName);
        case fiMiddle   :  return pH->HasGrammem(rPatronymic);

        case fiAbbr     :  return      Word.m_bFirstUpperAlpha
                                    && (Word.m_strWord.size() == 1)
                                    && Word.HasDes(ORLE);

        case fiStop     :  return Word.m_strWord == ".";
        case fiRoman    :  return Word.HasDes(ORoman);
		case fiOrdinal  :  return pH->HasPos(NUMERAL_P) && (unsigned char)Word.m_strWord[0]<224; //отбрасываем "Петр первый" вместо "Петр Первый"
        case fiProbName :  return Word.HasDes(ONam);
        default         :  return pH->m_strLemma == I.m_ItemStr;
    }

};

bool IsPartOfNonSingleOborot(const CHomonym* pH)
{
    return pH->m_bOborot1 != pH->m_bOborot2;
}

static bool SetFioFormat (CMAPost& C, const CFIOFormat& Format, CLineIter it)
{
   vector<SmallHomonymsVec> Hypots;
   SmallWordsVec FioWords;

   Hypots.resize(Format.m_Items.size());

   int CountOfVariants = 1;
   for (long ItemNo = 0; ItemNo < Format.m_Items.size() && it != C.m_Words.end(); ItemNo++, it=C.NextNotSpace(it))
   {
        FioWords.Add(it);
		CPostLemWord& W = *it;

        for (int HomNo=0; HomNo < W.GetHomonymsCount();  HomNo++)
		{
            CHomonym* pH = W.GetHomonym(HomNo);
			// иначе "Т.Е. ОТКАЗАТЬСЯ" будет ФИО
            if (IsPartOfNonSingleOborot(pH)) return false;

			if (IsPartFio(C, Format.m_Items[ItemNo], W, pH))
					Hypots[ItemNo].Add(pH);
		};
        if ( Hypots[ItemNo].empty() ) return false;
		CountOfVariants *= Hypots[ItemNo].size();
   };

   if (FioWords.size() != Format.m_Items.size()) return false; // не достроилось

   SmallHomonymsVec V; // текущий вариант
   vector<SmallHomonymsVec> Variants;
   Variants.reserve(CountOfVariants);
   V.m_ItemsCount = Hypots.size();
   GetCommonVariants(Hypots, V, Variants, 0);

   if (Format.m_GleicheCase)
	for (long VarNo=0; VarNo < Variants.size(); VarNo++)
	{
		QWORD Grammems = rAllCases | rAllNumbers;
		for (long i=0; i < Variants[VarNo].size(); i++)
		{
            Grammems  &=   Variants[VarNo].m_Items[i]->m_iGrammems;
		};
		if ((Grammems & rAllCases) == 0 || (Grammems & rAllNumbers) == 0 )
		{
			Variants.erase(Variants.begin()+VarNo);
			VarNo--;
		};

	};

   if (Variants.empty()) return false;

   for (size_t i=0; i <FioWords.size(); i++)
   {
       CPostLemWord& W = *FioWords[i];
       CHomonym* pH = Variants[0].m_Items[i];
       W.SetHomonymsDel(true);
       pH->m_bDelete = false;
       pH->DeleteOborotMarks(); // удаляем однословные оборотыб (многословных там быть не может)
       W.DeleteMarkedHomonymsBeforeClauses();
       if (W.HasDes(OSentEnd))
       {
           /*
             если ФИО содержало конец предложения, а после ФИО нет ни одной пометы
	         конца предложения, тогда надо поставить SENT_END на последнюю строчку ФИО.
           */
           W.DelDes(OSentEnd);
           FioWords.back()->AddDes(OSentEnd);
       }
   }

   // ставим графем. пометы
   FioWords[0]->AddDes(OFAM1);
   FioWords.back()->AddDes(OFAM2);

   return true;

};

/*
Петр Владимирович Иванов,
Иванов Петр Владимирович,
Петр Иванов,
Иванов Петр,
Петр Владимирович,
Райнер Мария Рильке,
Иоанн Павел II
М.Горбачев
*/

void CMAPost::Rule_Fio()
{
    vector<CFIOFormat> FioFormats;
    FioFormats.push_back(CFIOFormat("ИМЯ ОТЧЕСТВО ФАМИЛИЯ", true));
    FioFormats.push_back(CFIOFormat("ФАМИЛИЯ ИМЯ ОТЧЕСТВО", true));
    FioFormats.push_back(CFIOFormat("ИМЯ ФАМИЛИЯ", true));
    FioFormats.push_back(CFIOFormat("ФАМИЛИЯ ИМЯ", true));
    FioFormats.push_back(CFIOFormat("ИМЯ ОТЧЕСТВО", true));
    FioFormats.push_back(CFIOFormat("ИМЯ ИМЯ ИМ?", false)); // Эрих Мария Ремарк
    FioFormats.push_back(CFIOFormat("ИНИЦИАЛ ТОЧКА ИНИЦИАЛ ТОЧКА ФАМИЛИЯ", false)); // М.Горбачев
    FioFormats.push_back(CFIOFormat("ИНИЦИАЛ ТОЧКА ФАМИЛИЯ", false)); // М.Горбачев
    FioFormats.push_back(CFIOFormat("ИМЯ ИМЯ РИМСК_ЦК", false)); // Иоанн Павел II
    FioFormats.push_back(CFIOFormat("ИМЯ РИМСК_ЦК", false)); // Александр II
    FioFormats.push_back(CFIOFormat("ИМЯ ПОРЯД_ЧИСЛ", true)); // Александр Второй
    FioFormats.push_back(CFIOFormat("ИМЯ ИМЯ ПОРЯД_ЧИСЛ", false)); // Иоанн Павел Второй
    FioFormats.push_back(CFIOFormat("ДОН ЖУАН", false)); // Дон Жуан

    for (CLineIter it=m_Words.begin(); it !=  m_Words.end(); it++)
    {
        for (long FormatNo=0; FormatNo < FioFormats.size(); FormatNo++)
            if (SetFioFormat (*this, FioFormats[FormatNo], it))
            {
				SetFioFormat (*this, FioFormats[FormatNo], it);
                break;
            }
    };

};

<|endoftext|>


<commit_before>// @(#)root/hist:$Name:  $:$Id: THLimitsFinder.cxx,v 1.1 2002/01/15 10:22:27 brun Exp $
// Author: Rene Brun   14/01/2002
/*************************************************************************
 * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

//////////////////////////////////////////////////////////////////////////
//                                                                      //
// THLimitsFinder                                                       //
//                                                                      //
// Class to compute nice axis limits.
//
// This class is called by default by the histograming system
// and also by TTree::Draw, TTreePlayer::DrawSelect.
//
// A different finder may be specified via THLimitsFinder::SetFinder.
//
//////////////////////////////////////////////////////////////////////////

#include "TH1.h"
#include "THLimitsFinder.h"

THLimitsFinder *THLimitsFinder::fgLimitsFinder = 0;

ClassImp(THLimitsFinder)

//______________________________________________________________________________
THLimitsFinder::THLimitsFinder()
{
}

//______________________________________________________________________________
THLimitsFinder::~THLimitsFinder()
{
}

//______________________________________________________________________________
Int_t THLimitsFinder::FindGoodLimits(TH1 *h, Axis_t xmin, Axis_t xmax)
{
// compute the best axis limits for the X axis.
// If the bit kIsInteger is set, the number of channels is also recomputed.
// The axis parameters are replaced by the optimized parameters

   Int_t newbins;

   if (xmin >= xmax) {xmin -= 1; xmax += 1;}

   TAxis *xaxis = h->GetXaxis();

   THLimitsFinder::OptimizeLimits(xaxis->GetNbins(),
                                  newbins,xmin,xmax,
                                  xaxis->TestBit(TAxis::kIsInteger));

   h->SetBins(newbins,xmin,xmax);

   return 0;
}

//______________________________________________________________________________
Int_t THLimitsFinder::FindGoodLimits(TH1 *h, Axis_t xmin, Axis_t xmax, Axis_t ymin, Axis_t ymax)
{
// compute the best axis limits for the X and Y axis.
// If the bit kIsInteger is set, the number of channels is also recomputed.
// The axis parameters are replaced by the optimized parameters

   Int_t newbinsx,newbinsy;

   if (xmin >= xmax) {xmin -= 1; xmax += 1;}
   if (ymin >= ymax) {ymin -= 1; ymax += 1;}

   TAxis *xaxis = h->GetXaxis();

   THLimitsFinder::OptimizeLimits(xaxis->GetNbins(),
                                  newbinsx,xmin,xmax,
                                  xaxis->TestBit(TAxis::kIsInteger));
   TAxis *yaxis = h->GetYaxis();

   THLimitsFinder::OptimizeLimits(yaxis->GetNbins(),
                                  newbinsy,ymin,ymax,
                                  yaxis->TestBit(TAxis::kIsInteger));

   h->SetBins(newbinsx,xmin,xmax,newbinsy,ymin,ymax);
   return 0;
}

//______________________________________________________________________________
Int_t THLimitsFinder::FindGoodLimits(TH1 *h, Axis_t xmin, Axis_t xmax, Axis_t ymin, Axis_t ymax, Axis_t zmin, Axis_t zmax)
{
// compute the best axis limits for the X, Y and Z axis.
// If the bit kIsInteger is set, the number of channels is also recomputed.
// The axis parameters are replaced by the optimized parameters

   Int_t newbinsx,newbinsy,newbinsz;

   if (xmin >= xmax) {xmin -= 1; xmax += 1;}
   if (ymin >= ymax) {ymin -= 1; ymax += 1;}
   if (zmin >= zmax) {zmin -= 1; zmax += 1;}

   TAxis *xaxis = h->GetXaxis();

   THLimitsFinder::OptimizeLimits(xaxis->GetNbins(),
                                  newbinsx,xmin,xmax,
                                  xaxis->TestBit(TAxis::kIsInteger));
   TAxis *yaxis = h->GetYaxis();

   THLimitsFinder::OptimizeLimits(yaxis->GetNbins(),
                                  newbinsy,ymin,ymax,
                                  yaxis->TestBit(TAxis::kIsInteger));

   TAxis *zaxis = h->GetZaxis();

   THLimitsFinder::OptimizeLimits(zaxis->GetNbins(),
                                  newbinsz,zmin,zmax,
                                  zaxis->TestBit(TAxis::kIsInteger));

   h->SetBins(newbinsx,xmin,xmax,newbinsy,ymin,ymax,newbinsz,zmin,zmax);
   return 0;
}

//______________________________________________________________________________
THLimitsFinder *THLimitsFinder::GetLimitsFinder()
{
// Return pointer to the current finder.
// Create one if none exists
// Use SetLimitsFinder to set a user defined finder.

   if (!fgLimitsFinder) fgLimitsFinder = new THLimitsFinder();
   return fgLimitsFinder;
}

//______________________________________________________________________________
void THLimitsFinder::SetLimitsFinder(THLimitsFinder *finder)
{
// This static function can be used to specify a finder derived from THLimitsFinder.
// The finder may redefine the functions FindGoodLimits.
// Note that the redefined functions may call THLimitsFinder::FindGoodLimits.

   fgLimitsFinder = finder;
}

//______________________________________________________________________________
void THLimitsFinder::Optimize(Double_t A1,  Double_t A2,  Int_t nold ,Double_t &BinLow, Double_t &BinHigh,
                      Int_t &nbins, Double_t &BinWidth, Option_t *option)
{
// static function to compute reasonable axis limits
//
// Input parameters:
//
//  A1,A2 : Old WMIN,WMAX .
//  BinLow,BinHigh : New WMIN,WMAX .
//  nold   : Old NDIV .
//  nbins    : New NDIV .

   Int_t lwid, kwid;
   Int_t ntemp = 0;
   Int_t jlog  = 0;
   Double_t siground = 0;
   Double_t alb, awidth, sigfig;
   Double_t timemulti = 1;
   Int_t roundmode =0;

   Int_t OptionTime;
   if(strchr(option,'t')) OptionTime = 1;  else OptionTime = 0;

   Double_t AL = TMath::Min(A1,A2);
   Double_t AH = TMath::Max(A1,A2);
   if (AL == AH) AH = AL+1;
   // if nold  ==  -1 , program uses binwidth input from calling routine
   if (nold == -1 && BinWidth > 0 ) goto L90;
   ntemp = TMath::Max(nold,2);
   if (ntemp < 1) ntemp = 1;

L20:
   awidth = (AH-AL)/Double_t(ntemp);
   timemulti = 1;
   if (awidth >= FLT_MAX) goto LOK;  //in float.h
   if (awidth <= 0)       goto LOK;

//      If time representation, bin width should be rounded to seconds
//      minutes, hours or days

   if (OptionTime && awidth>=60) { // if width in seconds, treat it as normal
      //   width in minutes
      awidth /= 60; timemulti *=60;
      roundmode = 1; // round minutes (60)
      //   width in hours ?
      if (awidth>=60) {
         awidth /= 60; timemulti *= 60;
         roundmode = 2; // round hours (24)
         //   width in days ?
         if (awidth>=24) {
            awidth /= 24; timemulti *= 24;
            roundmode = 3; // round days (30)
            //   width in months ?
            if (awidth>=30.43685) { // Mean month length in 1900.
               awidth /= 30.43685; timemulti *= 30.43685;
               roundmode = 2; // round months (12)
               //   width in years ?
               if (awidth>=12) {
                  awidth /= 12; timemulti *= 12;
                  roundmode = 0; // round years (10)
               }
            }
         }
      }
   }
//      Get nominal bin width in exponential form

   jlog   = Int_t(TMath::Log10(awidth));
   if (jlog <-200 || jlog > 200) {
      BinLow   = 0;
      BinHigh  = 1;
      BinWidth = 0.01;
      nbins    = 100;
      return;
   }
   if (awidth <= 1 && (!OptionTime || timemulti==1) ) jlog--;
   sigfig = awidth*TMath::Power(10,-jlog);

//      Round mantissa

   switch (roundmode) {

//      Round mantissa up to 1, 1.5, 2, 3, or 6 in case of minutes
      case 1: // case 60
         if      (sigfig <= 1)    siground = 1;
         else if (sigfig <= 1.5 && jlog==1)    siground = 1.5;
         else if (sigfig <= 2)    siground = 2;
         else if (sigfig <= 3 && jlog ==1)    siground = 3;
         else if (jlog==0)        {siground = 1; jlog++;}
         else                     siground = 6;
         break;
      case 2: // case 12 and 24

//      Round mantissa up to 1, 1.2, 2, 2.4, 3 or 6 in case of hours or months
         if      (sigfig <= 1 && jlog==0)    siground = 1;
         else if (sigfig <= 1.2 && jlog==1)    siground = 1.2;
         else if (sigfig <= 2 && jlog==0)    siground = 2;
         else if (sigfig <= 2.4 && jlog==1)    siground = 2.4;
         else if (sigfig <= 3)    siground = 3;
         else if (sigfig <= 6)    siground = 6;
         else if (jlog==0)        siground = 12;
         else                     siground = 2.4;
         break;

//-      Round mantissa up to 1, 1.4, 2, or 7 in case of days (weeks)
      case 3: // case 30
         if      (sigfig <= 1 && jlog==0)    siground = 1;
         else if (sigfig <= 1.4 && jlog==1)    siground = 1.4;
         else if (sigfig <= 3 && jlog ==1)    siground = 3;
         else                     siground = 7;
         break;
      default :

//      Round mantissa up to 1, 2, 2.5, 5, or 10 in case of decimal number
         if      (sigfig <= 1)    siground = 1;
         else if (sigfig <= 2)    siground = 2;
         else if (sigfig <= 5 && (!OptionTime || jlog<1))  siground = 5;
         else if (sigfig <= 6 && OptionTime && jlog==1)    siground = 6;
         else                    {siground = 1;   jlog++; }
         break;
   }

   BinWidth = siground*TMath::Power(10,jlog);
   if (OptionTime) BinWidth *= timemulti;

//      Get new bounds from new width BinWidth

L90:
   alb  = AL/BinWidth;
   if (TMath::Abs(alb) > 1e9) {
      BinLow  = AL;
      BinHigh = AH;
      if (nbins > 10*nold && nbins > 10000) nbins = nold;
      return;
   }
   lwid   = Int_t(alb);
   if (alb < 0) lwid--;
   BinLow     = BinWidth*Double_t(lwid);
   alb        = AH/BinWidth + 1.00001;
   kwid = Int_t(alb);
   if (alb < 0) kwid--;
   BinHigh = BinWidth*Double_t(kwid);
   nbins = kwid - lwid;
   if (nold == -1) goto LOK;
   if (nold <= 5) {          //    Request for one bin is difficult case
      if (nold > 1 || nbins == 1)goto LOK;
      BinWidth = BinWidth*2;
      nbins    = 1;
      goto LOK;
   }
   if (2*nbins == nold && !OptionTime) {ntemp++; goto L20; }

LOK:
   Double_t oldBinLow = BinLow;
   Double_t oldBinHigh = BinHigh;
   Int_t oldnbins = nbins;

   Double_t atest = BinWidth*0.0001;
   if (TMath::Abs(BinLow-A1)  >= atest) { BinLow  += BinWidth;  nbins--; }
   if (TMath::Abs(BinHigh-A2) >= atest) { BinHigh -= BinWidth;  nbins--; }

   if (OptionTime && nbins==0) {
      nbins = 2*oldnbins;
      BinHigh = oldBinHigh;
      BinLow = oldBinLow;
      BinWidth = (oldBinHigh - oldBinLow)/nbins;
      Double_t atest = BinWidth*0.0001;
      if (TMath::Abs(BinLow-A1)  >= atest) { BinLow  += BinWidth;  nbins--; }
      if (TMath::Abs(BinHigh-A2) >= atest) { BinHigh -= BinWidth;  nbins--; }
   }
}

//______________________________________________________________________________
void THLimitsFinder::OptimizeLimits(Int_t nbins, Int_t &newbins, Axis_t &xmin, Axis_t &xmax, Bool_t isInteger)
{
// Optimize axis limits.
// When isInter=kTRUE, the function makes an integer binwidth
// and recompute the number of bins accordingly.

   Double_t binlow,binhigh,binwidth;
   Int_t n;
   Double_t dx = 0.1*(xmax-xmin);
   if (isInteger) dx = 5*(xmax-xmin)/nbins;
   Double_t umin = xmin - dx;
   Double_t umax = xmax + dx;
   if (umin < 0 && xmin >= 0) umin = 0;
   if (umax > 0 && xmax <= 0) umax = 0;

   THLimitsFinder::Optimize(umin,umax,nbins,binlow,binhigh,n,binwidth,"");

   if (binwidth <= 0 || binwidth > 1.e+39) {
      xmin = -1;
      xmax = 1;
   } else {
      xmin    = binlow;
      xmax    = binhigh;
   }
   if (isInteger) {
      Int_t ixmin = Int_t(xmin);
      Int_t ixmax = Int_t(xmax);
      Double_t dxmin = Double_t(ixmin);
      Double_t dxmax = Double_t(ixmax);
      if (xmin < 0 && xmin != dxmin) xmin = dxmin - 1;
      else                           xmin = dxmin;
      if (xmax > 0 && xmax != dxmax) xmax = dxmax + 1;
      else                           xmax = dxmax;
      if (xmin >= xmax) xmax = xmin+1;
      Int_t bw = Int_t((xmax-xmin)/nbins);
      if (bw == 0) bw = 1;
      nbins = Int_t((xmax-xmin)/bw);
      if (xmin +nbins*bw < umax) {nbins++; xmax = xmin +nbins*bw;}
      if (xmin > umin)           {nbins++; xmin = xmax -nbins*bw;}
   }
   newbins = nbins;
}
<commit_msg>Fix a numerical rounding problem in THLimitsFinder::Optimize affecting the axis labels optimisation in particular.<commit_after>// @(#)root/hist:$Name:  $:$Id: THLimitsFinder.cxx,v 1.2 2002/07/04 16:12:42 brun Exp $
// Author: Rene Brun   14/01/2002
/*************************************************************************
 * Copyright (C) 1995-2000, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

//////////////////////////////////////////////////////////////////////////
//                                                                      //
// THLimitsFinder                                                       //
//                                                                      //
// Class to compute nice axis limits.
//
// This class is called by default by the histograming system
// and also by TTree::Draw, TTreePlayer::DrawSelect.
//
// A different finder may be specified via THLimitsFinder::SetFinder.
//
//////////////////////////////////////////////////////////////////////////

#include "TH1.h"
#include "THLimitsFinder.h"

THLimitsFinder *THLimitsFinder::fgLimitsFinder = 0;

ClassImp(THLimitsFinder)

//______________________________________________________________________________
THLimitsFinder::THLimitsFinder()
{
}

//______________________________________________________________________________
THLimitsFinder::~THLimitsFinder()
{
}

//______________________________________________________________________________
Int_t THLimitsFinder::FindGoodLimits(TH1 *h, Axis_t xmin, Axis_t xmax)
{
// compute the best axis limits for the X axis.
// If the bit kIsInteger is set, the number of channels is also recomputed.
// The axis parameters are replaced by the optimized parameters

   Int_t newbins;

   if (xmin >= xmax) {xmin -= 1; xmax += 1;}

   TAxis *xaxis = h->GetXaxis();

   THLimitsFinder::OptimizeLimits(xaxis->GetNbins(),
                                  newbins,xmin,xmax,
                                  xaxis->TestBit(TAxis::kIsInteger));

   h->SetBins(newbins,xmin,xmax);

   return 0;
}

//______________________________________________________________________________
Int_t THLimitsFinder::FindGoodLimits(TH1 *h, Axis_t xmin, Axis_t xmax, Axis_t ymin, Axis_t ymax)
{
// compute the best axis limits for the X and Y axis.
// If the bit kIsInteger is set, the number of channels is also recomputed.
// The axis parameters are replaced by the optimized parameters

   Int_t newbinsx,newbinsy;

   if (xmin >= xmax) {xmin -= 1; xmax += 1;}
   if (ymin >= ymax) {ymin -= 1; ymax += 1;}

   TAxis *xaxis = h->GetXaxis();

   THLimitsFinder::OptimizeLimits(xaxis->GetNbins(),
                                  newbinsx,xmin,xmax,
                                  xaxis->TestBit(TAxis::kIsInteger));
   TAxis *yaxis = h->GetYaxis();

   THLimitsFinder::OptimizeLimits(yaxis->GetNbins(),
                                  newbinsy,ymin,ymax,
                                  yaxis->TestBit(TAxis::kIsInteger));

   h->SetBins(newbinsx,xmin,xmax,newbinsy,ymin,ymax);
   return 0;
}

//______________________________________________________________________________
Int_t THLimitsFinder::FindGoodLimits(TH1 *h, Axis_t xmin, Axis_t xmax, Axis_t ymin, Axis_t ymax, Axis_t zmin, Axis_t zmax)
{
// compute the best axis limits for the X, Y and Z axis.
// If the bit kIsInteger is set, the number of channels is also recomputed.
// The axis parameters are replaced by the optimized parameters

   Int_t newbinsx,newbinsy,newbinsz;

   if (xmin >= xmax) {xmin -= 1; xmax += 1;}
   if (ymin >= ymax) {ymin -= 1; ymax += 1;}
   if (zmin >= zmax) {zmin -= 1; zmax += 1;}

   TAxis *xaxis = h->GetXaxis();

   THLimitsFinder::OptimizeLimits(xaxis->GetNbins(),
                                  newbinsx,xmin,xmax,
                                  xaxis->TestBit(TAxis::kIsInteger));
   TAxis *yaxis = h->GetYaxis();

   THLimitsFinder::OptimizeLimits(yaxis->GetNbins(),
                                  newbinsy,ymin,ymax,
                                  yaxis->TestBit(TAxis::kIsInteger));

   TAxis *zaxis = h->GetZaxis();

   THLimitsFinder::OptimizeLimits(zaxis->GetNbins(),
                                  newbinsz,zmin,zmax,
                                  zaxis->TestBit(TAxis::kIsInteger));

   h->SetBins(newbinsx,xmin,xmax,newbinsy,ymin,ymax,newbinsz,zmin,zmax);
   return 0;
}

//______________________________________________________________________________
THLimitsFinder *THLimitsFinder::GetLimitsFinder()
{
// Return pointer to the current finder.
// Create one if none exists
// Use SetLimitsFinder to set a user defined finder.

   if (!fgLimitsFinder) fgLimitsFinder = new THLimitsFinder();
   return fgLimitsFinder;
}

//______________________________________________________________________________
void THLimitsFinder::SetLimitsFinder(THLimitsFinder *finder)
{
// This static function can be used to specify a finder derived from THLimitsFinder.
// The finder may redefine the functions FindGoodLimits.
// Note that the redefined functions may call THLimitsFinder::FindGoodLimits.

   fgLimitsFinder = finder;
}

//______________________________________________________________________________
void THLimitsFinder::Optimize(Double_t A1,  Double_t A2,  Int_t nold ,Double_t &BinLow, Double_t &BinHigh,
                      Int_t &nbins, Double_t &BinWidth, Option_t *option)
{
// static function to compute reasonable axis limits
//
// Input parameters:
//
//  A1,A2 : Old WMIN,WMAX .
//  BinLow,BinHigh : New WMIN,WMAX .
//  nold   : Old NDIV .
//  nbins    : New NDIV .

   Int_t lwid, kwid;
   Int_t ntemp = 0;
   Int_t jlog  = 0;
   Double_t siground = 0;
   Double_t alb, awidth, sigfig;
   Double_t timemulti = 1;
   Int_t roundmode =0;

   Int_t OptionTime;
   if(strchr(option,'t')) OptionTime = 1;  else OptionTime = 0;

   Double_t AL = TMath::Min(A1,A2);
   Double_t AH = TMath::Max(A1,A2);
   if (AL == AH) AH = AL+1;
   // if nold  ==  -1 , program uses binwidth input from calling routine
   if (nold == -1 && BinWidth > 0 ) goto L90;
   ntemp = TMath::Max(nold,2);
   if (ntemp < 1) ntemp = 1;

L20:
   awidth = (AH-AL)/Double_t(ntemp);
   timemulti = 1;
   if (awidth >= FLT_MAX) goto LOK;  //in float.h
   if (awidth <= 0)       goto LOK;

//      If time representation, bin width should be rounded to seconds
//      minutes, hours or days

   if (OptionTime && awidth>=60) { // if width in seconds, treat it as normal
      //   width in minutes
      awidth /= 60; timemulti *=60;
      roundmode = 1; // round minutes (60)
      //   width in hours ?
      if (awidth>=60) {
         awidth /= 60; timemulti *= 60;
         roundmode = 2; // round hours (24)
         //   width in days ?
         if (awidth>=24) {
            awidth /= 24; timemulti *= 24;
            roundmode = 3; // round days (30)
            //   width in months ?
            if (awidth>=30.43685) { // Mean month length in 1900.
               awidth /= 30.43685; timemulti *= 30.43685;
               roundmode = 2; // round months (12)
               //   width in years ?
               if (awidth>=12) {
                  awidth /= 12; timemulti *= 12;
                  roundmode = 0; // round years (10)
               }
            }
         }
      }
   }
//      Get nominal bin width in exponential form

   jlog   = Int_t(TMath::Log10(awidth));
   if (jlog <-200 || jlog > 200) {
      BinLow   = 0;
      BinHigh  = 1;
      BinWidth = 0.01;
      nbins    = 100;
      return;
   }
   if (awidth <= 1 && (!OptionTime || timemulti==1) ) jlog--;
   sigfig = awidth*TMath::Power(10,-jlog) -1e-10;
   //in the above statement, it is important to substract 1e-10
   //to avoid precision problems if the tests below

//      Round mantissa

   switch (roundmode) {

//      Round mantissa up to 1, 1.5, 2, 3, or 6 in case of minutes
      case 1: // case 60
         if      (sigfig <= 1)    siground = 1;
         else if (sigfig <= 1.5 && jlog==1)    siground = 1.5;
         else if (sigfig <= 2)    siground = 2;
         else if (sigfig <= 3 && jlog ==1)    siground = 3;
         else if (jlog==0)        {siground = 1; jlog++;}
         else                     siground = 6;
         break;
      case 2: // case 12 and 24

//      Round mantissa up to 1, 1.2, 2, 2.4, 3 or 6 in case of hours or months
         if      (sigfig <= 1 && jlog==0)    siground = 1;
         else if (sigfig <= 1.2 && jlog==1)    siground = 1.2;
         else if (sigfig <= 2 && jlog==0)    siground = 2;
         else if (sigfig <= 2.4 && jlog==1)    siground = 2.4;
         else if (sigfig <= 3)    siground = 3;
         else if (sigfig <= 6)    siground = 6;
         else if (jlog==0)        siground = 12;
         else                     siground = 2.4;
         break;

//-      Round mantissa up to 1, 1.4, 2, or 7 in case of days (weeks)
      case 3: // case 30
         if      (sigfig <= 1 && jlog==0)    siground = 1;
         else if (sigfig <= 1.4 && jlog==1)    siground = 1.4;
         else if (sigfig <= 3 && jlog ==1)    siground = 3;
         else                     siground = 7;
         break;
      default :

//      Round mantissa up to 1, 2, 2.5, 5, or 10 in case of decimal number
         if      (sigfig <= 1)    siground = 1;
         else if (sigfig <= 2)    siground = 2;
         else if (sigfig <= 5 && (!OptionTime || jlog<1))  siground = 5;
         else if (sigfig <= 6 && OptionTime && jlog==1)    siground = 6;
         else                    {siground = 1;   jlog++; }
         break;
   }

   BinWidth = siground*TMath::Power(10,jlog);
   if (OptionTime) BinWidth *= timemulti;

//      Get new bounds from new width BinWidth

L90:
   alb  = AL/BinWidth;
   if (TMath::Abs(alb) > 1e9) {
      BinLow  = AL;
      BinHigh = AH;
      if (nbins > 10*nold && nbins > 10000) nbins = nold;
      return;
   }
   lwid   = Int_t(alb);
   if (alb < 0) lwid--;
   BinLow     = BinWidth*Double_t(lwid);
   alb        = AH/BinWidth + 1.00001;
   kwid = Int_t(alb);
   if (alb < 0) kwid--;
   BinHigh = BinWidth*Double_t(kwid);
   nbins = kwid - lwid;
   if (nold == -1) goto LOK;
   if (nold <= 5) {          //    Request for one bin is difficult case
      if (nold > 1 || nbins == 1)goto LOK;
      BinWidth = BinWidth*2;
      nbins    = 1;
      goto LOK;
   }
   if (2*nbins == nold && !OptionTime) {ntemp++; goto L20; }

LOK:
   Double_t oldBinLow = BinLow;
   Double_t oldBinHigh = BinHigh;
   Int_t oldnbins = nbins;

   Double_t atest = BinWidth*0.0001;
   if (TMath::Abs(BinLow-A1)  >= atest) { BinLow  += BinWidth;  nbins--; }
   if (TMath::Abs(BinHigh-A2) >= atest) { BinHigh -= BinWidth;  nbins--; }

   if (OptionTime && nbins==0) {
      nbins = 2*oldnbins;
      BinHigh = oldBinHigh;
      BinLow = oldBinLow;
      BinWidth = (oldBinHigh - oldBinLow)/nbins;
      Double_t atest = BinWidth*0.0001;
      if (TMath::Abs(BinLow-A1)  >= atest) { BinLow  += BinWidth;  nbins--; }
      if (TMath::Abs(BinHigh-A2) >= atest) { BinHigh -= BinWidth;  nbins--; }
   }
}

//______________________________________________________________________________
void THLimitsFinder::OptimizeLimits(Int_t nbins, Int_t &newbins, Axis_t &xmin, Axis_t &xmax, Bool_t isInteger)
{
// Optimize axis limits.
// When isInter=kTRUE, the function makes an integer binwidth
// and recompute the number of bins accordingly.

   Double_t binlow,binhigh,binwidth;
   Int_t n;
   Double_t dx = 0.1*(xmax-xmin);
   if (isInteger) dx = 5*(xmax-xmin)/nbins;
   Double_t umin = xmin - dx;
   Double_t umax = xmax + dx;
   if (umin < 0 && xmin >= 0) umin = 0;
   if (umax > 0 && xmax <= 0) umax = 0;

   THLimitsFinder::Optimize(umin,umax,nbins,binlow,binhigh,n,binwidth,"");

   if (binwidth <= 0 || binwidth > 1.e+39) {
      xmin = -1;
      xmax = 1;
   } else {
      xmin    = binlow;
      xmax    = binhigh;
   }
   if (isInteger) {
      Int_t ixmin = Int_t(xmin);
      Int_t ixmax = Int_t(xmax);
      Double_t dxmin = Double_t(ixmin);
      Double_t dxmax = Double_t(ixmax);
      if (xmin < 0 && xmin != dxmin) xmin = dxmin - 1;
      else                           xmin = dxmin;
      if (xmax > 0 && xmax != dxmax) xmax = dxmax + 1;
      else                           xmax = dxmax;
      if (xmin >= xmax) xmax = xmin+1;
      Int_t bw = Int_t((xmax-xmin)/nbins);
      if (bw == 0) bw = 1;
      nbins = Int_t((xmax-xmin)/bw);
      if (xmin +nbins*bw < umax) {nbins++; xmax = xmin +nbins*bw;}
      if (xmin > umin)           {nbins++; xmin = xmax -nbins*bw;}
   }
   newbins = nbins;
}
<|endoftext|>


<commit_before>//
// Created by Evan Almonte on 10/29/2015.
//

#include "GoFishGame.hpp"
#include <cstdlib>
#include <ctime>

int main ( ) {
	srand (static_cast<unsigned int>(time (nullptr)));
	GoFishGame* aGame = new GoFishGame(2);
	aGame->play ( );
	return 0;
}<commit_msg>deletes the GoFishGame pointer after use as intended<commit_after>//
// Created by Evan Almonte on 10/29/2015.
//

#include "GoFishGame.hpp"
#include <cstdlib>
#include <ctime>

int main ( ) {
	srand (static_cast<unsigned int>(time (nullptr)));
	GoFishGame* aGame = new GoFishGame(2);
	aGame->play ( );
	delete aGame;
	return 0;
}<|endoftext|>


<commit_before>#ifndef CPPUT_TESTHARNESS_HPP
#define CPPUT_TESTHARNESS_HPP

///////////////////////////////////////////////////////////////////////////////
///
/// \brief    A light-weight and easy to use unit testing framework for C++
/// \details  Header-only unit testing framework that makes unit testing easy
///           and quick to set up.
/// \version  0.2.0
/// \date     December 2011
/// \author   Tommy Back
///
///
/// Simply include this header file to get started.
///
/// \code
/// #include <cpput/TestHarness.h>
/// #include <Foo.h>
///
/// TEST(Foo, some_descriptive_name)
/// {
///   // Arrange
///   Foo foo;
///   // Act
///   bool result = foo.isBar();
///   // Assert
///   ASSERT_TRUE(result);
/// }
/// \endcode
///
/// In case you want to keep a single file with tests that compile to an
/// executable you can also add the main function to the end of the file.
/// This is simple to do with the provided macro:
///
/// \code
/// DECLARE_TEST_MAIN_FUNCTION;
/// \endcode
///
/// For larger test suits, it's recommended to group the tests per class
/// in separate files and let the compiler combine them into a single
/// executable which has a main.cpp file that only has the main function
/// declared.
///
/// \example
/// [Test_Foo.cpp]
///
/// \code
/// #include <cpput/TestHarness.h>
/// #include <Foo.h>
///
/// TEST(Foo, foo_bar_z)
/// {
///   ...
/// \endcode
///
/// [main.cpp]
///
/// \code
/// #include <cpput/TestHarness.h>
///
/// DECLARE_TEST_MAIN_FUNCTION;
/// \endcode
///
///

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <sstream>
#include <ctime>

namespace cpput
{

struct Failure
{
  Failure(const std::string& filename, std::size_t line, const std::string& message)
    : filename_(filename)
    , line_(line)
    , message_(message)
  {
  }

  friend std::ostream& operator<<(std::ostream& os, const Failure& f)
  {
    os << f.filename_ << ", line " << f.line_ << ": " << f.message_;
    return os;
  }

  std::string filename_;
  std::size_t line_;
  std::string message_;
};

// ----------------------------------------------------------------------------

struct IOutputter
{
  virtual ~IOutputter() {}

  virtual void startTest(const std::string& className, const std::string& name) = 0;
  virtual void endTest(bool success) = 0;

  virtual void failure(const Failure& failure) = 0;
  virtual int getNumberOfFailures() const = 0;
};

// ----------------------------------------------------------------------------

class TextOutputter : public IOutputter
{
public:
  TextOutputter()
    : testCount_(0)
  {
  }

  virtual ~TextOutputter()
  {
    std::cout << "\n";
    if (failures_.empty())
    {
      std::cout << "All tests pass.\n";
      return;
    }
    // print details of all failures
    std::cout << "\n";
    for (std::size_t i=0; i<failures_.size(); ++i)
    {
      std::cout << failures_[i] << "\n";
    }
    std::cout << "\n" << failures_.size() << " out of " << testCount_ << " tests failed.\n";
  }

  virtual void startTest(const std::string& className, const std::string& name)
  {
    testCount_++;
  }

  virtual void endTest(bool success)
  {
    if (success)
      std::cout << '.';
    else
      std::cout << 'F';
  }

  virtual void failure(const Failure& failure)
  {
    failures_.push_back(failure);
  }

  virtual int getNumberOfFailures() const { return failures_.size(); }

private:
  int                  testCount_;
  std::vector<Failure> failures_;
};

// ----------------------------------------------------------------------------

class XmlOutputter : public IOutputter
{
public:
  XmlOutputter()
    : startTime_(0)
  {
    std::cout << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
    std::cout << "<testsuite>\n";
  }

  virtual ~XmlOutputter()
  {
    std::cout << "</testsuite>\n";
  }

  virtual void startTest(const std::string& className, const std::string& name)
  {
    startTime_ = std::clock();
    std::cout << "  <testcase classname=\""
              << className
              << "\" name=\""
              << name
              << "\" time=\"";
  }

  virtual void endTest(bool success)
  {
    if (success)
    {
      std::cout << static_cast<float>(std::clock()-startTime_)/CLOCKS_PER_SEC << "\"";
      std::cout << "/>\n";
    }
    else
      std::cout << "  </testcase>\n";
  }

  virtual void failure(const Failure& failure)
  {
    std::cout << static_cast<float>(std::clock()-startTime_)/CLOCKS_PER_SEC << "\"";
    std::cout << ">\n"
              << "    <failure>"
              << failure.message_
              << " in "
              << failure.filename_
              << ", line "
              << failure.line_
              << "</failure>\n";
    failureCount_++;
  }

  virtual int getNumberOfFailures() const
  {
    return failureCount_;
  }

private:
  std::clock_t startTime_;
  int          failureCount_;
};

// ----------------------------------------------------------------------------

struct Result
{
  Result(const std::string& testClassName,
         const std::string& testName,
         IOutputter& out)
    : out_(out)
    , pass_(true)
  {
    out_.startTest(testClassName, testName);
  }

  ~Result()
  {
    out_.endTest(pass_);
  }

  template <typename T, typename U>
  void addFailure(const char* filename,
                  std::size_t line,
                  T expected,
                  U actual)
  {
    pass_ = false;
    std::stringstream ss;
    ss << std::setprecision(20)
       << "failed comparison, expected " << expected
       << " got " << actual << "\n";
    Failure f(filename, line, ss.str());
    out_.failure(f);
  }

  void addFailure(const char* filename,
                  std::size_t line,
                  const char* message)
  {
    pass_ = false;
    Failure f(filename, line, message);
    out_.failure(f);
  }

  IOutputter& out_;
  bool        pass_;
};

// ----------------------------------------------------------------------------

class Repository;

class Case
{
  friend class Repository;

public:
  Case(const char* className, const char* name);
  virtual ~Case() {}

  void run(IOutputter& out)
  {
    Result result(test_unit_class_name_, test_unit_name_, out);
    try
    {
      do_run(result);
    }
    catch (const std::exception& e)
    {
      result.addFailure(__FILE__, __LINE__, std::string("Unexpected exception: ").append(e.what()).c_str());
    }
    catch (...)
    {
      result.addFailure(__FILE__, __LINE__, "Unspecified exception!");
    }
  }

  Case* next() { return test_unit_next_; }

private:
  virtual void do_run(Result& testResult_) = 0;

private:
  std::string test_unit_class_name_;
  std::string test_unit_name_;
  Case*       test_unit_next_;
};

// ----------------------------------------------------------------------------

class Repository
{
public:
  static Repository& instance()
  {
    static Repository repo;
    return repo;
  }

  void add(Case* tc)
  {
    if (!cases_)
    {
      cases_ = tc;
      return;
    }

    // add as last
    Case* tmp = cases_;
    while (tmp->test_unit_next_)
      tmp = tmp->test_unit_next_;
    tmp->test_unit_next_ = tc;
  }

  Case* getCases() { return cases_; }

private:
  Repository() : cases_(0) {}
  Repository(const Repository& other);
  Repository& operator=(const Repository& rhs) const;

private:
  Case* cases_;
};

inline Case::Case(const char* className, const char* name)
  : test_unit_class_name_(className)
  , test_unit_name_(name)
  , test_unit_next_(0)
{
  Repository::instance().add(this);
}

// ----------------------------------------------------------------------------

struct Runner
{
  static int run(bool xmlOutput=false)
  {
    if (xmlOutput)
    {
      XmlOutputter out;
      return do_run(out);
    }
    TextOutputter out;
    return do_run(out);
  }

private:
  static int do_run(IOutputter& out)
  {
    Case* c = Repository::instance().getCases();
    while (c)
    {
      c->run(out);
      c = c->next();
    }
    return out.getNumberOfFailures();
  }

private:
  Runner();
  Runner(const Runner& other);
  Runner& operator=(const Runner& rhs) const;
};

} // namespace cpput

// Convenience macro to get main function.
// TODO: check properly for -xmloutput
#define DECLARE_TEST_MAIN_FUNCTION \
int main(int argc, char* argv[]) { \
  return ::cpput::Runner::run(argc > 1); \
}

// ----------------------------------------------------------------------------
// Test Case Macros
// ----------------------------------------------------------------------------

/// Stand-alone test case.
///
#define TEST(group,name) \
class group##name##Test : public cpput::Case \
{ \
public: \
  group##name##Test() : cpput::Case(#group,#name) {} \
  virtual ~group##name##Test() {} \
private: \
  virtual void do_run(cpput::Result& testResult_); \
} group##name##TestInstance; \
inline void group##name##Test::do_run(cpput::Result& testResult_)

/// Test case with fixture.
///
#define TEST_F(group,name) \
class group##name##FixtureTest : public group { \
public: \
    void do_run(cpput::Result& testResult_); \
}; \
class group##name##Test : public cpput::Case { \
public: \
    group##name##Test() : Case(#group,#name) {} \
    virtual void do_run(cpput::Result& testResult_); \
} group##name##TestInstance; \
inline void group##name##Test::do_run(cpput::Result& testResult_) { \
  group##name##FixtureTest test; \
  test.do_run(testResult_); \
} \
inline void group##name##FixtureTest::do_run(cpput::Result& testResult_)

// ----------------------------------------------------------------------------
// Assertion Macros
// ----------------------------------------------------------------------------

#define ASSERT_TRUE(expression) \
{ \
  if (expression) \
    return; \
  testResult_.addFailure(__FILE__, __LINE__, #expression); \
}

#define ASSERT_FALSE(expression) ASSERT_TRUE(!(expression))

#define ASSERT_EQ(expected,actual) \
{ \
  if (!((expected) == (actual))) \
  { \
    testResult_.addFailure(__FILE__, __LINE__, expected, actual); \
    return; \
  } \
}

#define ASSERT_NEQ(expected,actual) \
{ \
  if (((expected) == (actual))) \
  { \
    testResult_.addFailure(__FILE__, __LINE__, expected, actual); \
    return; \
  } \
}

#define ASSERT_STREQ(expected,actual) { \
  if (!(std::string(expected) == std::string(actual))) \
  { \
    testResult_.addFailure(__FILE__, __LINE__, expected, actual); \
    return; \
  } \
}

#define ASSERT_NEAR(expected,actual,epsilon) \
{ \
  double actualTmp = actual; \
  double expectedTmp = expected; \
  double diff = expectedTmp - actualTmp; \
  if ((diff > epsilon) || (-diff > epsilon)) \
  { \
    testResult_.addFailure(__FILE__, __LINE__, expectedTmp, actualTmp); \
    return; \
  } \
}

#endif // CPPUT_TESTHARNESS_HPP
<commit_msg>Renamed IOutputter to ResultWriter.<commit_after>#ifndef CPPUT_TESTHARNESS_HPP
#define CPPUT_TESTHARNESS_HPP

///////////////////////////////////////////////////////////////////////////////
///
/// \brief    A light-weight and easy to use unit testing framework for C++
/// \details  Header-only unit testing framework that makes unit testing easy
///           and quick to set up.
/// \version  0.2.0
/// \date     December 2011
/// \author   Tommy Back
///
///
/// Simply include this header file to get started.
///
/// \code
/// #include <cpput/TestHarness.h>
/// #include <Foo.h>
///
/// TEST(Foo, some_descriptive_name)
/// {
///   // Arrange
///   Foo foo;
///   // Act
///   bool result = foo.isBar();
///   // Assert
///   ASSERT_TRUE(result);
/// }
/// \endcode
///
/// In case you want to keep a single file with tests that compile to an
/// executable you can also add the main function to the end of the file.
/// This is simple to do with the provided macro:
///
/// \code
/// DECLARE_TEST_MAIN_FUNCTION;
/// \endcode
///
/// For larger test suits, it's recommended to group the tests per class
/// in separate files and let the compiler combine them into a single
/// executable which has a main.cpp file that only has the main function
/// declared.
///
/// \example
/// [Test_Foo.cpp]
///
/// \code
/// #include <cpput/TestHarness.h>
/// #include <Foo.h>
///
/// TEST(Foo, foo_bar_z)
/// {
///   ...
/// \endcode
///
/// [main.cpp]
///
/// \code
/// #include <cpput/TestHarness.h>
///
/// DECLARE_TEST_MAIN_FUNCTION;
/// \endcode
///
///

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <sstream>
#include <ctime>

namespace cpput
{

struct Failure
{
  Failure(const std::string& filename, std::size_t line, const std::string& message)
    : filename_(filename)
    , line_(line)
    , message_(message)
  {
  }

  friend std::ostream& operator<<(std::ostream& os, const Failure& f)
  {
    os << f.filename_ << ", line " << f.line_ << ": " << f.message_;
    return os;
  }

  std::string filename_;
  std::size_t line_;
  std::string message_;
};

// ----------------------------------------------------------------------------

struct ResultWriter
{
  virtual ~ResultWriter() {}

  virtual void startTest(const std::string& className, const std::string& name) = 0;
  virtual void endTest(bool success) = 0;

  virtual void failure(const Failure& failure) = 0;
  virtual int getNumberOfFailures() const = 0;
};

// ----------------------------------------------------------------------------

class TextOutputter : public ResultWriter
{
public:
  TextOutputter()
    : testCount_(0)
  {
  }

  virtual ~TextOutputter()
  {
    std::cout << "\n";
    if (failures_.empty())
    {
      std::cout << "All tests pass.\n";
      return;
    }
    // print details of all failures
    std::cout << "\n";
    for (std::size_t i=0; i<failures_.size(); ++i)
    {
      std::cout << failures_[i] << "\n";
    }
    std::cout << "\n" << failures_.size() << " out of " << testCount_ << " tests failed.\n";
  }

  virtual void startTest(const std::string& className, const std::string& name)
  {
    testCount_++;
  }

  virtual void endTest(bool success)
  {
    if (success)
      std::cout << '.';
    else
      std::cout << 'F';
  }

  virtual void failure(const Failure& failure)
  {
    failures_.push_back(failure);
  }

  virtual int getNumberOfFailures() const { return failures_.size(); }

private:
  int                  testCount_;
  std::vector<Failure> failures_;
};

// ----------------------------------------------------------------------------

class XmlOutputter : public ResultWriter
{
public:
  XmlOutputter()
    : startTime_(0)
  {
    std::cout << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
    std::cout << "<testsuite>\n";
  }

  virtual ~XmlOutputter()
  {
    std::cout << "</testsuite>\n";
  }

  virtual void startTest(const std::string& className, const std::string& name)
  {
    startTime_ = std::clock();
    std::cout << "  <testcase classname=\""
              << className
              << "\" name=\""
              << name
              << "\" time=\"";
  }

  virtual void endTest(bool success)
  {
    if (success)
    {
      std::cout << static_cast<float>(std::clock()-startTime_)/CLOCKS_PER_SEC << "\"";
      std::cout << "/>\n";
    }
    else
      std::cout << "  </testcase>\n";
  }

  virtual void failure(const Failure& failure)
  {
    std::cout << static_cast<float>(std::clock()-startTime_)/CLOCKS_PER_SEC << "\"";
    std::cout << ">\n"
              << "    <failure>"
              << failure.message_
              << " in "
              << failure.filename_
              << ", line "
              << failure.line_
              << "</failure>\n";
    failureCount_++;
  }

  virtual int getNumberOfFailures() const
  {
    return failureCount_;
  }

private:
  std::clock_t startTime_;
  int          failureCount_;
};

// ----------------------------------------------------------------------------

struct Result
{
  Result(const std::string& testClassName,
         const std::string& testName,
         ResultWriter& out)
    : out_(out)
    , pass_(true)
  {
    out_.startTest(testClassName, testName);
  }

  ~Result()
  {
    out_.endTest(pass_);
  }

  template <typename T, typename U>
  void addFailure(const char* filename,
                  std::size_t line,
                  T expected,
                  U actual)
  {
    pass_ = false;
    std::stringstream ss;
    ss << std::setprecision(20)
       << "failed comparison, expected " << expected
       << " got " << actual << "\n";
    Failure f(filename, line, ss.str());
    out_.failure(f);
  }

  void addFailure(const char* filename,
                  std::size_t line,
                  const char* message)
  {
    pass_ = false;
    Failure f(filename, line, message);
    out_.failure(f);
  }

  ResultWriter& out_;
  bool        pass_;
};

// ----------------------------------------------------------------------------

class Repository;

class Case
{
  friend class Repository;

public:
  Case(const char* className, const char* name);
  virtual ~Case() {}

  void run(ResultWriter& out)
  {
    Result result(test_unit_class_name_, test_unit_name_, out);
    try
    {
      do_run(result);
    }
    catch (const std::exception& e)
    {
      result.addFailure(__FILE__, __LINE__, std::string("Unexpected exception: ").append(e.what()).c_str());
    }
    catch (...)
    {
      result.addFailure(__FILE__, __LINE__, "Unspecified exception!");
    }
  }

  Case* next() { return test_unit_next_; }

private:
  virtual void do_run(Result& testResult_) = 0;

private:
  std::string test_unit_class_name_;
  std::string test_unit_name_;
  Case*       test_unit_next_;
};

// ----------------------------------------------------------------------------

class Repository
{
public:
  static Repository& instance()
  {
    static Repository repo;
    return repo;
  }

  void add(Case* tc)
  {
    if (!cases_)
    {
      cases_ = tc;
      return;
    }

    // add as last
    Case* tmp = cases_;
    while (tmp->test_unit_next_)
      tmp = tmp->test_unit_next_;
    tmp->test_unit_next_ = tc;
  }

  Case* getCases() { return cases_; }

private:
  Repository() : cases_(0) {}
  Repository(const Repository& other);
  Repository& operator=(const Repository& rhs) const;

private:
  Case* cases_;
};

inline Case::Case(const char* className, const char* name)
  : test_unit_class_name_(className)
  , test_unit_name_(name)
  , test_unit_next_(0)
{
  Repository::instance().add(this);
}

// ----------------------------------------------------------------------------

struct Runner
{
  static int run(bool xmlOutput=false)
  {
    if (xmlOutput)
    {
      XmlOutputter out;
      return do_run(out);
    }
    TextOutputter out;
    return do_run(out);
  }

private:
  static int do_run(ResultWriter& out)
  {
    Case* c = Repository::instance().getCases();
    while (c)
    {
      c->run(out);
      c = c->next();
    }
    return out.getNumberOfFailures();
  }

private:
  Runner();
  Runner(const Runner& other);
  Runner& operator=(const Runner& rhs) const;
};

} // namespace cpput

// Convenience macro to get main function.
// TODO: check properly for -xmloutput
#define DECLARE_TEST_MAIN_FUNCTION \
int main(int argc, char* argv[]) { \
  return ::cpput::Runner::run(argc > 1); \
}

// ----------------------------------------------------------------------------
// Test Case Macros
// ----------------------------------------------------------------------------

/// Stand-alone test case.
///
#define TEST(group,name) \
class group##name##Test : public cpput::Case \
{ \
public: \
  group##name##Test() : cpput::Case(#group,#name) {} \
  virtual ~group##name##Test() {} \
private: \
  virtual void do_run(cpput::Result& testResult_); \
} group##name##TestInstance; \
inline void group##name##Test::do_run(cpput::Result& testResult_)

/// Test case with fixture.
///
#define TEST_F(group,name) \
class group##name##FixtureTest : public group { \
public: \
    void do_run(cpput::Result& testResult_); \
}; \
class group##name##Test : public cpput::Case { \
public: \
    group##name##Test() : Case(#group,#name) {} \
    virtual void do_run(cpput::Result& testResult_); \
} group##name##TestInstance; \
inline void group##name##Test::do_run(cpput::Result& testResult_) { \
  group##name##FixtureTest test; \
  test.do_run(testResult_); \
} \
inline void group##name##FixtureTest::do_run(cpput::Result& testResult_)

// ----------------------------------------------------------------------------
// Assertion Macros
// ----------------------------------------------------------------------------

#define ASSERT_TRUE(expression) \
{ \
  if (expression) \
    return; \
  testResult_.addFailure(__FILE__, __LINE__, #expression); \
}

#define ASSERT_FALSE(expression) ASSERT_TRUE(!(expression))

#define ASSERT_EQ(expected,actual) \
{ \
  if (!((expected) == (actual))) \
  { \
    testResult_.addFailure(__FILE__, __LINE__, expected, actual); \
    return; \
  } \
}

#define ASSERT_NEQ(expected,actual) \
{ \
  if (((expected) == (actual))) \
  { \
    testResult_.addFailure(__FILE__, __LINE__, expected, actual); \
    return; \
  } \
}

#define ASSERT_STREQ(expected,actual) { \
  if (!(std::string(expected) == std::string(actual))) \
  { \
    testResult_.addFailure(__FILE__, __LINE__, expected, actual); \
    return; \
  } \
}

#define ASSERT_NEAR(expected,actual,epsilon) \
{ \
  double actualTmp = actual; \
  double expectedTmp = expected; \
  double diff = expectedTmp - actualTmp; \
  if ((diff > epsilon) || (-diff > epsilon)) \
  { \
    testResult_.addFailure(__FILE__, __LINE__, expectedTmp, actualTmp); \
    return; \
  } \
}

#endif // CPPUT_TESTHARNESS_HPP
<|endoftext|>