diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt index fbeaf56..82cb378 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/AbstractInitializer.kt @@ -8,5 +8,5 @@ abstract class AbstractInitializer : ApplicationListener initialize() } - abstract fun initialize() + protected abstract fun initialize() } diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt index 12908cb..0f0921f 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/MixInitializer.kt @@ -4,6 +4,7 @@ import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase import dev.fyloz.colorrecipesexplorer.model.Mix import dev.fyloz.colorrecipesexplorer.model.MixMaterial import dev.fyloz.colorrecipesexplorer.service.MixService +import dev.fyloz.colorrecipesexplorer.utils.merge import mu.KotlinLogging import org.springframework.context.annotation.Configuration import java.util.* @@ -24,9 +25,7 @@ class MixInitializer( logger.debug("Validating mix materials positions...") mixService.getAll() - .filter { mix -> - mix.mixMaterials.any { it.position == 0 } - } + .filter { mix -> mix.mixMaterials.any { it.position == 0 } } .forEach(this::fixMixPositions) logger.debug("Mix materials positions are valid!") @@ -34,20 +33,19 @@ class MixInitializer( private fun fixMixPositions(mix: Mix) { val maxPosition = mix.mixMaterials.maxOf { it.position } - val nextPosition = maxPosition + 1 logger.warn("Mix ${mix.id} (${mix.mixType.name}, ${mix.recipe.name}) has invalid positions:") val invalidMixMaterials: Collection = with(mix.mixMaterials.filter { it.position == 0 }) { - if (nextPosition == 1 && this.size > 1) { + if (maxPosition == 0 && this.size > 1) { orderMixMaterials(this) } else { this } } - val fixedMixMaterials = increaseMixMaterialsPosition(invalidMixMaterials, nextPosition) - val updatedMixMaterials = mergeMixMaterials(mix.mixMaterials, fixedMixMaterials) + val fixedMixMaterials = increaseMixMaterialsPosition(invalidMixMaterials, maxPosition + 1) + val updatedMixMaterials = mix.mixMaterials.merge(fixedMixMaterials) with(mix.copy(mixMaterials = updatedMixMaterials.toMutableSet())) { mixService.update(this) @@ -61,11 +59,6 @@ class MixInitializer( logger.info("\tPosition of material ${it.material.id} (${it.material.name}) has been set to ${it.position}") } - private fun mergeMixMaterials(mixMaterials: Iterable, updatedMixMaterials: Iterable) = - mixMaterials - .filter { mixMaterial -> updatedMixMaterials.all { it.id != mixMaterial.id } } - .plus(updatedMixMaterials) - private fun orderMixMaterials(mixMaterials: Collection) = LinkedList(mixMaterials).apply { while (this.peek().material.materialType?.usePercentages == true) { diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/RecipeInitializer.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/RecipeInitializer.kt new file mode 100644 index 0000000..4d49ab5 --- /dev/null +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/config/initializers/RecipeInitializer.kt @@ -0,0 +1,72 @@ +package dev.fyloz.colorrecipesexplorer.config.initializers + +import dev.fyloz.colorrecipesexplorer.config.annotations.RequireDatabase +import dev.fyloz.colorrecipesexplorer.model.Recipe +import dev.fyloz.colorrecipesexplorer.model.RecipeGroupInformation +import dev.fyloz.colorrecipesexplorer.model.RecipeStep +import dev.fyloz.colorrecipesexplorer.service.RecipeService +import dev.fyloz.colorrecipesexplorer.utils.merge +import mu.KotlinLogging +import org.springframework.context.annotation.Configuration + +@Configuration +@RequireDatabase +class RecipeInitializer( + private val recipeService: RecipeService +) : AbstractInitializer() { + private val logger = KotlinLogging.logger {} + + override fun initialize() { + logger.debug("Executing recipe initializer...") + fixAllPositions() + } + + private fun fixAllPositions() { + logger.debug("Validating recipes steps positions...") + + recipeService.getAll() + .forEach(this::fixRecipePositions) + + logger.debug("Recipes steps positions are valid!") + } + + private fun fixRecipePositions(recipe: Recipe) { + val fixedGroupInformation = recipe.groupsInformation + .filter { it.steps != null } + .filter { groupInfo -> groupInfo.steps!!.any { it.position == 0 } } + .map { fixGroupInformationPositions(recipe, it) } + + val updatedGroupInformation = recipe.groupsInformation.merge(fixedGroupInformation) + + with(recipe.copy(groupsInformation = updatedGroupInformation.toMutableSet())) { + recipeService.update(this) + } + } + + private fun fixGroupInformationPositions( + recipe: Recipe, + groupInformation: RecipeGroupInformation + ): RecipeGroupInformation { + val steps = groupInformation.steps!! + val maxPosition = steps.maxOf { it.position } + + logger.warn("Recipe ${recipe.id} (${recipe.name}) has invalid positions:") + + val invalidRecipeSteps = steps.filter { it.position == 0 } + val fixedRecipeSteps = increaseRecipeStepsPosition(groupInformation, invalidRecipeSteps, maxPosition + 1) + val updatedRecipeSteps = steps.merge(fixedRecipeSteps) + + return groupInformation.copy(steps = updatedRecipeSteps.toMutableSet()) + } + + private fun increaseRecipeStepsPosition( + groupInformation: RecipeGroupInformation, + recipeSteps: Iterable, + firstPosition: Int + ) = + recipeSteps + .mapIndexed { index, recipeStep -> recipeStep.copy(position = firstPosition + index) } + .onEach { + logger.info("\tPosition of step ${it.id} (group: ${groupInformation.group.name}) has been set to ${it.position}") + } +} \ No newline at end of file diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt index b0649a7..a78ba28 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/model/Recipe.kt @@ -5,7 +5,6 @@ import dev.fyloz.colorrecipesexplorer.exception.AlreadyExistsException import dev.fyloz.colorrecipesexplorer.exception.NotFoundException import dev.fyloz.colorrecipesexplorer.model.account.Group import dev.fyloz.colorrecipesexplorer.model.account.group -import dev.fyloz.colorrecipesexplorer.rest.CRE_PROPERTIES import dev.fyloz.colorrecipesexplorer.rest.FILE_CONTROLLER_PATH import java.net.URLEncoder import java.nio.charset.StandardCharsets @@ -158,7 +157,7 @@ data class RecipeOutputDto( data class RecipeGroupInformation( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - val id: Long?, + override val id: Long?, @ManyToOne @JoinColumn(name = "group_id") @@ -169,7 +168,7 @@ data class RecipeGroupInformation( @OneToMany(cascade = [CascadeType.ALL], fetch = FetchType.EAGER, orphanRemoval = true) @JoinColumn(name = "recipe_group_information_id") var steps: MutableSet? -) +) : Model data class RecipeStepsDto( val groupId: Long, diff --git a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Collections.kt b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Collections.kt index a7a41ea..00b853c 100644 --- a/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Collections.kt +++ b/src/main/kotlin/dev/fyloz/colorrecipesexplorer/utils/Collections.kt @@ -1,9 +1,11 @@ package dev.fyloz.colorrecipesexplorer.utils +import dev.fyloz.colorrecipesexplorer.model.Model + /** Returns a list containing the result of the given [transform] applied to each item of the [Iterable]. If the given [transform] throws, the [Throwable] will be passed to the given [throwableConsumer]. */ inline fun Iterable.mapMayThrow( - throwableConsumer: (E) -> Unit = {}, - transform: (T) -> R + throwableConsumer: (E) -> Unit = {}, + transform: (T) -> R ): List = this.mapNotNull { try { transform(it) @@ -19,17 +21,17 @@ inline fun Iterable.mapMayThrow( /** Find duplicated in the given [Iterable] from keys obtained from the given [keySelector]. */ inline fun Iterable.findDuplicated(keySelector: (T) -> K) = - this.groupBy(keySelector) - .filter { it.value.count() > 1 } - .map { it.key } + this.groupBy(keySelector) + .filter { it.value.count() > 1 } + .map { it.key } /** Check if the given [Iterable] has gaps between each items, using keys obtained from the given [keySelector]. */ inline fun Iterable.hasGaps(keySelector: (T) -> Int) = - this.map(keySelector) - .toIntArray() - .sorted() - .filterIndexed { index, it -> it != index + 1 } - .isNotEmpty() + this.map(keySelector) + .toIntArray() + .sorted() + .filterIndexed { index, it -> it != index + 1 } + .isNotEmpty() /** Clears and fills the given [MutableCollection] with the given [elements]. */ fun MutableCollection.setAll(elements: Collection) { @@ -40,6 +42,12 @@ fun MutableCollection.setAll(elements: Collection) { /** Removes and returns all elements of a [MutableCollection] matching the given [predicate]. */ inline fun MutableCollection.excludeAll(predicate: (T) -> Boolean): Iterable { val matching = this.filter(predicate) - this.removeAll(matching) + this.removeAll(matching.toSet()) return matching } + +/** Merge to [Model] [Iterable]s and prevent id duplication. */ +fun Iterable.merge(other: Iterable) = + this + .filter { model -> other.all { it.id != model.id } } + .plus(other)