diff --git a/README.md b/README.md index b038104..3636722 100644 --- a/README.md +++ b/README.md @@ -53,13 +53,16 @@ root.withPath().branch.leaf.path().jsonPath // will return "$.branch.leaf" See more in [path-reflection.md](docs/path-reflection.md) -# Dynamic access +# [Dynamic access](docs/dynamic-access.md) + +All `@Buildable` classes can be dynamically accessed. Annotate class: ```kotlin -@DynamicallyAccessible +@Buildable data class Item( - val value: String + val value: String, + val list: List> // ... ) ``` @@ -67,8 +70,11 @@ data class Item( and access data dynamically with generated accessors: ```kotlin item.dynamicAccessor["value"] // returns item.value +item.dynamicAccessor["$.list[2]['element']"] // returns item.list[2]["element"] ``` +See more in [path-reflection.md](docs/dynamic-access.md) + # How to set up? 0. Have open source repositories connected to project: ```kotlin diff --git a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/BuildataSymbolProcessor.kt b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/BuildataSymbolProcessor.kt index 9d15469..24a693d 100644 --- a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/BuildataSymbolProcessor.kt +++ b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/BuildataSymbolProcessor.kt @@ -21,7 +21,6 @@ import com.google.devtools.ksp.processing.SymbolProcessor import com.google.devtools.ksp.symbol.KSAnnotated import com.google.devtools.ksp.symbol.KSClassDeclaration import io.github.virelion.buildata.ksp.Constants.BUILDABLE_FQNAME -import io.github.virelion.buildata.ksp.Constants.DYNAMICALLY_ACCESSIBLE_FQNAME import io.github.virelion.buildata.ksp.Constants.PATH_REFLECTION_FQNAME import io.github.virelion.buildata.ksp.extensions.printableFqName @@ -67,7 +66,7 @@ class BuildataSymbolProcessor( // Stream Dynamically accessible code-generated classes resolver - .getSymbolsWithAnnotation(DYNAMICALLY_ACCESSIBLE_FQNAME) + .getSymbolsWithAnnotation(BUILDABLE_FQNAME) .apply { filterIsInstance() .map { diff --git a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/BuilderClassTemplate.kt b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/BuilderClassTemplate.kt index b23eacc..af8928b 100644 --- a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/BuilderClassTemplate.kt +++ b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/BuilderClassTemplate.kt @@ -16,6 +16,7 @@ package io.github.virelion.buildata.ksp import io.github.virelion.buildata.ksp.extensions.typeForDocumentation +import io.github.virelion.buildata.ksp.path.StringNamePathIdentifier import io.github.virelion.buildata.ksp.utils.CodeBuilder internal class BuilderClassTemplate( @@ -120,7 +121,7 @@ internal class BuilderClassTemplate( propertiesDocumentation ) appendln("@BuildataDSL") - indentBlock("class $builderName() : Builder<$originalName>") { + indentBlock("class $builderName() : Builder<$originalName>, StringAccessible") { properties.forEach { it.generatePropertyDeclaration(this) } @@ -128,6 +129,8 @@ internal class BuilderClassTemplate( generateBuildFunction() emptyLine() generateBuilderPopulateWithFunction() + emptyLine() + generateAccessElementFunction() } } @@ -183,9 +186,31 @@ internal class BuilderClassTemplate( } } + fun CodeBuilder.generateAccessElementFunction() { + appendDocumentation( + """ + Access class element builder using string identifier + + @param key element name + @returns element, elements builder or null if not set + """.trimIndent() + ) + indentBlock("override fun accessElement(key: String): Any?") { + indentBlock("return when(key)") { + properties.forEach { + val pathIdentifier = StringNamePathIdentifier(it).getPropertyName() + appendln(""""$pathIdentifier" -> ${it.generateDirectAccessLine()}""") + } + appendln("""else -> throw MissingPropertyException(key, "$originalName")""") + } + } + } + companion object { val imports: List = listOf( "io.github.virelion.buildata.*", + "io.github.virelion.buildata.access.MissingPropertyException", + "io.github.virelion.buildata.access.StringAccessible", "kotlin.reflect.KClass" ).sorted() diff --git a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/ClassProperty.kt b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/ClassProperty.kt index 54d1633..932ebae 100644 --- a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/ClassProperty.kt +++ b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/ClassProperty.kt @@ -67,4 +67,16 @@ class ClassProperty( appendln("$name = it.$name") } } + + fun generateDirectAccessLine(): String { + return if (buildable) { + if (nullable) { + "if($backingPropName.setToNull) null else $backingPropName.builder" + } else { + "$backingPropName.builder" + } + } else { + "$backingPropName.container" + } + } } diff --git a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/Constants.kt b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/Constants.kt index 04b5220..41b04cc 100644 --- a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/Constants.kt +++ b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/Constants.kt @@ -17,6 +17,5 @@ package io.github.virelion.buildata.ksp object Constants { val BUILDABLE_FQNAME = "io.github.virelion.buildata.Buildable" - val DYNAMICALLY_ACCESSIBLE_FQNAME = "io.github.virelion.buildata.access.DynamicallyAccessible" val PATH_REFLECTION_FQNAME = "io.github.virelion.buildata.path.PathReflection" } diff --git a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/KSClassDeclarationProcessor.kt b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/KSClassDeclarationProcessor.kt index e334971..aa623a3 100644 --- a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/KSClassDeclarationProcessor.kt +++ b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/KSClassDeclarationProcessor.kt @@ -15,7 +15,6 @@ */ package io.github.virelion.buildata.ksp -import com.google.devtools.ksp.getDeclaredProperties import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.symbol.KSClassDeclaration import com.google.devtools.ksp.symbol.KSNode @@ -34,8 +33,7 @@ internal class KSClassDeclarationProcessor( ksClassDeclaration.apply { return AccessorExtensionsTemplate( pkg = this.packageName.asString(), - originalName = this.simpleName.getShortName(), - properties = ksClassDeclaration.getDeclaredProperties().map { it.simpleName.asString() }.toList() + originalName = this.simpleName.getShortName() ) } } diff --git a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/access/AccessorExtensionsTemplate.kt b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/access/AccessorExtensionsTemplate.kt index dc62c0e..e8c003e 100644 --- a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/access/AccessorExtensionsTemplate.kt +++ b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/access/AccessorExtensionsTemplate.kt @@ -20,8 +20,7 @@ import io.github.virelion.buildata.ksp.utils.CodeBuilder class AccessorExtensionsTemplate( override val pkg: String, - val originalName: String, - val properties: List + val originalName: String ) : GeneratedFileTemplate { override val name: String get() = "${originalName}_AccessorExtension" @@ -34,67 +33,24 @@ class AccessorExtensionsTemplate( } emptyLine() createAccessorExtensionProperty() - emptyLine() - createPropertyAccessExtensionFunction() - emptyLine() - createPropertyValueAccessExtensionFunction() } } private fun CodeBuilder.createAccessorExtensionProperty() { appendDocumentation( """ - Accessor that allows for dynamic access (via property name) to properties and values. + Accessor that allows for dynamic access (via property name or path) to properties and values. """.trimIndent() ) - appendln("val $originalName.dynamicAccessor: DynamicAccessor<$originalName> get() = DynamicAccessor(this)") - } - - private fun CodeBuilder.createPropertyAccessExtensionFunction() { - appendDocumentation( - """ - Get property of class under specified name - - @param name property name - @throws [io.github.virelion.buildata.access.MissingPropertyException] if property is missing - @returns property object or null in case it is missing - """.trimIndent() - ) - indentBlock("fun DynamicAccessor<$originalName>.getProperty(name: String): KProperty0<*>?") { - indentBlock("return when(name)") { - properties.forEach { propertyName -> - appendln(""""$propertyName" -> this.target::`$propertyName`""") - } - appendln("""else -> throw MissingPropertyException(name, "$pkg.$originalName")""") - } - } - } - - private fun CodeBuilder.createPropertyValueAccessExtensionFunction() { - appendDocumentation( - """ - Get value of property under specified name - - @param name property name - @throws [io.github.virelion.buildata.access.MissingPropertyException] if property is missing - @returns property value - """.trimIndent() - ) - indentBlock("operator fun DynamicAccessor<$originalName>.get(name: String): T") { - indentBlock("return when(name)") { - properties.forEach { propertyName -> - appendln(""""$propertyName" -> this.target.`$propertyName`""") - } - appendln("""else -> throw MissingPropertyException(name, "$pkg.$originalName")""") - } - append(" as T") + appendln("val $originalName.dynamicAccessor: DynamicAccessor get() =") + indent { + appendln("DynamicAccessor($originalName::class.builder().also { it.populateWith(this) })") } } companion object { val imports: List = listOf( - "io.github.virelion.buildata.access.*", - "kotlin.reflect.KProperty0" + "io.github.virelion.buildata.access.*" ).sorted() } } diff --git a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/path/StringNamePathIdentifier.kt b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/path/StringNamePathIdentifier.kt index 85c26dc..32b4b17 100644 --- a/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/path/StringNamePathIdentifier.kt +++ b/buildata-ksp-plugin/src/main/kotlin/io/github/virelion/buildata/ksp/path/StringNamePathIdentifier.kt @@ -30,7 +30,7 @@ value class StringNamePathIdentifier( val JACKSON_ALIAS = "com.fasterxml.jackson.annotation.JsonAlias" } - private fun getPropertyName(): String { + fun getPropertyName(): String { return getAnnotatedName() ?: classProperty.name } diff --git a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/BuilderElementProperty.kt b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/BuilderElementProperty.kt index 2da5714..2c048c1 100644 --- a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/BuilderElementProperty.kt +++ b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/BuilderElementProperty.kt @@ -24,7 +24,7 @@ import kotlin.reflect.KProperty class BuilderElementProperty : ReadWriteProperty { var initialized = false private set - private var container: T? = null + var container: T? = null /** * Sets property value. diff --git a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/BuilderNullableCompositeElementProperty.kt b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/BuilderNullableCompositeElementProperty.kt index fca30f4..4a734c9 100644 --- a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/BuilderNullableCompositeElementProperty.kt +++ b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/BuilderNullableCompositeElementProperty.kt @@ -26,7 +26,7 @@ import kotlin.reflect.KProperty class BuilderNullableCompositeElementProperty>( val builderProvider: () -> B ) : ReadWriteProperty { - private var setToNull = false + var setToNull = false var initialized = false private set var builder: B = builderProvider() diff --git a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/DynamicAccessor.kt b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/DynamicAccessor.kt index 5a1a34f..33896cf 100644 --- a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/DynamicAccessor.kt +++ b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/DynamicAccessor.kt @@ -15,7 +15,137 @@ */ package io.github.virelion.buildata.access +import io.github.virelion.buildata.Builder +import io.github.virelion.buildata.path.IntIndexPathIdentifier +import io.github.virelion.buildata.path.PathIdentifier +import io.github.virelion.buildata.path.RecordedPath +import io.github.virelion.buildata.path.StringIndexPathIdentifier +import io.github.virelion.buildata.path.StringNamePathIdentifier + /** * Marker for code-generated class dynamic elements access. */ -class DynamicAccessor(val target: T) +class DynamicAccessor(private val builder: Builder<*>) { + @Throws(ElementNotFoundException::class) + operator fun get(path: String): R? { + return get(*RecordedPath.Parser.parse(path).path.toTypedArray()) + } + + @Throws(ElementNotFoundException::class) + operator fun get(vararg path: PathIdentifier): R? { + if (path.isEmpty()) return builder.build() as R + + val element: PathAccessProcessingAccumulator = path + .fold(PathAccessProcessingAccumulator(builder, listOf())) { acc, pathIdentifier -> + resolvePathIdentifier(pathIdentifier, acc) + } + + if (element.item is Builder<*>) { + return element.item.build() as R + } + + return element.item as R + } + + private data class PathAccessProcessingAccumulator( + val item: Any?, + val pathProcessed: List + ) + + private fun resolvePathIdentifier( + pathElement: PathIdentifier, + acc: PathAccessProcessingAccumulator + ): PathAccessProcessingAccumulator { + return try { + when (pathElement) { + is IntIndexPathIdentifier -> resolveIntIndexPathIdentifier(pathElement, acc) + is StringIndexPathIdentifier -> resolveStringIndexPathIdentifier(pathElement, acc) + is StringNamePathIdentifier -> resolveStringNamePathIdentifier(pathElement, acc) + } + } catch (e: Exception) { handleException(e, pathElement, acc) } + } + + private fun resolveIntIndexPathIdentifier( + pathElement: IntIndexPathIdentifier, + acc: PathAccessProcessingAccumulator + ): PathAccessProcessingAccumulator { + return when (acc.item) { + is List<*> -> { + // additional check for KotlinJS + if (pathElement.index >= acc.item.size) throw IndexOutOfBoundsException() + acc.item[pathElement.index] + } + is Array<*> -> { + // additional check for KotlinJS + if (pathElement.index >= acc.item.size) throw IndexOutOfBoundsException() + acc.item[pathElement.index] + } + is Map<*, *> -> { + if (pathElement.index !in acc.item) throw IndexOutOfBoundsException() + acc.item[pathElement.index] + } + is IntAccessible -> acc.item.accessElement(pathElement.index) + else -> throw ElementNotFoundException( + pathProcessed = RecordedPath(acc.pathProcessed), + lastItemProcessed = acc.item, + lastProcessedPathIdentifier = pathElement + ) + }.let { PathAccessProcessingAccumulator(it, acc.pathProcessed + pathElement) } + } + + private fun resolveStringIndexPathIdentifier( + pathElement: StringIndexPathIdentifier, + acc: PathAccessProcessingAccumulator + ): PathAccessProcessingAccumulator { + return when (acc.item) { + is Map<*, *> -> { + if (pathElement.index !in acc.item) throw IndexOutOfBoundsException() + acc.item[pathElement.index] + } + is StringAccessible -> acc.item.accessElement(pathElement.index) + else -> throw ElementNotFoundException( + pathProcessed = RecordedPath(acc.pathProcessed), + lastItemProcessed = acc.item, + lastProcessedPathIdentifier = pathElement + ) + }.let { PathAccessProcessingAccumulator(it, acc.pathProcessed + pathElement) } + } + + private fun resolveStringNamePathIdentifier( + pathElement: StringNamePathIdentifier, + acc: PathAccessProcessingAccumulator + ): PathAccessProcessingAccumulator { + if (acc.item !is StringAccessible) { + throw ElementNotFoundException( + pathProcessed = RecordedPath(acc.pathProcessed), + lastItemProcessed = acc.item, + lastProcessedPathIdentifier = pathElement + ) + } + return acc.item.accessElement(pathElement.name) + .let { PathAccessProcessingAccumulator(it, acc.pathProcessed + pathElement) } + } + + private fun handleException( + e: Exception, + pathElement: PathIdentifier, + acc: PathAccessProcessingAccumulator + ): Nothing { + when (e) { + is MissingPropertyException, is IndexOutOfBoundsException -> { + val item = if (acc.item is Builder<*>) { + acc.item.build() + } else { + acc.item + } + + throw ElementNotFoundException( + pathProcessed = RecordedPath(acc.pathProcessed), + lastItemProcessed = item, + lastProcessedPathIdentifier = pathElement + ) + } + else -> throw e + } + } +} diff --git a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/ElementNotFoundException.kt b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/ElementNotFoundException.kt new file mode 100644 index 0000000..d0660f6 --- /dev/null +++ b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/ElementNotFoundException.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.access + +import io.github.virelion.buildata.path.PathIdentifier +import io.github.virelion.buildata.path.RecordedPath + +class ElementNotFoundException( + /** + * Path processed before element was not found + */ + val pathProcessed: RecordedPath, + + /** + * Last element processed + */ + val lastItemProcessed: Any?, + + /** + * Path identifier of element that was not found + */ + val lastProcessedPathIdentifier: PathIdentifier +) : RuntimeException( + "On $pathProcessed cannot $lastProcessedPathIdentifier access on $lastItemProcessed" +) diff --git a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/DynamicallyAccessible.kt b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/IntAccessible.kt similarity index 64% rename from buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/DynamicallyAccessible.kt rename to buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/IntAccessible.kt index 6ff17e9..9d3ad09 100644 --- a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/DynamicallyAccessible.kt +++ b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/IntAccessible.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Maciej Ziemba + * Copyright 2022 Maciej Ziemba * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,19 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package io.github.virelion.buildata.access -/** - * Use this annotation to mark data class as target for builder generation. - * - * ```kotlin - * @DynamicallyAccessible - * data class Item( - * val item: String - * // ... - * ) - * ``` - */ -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.RUNTIME) -annotation class DynamicallyAccessible +interface IntAccessible { + @Throws(IndexOutOfBoundsException::class) + fun accessElement(key: Int): Any? +} diff --git a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/MissingPropertyException.kt b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/MissingPropertyException.kt index df2d1c2..373c363 100644 --- a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/MissingPropertyException.kt +++ b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/MissingPropertyException.kt @@ -1,5 +1,5 @@ /* - * Copyright 2021 Maciej Ziemba + * Copyright 2022 Maciej Ziemba * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package io.github.virelion.buildata.access -class MissingPropertyException(val propertyName: String, className: String) : - RuntimeException("Property '$propertyName' could not be found in $className") +class MissingPropertyException( + val pathElement: String, + val className: String +) : Exception( + "Cannot access $pathElement in $className" +) diff --git a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/StringAccessible.kt b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/StringAccessible.kt new file mode 100644 index 0000000..3125dc8 --- /dev/null +++ b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/access/StringAccessible.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.access + +interface StringAccessible { + @Throws(MissingPropertyException::class) + fun accessElement(key: String): Any? +} diff --git a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/path/PathIdentifier.kt b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/path/PathIdentifier.kt index a2b0112..c1c97b1 100644 --- a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/path/PathIdentifier.kt +++ b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/path/PathIdentifier.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.path /** @@ -16,7 +32,7 @@ sealed class PathIdentifier * KClass.path().list[2] * ``` */ -class IntIndexPathIdentifier(val index: Int) : PathIdentifier() +data class IntIndexPathIdentifier(val index: Int) : PathIdentifier() /** * Element accessed as [String] index. @@ -29,7 +45,7 @@ class IntIndexPathIdentifier(val index: Int) : PathIdentifier() * KClass.path().map["element"] * ``` */ -class StringIndexPathIdentifier(val index: String) : PathIdentifier() +data class StringIndexPathIdentifier(val index: String) : PathIdentifier() /** * Element accessed as [String] property name. @@ -42,4 +58,4 @@ class StringIndexPathIdentifier(val index: String) : PathIdentifier() * KClass.path().str * ``` */ -class StringNamePathIdentifier(val name: String) : PathIdentifier() +data class StringNamePathIdentifier(val name: String) : PathIdentifier() diff --git a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/path/RecordedPath.kt b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/path/RecordedPath.kt index 63f1b93..877261f 100644 --- a/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/path/RecordedPath.kt +++ b/buildata-runtime/src/commonMain/kotlin/io/github/virelion/buildata/path/RecordedPath.kt @@ -23,15 +23,75 @@ import kotlin.jvm.JvmInline * Elements are ordered as they were accessed: first element accessed is first element of collection, etc. */ @JvmInline -value class RecordedPath(private val item: List = listOf()) { +value class RecordedPath(val path: List = listOf()) { + object Parser { + private val NO_TOKEN = 0.toChar() + + private fun createPathIdentifierOutOfToken(token: Char, identifier: String): PathIdentifier { + return when (token) { + '.' -> StringNamePathIdentifier(identifier) + '[' -> { + identifier.toIntOrNull() + ?.let { IntIndexPathIdentifier(it) } + ?: identifier + .removeSurrounding("'") + .removeSurrounding("\"") + .let { StringIndexPathIdentifier(it) } + } + else -> throw IllegalArgumentException("Unrecognized token $token") + } + } + + fun parse(path: String): RecordedPath { + if (path.trim() == "" || path.trim() == "$") return RecordedPath(listOf()) + + val preparedPath = path.removePrefix("$").let { + if (!(it.startsWith(".") || it.startsWith("["))) { + ".$it" + } else { + it + } + }.trim() + val iterator = preparedPath.iterator() + + val identifiers = mutableListOf() + var tokenType = NO_TOKEN + var identifier = "" + + while (iterator.hasNext()) { + when (val nextCharacter = iterator.nextChar()) { + '.', '[' -> { + if (tokenType != NO_TOKEN) { + identifiers += createPathIdentifierOutOfToken(tokenType, identifier) + identifier = "" + } + tokenType = nextCharacter + } + ']' -> { + require(tokenType == '[') { "Found closing bracket ], but there is no opening bracket [" } + } + else -> { + identifier += nextCharacter + } + } + } + + if (tokenType != NO_TOKEN) { + identifiers += createPathIdentifierOutOfToken(tokenType, identifier) + } + + return RecordedPath(identifiers) + } + } + operator fun plus(identifier: PathIdentifier): RecordedPath { - return RecordedPath(item + identifier) + return RecordedPath(path + identifier) } val jsonPath: String get() { val builder = StringBuilder() builder.append("$") - item.forEach { + path.forEach { when (it) { is IntIndexPathIdentifier -> builder.append("[${it.index}]") is StringIndexPathIdentifier -> builder.append("['${it.index}']") diff --git a/buildata-runtime/src/commonTest/kotlin/io/github/virelion/buildata/path/RecordedPathTest.kt b/buildata-runtime/src/commonTest/kotlin/io/github/virelion/buildata/path/RecordedPathTest.kt new file mode 100644 index 0000000..232f14d --- /dev/null +++ b/buildata-runtime/src/commonTest/kotlin/io/github/virelion/buildata/path/RecordedPathTest.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.path + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class RecordedPathTest { + private val parser = RecordedPath.Parser + private fun assertPathEquals( + expected: String, + actual: RecordedPath + ) { + assertEquals(expected, actual.jsonPath) + } + + @Test + fun emptyPathFromString() { + assertTrue(parser.parse("").path.isEmpty()) + assertTrue(parser.parse("$").path.isEmpty()) + } + + @Test + fun noDotPrefixAccess() { + assertPathEquals("$.el1", parser.parse("el1")) + assertPathEquals("$.el1.el2", parser.parse("el1.el2")) + } + + @Test + fun singleStringNameIdentifier() { + assertPathEquals("$.StringNamedElement", parser.parse("$.StringNamedElement")) + } + + @Test + fun doubleStringNameIdentifier() { + assertPathEquals( + "$.StringNamedElement1.StringNamedElement2", + parser.parse("$.StringNamedElement1.StringNamedElement2") + ) + } + + @Test + fun singleStringIndexIdentifier() { + assertPathEquals("$['StringNamedElement1']", parser.parse("$['StringNamedElement1']")) + assertPathEquals("$['StringNamedElement1']", parser.parse("$[\"StringNamedElement1\"]")) + assertPathEquals("$['StringNamedElement1']", parser.parse("$[StringNamedElement1]")) + } + + @Test + fun doubleStringIndexIdentifier() { + assertPathEquals("$['id1']['id2']", parser.parse("$['id1']['id2']")) + } + + @Test + fun singleIntIndexIdentifier() { + assertPathEquals("$[1]", parser.parse("$[1]")) + } + + @Test + fun doubleIntIndexIdentifier() { + assertPathEquals("$[1][2]", parser.parse("$[1][2]")) + assertPathEquals("$[1][2]", parser.parse("[1][2]")) + } + + @Test + fun mixedPaths() { + assertPathEquals("$.abc['StringNamedElement1']", parser.parse("$.abc[StringNamedElement1]")) + assertPathEquals("$.abc[1][2]['StringNamedElement1']", parser.parse("$.abc[1][2][StringNamedElement1]")) + assertPathEquals("$[1].abc[2].efg['StringNamedElement1'].h", parser.parse("$[1].abc[2].efg['StringNamedElement1'].h")) + } +} diff --git a/docs/dynamic-access.md b/docs/dynamic-access.md new file mode 100644 index 0000000..6ad523b --- /dev/null +++ b/docs/dynamic-access.md @@ -0,0 +1,52 @@ +# Dynamic access + +All `@Buildable` classes can be dynamically accessed. +## Usage +Let's consider following data tree structure. + +```kotlin +@Buildable +data class Root( + val branch1: Branch, + @PathElementName("CUSTOM_NAME") + val branch2: Branch?, + val list: List, + val map: Map +) + +@Buildable +data class Branch( + val stringValue: String +) +``` + +You can access elements in following way + +```kotlin +fun accessElement(root: Root) { + with(root.dynamicAccessor) { + this["branch1"] // returns root.branch1 + this["$.CUSTOM_NAME"] // returns root.branch2 + this["$.list"] // returns root.list + this["$.map"] // returns root.map + + this["$.branch1.stringValue"] // returns root.branch1.stringValue + this["$.list[0].stringValue"] // returns root.list[0].stringValue + this["$.map['someKey'].stringValue"] // returns root.list[0].stringValue + this["""$.map["someKey"].stringValue"""] // returns root.list[0].stringValue + this["$.map.someKey.stringValue"] // returns root.list[0].stringValue + } +} +``` + +If element is not found `ElementNotFoundException` is thrown. + +### Rename annotations support +Dynamic access honors rename annotations. + +Annotations: +- `@io.github.virelion.buildata.path.PathElementName("newName")` +- `@kotlinx.serialization.SerialName("newName")` +- `@com.fasterxml.jackson.annotation.JsonAlias("newName")` + +are supported out of the box. \ No newline at end of file diff --git a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/access/DynamicAccess.kt b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/access/DynamicAccess.kt index 0c5e994..7f58ae0 100644 --- a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/access/DynamicAccess.kt +++ b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/access/DynamicAccess.kt @@ -15,18 +15,55 @@ */ package io.github.virelion.buildata.integration.access -import io.github.virelion.buildata.access.DynamicallyAccessible +import io.github.virelion.buildata.Buildable +import io.github.virelion.buildata.access.IntAccessible +import io.github.virelion.buildata.access.StringAccessible +import io.github.virelion.buildata.path.PathElementName -@DynamicallyAccessible -class DynamicAccessRoot( +@Buildable +data class DynamicAccessRoot( val map: Map, + val intMap: Map = mapOf(), val list: List, + val array: Array, val element: DynamicAccessInner1, - val nullable_element: DynamicAccessInner1? + val nullable_element: DynamicAccessInner1?, + + @PathElementName("CUSTOM_NAME") + val customName: String = "" ) -@DynamicallyAccessible -class DynamicAccessInner1( +@Buildable +data class DynamicAccessInner1( val map: Map, val list: List, + val intMap: Map, + val array: Array, + val customStringAccessible: CustomStringAccessible = CustomStringAccessible, + val customIntAccessible: CustomIntAccessible = CustomIntAccessible, +) + +@Buildable +data class ComplexData( + val data: Map> ) + +object CustomStringAccessible : StringAccessible { + override fun accessElement(key: String): Any? { + if (key == "custom") { + return "custom" + } else { + throw IndexOutOfBoundsException() + } + } +} + +object CustomIntAccessible : IntAccessible { + override fun accessElement(key: Int): Any? { + if (key == 42) { + return "custom" + } else { + throw IndexOutOfBoundsException() + } + } +} diff --git a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/builder/inner/Level3Class.kt b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/builder/inner/Level3Class.kt index 0edf48d..bd7d960 100644 --- a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/builder/inner/Level3Class.kt +++ b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/builder/inner/Level3Class.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.builder.inner import io.github.virelion.buildata.Buildable diff --git a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/AnnotatedLeaf.kt b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/AnnotatedLeaf.kt index 0af795d..f7a5852 100644 --- a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/AnnotatedLeaf.kt +++ b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/AnnotatedLeaf.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path.inner import com.fasterxml.jackson.annotation.JsonAlias diff --git a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/Inner1.kt b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/Inner1.kt index b23d8a6..a665fe4 100644 --- a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/Inner1.kt +++ b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/Inner1.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path.inner import io.github.virelion.buildata.Buildable diff --git a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/Inner2.kt b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/Inner2.kt index 160e70d..f8a5460 100644 --- a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/Inner2.kt +++ b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/Inner2.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path.inner import io.github.virelion.buildata.Buildable diff --git a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/LeafNode.kt b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/LeafNode.kt index 3b00868..abbf47b 100644 --- a/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/LeafNode.kt +++ b/integration-test-project/project-types/jvm/src/main/kotlin/io/github/virelion/buildata/integration/path/inner/LeafNode.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path.inner import io.github.virelion.buildata.Buildable diff --git a/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/access/DynamicAccessTest.kt b/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/access/DynamicAccessTest.kt index 25f4ba9..4a993fc 100644 --- a/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/access/DynamicAccessTest.kt +++ b/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/access/DynamicAccessTest.kt @@ -1,81 +1,182 @@ +/* + * Copyright 2021 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.access -import io.github.virelion.buildata.access.MissingPropertyException +import io.github.virelion.buildata.access.ElementNotFoundException +import io.github.virelion.buildata.path.IntIndexPathIdentifier +import io.github.virelion.buildata.path.StringIndexPathIdentifier +import io.github.virelion.buildata.path.StringNamePathIdentifier import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertNotNull +import kotlin.test.assertFailsWith import kotlin.test.assertNull -import kotlin.test.assertSame +import kotlin.test.assertTrue class DynamicAccessTest { private val root = DynamicAccessRoot( mapOf("root" to "Root"), + mapOf(1 to "Root"), listOf("root"), + arrayOf("arrayElement"), DynamicAccessInner1( mapOf("inner1" to "Inner1"), listOf("inner1"), + mapOf(1 to "element"), + arrayOf("arrayElement"), ), - null + null, + "value" ) @Test fun isValueAccessPossible() { with(root.dynamicAccessor) { - assertSame(root.map, this["map"]) - assertSame(root.list, this["list"]) - assertSame(root.element, this["element"]) + assertEquals(root.map, this["map"]) + assertEquals(root.list, this["list"]) + assertEquals(root.array, this["array"]) + assertEquals(root.intMap, this["intMap"]) + assertEquals(root.element, this["element"]) assertNull(this["nullable_element"]) - with(this.get("element").dynamicAccessor) { - assertSame(root.element.map, this["map"]) - assertSame(root.element.list, this["list"]) + + assertEquals(root.element.map, this["element.map"]) + assertEquals(root.element.list, this["element.list"]) + assertEquals(root.element.intMap, this["element.intMap"]) + assertEquals(root.element.array, this["element.array"]) + + assertEquals(root.element.map["inner1"], this["element.map['inner1']"]) + assertEquals(root.element.list[0], this["element.list[0]"]) + assertEquals(root.element.intMap[1], this["element.intMap[1]"]) + assertEquals(root.element.array[0], this["element.array[0]"]) + assertEquals("custom", this["element.customStringAccessible['custom']"]) + assertEquals("custom", this["element.customIntAccessible[42]"]) + assertEquals("value", this["CUSTOM_NAME"]) + } + } + + @Test + fun cannotFindElement() { + with(root.dynamicAccessor) { + assertFailsWith { + println(this.get("notAnElement")) + }.apply { + assertTrue(this.pathProcessed.path.isEmpty()) + assertEquals(root, lastItemProcessed) + assertEquals(StringNamePathIdentifier("notAnElement"), lastProcessedPathIdentifier) } } } @Test - fun exceptionIsThrownWhenElementIsNotFound() { + fun cannotFindElementOnStringIndexMap() { with(root.dynamicAccessor) { - var exception: MissingPropertyException? = null - try { - this["InvalidElement"] - } catch (e: MissingPropertyException) { - exception = e + assertFailsWith { + println(this.get("map['element']")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("map")), this.pathProcessed.path) + assertEquals(root.map, lastItemProcessed) + assertEquals(StringIndexPathIdentifier("element"), lastProcessedPathIdentifier) } - assertNotNull(exception) - with(exception) { - assertEquals("InvalidElement", this.propertyName) - assertEquals("Property 'InvalidElement' could not be found in io.github.virelion.buildata.integration.access.DynamicAccessRoot", this.message) + } + } + + @Test + fun cannotFindElementOnIntIndexMap() { + with(root.dynamicAccessor) { + assertFailsWith { + println(this.get("intMap[42]")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("intMap")), this.pathProcessed.path) + assertEquals(root.intMap, lastItemProcessed) + assertEquals(IntIndexPathIdentifier(42), lastProcessedPathIdentifier) } } } @Test - fun isPropertyAccessPossible() { + fun cannotFindElementOnIntIndexList() { with(root.dynamicAccessor) { - assertSame(root::map.get(), this.getProperty("map")?.get()) - assertSame(root::list.get(), this.getProperty("list")?.get()) - assertSame(root::element.get(), this.getProperty("element")?.get()) - assertNull(this["nullable_element"]) - with(this.get("element").dynamicAccessor) { - assertSame(root.element::map.get(), this.getProperty("map")?.get()) - assertSame(root.element::list.get(), this.getProperty("list")?.get()) + assertFailsWith { + println(this.get("list[42]")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("list")), this.pathProcessed.path) + assertEquals(root.list, lastItemProcessed) + assertEquals(IntIndexPathIdentifier(42), lastProcessedPathIdentifier) + } + } + } + + @Test + fun cannotFindElementOnIntIndexArray() { + with(root.dynamicAccessor) { + assertFailsWith { + println(this.get("array[42]")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("array")), this.pathProcessed.path) + assertEquals(root.array, lastItemProcessed) + assertEquals(IntIndexPathIdentifier(42), lastProcessedPathIdentifier) } } } @Test - fun exceptionIsThrownWhenPropertyIsNotFound() { + fun cannotFindElementOnCustomStringAccessible() { with(root.dynamicAccessor) { - var exception: MissingPropertyException? = null - try { - this.getProperty("InvalidElement") - } catch (e: MissingPropertyException) { - exception = e + assertFailsWith { + println(this.get("element.customStringAccessible['otherKey']")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("element"), StringNamePathIdentifier("customStringAccessible")), this.pathProcessed.path) + assertEquals(root.element.customStringAccessible, lastItemProcessed) + assertEquals(StringIndexPathIdentifier("otherKey"), lastProcessedPathIdentifier) } - assertNotNull(exception) - with(exception) { - assertEquals("InvalidElement", this.propertyName) - assertEquals("Property 'InvalidElement' could not be found in io.github.virelion.buildata.integration.access.DynamicAccessRoot", this.message) + } + } + + @Test + fun cannotFindElementOnCustomIntAccessible() { + with(root.dynamicAccessor) { + assertFailsWith { + println(this.get("element.customIntAccessible[1]")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("element"), StringNamePathIdentifier("customIntAccessible")), this.pathProcessed.path) + assertEquals(root.element.customIntAccessible, lastItemProcessed) + assertEquals(IntIndexPathIdentifier(1), lastProcessedPathIdentifier) + } + } + } + + @Test + fun complexScenarioException() { + val data = ComplexData( + mapOf("mapKey" to listOf(root)) + ) + with(data.dynamicAccessor) { + assertFailsWith { + println(this.get("$.data['mapKey'][0].element.customIntAccessible[42]")) + }.apply { + assertEquals( + listOf( + StringNamePathIdentifier("data"), + StringIndexPathIdentifier("mapKey"), + IntIndexPathIdentifier(0) + ), + this.pathProcessed.path + ) + assertEquals(data.data["mapKey"]!![0], lastItemProcessed) + assertEquals(StringNamePathIdentifier("element"), lastProcessedPathIdentifier) } } } diff --git a/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/ListPathCalculation.kt b/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/ListPathCalculation.kt index 812292a..59fc4d2 100644 --- a/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/ListPathCalculation.kt +++ b/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/ListPathCalculation.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path import io.github.virelion.buildata.integration.path.inner.Inner2 diff --git a/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/MapPathCalculation.kt b/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/MapPathCalculation.kt index 49124ec..ce49a78 100644 --- a/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/MapPathCalculation.kt +++ b/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/MapPathCalculation.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path import io.github.virelion.buildata.integration.path.inner.Inner2 diff --git a/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/PathCalculation.kt b/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/PathCalculation.kt index 823b54c..09e2525 100644 --- a/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/PathCalculation.kt +++ b/integration-test-project/project-types/jvm/src/test/kotlin/io/github/virelion/buildata/integration/path/PathCalculation.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path import io.github.virelion.buildata.integration.path.inner.AnnotatedLeaf diff --git a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/access/DynamicAccess.kt b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/access/DynamicAccess.kt index 0c5e994..7f58ae0 100644 --- a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/access/DynamicAccess.kt +++ b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/access/DynamicAccess.kt @@ -15,18 +15,55 @@ */ package io.github.virelion.buildata.integration.access -import io.github.virelion.buildata.access.DynamicallyAccessible +import io.github.virelion.buildata.Buildable +import io.github.virelion.buildata.access.IntAccessible +import io.github.virelion.buildata.access.StringAccessible +import io.github.virelion.buildata.path.PathElementName -@DynamicallyAccessible -class DynamicAccessRoot( +@Buildable +data class DynamicAccessRoot( val map: Map, + val intMap: Map = mapOf(), val list: List, + val array: Array, val element: DynamicAccessInner1, - val nullable_element: DynamicAccessInner1? + val nullable_element: DynamicAccessInner1?, + + @PathElementName("CUSTOM_NAME") + val customName: String = "" ) -@DynamicallyAccessible -class DynamicAccessInner1( +@Buildable +data class DynamicAccessInner1( val map: Map, val list: List, + val intMap: Map, + val array: Array, + val customStringAccessible: CustomStringAccessible = CustomStringAccessible, + val customIntAccessible: CustomIntAccessible = CustomIntAccessible, +) + +@Buildable +data class ComplexData( + val data: Map> ) + +object CustomStringAccessible : StringAccessible { + override fun accessElement(key: String): Any? { + if (key == "custom") { + return "custom" + } else { + throw IndexOutOfBoundsException() + } + } +} + +object CustomIntAccessible : IntAccessible { + override fun accessElement(key: Int): Any? { + if (key == 42) { + return "custom" + } else { + throw IndexOutOfBoundsException() + } + } +} diff --git a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/CompositeDataClass.kt b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/CompositeDataClass.kt index 4c688f9..eac1c94 100644 --- a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/CompositeDataClass.kt +++ b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/CompositeDataClass.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2021 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.builder import io.github.virelion.buildata.Buildable @@ -7,4 +22,3 @@ import io.github.virelion.buildata.integration.builder.inner.Level1Class data class CompositeDataClass( val innerClass: Level1Class? ) - diff --git a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level1Class.kt b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level1Class.kt index 93f6bd9..1fd81a1 100644 --- a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level1Class.kt +++ b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level1Class.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2021 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.builder.inner import io.github.virelion.buildata.Buildable @@ -6,4 +21,4 @@ import io.github.virelion.buildata.Buildable data class Level1Class( val level2: Level2Class, val value: String, -) \ No newline at end of file +) diff --git a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level2Class.kt b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level2Class.kt index d999807..dcc2066 100644 --- a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level2Class.kt +++ b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level2Class.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2021 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.builder.inner import io.github.virelion.buildata.Buildable @@ -9,4 +24,4 @@ data class Level2Class( val nullableLevel3WithDefault: Level3Class? = Level3Class("DEFAULT"), val nullableLevel3WithNullDefault: Level3Class? = null, val value: String -) \ No newline at end of file +) diff --git a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level3Class.kt b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level3Class.kt index 79c81ae..bd7d960 100644 --- a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level3Class.kt +++ b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/builder/inner/Level3Class.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.builder.inner import io.github.virelion.buildata.Buildable @@ -5,4 +21,4 @@ import io.github.virelion.buildata.Buildable @Buildable data class Level3Class( val value: String -) \ No newline at end of file +) diff --git a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/Root.kt b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/Root.kt index 64f4b67..31dce7f 100644 --- a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/Root.kt +++ b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/Root.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2021 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path import io.github.virelion.buildata.Buildable diff --git a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/Inner1.kt b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/Inner1.kt index b5e768d..90848f1 100644 --- a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/Inner1.kt +++ b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/Inner1.kt @@ -33,4 +33,4 @@ data class Inner1( val mapOfNullables: Map = mapOf("null" to null), val nullableMap: Map? = null, val nullableMapOfNullables: Map? = mapOf("null" to null) -) \ No newline at end of file +) diff --git a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/Inner2.kt b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/Inner2.kt index 9dae7da..f8a5460 100644 --- a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/Inner2.kt +++ b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/Inner2.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path.inner import io.github.virelion.buildata.Buildable @@ -7,4 +23,4 @@ import io.github.virelion.buildata.path.PathReflection @PathReflection data class Inner2( val leaf: LeafNode = LeafNode() -) \ No newline at end of file +) diff --git a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/LeafNode.kt b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/LeafNode.kt index 9f5e7f3..abbf47b 100644 --- a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/LeafNode.kt +++ b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/LeafNode.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path.inner import io.github.virelion.buildata.Buildable @@ -18,4 +34,4 @@ data class LeafNode( val uShort: UShort = 0u, val float: Float = 0.0f, val double: Double = 0.0 -) \ No newline at end of file +) diff --git a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/LeafWithNullables.kt b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/LeafWithNullables.kt index 83455e4..4877954 100644 --- a/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/LeafWithNullables.kt +++ b/integration-test-project/project-types/multiplatform/src/commonMain/kotlin/io/github/virelion/buildata/integration/path/inner/LeafWithNullables.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2021 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path.inner import io.github.virelion.buildata.Buildable @@ -18,4 +33,4 @@ data class LeafWithNullables( val uShort: UShort? = 0u, val float: Float? = 0.0f, val double: Double? = 0.0 -) \ No newline at end of file +) diff --git a/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/access/DynamicAccessTest.kt b/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/access/DynamicAccessTest.kt index 25f4ba9..4a993fc 100644 --- a/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/access/DynamicAccessTest.kt +++ b/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/access/DynamicAccessTest.kt @@ -1,81 +1,182 @@ +/* + * Copyright 2021 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.access -import io.github.virelion.buildata.access.MissingPropertyException +import io.github.virelion.buildata.access.ElementNotFoundException +import io.github.virelion.buildata.path.IntIndexPathIdentifier +import io.github.virelion.buildata.path.StringIndexPathIdentifier +import io.github.virelion.buildata.path.StringNamePathIdentifier import kotlin.test.Test import kotlin.test.assertEquals -import kotlin.test.assertNotNull +import kotlin.test.assertFailsWith import kotlin.test.assertNull -import kotlin.test.assertSame +import kotlin.test.assertTrue class DynamicAccessTest { private val root = DynamicAccessRoot( mapOf("root" to "Root"), + mapOf(1 to "Root"), listOf("root"), + arrayOf("arrayElement"), DynamicAccessInner1( mapOf("inner1" to "Inner1"), listOf("inner1"), + mapOf(1 to "element"), + arrayOf("arrayElement"), ), - null + null, + "value" ) @Test fun isValueAccessPossible() { with(root.dynamicAccessor) { - assertSame(root.map, this["map"]) - assertSame(root.list, this["list"]) - assertSame(root.element, this["element"]) + assertEquals(root.map, this["map"]) + assertEquals(root.list, this["list"]) + assertEquals(root.array, this["array"]) + assertEquals(root.intMap, this["intMap"]) + assertEquals(root.element, this["element"]) assertNull(this["nullable_element"]) - with(this.get("element").dynamicAccessor) { - assertSame(root.element.map, this["map"]) - assertSame(root.element.list, this["list"]) + + assertEquals(root.element.map, this["element.map"]) + assertEquals(root.element.list, this["element.list"]) + assertEquals(root.element.intMap, this["element.intMap"]) + assertEquals(root.element.array, this["element.array"]) + + assertEquals(root.element.map["inner1"], this["element.map['inner1']"]) + assertEquals(root.element.list[0], this["element.list[0]"]) + assertEquals(root.element.intMap[1], this["element.intMap[1]"]) + assertEquals(root.element.array[0], this["element.array[0]"]) + assertEquals("custom", this["element.customStringAccessible['custom']"]) + assertEquals("custom", this["element.customIntAccessible[42]"]) + assertEquals("value", this["CUSTOM_NAME"]) + } + } + + @Test + fun cannotFindElement() { + with(root.dynamicAccessor) { + assertFailsWith { + println(this.get("notAnElement")) + }.apply { + assertTrue(this.pathProcessed.path.isEmpty()) + assertEquals(root, lastItemProcessed) + assertEquals(StringNamePathIdentifier("notAnElement"), lastProcessedPathIdentifier) } } } @Test - fun exceptionIsThrownWhenElementIsNotFound() { + fun cannotFindElementOnStringIndexMap() { with(root.dynamicAccessor) { - var exception: MissingPropertyException? = null - try { - this["InvalidElement"] - } catch (e: MissingPropertyException) { - exception = e + assertFailsWith { + println(this.get("map['element']")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("map")), this.pathProcessed.path) + assertEquals(root.map, lastItemProcessed) + assertEquals(StringIndexPathIdentifier("element"), lastProcessedPathIdentifier) } - assertNotNull(exception) - with(exception) { - assertEquals("InvalidElement", this.propertyName) - assertEquals("Property 'InvalidElement' could not be found in io.github.virelion.buildata.integration.access.DynamicAccessRoot", this.message) + } + } + + @Test + fun cannotFindElementOnIntIndexMap() { + with(root.dynamicAccessor) { + assertFailsWith { + println(this.get("intMap[42]")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("intMap")), this.pathProcessed.path) + assertEquals(root.intMap, lastItemProcessed) + assertEquals(IntIndexPathIdentifier(42), lastProcessedPathIdentifier) } } } @Test - fun isPropertyAccessPossible() { + fun cannotFindElementOnIntIndexList() { with(root.dynamicAccessor) { - assertSame(root::map.get(), this.getProperty("map")?.get()) - assertSame(root::list.get(), this.getProperty("list")?.get()) - assertSame(root::element.get(), this.getProperty("element")?.get()) - assertNull(this["nullable_element"]) - with(this.get("element").dynamicAccessor) { - assertSame(root.element::map.get(), this.getProperty("map")?.get()) - assertSame(root.element::list.get(), this.getProperty("list")?.get()) + assertFailsWith { + println(this.get("list[42]")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("list")), this.pathProcessed.path) + assertEquals(root.list, lastItemProcessed) + assertEquals(IntIndexPathIdentifier(42), lastProcessedPathIdentifier) + } + } + } + + @Test + fun cannotFindElementOnIntIndexArray() { + with(root.dynamicAccessor) { + assertFailsWith { + println(this.get("array[42]")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("array")), this.pathProcessed.path) + assertEquals(root.array, lastItemProcessed) + assertEquals(IntIndexPathIdentifier(42), lastProcessedPathIdentifier) } } } @Test - fun exceptionIsThrownWhenPropertyIsNotFound() { + fun cannotFindElementOnCustomStringAccessible() { with(root.dynamicAccessor) { - var exception: MissingPropertyException? = null - try { - this.getProperty("InvalidElement") - } catch (e: MissingPropertyException) { - exception = e + assertFailsWith { + println(this.get("element.customStringAccessible['otherKey']")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("element"), StringNamePathIdentifier("customStringAccessible")), this.pathProcessed.path) + assertEquals(root.element.customStringAccessible, lastItemProcessed) + assertEquals(StringIndexPathIdentifier("otherKey"), lastProcessedPathIdentifier) } - assertNotNull(exception) - with(exception) { - assertEquals("InvalidElement", this.propertyName) - assertEquals("Property 'InvalidElement' could not be found in io.github.virelion.buildata.integration.access.DynamicAccessRoot", this.message) + } + } + + @Test + fun cannotFindElementOnCustomIntAccessible() { + with(root.dynamicAccessor) { + assertFailsWith { + println(this.get("element.customIntAccessible[1]")) + }.apply { + assertEquals(listOf(StringNamePathIdentifier("element"), StringNamePathIdentifier("customIntAccessible")), this.pathProcessed.path) + assertEquals(root.element.customIntAccessible, lastItemProcessed) + assertEquals(IntIndexPathIdentifier(1), lastProcessedPathIdentifier) + } + } + } + + @Test + fun complexScenarioException() { + val data = ComplexData( + mapOf("mapKey" to listOf(root)) + ) + with(data.dynamicAccessor) { + assertFailsWith { + println(this.get("$.data['mapKey'][0].element.customIntAccessible[42]")) + }.apply { + assertEquals( + listOf( + StringNamePathIdentifier("data"), + StringIndexPathIdentifier("mapKey"), + IntIndexPathIdentifier(0) + ), + this.pathProcessed.path + ) + assertEquals(data.data["mapKey"]!![0], lastItemProcessed) + assertEquals(StringNamePathIdentifier("element"), lastProcessedPathIdentifier) } } } diff --git a/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/builder/CompositeBuilderTest.kt b/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/builder/CompositeBuilderTest.kt index 0f3a351..530d060 100644 --- a/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/builder/CompositeBuilderTest.kt +++ b/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/builder/CompositeBuilderTest.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2021 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.builder import io.github.virelion.buildata.integration.builder.inner.Level2Class diff --git a/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/ListPathCalculation.kt b/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/ListPathCalculation.kt index 812292a..59fc4d2 100644 --- a/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/ListPathCalculation.kt +++ b/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/ListPathCalculation.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path import io.github.virelion.buildata.integration.path.inner.Inner2 diff --git a/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/MapPathCalculation.kt b/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/MapPathCalculation.kt index 49124ec..ce49a78 100644 --- a/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/MapPathCalculation.kt +++ b/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/MapPathCalculation.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path import io.github.virelion.buildata.integration.path.inner.Inner2 diff --git a/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/PathCalculation.kt b/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/PathCalculation.kt index 27d503c..f3d1b34 100644 --- a/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/PathCalculation.kt +++ b/integration-test-project/project-types/multiplatform/src/commonTest/kotlin/io/github/virelion/buildata/integration/path/PathCalculation.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2022 Maciej Ziemba + * + * 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 io.github.virelion.buildata.integration.path import io.github.virelion.buildata.integration.path.inner.AnnotatedLeaf