// Main Program and Application for ALL five Exercises
// ===================================================
// What this program does is configured by enabling
// one or more of #define-s in file `config.h`
//
#include "config.h"
#if defined(TEST1) // ------------------ Unit-Testing for Exercise 1
#include <cassert>
#include <iostream>
int main() {
Clock c{23, 59, 58};
std::ostringstream expect;
// should only step up the seconds here
c.Update(expect);
assert(expect.str() == "Clock [hh:mm:ss] 23:59:59\n");
expect.str("");
// should step up the seconds which resets and in turn
// steps up the minutes which resets and in turn steps
// up the hours which resets.
// NOTE: This test will FAIL if LimitCounter::Count is
// not virtual and properly overwritten in
// OverflowCounter::Count
c.Update(expect);
assert(expect.str() == "Clock [hh:mm:ss] 00:00:00\n");
expect.str("");
// should only step up the seconds here
c.Update(expect);
assert(expect.str() == "Clock [hh:mm:ss] 00:00:01\n");
expect.str("");
// should only step up the seconds here
c.Update(expect);
assert(expect.str() == "Clock [hh:mm:ss] 00:00:02\n");
expect.str("");
c.SetHours(3);
c.SetMinutes(59);
c.SetSeconds(59);
// should reset seconds and minutes, stepping up hours
// NOTE: This one will FAIL if LimitCounter::Count uses
// plain Reset() to set value_ to zero. Correct
// implementation must use LimitCounter::Reset()
// to avoid the whole counter chain uop to hours
// is reset to zero
c.Update(expect);
assert(expect.str() == "Clock [hh:mm:ss] 04:00:00\n");
expect.str("");
c.Reset();
c.Update(expect);
// Note:
// c.Update() FIRST increments the Clock and only then
// shows "hh:mm:ss" so the expection is not "00:00:00"
assert(expect.str() == "Clock [hh:mm:ss] 00:00:01\n");
expect.str("");
std::cout << "passed all tests of clock functionality\n";
}
#elif defined(TEST2) // ---------------- Unit-Testing for Exercise 2
#include <cassert>
int main() {
try {
LimitCounter lc{0, 10};
lc.SetValue(11);
assert(!"LimitCounter exeception expected");
}
catch (LimitCounter::ValueOutOfRange &ex) {}
catch (...) {
assert(!"not the expected exception");
}
}
#elif defined(TEST3a) // -------- Unit-Testing for Exercise 3 Part 1
#include <cassert>
#include <initializer_list>
#include <type_traits>
#include "array.h"
int main() {
Array<int, 5> test_obj{0};
// -------------------------------------------------------------
assert((std::is_same<decltype(test_obj)::value_type, int>{}));
// -----------------------------------------^^^^^^^^^^----------
// --------------------------------
assert(test_obj.max_size() == 5);
// ----------------^^^^^^^^^^------
for (auto v : { 7, 14, 12, 2, 5 }) {
test_obj.Insert(v);
}
// ------------------------
assert(test_obj[0] == 7);
assert(test_obj[1] == 14);
assert(test_obj[2] == 12);
assert(test_obj[3] == 2);
assert(test_obj[4] == 5);
// ---------------^^^------
// -------------------------------------
assert(test_obj.Insert(111) == false);
// ^^^^^^ ^^^^^
test_obj.Erase(12);
assert(test_obj.Insert(111) == true);
// ----------------^^^^^^---------^^^^
// -----------------------------------------------------
assert((std::is_same<decltype(test_obj[0]), int&>{}));
// ---------------------^^^^^^^^---------^^^---^^^^------
// ----------------------------------------------------------------
auto const& const_test_obj = test_obj;
// ^^^^^
assert((std::is_same<decltype(const_test_obj[0]), int const&>{}));
// ---------------------^^^^^^^^---------------^^^---^^^^^^^^^^-----
// ------------------------
assert(test_obj[6] == 0);
test_obj[6] = 999;
assert(test_obj[6] == 0);
// ---------------!!!------
}
#elif defined(TEST3b) // -------- Unit-Testing for Exercise 3 Part 2
#include <cassert>
#include <memory>
#include <sstream>
#include "clock.h"
#include "observer_array.h"
class MockedTimer : public ObserverArray<3> {
public:
using ObserverArray<3>::ObserverArray;
void Tick(std::ostream& os) {
Notify(os);
}
};
struct ExpectStream : public std::ostringstream {
auto operator()(const char* s) {
auto result = (str() == s);
str({});
return result;
}
};
int main() {
MockedTimer test_obj{};
ExpectStream expect{};
test_obj.Notify(expect);
assert(expect(""));
auto clk1 = std::make_shared<Clock>(0, 1, 59);
auto clk2 = std::make_shared<Clock>(11, 59, 58);
test_obj.Attach(clk1);
test_obj.Notify(expect);
assert(expect("Clock [hh:mm:ss] 00:02:00\n"));
test_obj.Attach(clk2);
test_obj.Notify(expect);
assert(expect("Clock [hh:mm:ss] 00:02:01\n"
"Clock [hh:mm:ss] 11:59:59\n"));
test_obj.Detach(clk1);
test_obj.Notify(expect);
assert(expect("Clock [hh:mm:ss] 12:00:00\n"));
test_obj.Detach(clk2);
test_obj.Notify(expect);
assert(expect(""));
}
#elif defined(TEST3x)\
|| defined(TEST3y)\
|| defined(TEST3z) // -------- Unit-Testing for Exercise 3 Extras
#include <cassert>
#include <functional>
#include <iostream>
#include <memory>
#include <sstream>
#include "clock.h"
#include "observer_registry.h"
struct ExpectStream : public std::ostringstream {
auto operator()(const char* s) {
auto result = (str() == s);
str({});
return result;
}
};
#if defined(TEST3x)\
|| defined(TEST3y)
#ifdef TEST3x
class MockedTimer : public ObserverRegistry<3> {
public:
using ObserverRegistry<3>::ObserverRegistry;
void Tick(std::ostream& os) {
Notify(os);
}
};
#endif
#ifdef TEST3y
class MockedTimer : public ObserverRegistry<3, void(std::ostream&)> {
public:
using ObserverRegistry<3, void(std::ostream&)>::ObserverRegistry;
void Tick(std::ostream& os) {
Notify(os);
}
};
#endif
int main() {
MockedTimer test_obj{};
ExpectStream expect{};
test_obj.Notify(expect);
assert(expect(""));
auto clk1 = std::make_shared<Clock>(0, 1, 59);
auto clk2 = std::make_shared<Clock>(11, 59, 58);
#if 0
auto update_clk1 = [&clk1](std::ostream& arg) {
clk1->Update(arg);
}
auto clk1_id = test_obj.Attach(update_clk1);
#else
auto clk1_id = test_obj.Attach(
[&clk1](std::ostream& arg) {
clk1->Update(arg);
});
#endif
test_obj.Notify(expect);
assert(expect("Clock [hh:mm:ss] 00:02:00\n"));
#if 0
auto update_clk2 =
std::bind(&Clock::Update, std::ref(clk2), std::placeholders::_1);
auto clk2_id = test_obj.Attach(update_clk2);
#else
using namespace std::placeholders;
auto clk2_id = test_obj.Attach(std::bind(&Clock::Update, std::ref(clk2), _1));
#endif
test_obj.Notify(expect);
assert(expect("Clock [hh:mm:ss] 00:02:01\n"
"Clock [hh:mm:ss] 11:59:59\n"));
test_obj.Detach(clk1_id);
test_obj.Notify(expect);
assert(expect("Clock [hh:mm:ss] 12:00:00\n"));
test_obj.Detach(clk2_id);
test_obj.Notify(expect);
assert(expect(""));
std::cout << "Heureka, it works!\n";
}
#endif
#if defined(TEST3z)
class MockedTimer : public ObserverRegistry<3, void()> {
public:
using ObserverRegistry<3, void()>::ObserverRegistry;
void Tick(std::ostream& os) {
Notify();
}
};
int main() {
MockedTimer test_obj{};
ExpectStream expect{};
test_obj.Notify(expect);
assert(expect(""));
auto clk1 = std::make_shared<Clock>(0, 1, 59);
auto clk2 = std::make_shared<Clock>(11, 59, 58);
#if 0
auto update_clk1 = [&clk1](std::ostream& arg) {
clk1->Update(arg);
}
auto clk1_id = test_obj.Attach(update_clk1);
#else
auto clk1_id = test_obj.Attach(
[&clk1](std::ostream& arg) {
clk1->Update(arg);
});
#endif
test_obj.Notify(expect);
assert(expect("Clock [hh:mm:ss] 00:02:00\n"));
#if 0
auto update_clk2 =
std::bind(&Clock::Update, std::ref(clk2), std::placeholders::_1);
auto clk2_id = test_obj.Attach(update_clk2);
#else
using namespace std::placeholders;
auto clk2_id = test_obj.Attach(std::bind(&Clock::Update, std::ref(clk2), _1));
#endif
test_obj.Notify(expect);
assert(expect("Clock [hh:mm:ss] 00:02:01\n"
"Clock [hh:mm:ss] 11:59:59\n"));
test_obj.Detach(clk1_id);
test_obj.Notify(expect);
assert(expect("Clock [hh:mm:ss] 12:00:00\n"));
test_obj.Detach(clk2_id);
test_obj.Notify(expect);
assert(expect(""));
std::cout << "Heureka, it works!\n";
}
#endif
#elif defined(TEST4) // ---------------- Unit-Testing for Exercise 4
// ... TBD
#else // ----------------------------------- Manual Testing selected
// =============================================
// Support functions ShowValue and GetCommand
// (may go into their own compilation unit
// support.cpp with a corresponding header file
// support.h but are included directly for
// convenient use in onlineGDB)
#include <cctype>
#include <iostream>
void ShowValue() {
std::cout << std::endl;
}
template <typename T>
void ShowValue(T text)
{
std::cout << text << std::endl;
}
template <typename T, typename... Other>
void ShowValue(T text, Other... other)
{
std::cout << text << " ";
ShowValue(other...);
}
char GetCommand()
{
std::cout << "::> " << std::flush;
char cmd;
while (std::cin.get(cmd)) {
if (cmd == '\n') continue;
std::cin.ignore(99999, '\n');
if (std::isupper(cmd = std::toupper(cmd))) {
return cmd;
}
}
return 'Q';
}
// =============================================
// DEFINITION of Actual Application (usually has
// its own application.h file but is included
// directly for convenient use with onlineGDB)
#include <memory>
#include <string>
#include "clock.h"
#include "iclock_factory.h"
#include "timer.h"
class Application {
private:
IClockFactory& clock_factory_;
std::shared_ptr<Clock> clock_;
std::shared_ptr<Timer> timer_;
public:
explicit Application(IClockFactory& clock_factory)
: clock_factory_{clock_factory}
{
}
void SetClock(const std::shared_ptr<Clock>& clock) {
using namespace std::string_literals;
if (clock == nullptr) throw "no clock service available"s;
clock_ = clock;
}
void SetTimer(const std::shared_ptr<Timer>& timer) {
using namespace std::string_literals;
if (timer == nullptr) throw "no clock service available"s;
timer_ = timer;
}
void Run();
};
// =============================================
// System Manager Services (usually has its own
// header and implementation file but included
// directly for convenient use with onlineGDB)
#include "iclock_factory.h"
#include "timer.h"
constexpr unsigned kStartHours = 23;
constexpr unsigned kStartMinutes = 59;
constexpr unsigned kStartSeconds = 55;
class SystemManager : public IClockFactory {
private:
std::shared_ptr<Clock> clock_{};
std::shared_ptr<Timer> timer_{};
std::shared_ptr<Application> application_{};
public:
SystemManager() {
Create();
BuildRelations();
}
void Execute() const {
ShowValue("Clock Observer ...");
application_->Run();
}
#ifdef THREAD
timer_->Wait();
#endif
std::shared_ptr<Clock> GetNewClock(unsigned hours,
unsigned minutes,
unsigned seconds) override {
return std::make_shared<Clock>(hours, minutes, seconds);
}
private:
void Create() {
clock_.reset(new Clock{kStartHours, kStartMinutes, kStartSeconds});
timer_.reset(new Timer{});
application_.reset(new Application{*this});
}
void BuildRelations() const {
application_->SetClock(clock_);
application_->SetTimer(timer_);
}
};
// =============================================
// IMPLEMENTATION of Actual Application (usually
// has its own application.cpp file but included
// directly for convenient use with onlineGDB)
void Application::Run() {
while (true) {
ShowValue("\n");
#ifndef OBSERVER
#ifndef CONTAINER
ShowValue("'A': Tick clock");
ShowValue("'B': Reset clock");
#ifdef EXCEPTION
ShowValue("'C': Set IClock-Pointer to nullptr");
#endif
#endif
#endif
#if defined(OBSERVER) || defined(CONTAINER)
ShowValue("'A': Tick all clocks");
ShowValue("'B': Attach a clock");
ShowValue("'C': Detach a clock");
ShowValue("'D': Reset clock");
ShowValue("'E': Attach five different initialized clocks");
#endif
#ifdef THREAD
ShowValue("'F': Start the timer thread");
#endif
ShowValue("'Q': Quit this program");
ShowValue();
switch (GetCommand()) {
#ifndef OBSERVER
#ifndef CONTAINER
case 'A':
clock_->Update(std::cout);
break;
case 'B':
clock_->Reset();
break;
#ifdef EXCEPTION
case 'C':
SetClock(nullptr);
break;
#endif
#endif
#endif
#if defined(OBSERVER) || defined(CONTAINER)
case 'A':
timer_->Tick(std::cout);
break;
case 'B':
// timer_->Attach(clock_);
break;
case 'C':
// timer_->Detach(clock_);
break;
case 'D':
clock_->Reset();
break;
case 'E':
// timer_->Attach(clock_factory_.GetNewClock(1, 0, 0));
// timer_->Attach(clock_factory_.GetNewClock(2, 0, 0));
// timer_->Attach(clock_factory_.GetNewClock(3, 0, 0));
// timer_->Attach(clock_factory_.GetNewClock(4, 50, 40));
// timer_->Attach(clock_factory_.GetNewClock(5, 59, 50));
break;
#endif
#ifdef THREAD
case 'F':
timer_->Start();
break;
#endif
case 'Q':
#ifdef THREAD
timer_->Stop();
#endif
return;
default:
break;
}
}
}
#include <string>
int main() {
const std::unique_ptr<SystemManager> system_manager{new SystemManager};
try {
system_manager->Execute();
}
catch (const std::string &ex) {
std::clog << "Execution Terminated (details: " << ex << ")\n";
}
catch (const char *ex) {
std::clog << "Execution Terminated: " << ex << ".\n";
}
catch (const std::exception &ex) {
std::clog << "Execution Terminated by '" << ex.what() << "' exception\n";
}
catch (...) {
std::clog << "Execution Terminated by unknkwon exception\n";
}
}
#endif
#include "limit_counter.h"
#include <string>
#include <stdexcept>
LimitCounter::LimitCounter (const unsigned value, const unsigned limit)
: value_{value}, limit_{limit}
{
}
void
LimitCounter::SetValue (const unsigned value)
{
value_ = value;
}
unsigned
LimitCounter::GetValue () const
{
return value_;
}
bool
LimitCounter::Count ()
{
const auto temp_value = GetValue () + 1;
if (temp_value >= limit_)
{
LimitCounter::Reset ();
return true;
}
SetValue (temp_value);
return false;
}
void
LimitCounter::Reset ()
{
value_ = 0;
}
#ifndef LIMIT_COUNTER_H
#define LIMIT_COUNTER_H
#include <stdexcept>
class LimitCounter {
private:
unsigned value_;
const unsigned limit_;
public:
LimitCounter(unsigned value, unsigned limit);
virtual ~LimitCounter() = default;
void SetValue(unsigned value);
unsigned GetValue() const;
virtual bool Count();
virtual void Reset();
};
#endif // include guard
#include "overflow_counter.h"
OverflowCounter::OverflowCounter(const unsigned value, const unsigned limit,
LimitCounter& next_counter)
: LimitCounter{value, limit}, next_counter_{next_counter}
{
}
void OverflowCounter::Reset()
{
LimitCounter::Reset();
next_counter_.Reset();
}
bool OverflowCounter::Count()
{
const auto is_overflow = LimitCounter::Count();
if (is_overflow) {
next_counter_.Count();
}
return is_overflow;
}
#ifndef OVERFLOW_COUNTER_H
#define OVERFLOW_COUNTER_H
#include "limit_counter.h"
class OverflowCounter : public LimitCounter {
private:
LimitCounter& next_counter_;
public:
OverflowCounter(unsigned value, unsigned limit, LimitCounter& next_counter);
void Reset() override;
bool Count() override;
};
#endif // include guard
#ifndef CLOCK_H
#define CLOCK_H
#include <iosfwd>
#include "limit_counter.h"
#include "overflow_counter.h"
class Clock {
private:
LimitCounter hours_;
OverflowCounter minutes_;
OverflowCounter seconds_;
public:
Clock(unsigned hours, unsigned minutes, unsigned seconds);
void SetHours(unsigned hours);
void SetMinutes(unsigned minutes);
void SetSeconds(unsigned seconds);
unsigned GetHours() const;
unsigned GetMinutes() const;
unsigned GetSeconds() const;
void Reset();
void Update(std::ostream &os);
void Show(std::ostream&) const;
};
#endif // include guard
#include "clock.h"
#include <iomanip>
#include <iostream>
Clock::Clock(const unsigned hours, const unsigned minutes,
const unsigned seconds)
: hours_{hours, 24},
minutes_{minutes, 60, hours_},
seconds_{seconds, 60, minutes_}
{
}
void Clock::SetHours(const unsigned hours) { hours_.SetValue(hours); }
void Clock::SetMinutes(const unsigned minutes) { minutes_.SetValue(minutes); }
void Clock::SetSeconds(const unsigned seconds) { seconds_.SetValue(seconds); }
unsigned Clock::GetHours() const { return hours_.GetValue(); }
unsigned Clock::GetMinutes() const { return minutes_.GetValue(); }
unsigned Clock::GetSeconds() const { return seconds_.GetValue(); }
void Clock::Reset() {
seconds_.Reset();
}
void Clock::Update(std::ostream& os)
{
seconds_.Count();
Show(os);
}
void Clock::Show(std::ostream& os) const
{
os << "Clock [hh:mm:ss] " << std::setfill('0') << std::setw(2)
<< GetHours() << ":" << std::setfill('0') << std::setw(2)
<< GetMinutes() << ":" << std::setfill('0') << std::setw(2)
<< GetSeconds() << "\n";
}
#ifndef ICLOCK_FACTORY_H
#define ICLOCK_FACTORY_H
#include <memory>
#include "clock.h"
class IClockFactory {
public:
virtual ~IClockFactory() = default;
virtual
std::shared_ptr<Clock> GetNewClock(unsigned hours,
unsigned minutes,
unsigned seconds) = 0;
};
#endif // include guard
#ifndef OBSERVER_ARRAY_H
#define OBSERVER_ARRAY_H
#include <cstddef>
#include <functional>
#include <iosfwd>
#include <iterator>
#include <memory>
template <int N, typename CB>
class ObserverRegistry {
public:
using CallbackID = int;
using Callback = std::function<CB>;
private:
Callback registry_[N] {};
public:
ObserverRegistry(const ObserverRegistry& other) = delete;
ObserverRegistry(ObserverRegistry&& other) = delete;
ObserverRegistry& operator=(const ObserverRegistry& other) = delete;
ObserverRegistry& operator=(ObserverRegistry&& other) = delete;
CallbackID Attach(Callback cb);
void Detach(CallbackID id);
void Notify(std::ostream& os);
protected:
ObserverRegistry() = default;
~ObserverRegistry() = default;
};
template <int N, typename CB>
// typename ObserverRegistry<MaxObservers>::CallbackID ObserverRegistry<MaxObservers>::Attach(Callback cb)
auto ObserverRegistry<N, CB>::Attach(Callback cb) -> CallbackID
{
CallbackID result = 0;
for (auto& e : registry_) {
if (e == nullptr) {
e = cb;
return result;
}
result++;
}
return -1;
}
template <int N, typename CB>
void ObserverRegistry<N, CB>::Detach(CallbackID id) {
if (0 <= id && id < N) {
registry_[id] = nullptr;
}
}
template <int N, typename CB>
void ObserverRegistry<N, CB>::Notify(std::ostream& os)
{
for (auto &e : registry_) {
if (e != nullptr) e(os);
}
}
#endif // include guard
#ifndef TIMER_H
#define TIMER_H
#include <iosfwd>
#include "config.h"
//#include "thread_base.h"
#ifdef OBSERVER
#ifndef CONTAINER
#include "observer_registry.h"
#endif
#endif
#ifdef CONTAINER
#include "observer_container.h"
#endif
constexpr unsigned kMaxObservers = 10;
constexpr unsigned kDelayTimeMs = 1000;
class Timer : public ObserverRegistry<kMaxObservers, void(std::ostream&)> {
public:
void Tick(std::ostream& os);
#ifdef THREAD
void Execute() override;
#endif
};
#endif // include guard
#include "timer.h"
void Timer::Tick(std::ostream& os) {
Notify(os);
}
#ifdef THREAD
#include <iostream>
void Timer::Execute() {
while (IsEnabled()) {
Delay(kDelayTimeMs);
Tick(std::cout);
}
}
#endif
#ifndef CONFIG_H
#define CONFIG_H
//
// Exercise 1: Manual Testing: no #define-s enabled
// Unit-Testing: enable #define TEST1
// Exercise 2: Manual Testing: #define-s EXCEPTION
// Unit-Testing: enable #define TEST2
// Exercise 3: Manual Testing: #define-s OBSERVER
// Part 1 Unit-Testing: enable #define TEST3a
// Part 2 Unit-Testing: enable #define TEST3b
// Extra 1 Unit-Testing: enable #define TEST3x
// Extra 2 Unit-Testing: enable #define TEST3y
// Exercise 4: Manual Testing: #define-s CONTAINER
// Unit-Testing: enable #define TEST4
// Exercise 5: Manual Testing: enable THREAD
// Unit-Testing: (none provided)
// NOTE: #define-s for manual testing accumulate, i.e.
// they are meant to be enabled and stay enabled
// as the exercise progresses. But if ANY of
// TEST1, TEST2, TEST3, TEST4 is enabled ONLY
// the respective unit-testing takes place.
// enable manual test cases
// ---------------------------------------------
#define EXCEPTION
#define OBSERVER
//#define CONTAINER
//#define THREAD
// enable/disable unit tests
// ---------------------------------------------
//#define TEST1
//#define TEST2
//#define TEST3a
//#define TEST3b
//#define TEST3x
//#define TEST3y
#define TEST3z
//#define TEST4
#endif // include guard