FullOfficeAssembler

The Generator is not the top level of putting office information together.  That is done by the FullOfficeAssembler, which makes use of an implementation of IFullOfficeGenerator, but also uses the date subsystem to determine the values to retrieve, as well as the use followed.

class FullOfficeAssembler

{

public:

  FullOfficeAssembler(const IDayRecordSource &inRecordSource,

                      const IDateCalculator &inDateCalculator,

                      const IFullOfficeGenerator &inGenerator,

                      const IKalendar &inKalendar, const IPsalter &inPsalter,

                      const Use inUse):

      m_recordSource(inRecordSource),

      m_dateCalculator(inDateCalculator), m_generator(inGenerator),

      m_kalendar(inKalendar), m_psalter(inPsalter), m_use(inUse)

  { }

  bool assembleOffice(const std::chrono::year_month_day inDate,

                      const Log::ILogger &inLogger) const;

private:

  const IDayRecordSource &m_recordSource;

  const IDateCalculator &m_dateCalculator;

  const IFullOfficeGenerator &m_generator;

  const IKalendar &m_kalendar;

  const IPsalter &m_psalter;

  Use m_use;

  bool getFirstLevelInfo(std::unique_ptr<IFullDayInfo> &outInfo,

                         const std::string &inName) const;

  void generateFromTwoSources(const IFullDayInfo &inBaseSource,

                              const IFullDayInfo &inDerivedSource,

                              const unsigned int inDay) const;

  void generateFromThreeSources(const IFullDayInfo &inBaseSource,

                                const IFullDayInfo &inDerivedSource,

                                const unsigned int inDay) const;

  void handleEasterWeekOrHolyWeek(const std::chrono::weekday inWeekday,

                                  const unsigned int inMonth,

                                  const unsigned int inDay) const;

};

assembleOffice() processes the date input.  Logical blocks are extracted into private functions to encapsulate repeated logic or (in a couple of cases) to make the high-level code more readable.

bool FullOfficeAssembler::assembleOffice(

    const std::chrono::year_month_day inDate,

    const Log::ILogger &inLogger) const

{

  auto day = static_cast<unsigned>(inDate.day());

  auto month = static_cast<unsigned>(inDate.month());

  auto weekday = std::chrono::weekday(inDate);

  Feast f = m_kalendar.getFeast(day, month);

// Handle the Holy Week exception first, because it has precedence over everything else

  if (m_dateCalculator.isInEasterPrivilege(day, month))

    {

      handleEasterWeekOrHolyWeek(weekday, month, day);

      return true;

    }

// First class Sundays and feasts are the next level down in precedence (as is Ash Wednesday)

  if ((f.grade == Grades::DOUBLE_FIRST_CLASS)

      || (f.grade == Grades::PRIVILEGED_FERIA)

      || (f.grade == Grades::DOMINICA_FIRST_CLASS))

    {

      auto info = m_recordSource.getFullDayInfo(f.name);

      if (!info->isPartial())

        {

          m_generator.generateFromSingleSource(*info, *info);

        }

      else

        {

          auto parentInfo = m_recordSource.getFullDayInfo(info->getParent());

          generateFromTwoSources(*parentInfo, *info, day);

        }

      return true;

    }

// In some cases, a feast takes precedence over a Sunday...

  auto [ferialName, sundayName] = m_dateCalculator.getFerialName(day, month);

  HymnSeasons s = m_dateCalculator.getSeason(day, month);

  if (f.grade == Grades::DOUBLE_SECOND_CLASS)

    {

      // Check for edge case ...

      if ((weekday == std::chrono::Sunday) && (s == HymnSeasons::ADVENT)

          && (sundayName == "First Sunday in Advent"s))

        {

          auto info = m_recordSource.getFullDayInfo(sundayName);

          m_generator.generateFromSingleSource(*info, *info);

        }

      else

        {

          std::unique_ptr<IFullDayInfo> info;

          if (!getFirstLevelInfo(info, f.name))

            {

              auto parentInfo

                  = m_recordSource.getFullDayInfo(info->getParent());

              generateFromTwoSources(*parentInfo, *info, day);

            }

        }

      return true;

    }

// For everything else, a Sunday takes precedence over the feast

  if (weekday == std::chrono::Sunday)

    {

      std::unique_ptr<IFullDayInfo> info;

      if (!getFirstLevelInfo(info, sundayName))

        {

          if (info->isEmpty())

            {

              inLogger.log(

                  Log::Severity::Fatal,

                  "Could not determine sunday information for Sunday office");

              return false;

            }

          if (info->getParent() != "SUNDAY"s)

            {

              auto parentInfo

                  = m_recordSource.getFullDayInfo(info->getParent());

              if (!info->hasPartialParent())

                {

                  generateFromTwoSources(*parentInfo, *info, day);

                }

              else // Must be Sunday After Epiphany or Pentecost ...

                {

                  generateFromThreeSources(*parentInfo, *info, day);

                }

            }

          else

            {

              SundayFullDayInfo parentInfo(m_psalter, sundayName, s,

                                           info->grade(),

                                           (m_use != Use::Anglican));

              generateFromTwoSources(parentInfo, *info, day);

            }

        }

      return true;

    }

// Special logic for octaves

  if (f.grade == Grades::GREATER_OCTAVE)

    {

      std::string parentFeastName;

      auto index = f.name.find("in the Octave of");

      if (index == std::string::npos)

{

  parentFeastName = f.name;

}

      else

{

  std::string temp = f.name.substr(index + 17);

  if (temp.starts_with("the "))

    parentFeastName = temp.substr(4);

  else

    parentFeastName = temp;

}

      auto info = m_recordSource.getFullDayInfo(parentFeastName);

      if (!info->isPartial())

        {

          int daysAfterFeast = 1;

          if (parentFeastName == "Annunciation")

            {

              int diff = static_cast<int>(day) - 25;

              if (diff > 0)

                daysAfterFeast = diff;

            }

          else if ((parentFeastName == "Nativity of St. John the Baptist") && day > 24)

            {

              daysAfterFeast = day - 24;

            }

          else if (parentFeastName == "SS. Peter and Paul, App.")

            {

              if (day > 29)

                {

                  daysAfterFeast = day - 29;

                }

              else

                {

                  daysAfterFeast = day + 1;

                }

            }

          else if ((parentFeastName == "Assumption of the Blessed Virgin Mary")

                   && day > 15)

            {

              daysAfterFeast = day - 15;

            }

          else if (parentFeastName == "All Saints")

            {

              daysAfterFeast = day - 1;

              if ((daysAfterFeast == 1)

                  || ((daysAfterFeast == 2)

                      && (weekday == std::chrono::Monday)))

                {

                  auto asinfo = m_recordSource.getFullDayInfo("All Souls");

                  m_generator.generateFromSingleSource(*asinfo, *asinfo);

                  return true;

                }

            }

          else if ((parentFeastName

                    == "Immaculate Conception of the Blessed Virgin Mary")

                   && day > 8)

            {

              daysAfterFeast = day - 8;

            }

          m_generator.generateDayInGreaterOctave(*info, *info, daysAfterFeast);

        }

      else

        {

          throw OfficeParseException(

              "Day in greater octave with only partial information for parent: "

              + f.name);

        }

      return true;

    }

// More special logic for octave days

  else if (f.grade == Grades::OCTAVE_DAY)

    {

      std::string parentFeastName;

      if (f.name.starts_with("Octave Day of "))

{

  parentFeastName = f.name.substr(14);

  if (parentFeastName.starts_with("the "))

    parentFeastName = parentFeastName.substr(4);

}

      else

parentFeastName = f.name;

      auto info = m_recordSource.getFullDayInfo(parentFeastName);

      if (!info->isPartial())

        {

          m_generator.generateOctaveDay(*info, *info);

        }

      else

        {

          auto parentInfo = m_recordSource.getFullDayInfo(info->getParent());

          m_generator.generateOctaveDayForApostles(*parentInfo, *info, *info);

        }

      return true;

    }

// General logic otherwise applies: use feast if given otherwise standard feria for the season

  if (!f.name.empty())

    {

      std::unique_ptr<IFullDayInfo> info;

      if (!getFirstLevelInfo(info, f.name))

        {

          if (info->isEmpty())

            {

              inLogger.log(

                  Log::Severity::Fatal,

                  "Could not determine feast information for Kalendar office: "

                      + f.name);

              return false;

            }

          auto parentInfo = m_recordSource.getFullDayInfo(info->getParent());

          if (parentInfo->isEmpty())

            {

              inLogger.log(

                  Log::Severity::Fatal,

                  "Could not determine parent information for partial Kalendar office: "

                      + info->getParent());

              return false;

            }

          if (!info->hasPartialParent())

            {

              generateFromTwoSources(*parentInfo, *info, day);

            }

          else

            {

              generateFromThreeSources(*parentInfo, *info, day);

            }

        }

    }

  else // cannot be Sunday; handled above

    {

      if (ferialName.find("Saturday") != std::string::npos)

        {

          MarianInfoSource theSource(m_psalter, s, month, day, ferialName);

          m_generator.generateFromSingleSource(theSource,

                                               theSource.getGeneralInfo());

          return true;

        }

      auto info = m_recordSource.getFullDayInfo(ferialName);

      if (info->isEmpty()) // Treat as feria after Sunday outside of Lent,

                           // Advent, Easter

        {

          FerialFullDayInfo baseSrc(m_psalter, DateToDays(inDate), s,

                                    Grades::FERIA, m_use != Use::Anglican);

          auto info2 = m_recordSource.getFullDayInfo(sundayName);

          if (info2->isEmpty())

            {

              inLogger.log(

                  Log::Severity::Fatal,

                  "Could not determine sunday information for ferial office: "

                      + sundayName);

              return false;

            }

          m_generator.generateFromCombinedSources(baseSrc, *info2, baseSrc,

                                                  info2->getCollect(), day);

        }

      else

        {

          if (!info->isPartial())

            {

              m_generator.generateFromSingleSource(*info, *info);

            }

          else

            {

              auto parentInfo

                  = m_recordSource.getFullDayInfo(info->getParent());

              if (parentInfo->isEmpty())

                {

                  inLogger.log(

                      Log::Severity::Fatal,

                      "Could not determine parent information for ferial office: "

                          + info->getParent());

                  return false;

                }

              if (!info->hasPartialParent())

                {

                  generateFromTwoSources(*parentInfo, *info, day);

                }

              else

                {

                  generateFromThreeSources(*parentInfo, *info, day);

                }

            }

        }

    }

  return true;

}

The function returns true if the process succeeded. It returns false if there is no information available for the day.

The private functions handle the fine details and call the generator:

bool FullOfficeAssembler::getFirstLevelInfo(

    std::unique_ptr<IFullDayInfo> &outInfo, const std::string &inName) const

{

  outInfo = m_recordSource.getFullDayInfo(inName);

  if (!outInfo->isPartial())

    {

      m_generator.generateFromSingleSource(*outInfo, *outInfo);

      return true;

    }

  return false;

}

void FullOfficeAssembler::generateFromTwoSources(

    const IFullDayInfo &inBaseSource, const IFullDayInfo &inDerivedSource,

    const unsigned int inDay) const

{

  auto collect = inDerivedSource.getCollect();

  if (collect.empty())

    collect = inBaseSource.getCollect();

  m_generator.generateFromCombinedSources(inBaseSource, inDerivedSource,

                                          inDerivedSource, collect, inDay);

}

void FullOfficeAssembler::generateFromThreeSources(

    const IFullDayInfo &inBaseSource, const IFullDayInfo &inDerivedSource,

    const unsigned int inDay) const

{

  auto secondParentInfo

      = m_recordSource.getFullDayInfo(inBaseSource.getParent());

  auto collect = inDerivedSource.getCollect();

  if (collect.empty())

    collect = inBaseSource.getCollect();

  if (collect.empty())

    collect = secondParentInfo->getCollect();

  // Info will supply major antiphons and possibly collect,

  // only

  auto antiphons = inDerivedSource.getPartialInfo().getMajorHourAntiphons();

  m_generator.generateFromCombinedSourcesWithVariantAntiphons(

      *secondParentInfo, inBaseSource, inDerivedSource, collect, inDay,

      antiphons);

}

void FullOfficeAssembler::handleEasterWeekOrHolyWeek(

    const std::chrono::weekday inWeekday, const unsigned int inMonth,

    const unsigned int inDay) const

{

  if (inWeekday == std::chrono::Sunday)

    {

      auto name = m_dateCalculator.getSundayName(inDay, inMonth);

      auto info = m_recordSource.getFullDayInfo(name);

      m_generator.generateFromSingleSource(*info, *info);

    }

  else

    {

      auto [ferialName, sundayName]

          = m_dateCalculator.getFerialName(inDay, inMonth);

      if (sundayName == "Easter")

        {

          m_generator.generateForSpecialDay(SpecialDays::EASTER_WEEK);

        }

      auto l = [&](const std::string &inName) {

        auto info = m_recordSource.getFullDayInfo(inName);

        if (!info->isPartial())

          {

            m_generator.generateFromSingleSource(*info, *info);

          }

        else

          {

            auto parentInfo = m_recordSource.getFullDayInfo(info->getParent());

            generateFromTwoSources(*parentInfo, *info, inDay);

          }

      };

      switch (inWeekday.c_encoding())

        {

        case 1:

  l("Monday in Holy Week");

          break;

        case 2:

  l("Tuesday in Holy Week");

          break;

        case 3:

  l("Wednesday in Holy Week");

          break;

        case 4:

          m_generator.generateForSpecialDay(SpecialDays::MAUNDY_THURSDAY);

          break;

        case 5:

          m_generator.generateForSpecialDay(SpecialDays::GOOD_FRIDAY);

          break;

        case 6:

          m_generator.generateForSpecialDay(SpecialDays::HOLY_SATURDAY);

          break;

        }

    }

}

Note that this is almost a top-level class: everything called before it is setup.  The set of interface classes passed in and held by the assembler is what dependency injection looks like in C++: all manual.


Comments

Popular posts from this blog

Boundaries

Overview

Considerations on an Optimization