All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.squareup.workflow1.TreeSnapshot.kt Maven / Gradle / Ivy

The newest version!
package com.squareup.workflow1

import com.squareup.workflow1.TreeSnapshot.Companion.forRootOnly
import com.squareup.workflow1.TreeSnapshot.Companion.parse
import com.squareup.workflow1.internal.WorkflowNodeId
import okio.Buffer
import okio.ByteString
import kotlin.LazyThreadSafetyMode.NONE

/**
 * Aggregate of all the snapshots of a tree of workflows.
 *
 * Can be serialized with [toByteString] and deserialized with [parse].
 *
 * For tests, you can get a [TreeSnapshot] from a [RenderingAndSnapshot] or by creating one for
 * your root workflow only by calling [forRootOnly].
 *
 * @constructor
 * @param childTreeSnapshots A function that will lazily parse the child snapshots for this tree
 * when invoked. It will be cached in the [childTreeSnapshots] property.
 */
public class TreeSnapshot internal constructor(
  workflowSnapshot: Snapshot?,
  childTreeSnapshots: () -> Map
) {
  /**
   * The [Snapshot] for the root workflow, or null if that snapshot was empty or unspecified.
   * Computed lazily to avoid serializing the snapshot until necessary.
   */
  internal val workflowSnapshot: Snapshot? by lazy(NONE) {
    workflowSnapshot?.takeUnless { it.bytes.size == 0 }
  }

  /**
   * The map of child snapshots by child [WorkflowNodeId]. Computed lazily so the entire snapshot
   * tree isn't parsed upfront.
   */
  internal val childTreeSnapshots: Map
    by lazy(NONE, childTreeSnapshots)

  /**
   * Writes this [Snapshot] and all its children into a [ByteString]. The snapshot can be restored
   * with [parse].
   *
   * Any children snapshots for workflows whose [WorkflowIdentifier]s are
   * [unsnapshottable][unsnapshottableIdentifier] will not be serialized.
   */
  public fun toByteString(): ByteString = Buffer().let { sink ->
    sink.writeByteStringWithLength(workflowSnapshot?.bytes ?: ByteString.EMPTY)
    val childBytes: List> =
      childTreeSnapshots.mapNotNull { (childId, childSnapshot) ->
        val childIdBytes = childId.toByteStringOrNull() ?: return@mapNotNull null
        val childSnapshotBytes = childSnapshot.toByteString()
          .takeUnless { it.size == 0 }
          ?: return@mapNotNull null
        return@mapNotNull Pair(childIdBytes, childSnapshotBytes)
      }
    sink.writeInt(childBytes.size)
    childBytes.forEach { (childIdBytes, childSnapshotBytes) ->
      sink.writeByteStringWithLength(childIdBytes)
      sink.writeByteStringWithLength(childSnapshotBytes)
    }
    sink.readByteString()
  }

  override fun equals(other: Any?): Boolean = when {
    other === this -> true
    other !is TreeSnapshot -> false
    else ->
      other.workflowSnapshot == workflowSnapshot &&
        other.childTreeSnapshots == childTreeSnapshots
  }

  override fun hashCode(): Int {
    var result = workflowSnapshot.hashCode()
    result = 31 * result + childTreeSnapshots.hashCode()
    return result
  }

  public companion object {
    /**
     * Returns a [TreeSnapshot] that only contains a [Snapshot] for the root workflow, and no child
     * snapshots.
     */
    public fun forRootOnly(rootSnapshot: Snapshot?): TreeSnapshot =
      TreeSnapshot(rootSnapshot, ::emptyMap)

    /**
     * Parses a "root" snapshot and the list of child snapshots with associated [WorkflowNodeId]s
     * from a [ByteString] returned by [toByteString].
     *
     * Never returns an empty root snapshot: if the root snapshot is empty it will be null.
     * Child snapshots, however, are always returned as-is. They must be recursively passed to this
     * function to continue parsing the tree.
     *
     * Note that this method is mostly lazy. It will parse the list of child [TreeSnapshot]s, but
     * will not recursively parse each of those.
     */
    @OptIn(ExperimentalStdlibApi::class)
    public fun parse(bytes: ByteString): TreeSnapshot = bytes.parse { source ->
      val rootSnapshot = source.readByteStringWithLength()
      return TreeSnapshot(Snapshot.of(rootSnapshot)) {
        val childSnapshotCount = source.readInt()
        buildMap(childSnapshotCount) {
          for (i in 0 until childSnapshotCount) {
            val idBytes = source.readByteStringWithLength()
            val id = WorkflowNodeId.parse(idBytes)
            val childSnapshot = source.readByteStringWithLength()
            this[id] = parse(childSnapshot)
          }
        }
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy