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

wvlet.airframe.launcher.StringTree.scala Maven / Gradle / Ivy

There is a newer version: 24.9.0
Show newest version
/*
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package wvlet.airframe.launcher

import wvlet.airframe.launcher.StringTree.Leaf
import wvlet.log.LogSupport
import wvlet.airframe.surface.reflect.Path

/**
  * Holder of structured data consisting of named values. ValueHolder is immutable, so the set operations in this class
  * return another ValueHolder and never modify the original ValueHolder.
  *
  * {{{
  * A(a, B(b, c))
  *
  * { a: apple, B:{b:book, c:car} }
  *
  * val n1 = EmptyNode.set("a", apple) => Node(a -> Leaf(apple))
  * val n2 = n1.set("B.b", "book") => Node(a -> Leaf(apple), B -> EmptyNode.set("b", "book")) => Node(a -> apple, B -> Node(b -> Leaf(book)))
  * val n3 = n2.set("B.c", "car") => Node(a -> apple, B -> Node(b -> Leaf(book), c -> Leaf(car)))
  *
  * }}}
  */
sealed trait StringTree {
  def +(e: (Path, StringTree)): StringTree = setNode(e._1, e._2)
  def ++(it: Iterable[(Path, StringTree)]): StringTree =
    it.foldLeft[StringTree](this) { (h, e) =>
      h.setNode(e._1, e._2)
    }

  /**
    * Set a value at the specified path
    *
    * @param path
    *   string representation of Path
    * @param value
    * @return
    *   updated value holder
    */
  def set(path: String, value: String): StringTree = setNode(Path(path), Leaf(value))

  def set(path: Path, value: String): StringTree = setNode(path, Leaf(value))

  /**
    * Set a value at the specified path
    *
    * @param path
    *   path
    * @param value
    *   String value to set
    * @return
    *   updated value holder
    */
  def setNode(path: Path, node: StringTree): StringTree

  /**
    * Extract a part of the value holder under the path
    *
    * @param path
    * @return
    *   value holder under the path
    */
  def get(path: String): StringTree = get(Path(path))

  /**
    * Extract a part of the value holder under the path
    *
    * @param path
    * @return
    *   value holder under the path
    */
  def get(path: Path): StringTree

  /**
    * Depth first search iterator
    *
    * @return
    */
  def dfs: Iterator[(Path, String)] = dfs(Path.current)

  /**
    * Depth first search iterator under the specified path
    *
    * @param path
    * @return
    */
  def dfs(path: Path): Iterator[(Path, String)]

  /**
    * Depth first search iterator under the specified path
    *
    * @param path
    * @return
    */
  def dfs(path: String): Iterator[(Path, String)] = dfs(Path(path))

  def isEmpty = false

  def toMsgPack: Array[Byte] = {
    StringTreeCodec.toMsgPack(this)
  }
}

object StringTree extends LogSupport {
  import collection.immutable.{Map => IMap}

  def apply(elems: Iterable[(Path, StringTree)]): StringTree = EmptyNode.++(elems)

  val empty: StringTree = EmptyNode

  private[launcher] case object EmptyNode extends StringTree {
    def setNode(path: Path, value: StringTree) = {
      if (path.isEmpty) {
        value
      } else {
        Node(IMap.empty[String, StringTree]).setNode(path, value)
      }
    }
    def get(path: Path)     = EmptyNode
    def extract(path: Path) = EmptyNode

    def dfs(path: Path)  = Iterator.empty
    override def isEmpty = true
  }

  private[launcher] case class Node(child: IMap[String, StringTree]) extends StringTree {
    override def toString: String =
      s"{${child.map(e => s"${e._1}:${e._2}").mkString(", ")}}"

    def setNode(path: Path, value: StringTree): StringTree = {
      if (path.isEmpty) {
        throw new IllegalStateException("path cannot be empty")
      } else {
        val p = child.getOrElse(path.head, EmptyNode).setNode(path.tailPath, value)
        Node(child + (path.head -> p))
      }
    }

    def get(path: Path) = {
      if (path.isEmpty) {
        this
      } else {
        child.get(path.head) map { _.get(path.tailPath) } getOrElse EmptyNode
      }
    }

    def dfs(path: Path) = (for ((name, h) <- child) yield h.dfs(path / name)).reduce(_ ++ _)
  }

  private[launcher] case class Leaf(value: String) extends StringTree {
    override def toString = value.toString
    def setNode(path: Path, value: StringTree) = {
      SeqLeaf(Seq(this, EmptyNode.setNode(path, value)))
    }

    def get(path: Path) = {
      if (path.isEmpty) {
        this
      } else {
        EmptyNode
      }
    }

    def dfs(path: Path) = Iterator.single(path -> value)
  }

  /**
    * Node containing multiple elements
    * @param elems
    */
  private[launcher] case class SeqLeaf(elems: Seq[StringTree]) extends StringTree {
    override def toString = s"[${elems.mkString(", ")}]"

    def setNode(path: Path, value: StringTree) = {
      SeqLeaf(elems :+ EmptyNode.setNode(path, value))
    }

    def get(path: Path) =
      if (path.isEmpty) {
        this
      } else {
        EmptyNode
      }

    def dfs(path: Path) = elems.map(e => e.dfs(path)).reduce(_ ++ _)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy