10bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets/*
20bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets * Copyright (C) 2017 The Android Open Source Project
30bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets *
40bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets * Licensed under the Apache License, Version 2.0 (the "License");
50bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets * you may not use this file except in compliance with the License.
60bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets * You may obtain a copy of the License at
70bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets *
80bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets *      http://www.apache.org/licenses/LICENSE-2.0
90bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets *
100bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets * Unless required by applicable law or agreed to in writing, software
110bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets * distributed under the License is distributed on an "AS IS" BASIS,
120bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
130bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets * See the License for the specific language governing permissions and
140bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets * limitations under the License.
150bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets */
160bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
17bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viverettepackage androidx.lifecycle
180bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
19bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.lifecycle.model.AdapterClass
20bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.lifecycle.model.EventMethodCall
21bdc4c86d3dff74f6634a38e2f7b316b0e823a2c8Alan Viveretteimport androidx.lifecycle.model.getAdapterName
2262693d74bb01e67e62a6601c4a79ac67136a458fshepshapardimport com.squareup.javapoet.AnnotationSpec
233a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinetsimport com.squareup.javapoet.ClassName
243a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinetsimport com.squareup.javapoet.FieldSpec
253a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinetsimport com.squareup.javapoet.JavaFile
263a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinetsimport com.squareup.javapoet.MethodSpec
273a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinetsimport com.squareup.javapoet.ParameterSpec
283a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinetsimport com.squareup.javapoet.TypeName
293a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinetsimport com.squareup.javapoet.TypeSpec
3062693d74bb01e67e62a6601c4a79ac67136a458fshepshapardimport javax.annotation.processing.ProcessingEnvironment
310bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinetsimport javax.lang.model.element.Modifier
320bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinetsimport javax.lang.model.element.TypeElement
338dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmerimport javax.tools.StandardLocation
340bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
3562693d74bb01e67e62a6601c4a79ac67136a458fshepshapardfun writeModels(infos: List<AdapterClass>, processingEnv: ProcessingEnvironment) {
3662693d74bb01e67e62a6601c4a79ac67136a458fshepshapard    infos.forEach({ writeAdapter(it, processingEnv) })
370bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets}
380bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
3962693d74bb01e67e62a6601c4a79ac67136a458fshepshapardprivate val GENERATED_PACKAGE = "javax.annotation"
4062693d74bb01e67e62a6601c4a79ac67136a458fshepshapardprivate val GENERATED_NAME = "Generated"
410bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinetsprivate val LIFECYCLE_EVENT = Lifecycle.Event::class.java
420bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
430bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinetsprivate val T = "\$T"
440bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinetsprivate val N = "\$N"
450bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinetsprivate val L = "\$L"
4662693d74bb01e67e62a6601c4a79ac67136a458fshepshapardprivate val S = "\$S"
470bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
4891df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinetsprivate val OWNER_PARAM: ParameterSpec = ParameterSpec.builder(
4991df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        ClassName.get(LifecycleOwner::class.java), "owner").build()
5091df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinetsprivate val EVENT_PARAM: ParameterSpec = ParameterSpec.builder(
5191df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        ClassName.get(LIFECYCLE_EVENT), "event").build()
5291df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinetsprivate val ON_ANY_PARAM: ParameterSpec = ParameterSpec.builder(TypeName.BOOLEAN, "onAny").build()
5391df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets
5491df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinetsprivate val METHODS_LOGGER: ParameterSpec = ParameterSpec.builder(
5591df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        ClassName.get(MethodCallsLogger::class.java), "logger").build()
5691df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets
5791df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinetsprivate const val HAS_LOGGER_VAR = "hasLogger"
5891df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets
5962693d74bb01e67e62a6601c4a79ac67136a458fshepshapardprivate fun writeAdapter(adapter: AdapterClass, processingEnv: ProcessingEnvironment) {
6091df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets    val receiverField: FieldSpec = FieldSpec.builder(ClassName.get(adapter.type), "mReceiver",
610bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            Modifier.FINAL).build()
6291df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets    val dispatchMethodBuilder = MethodSpec.methodBuilder("callMethods")
630bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            .returns(TypeName.VOID)
6491df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets            .addParameter(OWNER_PARAM)
6591df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets            .addParameter(EVENT_PARAM)
6691df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets            .addParameter(ON_ANY_PARAM)
6791df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets            .addParameter(METHODS_LOGGER)
680bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            .addModifiers(Modifier.PUBLIC)
690bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            .addAnnotation(Override::class.java)
700bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets    val dispatchMethod = dispatchMethodBuilder.apply {
7191df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets
7291df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        addStatement("boolean $L = $N != null", HAS_LOGGER_VAR, METHODS_LOGGER)
7391df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        val callsByEventType = adapter.calls.groupBy { it.method.onLifecycleEvent.value }
7491df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        beginControlFlow("if ($N)", ON_ANY_PARAM).apply {
7591df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets            writeMethodCalls(callsByEventType[Lifecycle.Event.ON_ANY] ?: emptyList(), receiverField)
7691df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        }.endControlFlow()
7791df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets
7891df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        callsByEventType
7991df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                .filterKeys { key -> key != Lifecycle.Event.ON_ANY }
80619b2be303df81e1b807112100d9a0e92f43e951Sergey Vasilinets                .forEach { (event, calls) ->
8191df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                    beginControlFlow("if ($N == $T.$L)", EVENT_PARAM, LIFECYCLE_EVENT, event)
8291df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                    writeMethodCalls(calls, receiverField)
8391df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                    endControlFlow()
840bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets                }
850bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets    }.build()
860bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
873a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinets    val receiverParam = ParameterSpec.builder(
883a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinets            ClassName.get(adapter.type), "receiver").build()
890bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
903a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinets    val syntheticMethods = adapter.syntheticMethods.map {
910bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets        val method = MethodSpec.methodBuilder(syntheticName(it))
920bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets                .returns(TypeName.VOID)
930bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets                .addModifiers(Modifier.PUBLIC)
940bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets                .addModifiers(Modifier.STATIC)
950bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets                .addParameter(receiverParam)
960bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets        if (it.parameters.size >= 1) {
9791df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets            method.addParameter(OWNER_PARAM)
980bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets        }
990bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets        if (it.parameters.size == 2) {
10091df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets            method.addParameter(EVENT_PARAM)
1010bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets        }
1020bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
1030bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets        val count = it.parameters.size
1040bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets        val paramString = generateParamString(count)
1050bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets        method.addStatement("$N.$L($paramString)", receiverParam, it.name(),
10691df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                *takeParams(count, OWNER_PARAM, EVENT_PARAM))
1070bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets        method.build()
1080bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets    }
1090bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
1100bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets    val constructor = MethodSpec.constructorBuilder()
1110bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            .addParameter(receiverParam)
1120bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            .addStatement("this.$N = $N", receiverField, receiverParam)
1130bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            .build()
1140bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
1153a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinets    val adapterName = getAdapterName(adapter.type)
11662693d74bb01e67e62a6601c4a79ac67136a458fshepshapard    val adapterTypeSpecBuilder = TypeSpec.classBuilder(adapterName)
1170bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            .addModifiers(Modifier.PUBLIC)
11891df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets            .addSuperinterface(ClassName.get(GeneratedAdapter::class.java))
1190bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            .addField(receiverField)
1200bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            .addMethod(constructor)
1210bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            .addMethod(dispatchMethod)
1220bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets            .addMethods(syntheticMethods)
12362693d74bb01e67e62a6601c4a79ac67136a458fshepshapard
12462693d74bb01e67e62a6601c4a79ac67136a458fshepshapard    addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder, processingEnv)
12562693d74bb01e67e62a6601c4a79ac67136a458fshepshapard
12662693d74bb01e67e62a6601c4a79ac67136a458fshepshapard    JavaFile.builder(adapter.type.getPackageQName(), adapterTypeSpecBuilder.build())
12762693d74bb01e67e62a6601c4a79ac67136a458fshepshapard            .build().writeTo(processingEnv.filer)
1288dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer
1298dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer    generateKeepRule(adapter.type, processingEnv)
13062693d74bb01e67e62a6601c4a79ac67136a458fshepshapard}
13162693d74bb01e67e62a6601c4a79ac67136a458fshepshapard
13262693d74bb01e67e62a6601c4a79ac67136a458fshepshapardprivate fun addGeneratedAnnotationIfAvailable(adapterTypeSpecBuilder: TypeSpec.Builder,
13391df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                                              processingEnv: ProcessingEnvironment) {
13462693d74bb01e67e62a6601c4a79ac67136a458fshepshapard    val generatedAnnotationAvailable = processingEnv
13562693d74bb01e67e62a6601c4a79ac67136a458fshepshapard            .elementUtils
13662693d74bb01e67e62a6601c4a79ac67136a458fshepshapard            .getTypeElement(GENERATED_PACKAGE + "." + GENERATED_NAME) != null
13762693d74bb01e67e62a6601c4a79ac67136a458fshepshapard    if (generatedAnnotationAvailable) {
13862693d74bb01e67e62a6601c4a79ac67136a458fshepshapard        val generatedAnnotationSpec =
13962693d74bb01e67e62a6601c4a79ac67136a458fshepshapard                AnnotationSpec.builder(ClassName.get(GENERATED_PACKAGE, GENERATED_NAME)).addMember(
14091df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                        "value",
14191df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                        S,
14291df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                        LifecycleProcessor::class.java.canonicalName).build()
14362693d74bb01e67e62a6601c4a79ac67136a458fshepshapard        adapterTypeSpecBuilder.addAnnotation(generatedAnnotationSpec)
14462693d74bb01e67e62a6601c4a79ac67136a458fshepshapard    }
1450bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets}
1460bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
1478dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmerprivate fun generateKeepRule(type: TypeElement, processingEnv: ProcessingEnvironment) {
1488dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer    val adapterClass = type.getPackageQName() + "." + getAdapterName(type)
1498dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer    val observerClass = type.toString()
1508dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer    val keepRule = """# Generated keep rule for Lifecycle observer adapter.
1512196a6113412dc0f2c54ad9d2fe47ab013f56852Adam Koski        |-if class $observerClass {
1522196a6113412dc0f2c54ad9d2fe47ab013f56852Adam Koski        |    <init>(...);
153eb9bc738f21a41021d236d0e30c3034d3163e613Sergey Vasilinets        |}
154eb9bc738f21a41021d236d0e30c3034d3163e613Sergey Vasilinets        |-keep class $adapterClass {
155eb9bc738f21a41021d236d0e30c3034d3163e613Sergey Vasilinets        |    <init>(...);
156eb9bc738f21a41021d236d0e30c3034d3163e613Sergey Vasilinets        |}
1578dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer        |""".trimMargin()
1588dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer
1598dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer    // Write the keep rule to the META-INF/proguard directory of the Jar file. The file name
1608dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer    // contains the fully qualified observer name so that file names are unique. This will allow any
1618dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer    // jar file merging to not overwrite keep rule files.
1628dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer    val path = "META-INF/proguard/$observerClass.pro"
1638dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer    val out = processingEnv.filer.createResource(StandardLocation.CLASS_OUTPUT, "", path)
1648dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer    out.openWriter().use { it.write(keepRule) }
1658dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer}
1668dd2658f5c9c58c197b7d02d8214af22a5de60abShane Farmer
16791df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinetsprivate fun MethodSpec.Builder.writeMethodCalls(calls: List<EventMethodCall>,
1680bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets                                                receiverField: FieldSpec) {
1693a9bd3acec72820f173ebf52a1cb7bb1d52eeabfSergey Vasilinets    calls.forEach { (method, syntheticAccess) ->
1700bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets        val count = method.method.parameters.size
17191df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        val callType = 1 shl count
17291df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        val methodName = method.method.name()
17391df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        beginControlFlow("if (!$L || $N.approveCall($S, $callType))",
17491df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                HAS_LOGGER_VAR, METHODS_LOGGER, methodName).apply {
17591df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets
17691df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets            if (syntheticAccess == null) {
17791df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                val paramString = generateParamString(count)
17891df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                addStatement("$N.$L($paramString)", receiverField,
17991df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                        methodName,
18091df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                        *takeParams(count, OWNER_PARAM, EVENT_PARAM))
18191df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets            } else {
18291df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                val originalType = syntheticAccess
18391df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                val paramString = generateParamString(count + 1)
18491df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                val className = ClassName.get(originalType.getPackageQName(),
18591df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                        getAdapterName(originalType))
18691df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                addStatement("$T.$L($paramString)", className,
18791df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                        syntheticName(method.method),
18891df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets                        *takeParams(count + 1, receiverField, OWNER_PARAM, EVENT_PARAM))
18991df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets            }
19091df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets        }.endControlFlow()
1910bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets    }
19291df2cf6dacb3035df3f6b98e895cdc9f025553aSergey Vasilinets    addStatement("return")
1930bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets}
1940bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
1950bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinetsprivate fun takeParams(count: Int, vararg params: Any) = params.take(count).toTypedArray()
1960bbdd57e9654789f419177f1ff90221d5872b116Sergey Vasilinets
1972196a6113412dc0f2c54ad9d2fe47ab013f56852Adam Koskiprivate fun generateParamString(count: Int) = (0 until count).joinToString(",") { N }
198