Exceptions: try, catch, handle, finally, a dispatcher, try with resources and try guards

The complete source of exceptions_big_cats.ek9, compiled by the EK9 compiler. Shown on exceptions.html.

#!ek9
defines module site.exceptions.bigCats

  defines constant
    missingFile <- "MightNotExist.txt"

  defines type

    BigCat
      Lion
      Tiger
      WildCat
      Leopard
      Lynx

  defines class

    AnException extends Exception
      supportingInformation <- String()

      AnException()
        ->
          primaryReason as String
          supportingInformation as String
          exitCode as Integer
        super(primaryReason, exitCode)
        this.supportingInformation :=: supportingInformation

      supportingInformation() as pure
        <- rtn as String: supportingInformation

      override operator $ as pure
        <- rtn as String: `${reason()} ${supportingInformation()} exit code ${exitCode()}`

      default operator ?
      default operator $$

    OtherException extends Exception
      retryAfter <- DateTime()

      OtherException()
        ->
          reason as String
          retryAfter as DateTime
        super(reason)
        this.retryAfter :=: retryAfter

      retryAfter() as pure
        <- rtn as DateTime: retryAfter

      override operator $ as pure
        <- rtn as String: `${reason()} retry after ${$retryAfter()}`

      default operator ?
      default operator $$

    ExceptionExample
      clock as Clock: SystemClock()
      deferProcessingUntilAfter <- DateTime()

      default private ExceptionExample()

      ExceptionExample()
        -> clock as Clock
        this.clock: clock

      checkExceptionHandling()
        -> aCat as BigCat
        <- didProcess <- false

        stdout <- Stdout()

        try
          if deferProcessing()
            stdout.println(`Deferred until after ${deferProcessingUntilAfter} ${aCat} not processed`)
          else
            triggerResult <- triggerPossibleException(aCat)
            stdout.println(triggerResult)
            didProcess: true
        catch
          -> ex as Exception
          stdout.println(handleException(ex))
        finally
          stdout.println(`Finished checking ${aCat}`)

      triggerPossibleException()
        -> aCat as BigCat
        <- rtn as String: String()

        switch aCat
          case BigCat.Lion
            throw Exception($aCat, 1)
          case BigCat.Tiger
            throw AnException("Too dangerous", $aCat, 2)
          case BigCat.WildCat, BigCat.Lynx
            rtn: `Success with ${aCat}`
          case BigCat.Leopard
            throw OtherException($aCat, clock.dateTime() + PT2H)
          default
            rtn: `Success with ${aCat}`

      deferProcessing()
        <- rtn as Boolean: deferProcessingUntilAfter? <- deferProcessingUntilAfter > clock.dateTime() else false

      private handleException() as dispatcher
        -> ex as Exception
        <- rtn as String: $ex

      private handleException()
        -> ex as AnException
        <- rtn as String: $ex
        if ex.exitCode()?
          tidyUpReadyForProgramExit()

      private handleException()
        -> ex as OtherException
        <- rtn as String: $ex
        this.deferProcessingUntilAfter: ex.retryAfter()

      private tidyUpReadyForProgramExit()
        Stdout().println("Would tidy up any state ready for program exit")

      default operator ?

    FileExceptionExample

      demonstrateFileNotFound()
        stdout <- Stdout()

        //A failure while ACQUIRING a resource is not handled by the try that declares it;
        //it goes to the enclosing try. Once acquired, a resource is always closed.
        try
          try
            -> input <- openInput(missingFile)
            cat input > stdout
        //rather than use catch 'handle' can be used
        handle
          -> ex as Exception
          stdout.println($ex)
        finally
          stdout.println("Automatically closed file if opened")

      demonstrateNamedFileNotFound()
        -> fileName as String

        mainResults <- try
          -> input1 <- openInput(fileName)
          <- rtn as List of String: cat input1 | collect as List of String

        //Let the exceptions fly back - don't handle in here.
        Stdout().println(`Main Results ${mainResults}`)

      demonstrateFilesNotFound()

        mainResults <- try
          ->
            input1 <- openInput(missingFile)
            input2 <- openInput("AlsoMightNotExist.txt")
          <-
            rtn as List of String: cat input1, input2 | collect as List of String

        //Let the exceptions fly back - don't handle in here.
        Stdout().println(`Main Results ${mainResults}`)

  defines function

    //A missing file is not an Exception in EK9: the TextFile is just unset.
    //Here that is turned into an Exception, so the caller decides what it means.
    openInput()
      -> fileName as String
      <- rtn as CloseableStringInput?

      file <- TextFile(FileSystemPath().inCurrentWorkingDirectory(fileName))
      if not file?
        throw Exception(`File Not Found: ${fileName}`)
      rtn: file.input()

  defines program

    TryCatchExample()
      stdout <- Stdout()

      //Rather than use SystemClock - simulate one so that date time can be altered.
      //This is a way of doing a 'Fake'/'Stub'.
      simulatedClock <- () with trait of Clock as class
        currentDateTime as DateTime: 1971-02-01T12:00:00Z

        override dateTime() as pure
          <- rtn as DateTime: currentDateTime

        override millisecond() as pure
          <- rtn as Millisecond: Millisecond(0)

        setCurrentDateTime()
          -> newDateTime as DateTime
          this.currentDateTime = newDateTime

        override operator ? as pure
          <- rtn <- true

      //use the simulated clock
      example1 <- ExceptionExample(simulatedClock)

      for cat in BigCat
        if example1.checkExceptionHandling(cat)
          stdout.println(`Processing of ${cat} was completed`)
        else
          stdout.println(`Processing of ${cat} was NOT completed`)

      //just try Lynx again
      require not example1.checkExceptionHandling(BigCat.Lynx)

      //alter the time just passed the retry after time.
      simulatedClock.setCurrentDateTime(simulatedClock.dateTime() + PT2H1M)
      //Now it should be processed.
      require example1.checkExceptionHandling(BigCat.Lynx)

      example2 <- FileExceptionExample()
      example2.demonstrateFileNotFound()

      try
        example2.demonstrateFilesNotFound()
      catch
        -> ex as Exception
        stdout.println(`TryCatchExample: ${ex}`)

  defines function

    knownFileName()
      -> key as String
      <- rtn as String: String()

      if key == "report"
        rtn: missingFile

  defines program

    TryWithGuard()
      stdout <- Stdout()
      someVar as String?
      try someVar :=? knownFileName("unknown")
        stdout.println("TryWithGuard: the try body ran")
        FileExceptionExample().demonstrateNamedFileNotFound(someVar)
      catch
        -> ex as Exception
        stdout.println(`TryWithGuard: ${ex}`)
      stdout.println("TryWithGuard: carried on")

    TryWithAssignment()
      stdout <- Stdout()
      someVar as String?
      try someVar: knownFileName("report")
        stdout.println("TryWithAssignment: the try body ran")
        FileExceptionExample().demonstrateNamedFileNotFound(someVar)
      catch
        -> ex as Exception
        stdout.println(`TryWithAssignment: ${ex}`)

    TryWithDeclaration()
      stdout <- Stdout()

      try someVar <- knownFileName("unknown")
        stdout.println("TryWithDeclaration: the try body ran")
        FileExceptionExample().demonstrateNamedFileNotFound(someVar)
      catch
        -> ex as Exception
        stdout.println(`TryWithDeclaration: ${ex}`)

      //This is a 'different' 'someVar' - the previous one is now out of scope.
      someVar <- false
      require someVar?
      stdout.println("TryWithDeclaration: carried on")

Download exceptions_big_cats.ek9

Output

TryCatchExample

Exception: Lion: Exit Code: 1
Finished checking Lion
Processing of Lion was NOT completed
Would tidy up any state ready for program exit
Too dangerous Tiger exit code 2
Finished checking Tiger
Processing of Tiger was NOT completed
Success with WildCat
Finished checking WildCat
Processing of WildCat was completed
Leopard retry after 1971-02-01T14:00:00Z
Finished checking Leopard
Processing of Leopard was NOT completed
Deferred until after 1971-02-01T14:00:00Z Lynx not processed
Finished checking Lynx
Processing of Lynx was NOT completed
Deferred until after 1971-02-01T14:00:00Z Lynx not processed
Finished checking Lynx
Success with Lynx
Finished checking Lynx
Exception: File Not Found: MightNotExist.txt
Automatically closed file if opened
TryCatchExample: Exception: File Not Found: MightNotExist.txt

TryWithGuard

TryWithGuard: carried on

TryWithAssignment

TryWithAssignment: the try body ran
TryWithAssignment: Exception: File Not Found: MightNotExist.txt

TryWithDeclaration

TryWithDeclaration: carried on