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

tagan.codegen-impl.1.4.1.source-code.SlotSwitchingGenerator.kt Maven / Gradle / Ivy

/*
 * Copyright 2022 Yandex LLC
 *
 * 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 com.yandex.yatagan.codegen.impl

import com.squareup.javapoet.ClassName
import com.yandex.yatagan.codegen.poetry.TypeSpecBuilder
import com.yandex.yatagan.codegen.poetry.buildExpression
import com.yandex.yatagan.core.graph.BindingGraph
import com.yandex.yatagan.core.graph.bindings.Binding
import javax.inject.Inject
import javax.inject.Singleton
import javax.lang.model.element.Modifier

@Singleton
internal class SlotSwitchingGenerator @Inject constructor(
    private val thisGraph: BindingGraph,
    options: ComponentGenerator.Options,
) : ComponentGenerator.Contributor {
    private val maxSlotsPerSwitch = options.maxSlotsPerSwitch

    init {
        require(maxSlotsPerSwitch > 1 || maxSlotsPerSwitch == -1) {
            "maxSlotsPerSwitch should be at least 2 or -1 (infinity)"
        }
    }

    private var isUsed = false
    private val bindings = arrayListOf()
    private val seen = hashSetOf()

    fun requestSlot(forBinding: Binding): Int {
        isUsed = true
        if (forBinding in seen) {
            return bindings.indexOf(forBinding)
        }
        bindings += forBinding
        seen += forBinding
        return bindings.lastIndex
    }

    override fun generate(builder: TypeSpecBuilder) {
        if (!isUsed) return
        builder.method(FactoryMethodName) {
            modifiers(/*package-private*/)
            returnType(ClassName.OBJECT)
            parameter(ClassName.INT, "slot")
            if (maxSlotsPerSwitch > 1 && bindings.size > maxSlotsPerSwitch) {
                // Strategy with two-level-nested switches

                val outerSlotsCount = bindings.size / maxSlotsPerSwitch
                controlFlow("switch(slot / $maxSlotsPerSwitch)") {
                    for(outerSlot in 0 .. outerSlotsCount) {
                        val nestedFactoryName = "$FactoryMethodName\$$outerSlot"
                        builder.method(nestedFactoryName) {
                            modifiers(Modifier.PRIVATE)
                            returnType(ClassName.OBJECT)
                            parameter(ClassName.INT, "slot")
                            controlFlow("switch(slot)") {
                                val startIndex = outerSlot * maxSlotsPerSwitch
                                var nestedSlot = 0
                                while((startIndex + nestedSlot) < bindings.size && nestedSlot < maxSlotsPerSwitch) {
                                    val binding = bindings[startIndex + nestedSlot]
                                    +buildExpression {
                                        +"case $nestedSlot: return "
                                        binding.generateAccess(
                                            builder = this,
                                            inside = thisGraph,
                                            isInsideInnerClass = false,
                                        )
                                    }
                                    ++nestedSlot
                                }
                                +"default: throw new %T()".formatCode(Names.AssertionError)
                            }
                        }
                        +buildExpression {
                            +"case $outerSlot: return %N(slot %L 100)".formatCode(nestedFactoryName, "%")
                        }
                    }
                    +"default: throw new %T()".formatCode(Names.AssertionError)
                }
            } else {
                // Single switch statement

                controlFlow("switch(slot)") {
                    bindings.forEachIndexed { slot, binding ->
                        +buildExpression {
                            +"case $slot: return "
                            binding.generateAccess(
                                builder = this,
                                inside = thisGraph,
                                isInsideInnerClass = false,
                            )
                        }
                    }
                    +"default: throw new %T()".formatCode(Names.AssertionError)
                }
            }
        }
    }

    companion object {
        const val FactoryMethodName = "switch\$\$access"
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy