com.beeproduced.bee.persistent.selection.SimpleSelection.kt Maven / Gradle / Ivy
package com.beeproduced.bee.persistent.selection
import java.io.File
import java.nio.file.FileSystems
import java.nio.file.PathMatcher
import java.nio.file.Paths
/**
* Inspired by
* https://github.com/graphql-java/graphql-java/blob/master/src/main/java/graphql/schema/DataFetchingFieldSelectionSetImpl.java
*
* @author Kacper Urbaniec
* @version 2023-02-02
*/
class SimpleSelection
internal constructor(
private val immediateFields: MutableList,
private val fields: MutableMap,
private val fieldGlobPaths: MutableSet,
override val skipOvers: SkipOverCollection,
) : DataSelection {
companion object {
const val SEP = "/"
val UNIX_TARGET = SEP == File.separator
private fun osField(field: String): String {
return if (UNIX_TARGET) field else field.replace(SEP, "\\")
}
private fun removeLeadingSlash(fieldGlobPattern: String): String {
return if (fieldGlobPattern.startsWith(SEP)) fieldGlobPattern.substring(1)
else fieldGlobPattern
}
private fun globMatcher(fieldGlobPattern: String): PathMatcher {
val osFieldGlobPattern = removeLeadingSlash(fieldGlobPattern)
return FileSystems.getDefault().getPathMatcher("glob:$osFieldGlobPattern")
}
fun String.substringBeforeOrNull(delimiter: String): String? {
val index = indexOf(delimiter)
return if (index == -1) null else substring(0, index)
}
}
constructor(
fields: Set,
skips: Collection = emptyList(),
) : this(mutableListOf(), mutableMapOf(), mutableSetOf(), SkipOverCollection(skips)) {
setupFields(fields, immediateFields, this.fields, fieldGlobPaths)
}
private fun setupFields(
start: Set,
immediateFields: MutableList,
fields: MutableMap,
fieldGlobPaths: MutableSet,
) {
for (node in start) {
immediateFields.add(node)
val path = node.field
fields[path] = node
fieldGlobPaths.add(path)
node.fields?.let { nodeFields ->
nodeFields.forEach { child -> setupFields(child, path, fields, fieldGlobPaths) }
}
}
}
private fun setupFields(
node: FieldNodeDefinition,
parentPath: String,
fields: MutableMap,
fieldGlobPaths: MutableSet,
) {
val path = "$parentPath$SEP${node.field}"
fields[path] = node
fieldGlobPaths.add(path)
node.fields?.let { nodeFields ->
nodeFields.forEach { child -> setupFields(child, path, fields, fieldGlobPaths) }
}
}
data class FieldNode(
override val field: String,
override val fields: Set? = null,
) : FieldNodeDefinition {
override val type: String? = null
}
data class TypedFiledNode(
override val field: String,
override val type: String?,
override val fields: Set? = null,
) : FieldNodeDefinition
private data class MutableFieldNode(
override val field: String,
override val type: String?,
override var fields: MutableSet? = null,
) : FieldNodeDefinition
override fun contains(fieldGlobPattern: String): Boolean {
if (fieldGlobPattern.isEmpty()) return false
val globMatcher = globMatcher(fieldGlobPattern)
for (field in fieldGlobPaths) {
val path = Paths.get(osField(field))
if (globMatcher.matches(path)) return true
}
return false
}
override fun subSelect(fieldGlobPattern: String): DataSelection? {
if (fieldGlobPattern.isEmpty()) return null
val start = mutableSetOf()
val globMatcher = globMatcher(fieldGlobPattern)
for ((field, node) in fields) {
val nodeFields = node.fields
val path = Paths.get(osField(field))
if (globMatcher.matches(path) && !nodeFields.isNullOrEmpty()) {
start.addAll(nodeFields)
}
}
return if (start.isEmpty()) null else SimpleSelection(start, skipOvers.remainingSkips())
}
override fun merge(vararg selections: SimpleSelection): DataSelection {
return merge(selections.asList())
}
override fun merge(selections: Collection): DataSelection {
if (selections.isEmpty()) return this
val immediateFieldsTmp: MutableMap = mutableMapOf()
val fieldsTmp: MutableMap = mutableMapOf()
val fieldGlobPathsTmp: MutableSet = mutableSetOf()
val allSelections = selections.toMutableList().also { it.add(this) }
val immediateFields = allSelections.map { it.immediateFields }.flatten()
for (node in immediateFields) {
val mutableNode =
if (!immediateFieldsTmp.contains(node.field)) {
val mutableNode = MutableFieldNode(node.field, node.type)
immediateFieldsTmp[node.field] = mutableNode
fieldsTmp[node.field] = mutableNode
fieldGlobPathsTmp.add(mutableNode.field)
mutableNode
} else immediateFieldsTmp.getValue(node.field) as MutableFieldNode
node.fields?.let { nodeFields ->
nodeFields.forEach { child ->
merge(child, mutableNode, node.field, fieldsTmp, fieldGlobPathsTmp)
}
}
}
val skipOverEntries =
SkipOverCollection(allSelections.map { it.skipOvers.remainingSkips() }.flatten())
return SimpleSelection(
immediateFields = immediateFieldsTmp.values.toMutableList(),
fields = fieldsTmp,
fieldGlobPaths = fieldGlobPathsTmp,
skipOverEntries,
)
}
override fun typeSelect(typeName: String): DataSelection? {
if (typeName.isEmpty()) return null
val start = mutableSetOf()
for ((_, node) in fields) {
if (node.type != null && node.type == typeName) start.add(node)
}
return if (start.isEmpty()) null else SimpleSelection(start, skipOvers.remainingSkips())
}
private fun merge(
node: FieldNodeDefinition,
parent: MutableFieldNode,
parentPath: String,
fieldsTmp: MutableMap,
fieldGlobPathsTmp: MutableSet,
) {
val path = "$parentPath$SEP${node.field}"
val mutableFieldNode =
if (fieldsTmp.contains(path)) {
fieldsTmp.getValue(path)
} else {
val currentNode = MutableFieldNode(node.field, node.type)
fieldsTmp[path] = currentNode
currentNode
}
as MutableFieldNode
parent.fields?.add(mutableFieldNode) ?: run { parent.fields = mutableSetOf(mutableFieldNode) }
fieldGlobPathsTmp.add(path)
node.fields?.let { nodeFields ->
nodeFields.forEach { child ->
merge(child, mutableFieldNode, path, fieldsTmp, fieldGlobPathsTmp)
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy