Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
main.com.enniovisco.checking.TraceBuilder.kt Maven / Gradle / Ivy
Go to download
A formal approach to monitoring web pages as spatio-temporal traces.
package com.enniovisco.checking
import com.enniovisco.dsl.*
import com.enniovisco.space.*
import com.enniovisco.tracking.commands.*
import io.github.moonlightsuite.moonlight.offline.signal.*
import io.github.oshai.kotlinlogging.*
import kotlin.math.*
typealias Snapshot = Map
/**
* Builder class that generates a signal based on the data
* @param grid spatial model to consider for building the signal
* @param data data source from which the signal will be extracted
*/
class TraceBuilder(
private val grid: Grid,
private val data: List>
) {
private var metadata: Boolean = false
private val elements: MutableList = ArrayList()
private val modifiers: MutableMap Boolean> =
HashMap()
private val log = KotlinLogging.logger {}
/**
* Modifier determining whether to also load page metadata in the signal
*/
fun useMetadata() = apply { metadata = true }
/**
* Method to determine whether also metadata is/will be loaded in the signal
*/
fun hasMetadata() = metadata
/**
* Method that allows to consider into a signal a list of selected elements
* @param elems list of element ids to select
*/
fun useElements(elems: Map Boolean>) =
apply { elems.forEach { useElement(it) } }
/**
* Method that allows to consider into a signal a given selected element
* @param elem element id to select
*/
fun useElement(elem: Map.Entry Boolean>) =
apply { elements.add(elem.key); modifiers[elem.key] = elem.value }
/**
* Method that allows to consider into a signal a given selected element
* @param elem element id to select, must not have a bound!
*/
fun useElement(elem: String) =
apply { elements.add(elem); modifiers[elem] = { _, _ -> true } }
/**
* Method that allows to determine whether a given selected element
* has/will be loaded into the signal
* @param elem element id to select
*/
fun hasElement(elem: String) = elements.contains(elem)
/**
* Method that completes and returns the building of a
* [SpatialTemporalSignal] for the given page trace
*/
fun build(): SpatialTemporalSignal> {
val signal = SpatialTemporalSignal>(grid.size)
for (t in data.indices) {
signal.add(t.toDouble()) { location -> activeElements(t, location) }
}
return signal
}
private fun activeElements(t: Int, location: Int): List {
val snapshot = data[t]
val boxes =
elements.map { checkAtom(Atom(it), snapshot, loc = location) }
return if (metadata)
listOf(screenToBox(t).isContained(location)) + boxes
else
boxes
}
data class Atom(val fullName: String) {
val selector: String
val property: String
val value: String
private val log = KotlinLogging.logger {}
init {
try {
val (selector, property, value) = parseSelector(fullName)
this.selector = selector
this.property = property
this.value = value
} catch (e: IllegalArgumentException) {
val msg = "Unable to parse selector '$fullName'."
log.error { msg }
throw IllegalArgumentException(msg, e)
}
}
fun comparator(): String {
val allowedOnes = listOf(">=", "<=", "<<", ">>", "==", "@", "&")
return when (val res = allowedOnes.find { fullName.contains(it) }) {
null -> ""
else -> res
}
}
}
private fun checkAtom(atom: Atom, snapshot: Snapshot, loc: Int): Boolean {
val elements = dataToBoxes(atom.selector, snapshot)
val matchingElements = matchingElems(elements, loc)
return if (matchingElements.isNotEmpty() && atom.property != "") {
compareMatchingElems(matchingElements, atom, snapshot)
} else matchingElements.isNotEmpty()
}
private fun compareMatchingElems(
matchingElems: List,
atom: Atom,
snapshot: Snapshot
): Boolean {
return matchingElems.any { i ->
val current = snapshot["${atom.selector}::$i::${atom.property}"]!!
applyComparison(atom, current, snapshot)
}
}
private fun matchingElems(elements: List, loc: Int): List {
return elements.withIndex()
.filter { (_: Int, element: Box) -> element.isContained(loc) }
.map { it.index }
//.reduce { acc, b -> acc || b }
}
private fun applyComparison(
atom: Atom,
value: String,
snapshot: Map
): Boolean {
val op = atom.comparator()
val comparison = atom.value
val id = atom.fullName
return when (op) {
">>" -> parsePixels(value) > parsePixels(comparison)
">=" -> parsePixels(value) >= parsePixels(comparison)
"<<" -> parsePixels(value) < parsePixels(comparison)
"<=" -> parsePixels(value) <= parsePixels(comparison)
"==" -> value == comparison
"&" -> sameAsBound(comparison, value, snapshot, mod(id))
else -> true
}
}
private fun mod(id: String) = modifiers[id] ?: { x, y -> x == y }
private fun sameAsBound(
bound: String,
value: String,
snapshot: Map,
modifier: (String, String) -> Boolean
): Boolean {
val boundValue = snapshot["$BOUNDS_PREFIX$bound"]
try {
if (boundValue == null) {
throw IllegalArgumentException("Bound '$bound' has value '${snapshot["$BOUNDS_PREFIX$bound"]}'.")
}
return modifier(value, boundValue)
} catch (e: NullPointerException) {
throw IllegalArgumentException("Bound '$bound' not found.")
}
}
private fun Box.isContained(location: Int): Boolean {
return contains(grid.toXY(location))
}
private fun screenToBox(time: Int): Box {
try {
val maxX = data[time]["vvp_width"]!!
val maxY = data[time]["vvp_height"]!!
return Box.from(minX = "0", minY = "0", maxX = maxX, maxY = maxY)
} catch (e: NullPointerException) {
throw IllegalArgumentException(
"Unable to find box coordinates for the window."
)
}
}
private fun dataToBoxes(id: String, snapshot: Snapshot): List {
return try {
val size: Int = snapshot["$id::size::"]!!.toInt()
(0 until size).map { dataToBox(id, it, snapshot) }
} catch (e: NullPointerException) {
// log.warn { "Unable to find box coordinates for id: $id. Skipping." }
// throw IllegalArgumentException(
// "Unable to find box coordinates " +
// "for id: $id."
// )
// listOf(Box(0, 0, 0, 0))
emptyList()
}
}
private fun dataToBox(selector: String, i: Int, snapshot: Snapshot): Box {
val minX: Int = parsePixels(snapshot["$selector::$i::x"]!!)
val minY: Int = parsePixels(snapshot["$selector::$i::y"]!!)
val maxX: Int = minX + parsePixels(snapshot["$selector::$i::width"]!!)
val maxY: Int = minY + parsePixels(snapshot["$selector::$i::height"]!!)
return Box(minX = minX, minY = minY, maxX = maxX, maxY = maxY)
}
private fun parsePixels(value: String): Int {
// return if (value == "auto") {
// 0
// } else {
return value.replace("px", "").toDouble().roundToInt()
// }
}
}