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

com.arcadeanalytics.data.Sprite.kt Maven / Gradle / Ivy

There is a newer version: 1.0.8
Show newest version
/*-
 * #%L
 * Arcade Data
 * %%
 * Copyright (C) 2018 - 2019 ArcadeAnalytics
 * %%
 * 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.
 * #L%
 */
package com.arcadeanalytics.data

import com.google.common.collect.ArrayListMultimap
import com.google.common.collect.ListMultimap
import com.google.common.collect.Lists
import java.util.*
import java.util.regex.Pattern

/**
 * The Sprite is a flexible data container based on ListMultimap to be used in ETL tasks
 *
 * The unit test ha a lot of usage examples
 *
 * @author Roberto Franchini
 */
private const val copySuffix = "____COPY___"

class Sprite {

    val data: ListMultimap = ArrayListMultimap.create()

    fun add(field: String, value: Any?): Sprite {
        if (value is Collection<*>)
            value.asSequence()
                    .filter { it != null }
                    .forEach { v -> data.put(field, v) }
        else if (value != null) data.put(field, value)
        return this
    }

    fun add(field: String, value: String): Sprite {
        if (value.isNotBlank())
            data.put(field, value)
        return this
    }


    fun entries(): MutableCollection>? {
        return data.entries()
    }

    fun fields(): Set {
        return HashSet(data.keySet())
    }

    fun fields(pattern: Pattern): Set {
        return data.keySet()
                .filter { k -> pattern.matcher(k).matches() }
                .toSet()
    }

    fun fields(pattern: Regex): Set {
        return data.keySet()
                .filter { k -> pattern.matches(k) }
                .toSet()
    }

    fun rename(field: String, renamed: String): Sprite {
        with(data) {
            copy(field, renamed)
            removeAll(field)
        }
        return this
    }


    fun copy(from: String, to: String): Sprite {
        with(data) {
            val fromValues = Lists.newArrayList(get(from))
            addAll(to, fromValues)

        }
        return this
    }


    fun addAll(field: String, values: Iterable): Sprite {
        values.forEach { add(field, it) }
        return this
    }

    fun addAll(field: String, values: List): Sprite {
        values.forEach { add(field, it) }
        return this
    }

    fun addAllIfNotExists(field: String, values: Iterable): Sprite {
        values.forEach { addIfNotExists(field, it) }
        return this
    }

    fun addIfNotExists(field: String, fieldValue: Any): Sprite {

        if (hasNotValue(field, fieldValue)) {
            add(field, fieldValue)
        }

        return this
    }

    fun hasNotValue(value: Any): Boolean {
        return !data.containsValue(value)
    }

    fun hasNotValue(field: String, value: Any): Boolean {
        return !data.containsEntry(field, value)
    }

    fun hasValue(value: Any): Boolean {
        return data.containsValue(value)
    }

    fun hasValue(field: String, value: Any): Boolean {
        return data.containsEntry(field, value)
    }

    fun hasField(field: String): Boolean {
        return data.containsKey(field)
    }

    fun load(input: Map): Sprite {
        input.entries
                .forEach { add(it.key, it.value) }
        return this
    }


    fun remove(field: String, fieldValue: Any): Sprite {
        data.remove(field, fieldValue)
        return this
    }

    fun remove(field: String): Sprite {
        data.removeAll(field)
        return this
    }

    fun remove(pattern: Pattern): Sprite {
        val fields = fields(pattern)
        return remove(fields)
    }

    fun remove(fields: Collection): Sprite {
        fields.forEach { f -> data.removeAll(f) }
        return this
    }


    fun  apply(pattern: Pattern, fieldModifier: (F) -> T): Sprite {
        fields(pattern)
                .forEach { f -> apply(f, fieldModifier) }

        return this

    }

    fun  apply(pattern: Regex, fieldModifier: (F) -> T): Sprite {
        fields(pattern)
                .forEach { f -> apply(f, fieldModifier) }

        return this

    }

    fun  apply(field: String, fieldModifier: (F) -> T): Sprite {

        if (hasField(field)) {
            val newValues = newValuesOf(field, fieldModifier)
            remove(field)
            addAll(field, newValues)
        }

        return this
    }

    fun  apply(from: String, transformer: (F) -> T, to: String): Sprite {

        if (hasField(from)) {
            val newValues = newValuesOf(from, transformer)
            addAll(to, newValues)
        }

        return this
    }

    private fun  newValuesOf(field: String, fieldModifier: (F) -> T): List {
        val originalValues: List = rawValuesOf(field)
        return originalValues.map { it -> fieldModifier(it) }.toList()
    }


    fun  rawValuesOf(field: String): List {
        return data.get(field).orEmpty() as List
    }


    fun rename(field: Pattern, renamed: (v: String) -> String): Sprite {
        fields(field)
                .forEach { f ->
                    val cleaned = renamed(f)
                    rename(f, cleaned)
                }

        return this
    }

    fun joinValuesOf(field: String,
                     separator: CharSequence = ", ",
                     prefix: CharSequence = "",
                     postfix: CharSequence = "",
                     limit: Int = -1,
                     truncated: CharSequence = "..."): Sprite {

        val merged = rawValuesOf(field)
                .joinToString(separator, prefix, postfix, limit, truncated)

        remove(field)
                .add(field, merged)
        return this

    }


    fun valueOf(field: String): String {
        return rawValueOf(field).let(Any::toString)
    }

    fun valuesOf(field: String): List {

        return rawValuesOf(field).map { it -> it.toString() }.toList()
    }

    fun valuesOf(regex: Regex): List {

        return fields(regex)
                .map { field ->
                    rawValuesOf(field)
                            .map { it.toString() }
                }
                .flatMap {
                    it.toList()
                }
                .toList()

    }

    fun valuesOf(regex: Pattern): List {

        return fields(regex)
                .map { field ->
                    rawValuesOf(field)
                            .map { it.toString() }
                }
                .flatMap {
                    it.toList()
                }
                .toList()

    }


    fun  rawValueOf(field: String): T {
        return rawValuesOf(field).first()
    }


    fun isMultiValue(field: String): Boolean {
        return data.get(field).size > 1
    }

    fun isSingleValue(field: String): Boolean {
        return data.get(field).size == 1
    }

    fun sizeOf(field: String): Int {
        return data.get(field).size
    }

    fun asMultimap(): MutableMap>? {
        return data.asMap()
    }

    fun asMap(): MutableMap? {
        val map = HashMap()
        for (key in data.keySet()) {
            val values = data.get(key)
            map[key] = values.iterator().next()
        }

        return map
    }


    fun asStringMap(): Map {
        val map = HashMap()

        fields().map { f -> map.put(f, valueOf(f)) }

        return map
    }


    fun splitValues(field: String, separator: String): Sprite {

        val copySuffix = copySuffix
        copy(field, "$field$copySuffix")
                .remove(field)
                .valuesOf("$field$copySuffix")
                .map { value -> value.split(separator) }
                .forEach { splitted -> add(field, splitted) }

        apply(field, { v: String -> v.trim() })

        remove("$field$copySuffix")

        return this
    }

    fun isEmpty(): Boolean {

        return data.isEmpty
    }


    override fun hashCode(): Int {
        val prime = 31
        var result = 1
        result = prime * result + data.hashCode()
        return result
    }

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (other == null) return false
        if (javaClass != other.javaClass) return false

        val otherSprite = other as Sprite

        if (data != otherSprite.data) return false

        return true
    }

    override fun toString(): String {

        return data.toString()
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy