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

main.eu.europa.ec.eudi.sdjwt.JsonPointer.kt Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2023 European Commission
 *
 * 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 eu.europa.ec.eudi.sdjwt

/**
 * A pointer to a specific value withing a JSON object.
 * @see RFC6901
 */
class JsonPointer private constructor(internal val tokens: List) {

    /**
     * Gets the parent of this pointer, if any.
     */
    fun parent(): JsonPointer? =
        when (this) {
            Root -> null
            else -> JsonPointer(tokens.dropLast(1))
        }

    /**
     * Gets a new pointer that points to a child of this pointer.
     */
    fun child(child: String): JsonPointer = JsonPointer(tokens + child)

    /**
     * Gets a new pointer that points to a child of this pointer. Applicable for array elements.
     */
    fun child(index: Int): JsonPointer {
        require(index >= 0) { "index must be greater than or equal to '0'" }
        return child(index.toString())
    }

    override fun toString(): String =
        tokens.fold("") { accumulator, current -> "$accumulator/${current.encode()}" }

    override fun equals(other: Any?): Boolean =
        when {
            this === other -> true
            other is JsonPointer -> tokens == other.tokens
            else -> false
        }

    override fun hashCode(): Int = tokens.hashCode()

    companion object {

        /**
         * The root element.
         */
        val Root: JsonPointer = JsonPointer(emptyList())

        /**
         * Parses a string as a pointer.
         * @return the JsonPointer or null if the provided [pointer] is not
         * a valid representation
         */
        fun parse(pointer: String): JsonPointer? = runCatching {
            when {
                pointer.isEmpty() -> Root
                !pointer.startsWith("/") -> null
                else -> {
                    val tokens = pointer.split("/")
                        .drop(1)
                        .map { it.decode() }
                    JsonPointer(tokens)
                }
            }
        }.getOrNull()

        /**
         * Encodes a token by replacing instances of '~' with '~0', and instances of '/' with '~1'.
         */
        private fun String.encode() = replace("~", "~0").replace("/", "~1")

        /**
         * Regex used to check whether tokens contain invalid escape sequences.
         */
        private val invalidEscapeSequence = "(~[^0-1])|(~\$)".toRegex()

        /**
         * Decodes a token by replacing instances of '~1' with '/', and instances of '~0' with '~'.
         */
        private fun String.decode(): String {
            require(!matches(invalidEscapeSequence)) { "token contains an invalid escape sequence" }
            return replace("~1", "/").replace("~0", "~")
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy