Advertisement
titovtima

Untitled

Mar 11th, 2021
868
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Kotlin 24.40 KB | None | 0 0
  1. package com.h0tk3y.jsonwalk
  2.  
  3. import kotlinx.serialization.encodeToString
  4. import kotlinx.serialization.json.*
  5. import java.io.File
  6. import java.util.*
  7.  
  8. fun main(args: Array<String>) {
  9.     val jsonFilePath = when {
  10.         args.getOrNull(0) == "--file" -> args.getOrNull(1) ?: error("expected filename after --file")
  11.         args.getOrNull(0) == "--new" -> null
  12.         else -> error("expected args: --file <filename> or --new")
  13.     }
  14.  
  15.     val rootElement = jsonFilePath?.let {
  16.         MutableElement.fromJsonElement(Json.parseToJsonElement(File(it).readText()))
  17.     } ?: MutableElement.ObjectElement(mutableMapOf())
  18.  
  19.     val state = State(rootElement)
  20.  
  21.     loop(state)
  22. }
  23.  
  24. fun loop(state: State) {
  25.     while (true) {
  26.         val line = readLine()
  27.         val lineWord = line?.substringBefore(" ") ?: return
  28.         val command = when (lineWord) {
  29.             "path" -> Command.Path
  30.             "print" -> Command.Print
  31.             "up" -> Command.Up
  32.             "go" -> Command.Go(ElementPath.parse(line.substringAfter(" ")))
  33.             "findpaths" -> Command.FindPaths(ElementPath.parse(line.substringAfter(" ")))
  34.             "delete" -> Command.Delete(ElementPath.parse(line.substringAfter(" ")))
  35.             "replace", "insert" -> run f@{
  36.                 val argsLine = line.substringAfter(" ")
  37.                 if (" " !in argsLine) return@f null
  38.                 val (pathString, elementString) = argsLine.split(" ", limit = 2)
  39.                 val path = ElementPath.parse(pathString)
  40.                 val createElement = { MutableElement.fromJsonElement(Json.parseToJsonElement(elementString)) }
  41.                 when (lineWord) {
  42.                     "replace" -> Command.Replace(path, createElement)
  43.                     "insert" -> Command.Insert(path, createElement)
  44.                     else -> error("unexpected")
  45.                 }
  46.             }
  47.             "copy" -> Command.Copy(ElementPath.parse(line.substringAfter(" ")))
  48.             "paste" -> Command.Paste
  49.             "exit" -> return
  50.             else -> null
  51.         }
  52.         val result = if (command != null) state.applyCommand(command) else CommandResult.Failure
  53.         when (result) {
  54.             CommandResult.Success -> println("OK")
  55.             CommandResult.Failure -> println("fail")
  56.             is CommandResult.ResultValue -> println("OK ${result.value}")
  57.             is CommandResult.ResultValues -> {
  58.                 println("OK ${result.values.size}")
  59.                 result.values.forEach { println(it) }
  60.             }
  61.             is CommandResult.InvalidPath -> println("bad-path ${result.atIndex}")
  62.         }
  63.     }
  64. }
  65.  
  66. class State(
  67.     val rootElement: MutableElement
  68. ) {
  69.     private var currentPath: ElementPath
  70.  
  71.     init {
  72.         require(rootElement is MutableElement.ObjectElement || rootElement is MutableElement.ArrayElement) {
  73.             "the root element must be an object element or an array element; $rootElement is passed"
  74.         }
  75.  
  76.         currentPath = ElementPath(listOf())
  77.     }
  78.  
  79.     fun applyCommand(command: Command): CommandResult {
  80.         return when (command) {
  81.             is Command.Path -> doPath()
  82.             Command.Print -> doPrint()
  83.             Command.Up -> doUp()
  84.             is Command.Go -> doGo(command)
  85.             is Command.FindPaths -> doFindPaths(command)
  86.             is Command.Delete -> doDelete(command)
  87.             is Command.Replace -> doReplace(command)
  88.             is Command.Insert -> doInsert(command)
  89.             is Command.Copy -> doCopy(command)
  90.             is Command.Paste -> doPaste()
  91.         }
  92.     }
  93.  
  94.     private fun findInvalidPath(
  95.         path: ElementPath,
  96.         checkElement: (MutableElement?) -> Boolean = { it != null }
  97.     ): CommandResult.InvalidPath {
  98.         for (i in currentPath.segments.size until path.segments.size) {
  99.             if (!checkElement(path.getElement(rootElement, i)))
  100.                 return CommandResult.InvalidPath(i - currentPath.segments.size)
  101.         }
  102.         return CommandResult.InvalidPath(path.segments.size - 1)
  103.     }
  104.  
  105.     private fun objectElementOrArrayElement(element: MutableElement?): Boolean {
  106.         return element is MutableElement.ObjectElement || element is MutableElement.ArrayElement
  107.     }
  108.  
  109.  
  110.     /** Always returns [CommandResult.ResultValue] containing the current path starting from the root element, for
  111.      * example, `foo/bar/[0]/baz/[1]`. */
  112.     private fun doPath(): CommandResult.ResultValue {
  113.         return CommandResult.ResultValue(currentPath.toString())
  114.     }
  115.  
  116.     /**
  117.      * Returns [CommandResult.ResultValue] containing the string representation of the current element.
  118.      */
  119.     private fun doPrint(): CommandResult.ResultValue {
  120.         /** Use [Json.encodeToString] */
  121.         return CommandResult.ResultValue(Json.encodeToString(currentPath.getElement(rootElement)?.toJsonElement()))
  122.     }
  123.  
  124.     /**
  125.      * Go to the parent element, one level up. Returns [CommandResult.Failure] if no parent element exists,
  126.      * [CommandResult.Success] otherwise.
  127.      */
  128.     private fun doUp(): CommandResult {
  129.         if (currentPath.segments.isEmpty())
  130.             return CommandResult.Failure
  131.         currentPath = ElementPath(currentPath.segments.dropLast(1))
  132.         return CommandResult.Success
  133.     }
  134.  
  135.     /**
  136.      * Changes the current path to an existing object or array element. If the relative path in [Command.Go.path]
  137.      * points to a non-existing element or an element that is not an object or array, [CommandResult.InvalidPath] is
  138.      * returned. Returns [CommandResult.Success] if the path is changed.
  139.      */
  140.     private fun doGo(go: Command.Go): CommandResult {
  141.  
  142.         val newPath = currentPath.plus(go.path)
  143.         if (objectElementOrArrayElement(newPath.getElement(rootElement))) {
  144.             currentPath = newPath
  145.             return CommandResult.Success
  146.         }
  147.  
  148.         return findInvalidPath(newPath) { element -> objectElementOrArrayElement(element) }
  149.     }
  150.  
  151.     /**
  152.      * Returns [CommandResult.ResultValues] with matching paths. A paths is considered matched if ends with the same
  153.      * segments as the requested [Command.FindPaths.path].
  154.      *
  155.      * In the bonus task, the latter may contain `*` segments, which match any single segment.
  156.      *
  157.      * Example: in an object `{ "a": { "b": { "c": 123 } } }`, there are three paths, `a`, `a/b`, and `a/b/c`.
  158.      * Only the second one matches a request for `a/b` (as `a/b/c` doesn't end with `a/b`).
  159.      */
  160.  
  161.     private fun doFindPaths(command: Command.FindPaths): CommandResult.ResultValues {
  162.         val paths: MutableList<ElementPath> = mutableListOf()
  163.         val prefix: MutableList<String> = mutableListOf()
  164.  
  165.         fun findPaths(element: MutableElement): MutableList<ElementPath> {
  166.  
  167.             paths.add(ElementPath(prefix.toMutableList()))
  168.  
  169.             fun goToNextElement(string: String, element: MutableElement) {
  170.                 prefix.add(string)
  171.                 findPaths(element)
  172.                 prefix.removeLast()
  173.             }
  174.  
  175.             when (element) {
  176.                 is MutableElement.ObjectElement -> {
  177.                     element.items.forEach { goToNextElement(it.key, it.value) }
  178.                 }
  179.                 is MutableElement.ArrayElement -> {
  180.                     element.items.forEachIndexed { index, it -> goToNextElement("[${index}]", it) }
  181.                 }
  182.             }
  183.             return paths
  184.         }
  185.  
  186.         val currentElement = currentPath.getElement(rootElement)!!
  187.         val resultPaths = findPaths(currentElement).map { it.toString() }.distinct()
  188.         return CommandResult.ResultValues(resultPaths.filter { it.endsWith(command.path.toString()) })
  189.     }
  190.  
  191.  
  192.     /**
  193.      * Deletes the element found by the relative path in [Command.Delete.path], removing the element from its parent.
  194.      * If the parent is an array element, the successor elements move one index lower.
  195.      *
  196.      * In the bonus task, the path may contain star segments, so more than one element may be removed.
  197.      *
  198.      * If the path points to a non-existing element, [CommandResult.InvalidPath] is returned. [CommandResult.Success]
  199.      * is returned otherwise.
  200.      */
  201.     private fun doDelete(command: Command.Delete): CommandResult {
  202.         if (currentPath.plus(command.path).getElement(rootElement) != null) {
  203.             val parent = ElementPath(currentPath.segments.plus(command.path.segments).dropLast(1))
  204.                 .getElement(rootElement)
  205.             when (parent) {
  206.                 is MutableElement.ObjectElement -> parent.items.remove(command.path.segments.last())
  207.                 is MutableElement.ArrayElement ->
  208.                     parent.items.remove(parent.items[command.path.segments.last().drop(1).dropLast(1).toInt()])
  209.             }
  210.             return CommandResult.Success
  211.         }
  212.         return findInvalidPath(currentPath.plus(command.path))
  213.     }
  214.  
  215.     /**
  216.      * Replaces the element by the specified [Command.Replace.path] with a new element provided by
  217.      * [Command.Replace.newElement].
  218.      *
  219.      * In the bonus task, the path may contain `*` segments, so more than one element may be replaced.
  220.      */
  221.     private fun doReplace(replace: Command.Replace): CommandResult {
  222.         val elementPath = currentPath.plus(replace.path)
  223.         val toReplace = elementPath.getElement(rootElement)
  224.         if (toReplace != null) {
  225.             when (val parent = ElementPath(elementPath.segments.dropLast(1)).getElement(rootElement)) {
  226.                 is MutableElement.ArrayElement -> {
  227.                     val index = replace.path.segments.last().substring(1, 2).toInt()
  228.                     parent.items[index] = replace.newElement.invoke()
  229.                     return CommandResult.Success
  230.                 }
  231.                 is MutableElement.ObjectElement -> {
  232.                     val index = replace.path.segments.last()
  233.                     parent.items[index] = replace.newElement.invoke()
  234.                     return CommandResult.Success
  235.                 }
  236.             }
  237.         }
  238.         return findInvalidPath(replace.path)
  239.     }
  240.  
  241.     private fun String.isArrayIndex() = this[0] == '[' && this.last() == ']'
  242.     private fun String.getArrayIndex() = this.drop(1).dropLast(1).toIntOrNull()
  243.  
  244.     /**
  245.      * Inserts the element provided by [Command.Insert.newElement] at the designated relative [Command.Insert.path] from
  246.      * the current position.
  247.      *
  248.      * If any intermediate elements in the path do not exist, they are created (they may be object elements as well as
  249.      * array elements if the next segment is an index).
  250.      *
  251.      * If the insertion point position is already taken, [CommandResult.InvalidPath] is returned and no change is made.
  252.      *
  253.      * In an [MutableElement.ArrayElement], as special index segment [ElementPath.appendSegment] specifies that the new
  254.      * element should be added after the last element.
  255.      *
  256.      * If an *intermediate* segment is located in an [MutableElement.ArrayElement] and the index is taken by another
  257.      * element, no change should be made, and the result should be [CommandResult.InvalidPath]. The append segment or
  258.      * the index after the last element are fine to use in intermediate array elements.
  259.      *
  260.      * If the *last* segment is an index in an [MutableElement.ArrayElement], then the new element should be added at
  261.      * the specified index, moving the successor elements one index forward.
  262.      */
  263.     private fun doInsert(insert: Command.Insert): CommandResult {
  264.         var currentElement = currentPath.getElement(rootElement) ?: return CommandResult.Failure
  265.         var parentElement: MutableElement? = null
  266.         var elementToAdd: MutableElement? = null
  267.         var segmentToAdd: String? = null
  268.         for (segmentIndex in insert.path.segments.indices) {
  269.             val currentSegment = insert.path.segments[segmentIndex]
  270.             when (currentElement) {
  271.                 is MutableElement.ArrayElement -> {
  272.                     if (!currentSegment.isArrayIndex())
  273.                         return CommandResult.InvalidPath(segmentIndex)
  274.                     val indexToAdd = currentSegment.getArrayIndex() ?: currentElement.items.size
  275.                     if (indexToAdd > currentElement.items.size)
  276.                         return CommandResult.InvalidPath(segmentIndex)
  277.                     when {
  278.                         segmentIndex == insert.path.segments.size - 1 -> {
  279.                             currentElement.items.add(indexToAdd, insert.newElement.invoke())
  280.                         }
  281.                         insert.path.segments[segmentIndex+1].isArrayIndex() -> {
  282.                             if (indexToAdd < currentElement.items.size) {
  283.                                 if (currentElement.items[indexToAdd] !is MutableElement.ArrayElement)
  284.                                     return CommandResult.InvalidPath(segmentIndex+1)
  285.                                 currentElement = currentElement.items[indexToAdd]
  286.                             } else {
  287.                                 if (parentElement == null) {
  288.                                     parentElement = currentElement
  289.                                     elementToAdd = MutableElement.ArrayElement(mutableListOf())
  290.                                     segmentToAdd = indexToAdd.toString()
  291.                                     currentElement = elementToAdd
  292.                                 } else {
  293.                                     currentElement.items.add(MutableElement.ArrayElement(mutableListOf()))
  294.                                     currentElement = currentElement.items[indexToAdd]
  295.                                 }
  296.                             }
  297.                         }
  298.                         else -> {
  299.                             if (indexToAdd < currentElement.items.size) {
  300.                                 if (currentElement.items[indexToAdd] !is MutableElement.ObjectElement)
  301.                                     return CommandResult.InvalidPath(segmentIndex+1)
  302.                                 currentElement = currentElement.items[indexToAdd]
  303.                             } else {
  304.                                 if (parentElement == null) {
  305.                                     parentElement = currentElement
  306.                                     elementToAdd = MutableElement.ObjectElement(mutableMapOf())
  307.                                     segmentToAdd = indexToAdd.toString()
  308.                                     currentElement = elementToAdd
  309.                                 } else {
  310.                                     currentElement.items.add(MutableElement.ObjectElement(mutableMapOf()))
  311.                                     currentElement = currentElement.items[indexToAdd]
  312.                                 }
  313.                             }
  314.                         }
  315.                     }
  316.                 }
  317.                 is MutableElement.ObjectElement -> {
  318.                     if (currentSegment.isArrayIndex())
  319.                         return CommandResult.InvalidPath(segmentIndex)
  320.                     when {
  321.                         segmentIndex == insert.path.segments.size - 1 -> {
  322.                             currentElement.items[currentSegment] = insert.newElement.invoke()
  323.                         }
  324.                         insert.path.segments[segmentIndex+1].isArrayIndex() -> {
  325.                             if (currentElement.items.containsKey(currentSegment)) {
  326.                                 if (currentElement.items[currentSegment] !is MutableElement.ArrayElement)
  327.                                     return CommandResult.InvalidPath(segmentIndex + 1)
  328.                                 currentElement = currentElement.items[currentSegment]
  329.                                     ?: MutableElement.ArrayElement(mutableListOf())
  330.                             } else {
  331.                                 if (parentElement == null) {
  332.                                     parentElement = currentElement
  333.                                     elementToAdd = MutableElement.ArrayElement(mutableListOf())
  334.                                     segmentToAdd = currentSegment
  335.                                     currentElement = elementToAdd
  336.                                 } else {
  337.                                     currentElement.items[currentSegment] = MutableElement.ArrayElement(mutableListOf())
  338.                                     currentElement = currentElement.items[currentSegment]
  339.                                         ?: MutableElement.ArrayElement(mutableListOf())
  340.                                 }
  341.                             }
  342.                         }
  343.                         else -> {
  344.                             if (currentElement.items.containsKey(currentSegment)) {
  345.                                 if (currentElement.items[currentSegment] !is MutableElement.ObjectElement)
  346.                                     return CommandResult.InvalidPath(segmentIndex + 1)
  347.                                 currentElement = currentElement.items[currentSegment]
  348.                                     ?: MutableElement.ArrayElement(mutableListOf())
  349.                             } else {
  350.                                 if (parentElement == null) {
  351.                                     parentElement = currentElement
  352.                                     elementToAdd = MutableElement.ObjectElement(mutableMapOf())
  353.                                     segmentToAdd = currentSegment
  354.                                     currentElement = elementToAdd
  355.                                 } else {
  356.                                     currentElement.items[currentSegment] = MutableElement.ObjectElement(mutableMapOf())
  357.                                     currentElement = currentElement.items[currentSegment]
  358.                                         ?: MutableElement.ArrayElement(mutableListOf())
  359.                                 }
  360.                             }
  361.                         }
  362.                     }
  363.                 }
  364.             }
  365.         }
  366.         if (parentElement != null && elementToAdd != null && segmentToAdd != null) {
  367.             if (parentElement is MutableElement.ObjectElement)
  368.                 parentElement.items[segmentToAdd] = elementToAdd
  369.             if (parentElement is MutableElement.ArrayElement)
  370.                 parentElement.items.add(segmentToAdd.toInt(), elementToAdd)
  371.         }
  372.         return CommandResult.Success
  373.     }
  374.  
  375.     /**
  376.      * Remembers the path matching [Command.Copy.path] as well as the element, for future insertion into a different
  377.      * element in [doPaste]. In the bonus task, the requested path may match more than one element, all of which need
  378.      * to be copied to the corresponding relative paths at the destination.
  379.      * Assume that no mutating operation is performed between copy and paste.
  380.      */
  381.  
  382.     private var rememberedElement: MutableElement? = null
  383.     private var rememberedPath: ElementPath? = null
  384.  
  385.     private fun doCopy(command: Command.Copy): CommandResult {
  386.         rememberedElement = currentPath.plus(command.path).getElement(rootElement)?.copy()
  387.         rememberedPath = command.path
  388.         if (rememberedElement != null && rememberedPath != null) {
  389.             return CommandResult.Success
  390.         }
  391.         return findInvalidPath(currentPath.plus(command.path))
  392.     }
  393.  
  394.     /**
  395.      * Inserts the element remembered with [doCopy] at the corresponding relative path in the current position.
  396.      */
  397.     private fun doPaste(): CommandResult {
  398.         val tmpPath = rememberedPath
  399.         val tmpElement = rememberedElement
  400.         if (tmpElement == null || tmpPath == null) {
  401.             return CommandResult.Failure
  402.         }
  403.         val result = doInsert(Command.Insert(tmpPath) { tmpElement })
  404.         if (result is CommandResult.InvalidPath) {
  405.             return CommandResult.InvalidPath(result.atIndex)
  406.         }
  407.         return CommandResult.Success
  408.     }
  409. }
  410.  
  411. class ElementPath(val segments: List<String>) {
  412.     override fun toString() = segments.joinToString(segmentSeparator)
  413.  
  414.     fun hasIndexAt(index: Int) = indexAtOrNull(index) != null
  415.     fun indexAtOrNull(index: Int) = toIndexOrNull(segments[index])
  416.     fun hasStarSegmentAt(index: Int) = isStarSegment(segments[index])
  417.     fun hasAppendSegmentAt(index: Int) = isAppendSegment(segments[index])
  418.  
  419.     fun getElement(rootElement: MutableElement, index: Int = segments.size - 1): MutableElement? {
  420.         var element: MutableElement? = rootElement
  421.         for (i in 0..index) {
  422.             if (element == null) return null
  423.             element = element.getChild(segments[i])
  424.         }
  425.         return element
  426.     }
  427.  
  428.     fun plus(path: ElementPath) = ElementPath(segments.plus(path.segments))
  429.  
  430.     companion object {
  431.         const val segmentSeparator = "/"
  432.         const val appendSegment = "[_]"
  433.         const val starSegment = "*"
  434.  
  435.         fun toIndexOrNull(segment: String): Int? =
  436.             segment.removeSurrounding("[", "]").takeIf { it != segment }?.toIntOrNull()
  437.  
  438.         fun isAppendSegment(segment: String) = segment == appendSegment
  439.         fun isStarSegment(segment: String) = segment == starSegment
  440.  
  441.         fun parse(string: String) = ElementPath(string.split(segmentSeparator))
  442.     }
  443. }
  444.  
  445. sealed class CommandResult {
  446.     object Success : CommandResult()
  447.     object Failure : CommandResult()
  448.     class ResultValue(val value: String) : CommandResult()
  449.     class ResultValues(val values: List<String>) : CommandResult()
  450.     class InvalidPath(val atIndex: Int) : CommandResult()
  451. }
  452.  
  453. sealed class Command {
  454.     object Path : Command()
  455.     object Up : Command()
  456.     object Print : Command()
  457.     class Go(val path: ElementPath) : Command()
  458.     class Replace(val path: ElementPath, val newElement: () -> MutableElement) : Command()
  459.     class Insert(val path: ElementPath, val newElement: () -> MutableElement) : Command()
  460.     class Copy(val path: ElementPath) : Command()
  461.     object Paste : Command()
  462.     class FindPaths(val path: ElementPath) : Command()
  463.     class Delete(val path: ElementPath) : Command()
  464. }
  465.  
  466. sealed class MutableElement {
  467.     class PrimitiveNumber(var number: Number) : MutableElement()
  468.     class PrimitiveString(var string: String) : MutableElement()
  469.     class PrimitiveBoolean(var value: Boolean) : MutableElement()
  470.     object PrimitiveNull : MutableElement()
  471.  
  472.     class ArrayElement(val items: MutableList<MutableElement>) : MutableElement()
  473.     class ObjectElement(val items: MutableMap<String, MutableElement>) : MutableElement()
  474.  
  475.     fun toJsonElement(): JsonElement = when (this) {
  476.         is PrimitiveNumber -> JsonPrimitive(number)
  477.         is PrimitiveString -> JsonPrimitive(string)
  478.         is PrimitiveBoolean -> JsonPrimitive(value)
  479.         PrimitiveNull -> JsonNull
  480.         is ArrayElement -> JsonArray(items.map { it.toJsonElement() })
  481.         is ObjectElement -> JsonObject(items.mapValues { it.value.toJsonElement() })
  482.     }
  483.  
  484.     fun copy() : MutableElement? {
  485.         when (this) {
  486.             is PrimitiveBoolean -> return PrimitiveBoolean(value)
  487.             PrimitiveNull -> return null
  488.             is PrimitiveNumber -> return PrimitiveNumber(number)
  489.             is PrimitiveString -> return PrimitiveString(string)
  490.  
  491.             is ArrayElement -> return ArrayElement(items.toMutableList())
  492.             is ObjectElement -> return ObjectElement(items.toMutableMap())
  493.         }
  494.     }
  495.  
  496.     fun getChild(child: String): MutableElement? =
  497.         when (this) {
  498.             is ArrayElement -> {
  499.                 val index = child.drop(1).dropLast(1).toString().toIntOrNull()
  500.                 if (index == null)
  501.                     null
  502.                 else
  503.                     items.getOrNull(index)
  504.             }
  505.             is ObjectElement -> items[child]
  506.             else -> null
  507.         }
  508.  
  509.     companion object {
  510.         fun fromJsonElement(json: JsonElement): MutableElement = when (json) {
  511.             is JsonPrimitive -> when {
  512.                 json.isString -> PrimitiveString(json.content)
  513.                 json is JsonNull -> PrimitiveNull
  514.                 json.content == "true" || json.content == "false" -> PrimitiveBoolean(json.content.toBoolean())
  515.                 json.content.toIntOrNull() != null -> PrimitiveNumber(json.content.toInt())
  516.                 json.content.toDoubleOrNull() != null -> PrimitiveNumber(json.content.toDouble())
  517.                 else -> PrimitiveString(json.content)
  518.             }
  519.             is JsonArray -> ArrayElement(json.mapTo(mutableListOf()) { fromJsonElement(it) })
  520.             is JsonObject -> ObjectElement(json.keys.associateTo(mutableMapOf()) { key ->
  521.                 key to fromJsonElement(json.getValue(key))
  522.             })
  523.         }
  524.     }
  525. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement