// 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(TEST4) // ---------------- Unit-Testing for Exercise 4
#include <cassert>
#include <memory>
#include <sstream>
#include "clock.h"
#include "observer_container.h"
class MockedTimer : public ObserverContainer {
public:
using ObserverContainer::ObserverContainer;
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(""));
}
#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 "iobserver.h"
#include "limit_counter.h"
#include "overflow_counter.h"
class Clock : public IObserver {
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 IOBSERVER_H
#define IOBSERVER_H
#include <iosfwd>
class IObserver {
public:
virtual ~IObserver() = default;
virtual void Update(std::ostream& os) = 0;
};
#endif // include guard
#ifndef TIMER_H
#define TIMER_H
#include "config.h"
#include "thread_base.h"
#ifdef OBSERVER
#ifndef CONTAINER
#include "observer_array.h"
#endif
#endif
#ifdef CONTAINER
#include "observer_container.h"
#endif
constexpr unsigned kMaxObservers = 10;
constexpr unsigned kDelayTimeMs = 1000;
class Timer : public ObserverContainer {
public:
void Tick(std::ostream& os);
// ... TBD: add necessary member function declarations
#ifdef THREAD
void Execute() override;
#endif
};
#endif // include guard
#include "timer.h"
void Timer::Tick(std::ostream& os) {
Notify(os);
}
// ... TBD: add necessary member function implementations
#ifdef THREAD
#include <iostream>
void Timer::Execute() {
while (IsEnabled()) {
Delay(kDelayTimeMs);
Tick(std::cout);
}
}
#endif
#ifndef OBSERVER_CONTAINER_H
#define OBSERVER_CONTAINER_H
#include <iosfwd>
#include <list>
#include <memory>
#include "iobserver.h"
class ObserverContainer {
private:
using ObserverRef = std::shared_ptr<IObserver>;
using ObserverRefList = std::list<ObserverRef>;
ObserverRefList observers_{};
public:
void Attach(std::shared_ptr<IObserver> observer);
void Detach(std::shared_ptr<IObserver> observer);
void Notify(std::ostream& os);
protected:
ObserverContainer() = default;
~ObserverContainer() = default;
};
#endif // include guard
#include "observer_container.h"
#include <algorithm>
#include <iosfwd>
void ObserverContainer::Attach(const std::shared_ptr<IObserver> observer)
{
// avoid duplicates already on insertion:
if (!observer) return;
const auto it = std::find(observers_.cbegin(), observers_.cend(), observer);
if (it != observers_.cend()) return;
observers_.emplace_back(observer);
}
void ObserverContainer::Detach(const std::shared_ptr<IObserver> observer)
{
// no worries about duplicates as such are avoided on insertion
if (!observer) return;
const auto it = std::find(observers_.cbegin(), observers_.cend(), observer);
if (it == observers_.cend()) return;
observers_.erase(it);
}
void ObserverContainer::Notify(std::ostream& os)
{
// NOTE: not safe against concurrent `Attach` or `Detach` !!!
for (const auto &e : observers_) {
e->Update(os);
}
}
#ifndef THREAD_BASE_H
#define THREAD_BASE_H
#include <thread>
class ThreadBase {
private:
public:
virtual ~ThreadBase() = default;
// ... TBD: add necessary member function declarations
protected:
};
#endif // include guard
#include "thread_base.h"
#include <chrono>
// ... TBD: add necessary member function implementations
#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
// 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 TEST4
#endif // include guard