Skip to content
Zbyněk Šlajchrt edited this page Jun 12, 2015 · 17 revisions

11. Fragment Wrappers

In the previous lesson we learned how to use dimension wrappers to decorate abstract methods in a dimension trait by optional behavior. In this lesson we will introduce another type of wrappers called fragment wrappers. In contrast to the dimension wrappers, which can be applied on any fragment implementing the wrapper's dimension, a fragment wrapper can wrap only a specific fragment. Such wrappers are usually used in situations when the dimension is too abstract for the wrapper's behavior and therefore it must be associated with a particular implementation.

The goal of this part is to

  1. Create a simple fragment wrapper
  2. Use delegation to configure the wrapper
  3. Incorporate the wrapper to the morph model as an optional component

Let us concentrate on the MemoryOutputChannel fragment. It contains a reference to the buffer for storing the printed text. Since the size of this buffer is not limited in any way, there is a risk that it may overflow. Our task is to provide some protection of this buffer, which will prevent the printer from overflowing the buffer's limit. This limit will be a configurable property of the BufferWatchDog fragment wrapper, which implements the protection mechanism.

We will begin with the wrapper configuration. In a previous step we saw that a fragment can be configured upon its construction by means of delivering an instance of the delegate trait. This delegate trait is marked with the dlg markup in the list of the fragment's parents. The same is true for wrappers. The BufferWatchDogConfig trait will be BufferWatchDog's delegate trait containing the only configurable property - the buffer limit.

trait BufferWatchDogConfig {
  val bufferWatchDogLimit: Int
}

case class DefaultBufferWatchDogConfig(bufferWatchDogLimit: Int) extends BufferWatchDogConfig

We also implemented a default implementation of the delegate trait DefaultBufferWatchDogConfig.

Similarly to dimension wrappers, a fragment wrapper is annotated with a pair of annotations @fragment @wrapper. It is allowed to extend exactly one fragment trait, which is MemoryOutputChannel in this case. Additionally, we must also specify the delegate trait BufferWatchDogConfig using the dlg markup.

@fragment @wrapper
trait BufferWatchDog extends MemoryOutputChannel with dlg[BufferWatchDogConfig] {

  override def printText(text: String): Unit = {
    if (outputBuffer.size + text.length > bufferWatchDogLimit) {
      throw new IllegalStateException("Full")
    }
    super.printText(text)
  }
}

Since the wrapper extends MemoryOutputChannel it can access its protected members. The buffer is among them, so the wrapper can control its size in the overridden printText method. This method refuses any attempt to print a text as long as the buffer is full.

Now we can create a wrapper instance by passing an instance of the configuration class DefaultBufferWatchDogConfig.

implicit val bufWDFrag = single[BufferWatchDog, BufferWatchDogConfig](DefaultBufferWatchDogConfig(10))

The other configurations are the same as in the previous step.

val contactData = ContactData("Pepa", "Novák", male = true, email="[email protected]", Locale.CANADA)
implicit val offlineContactFrag = single[OfflineContact, Contact](contactData)
implicit val onlineContactFrag = single[OnlineContact, Contact](contactData)

Applying of both types of wrappers on a fragment follows the same rules. Thus a fragment wrapper is appended immediately after its fragment or another wrapper decorating the same fragment. Here, we are attaching BufferWatchDog right after MemoryOutputChannel. This configuration ensures that the buffer watch dog will not be active unless the memory channel is. Furthermore, we make the wrapper optional by wrapping to to \?[]. The forward slash indicates that the wrapper is by default on (as long as the memory channel is on, of course).

val contactKernel = singleton[(OfflineContact or OnlineContact) with
   (ContactRawPrinter or ContactPrettyPrinter) with
   (StandardOutputChannel or (MemoryOutputChannel with \?[BufferWatchDog])) with
   \?[NewLineAppender] with
   \?[ChannelMonitor]]

Now we can create a stack of morning strategies and instantiate a contact morph.

var contactCoord: Int = 1
val contactStr = promote[OfflineContact or OnlineContact](
   RootStrategy[contactKernel.Model](), contactCoord)

var printerCoord: Int = 1
val printerStr = promote[ContactRawPrinter or ContactPrettyPrinter](
   contactStr, printerCoord)

var channelCoord: Int = 1
val channelStr = promote[StandardOutputChannel or MemoryOutputChannel](
   printerStr, channelCoord)

var bufferCoord: Int = 1
val bufferWatchDogStr = promote[\?[BufferWatchDog]](channelStr, bufferCoord)

var channelWrappersCoord: Int = 3
val wrappersStr = promote[\?[ChannelMonitor] with \?[NewLineAppender]](
   bufferWatchDogStr, channelWrappersCoord)

var contact = contactKernel.!
contact = contact.remorph(wrappersStr)

We allocated a special dimension for BufferWatchDog in order to make its control convenient. Also we adjusted the strategies coordinates in such a way that MemoryOutputChannel and BufferWatchDog will be activated.

The buffer limit is adjusted low enough that the following statement will be prevented from printing by the buffer watch dog.

contact.printContact()