From 7b55d0b598cb13287d0c3e0fa5c179ea1def8636 Mon Sep 17 00:00:00 2001
From: Janne Mareike Koschinski <janne@kuschku.de>
Date: Sun, 21 Feb 2021 16:52:20 +0100
Subject: [PATCH] Cleanup annotation/symbol processing

---
 .../libquassel/generator/InvokerProcessor.kt  | 127 ++-------------
 .../annotation/RpcFunctionAnnotation.kt       |  37 +++++
 .../annotation/RpcObjectAnnotation.kt         |  31 ++++
 .../generator/kotlinmodel/KotlinModel.kt      |  36 +++++
 .../kotlinmodel/KotlinModelVisitor.kt         |  16 ++
 .../libquassel/generator/rpcmodel/RpcModel.kt |  52 ++++++
 .../generator/rpcmodel/RpcModelVisitor.kt     |  17 ++
 .../generator/util/kotlinpoet/ArgString.kt    |  38 +++++
 .../util/kotlinpoet/WhenBlockBuilder.kt       |  44 +++++
 .../kotlinpoet/buildWhen.kt}                  |  13 +-
 .../kotlinpoet/withIndent.kt}                 |  13 +-
 .../generator/util/ksp/asClassName.kt         |  34 ++++
 .../generator/util/{ => ksp}/asType.kt        |   0
 .../generator/util/ksp/asTypeName.kt          |  49 ++++++
 .../util/{ => ksp}/findAnnotationWithType.kt  |   0
 .../{ => ksp}/getClassDeclarationByName.kt    |   0
 .../generator/util/{ => ksp}/getMember.kt     |   2 +-
 .../generator/util/{ => ksp}/hasAnnotation.kt |   0
 .../generator/util/{ => ksp}/toEnum.kt        |   5 +-
 .../libquassel/generator/util/toClassName.kt  |  42 -----
 .../generator/visitors/KSDeclarationParser.kt |  97 +++++++++++
 .../generator/visitors/KotlinSaver.kt         |  33 ++++
 .../generator/visitors/RpcModelProcessor.kt   | 151 ++++++++++++++++++
 .../libquassel/protocol/variant/QVariant.kt   |  13 ++
 .../variant/WrongVariantTypeException.kt      |  12 +-
 .../exceptions/UnknownMethodException.kt      |  11 +-
 .../exceptions/WrongObjectTypeException.kt    |   9 +-
 .../libquassel/state/invoker/Invoker.kt       |  21 +++
 28 files changed, 711 insertions(+), 192 deletions(-)
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/RpcFunctionAnnotation.kt
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/RpcObjectAnnotation.kt
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModel.kt
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModelVisitor.kt
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/rpcmodel/RpcModel.kt
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/rpcmodel/RpcModelVisitor.kt
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/ArgString.kt
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/WhenBlockBuilder.kt
 rename libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/{model/SyncedCallModel.kt => util/kotlinpoet/buildWhen.kt} (54%)
 rename libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/{annotation/SyncedCallAnnotationModel.kt => util/kotlinpoet/withIndent.kt} (61%)
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/asClassName.kt
 rename libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/{ => ksp}/asType.kt (100%)
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/asTypeName.kt
 rename libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/{ => ksp}/findAnnotationWithType.kt (100%)
 rename libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/{ => ksp}/getClassDeclarationByName.kt (100%)
 rename libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/{ => ksp}/getMember.kt (96%)
 rename libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/{ => ksp}/hasAnnotation.kt (100%)
 rename libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/{ => ksp}/toEnum.kt (86%)
 delete mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toClassName.kt
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KSDeclarationParser.kt
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KotlinSaver.kt
 create mode 100644 libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/RpcModelProcessor.kt
 rename libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedParameterModel.kt => libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/WrongVariantTypeException.kt (60%)
 rename libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedObjectModel.kt => libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/exceptions/UnknownMethodException.kt (66%)
 rename libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedObjectAnnotationModel.kt => libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/exceptions/WrongObjectTypeException.kt (69%)
 create mode 100644 libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/invoker/Invoker.kt

diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt
index 9e3c1a2..9fa3bf9 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/InvokerProcessor.kt
@@ -10,28 +10,16 @@
 
 package de.justjanne.libquassel.generator
 
-import com.google.devtools.ksp.getDeclaredFunctions
 import com.google.devtools.ksp.processing.CodeGenerator
 import com.google.devtools.ksp.processing.KSPLogger
 import com.google.devtools.ksp.processing.Resolver
 import com.google.devtools.ksp.processing.SymbolProcessor
 import com.google.devtools.ksp.symbol.KSAnnotated
-import com.google.devtools.ksp.symbol.KSClassDeclaration
-import com.google.devtools.ksp.symbol.KSFunctionDeclaration
-import com.google.devtools.ksp.symbol.KSType
-import com.google.devtools.ksp.symbol.KSValueParameter
-import com.google.devtools.ksp.symbol.KSVisitorVoid
 import de.justjanne.libquassel.annotations.ProtocolSide
-import de.justjanne.libquassel.annotations.SyncedCall
 import de.justjanne.libquassel.annotations.SyncedObject
-import de.justjanne.libquassel.generator.annotation.SyncedCallAnnotationModel
-import de.justjanne.libquassel.generator.annotation.SyncedObjectAnnotationModel
-import de.justjanne.libquassel.generator.model.SyncedCallModel
-import de.justjanne.libquassel.generator.model.SyncedObjectModel
-import de.justjanne.libquassel.generator.model.SyncedParameterModel
-import de.justjanne.libquassel.generator.util.findAnnotationWithType
-import de.justjanne.libquassel.generator.util.getMember
-import de.justjanne.libquassel.generator.util.toEnum
+import de.justjanne.libquassel.generator.visitors.KSDeclarationParser
+import de.justjanne.libquassel.generator.visitors.KotlinSaver
+import de.justjanne.libquassel.generator.visitors.RpcModelProcessor
 
 class InvokerProcessor : SymbolProcessor {
   lateinit var codeGenerator: CodeGenerator
@@ -48,111 +36,18 @@ class InvokerProcessor : SymbolProcessor {
   }
 
   override fun process(resolver: Resolver): List<KSAnnotated> {
-    val classes = resolver.getSymbolsWithAnnotation(SyncedObject::class.java.canonicalName)
+    resolver.getSymbolsWithAnnotation(SyncedObject::class.java.canonicalName)
+      .mapNotNull { it.accept(KSDeclarationParser(resolver, logger), Unit) }
       .flatMap {
-        val visitor = QuasselRpcVisitor(resolver)
-        it.accept(visitor, Unit)
-        visitor.classes
+        listOfNotNull(
+          it.accept(RpcModelProcessor(), ProtocolSide.CLIENT),
+          it.accept(RpcModelProcessor(), ProtocolSide.CORE),
+        )
       }
-
-    val message = classes.flatMap {
-      listOf(
-        "@SyncedObject(name=${it.rpcName})",
-        it.name,
-      ) + it.methods.flatMap {
-        listOf(
-          "@SyncedCall(name=${it.rpcName}, target=${it.side})",
-          it.name,
-        ) + it.parameters.flatMap {
-          listOf(
-            "${it.name}: ${it.type}"
-          )
-        }.map { "  $it" }
-      }.map { "  $it" }
-    }.joinToString("\n")
-    logger.logging("Data: \n\n$message\n\n")
+      .map { it.accept(KotlinSaver(), codeGenerator) }
 
     return emptyList()
   }
 
-  override fun finish() {
-    // TODO
-  }
-
-  inner class QuasselRpcVisitor(
-    private val resolver: Resolver
-  ) : KSVisitorVoid() {
-    val classes = mutableListOf<SyncedObjectModel>()
-    var functions = mutableListOf<SyncedCallModel>()
-    var parameters = mutableListOf<SyncedParameterModel>()
-
-    private fun collectFunctions(): List<SyncedCallModel> {
-      val result = functions
-      functions = mutableListOf()
-      return result
-    }
-
-    private fun collectParameters(): List<SyncedParameterModel> {
-      val result = parameters
-      parameters = mutableListOf()
-      return result
-    }
-
-    override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
-      try {
-        val annotation = classDeclaration.findAnnotationWithType<SyncedObject>(resolver) ?: return
-        val syncedObjectModel = SyncedObjectAnnotationModel(
-          name = annotation.getMember("name"),
-        )
-        classDeclaration.getDeclaredFunctions().map { it.accept(this, Unit) }
-        classes.add(
-          SyncedObjectModel(
-            classDeclaration.qualifiedName?.asString() ?: return,
-            syncedObjectModel.name,
-            collectFunctions()
-          )
-        )
-      } catch (t: Throwable) {
-        logger.error("Error processing class ${classDeclaration.qualifiedName?.asString()}", classDeclaration)
-        logger.exception(t)
-      }
-    }
-
-    override fun visitFunctionDeclaration(function: KSFunctionDeclaration, data: Unit) {
-      val annotation = function.findAnnotationWithType<SyncedCall>(resolver) ?: return
-      try {
-        val syncedCallModel = SyncedCallAnnotationModel(
-          name = annotation.getMember("name"),
-          target = annotation.getMember<KSType>("target")?.toEnum<ProtocolSide>(),
-        )
-        function.parameters.map { it.accept(this, Unit) }
-        functions.add(
-          SyncedCallModel(
-            function.simpleName.asString(),
-            syncedCallModel.name,
-            syncedCallModel.target,
-            collectParameters()
-          )
-        )
-      } catch (t: Throwable) {
-        logger.error("Error processing function ${function.qualifiedName?.asString()}", function)
-        logger.exception(t)
-      }
-    }
-
-    override fun visitValueParameter(valueParameter: KSValueParameter, data: Unit) {
-      try {
-        super.visitValueParameter(valueParameter, data)
-        parameters.add(
-          SyncedParameterModel(
-            valueParameter.name?.asString(),
-            valueParameter.type.resolve()
-          )
-        )
-      } catch (t: Throwable) {
-        logger.error("Error processing parameter ${valueParameter.name?.asString()}", valueParameter)
-        logger.exception(t)
-      }
-    }
-  }
+  override fun finish() = Unit
 }
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/RpcFunctionAnnotation.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/RpcFunctionAnnotation.kt
new file mode 100644
index 0000000..1f1f64f
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/RpcFunctionAnnotation.kt
@@ -0,0 +1,37 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.annotation
+
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.symbol.KSAnnotated
+import com.google.devtools.ksp.symbol.KSType
+import de.justjanne.libquassel.annotations.ProtocolSide
+import de.justjanne.libquassel.annotations.SyncedCall
+import de.justjanne.libquassel.generator.util.findAnnotationWithType
+import de.justjanne.libquassel.generator.util.getMember
+import de.justjanne.libquassel.generator.util.toEnum
+
+data class RpcFunctionAnnotation(
+  val name: String?,
+  val target: ProtocolSide?
+) {
+  companion object {
+    fun of(it: KSAnnotated, resolver: Resolver): RpcFunctionAnnotation? {
+      val annotation = it.findAnnotationWithType<SyncedCall>(resolver)
+        ?: return null
+      return RpcFunctionAnnotation(
+        name = annotation.getMember("name"),
+        target = annotation.getMember<KSType>("target")
+          ?.toEnum<ProtocolSide>(),
+      )
+    }
+  }
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/RpcObjectAnnotation.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/RpcObjectAnnotation.kt
new file mode 100644
index 0000000..8a03ba5
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/RpcObjectAnnotation.kt
@@ -0,0 +1,31 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.annotation
+
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.symbol.KSAnnotated
+import de.justjanne.libquassel.annotations.SyncedObject
+import de.justjanne.libquassel.generator.util.findAnnotationWithType
+import de.justjanne.libquassel.generator.util.getMember
+
+data class RpcObjectAnnotation(
+  val name: String?
+) {
+  companion object {
+    fun of(it: KSAnnotated, resolver: Resolver): RpcObjectAnnotation? {
+      val annotation = it.findAnnotationWithType<SyncedObject>(resolver)
+        ?: return null
+      return RpcObjectAnnotation(
+        name = annotation.getMember("name"),
+      )
+    }
+  }
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModel.kt
new file mode 100644
index 0000000..56ab968
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModel.kt
@@ -0,0 +1,36 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.kotlinmodel
+
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSFunctionDeclaration
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.FileSpec
+
+sealed class KotlinModel {
+  data class FileModel(
+    val source: KSClassDeclaration,
+    val data: FileSpec
+  ) : KotlinModel() {
+    override fun <D, R> accept(visitor: KotlinModelVisitor<D, R>, data: D) =
+      visitor.visitFileModel(this, data)
+  }
+
+  data class FunctionModel(
+    val source: KSFunctionDeclaration,
+    val data: CodeBlock
+  ) : KotlinModel() {
+    override fun <D, R> accept(visitor: KotlinModelVisitor<D, R>, data: D) =
+      visitor.visitFunctionModel(this, data)
+  }
+
+  abstract fun <D, R> accept(visitor: KotlinModelVisitor<D, R>, data: D): R
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModelVisitor.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModelVisitor.kt
new file mode 100644
index 0000000..3c95092
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/kotlinmodel/KotlinModelVisitor.kt
@@ -0,0 +1,16 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.kotlinmodel
+
+interface KotlinModelVisitor<D, R> {
+  fun visitFileModel(model: KotlinModel.FileModel, data: D): R
+  fun visitFunctionModel(model: KotlinModel.FunctionModel, data: D): R
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/rpcmodel/RpcModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/rpcmodel/RpcModel.kt
new file mode 100644
index 0000000..51ee73a
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/rpcmodel/RpcModel.kt
@@ -0,0 +1,52 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.rpcmodel
+
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSFunctionDeclaration
+import com.google.devtools.ksp.symbol.KSValueParameter
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.TypeName
+import de.justjanne.libquassel.annotations.ProtocolSide
+
+sealed class RpcModel {
+  data class ObjectModel(
+    val source: KSClassDeclaration,
+    val name: ClassName,
+    val rpcName: String?,
+    val methods: List<FunctionModel>
+  ) : RpcModel() {
+    override fun <D, R> accept(visitor: RpcModelVisitor<D, R>, data: D) =
+      visitor.visitObjectModel(this, data)
+  }
+
+  data class FunctionModel(
+    val source: KSFunctionDeclaration,
+    val name: String,
+    val rpcName: String?,
+    val side: ProtocolSide?,
+    val parameters: List<ParameterModel>
+  ) : RpcModel() {
+    override fun <D, R> accept(visitor: RpcModelVisitor<D, R>, data: D) =
+      visitor.visitFunctionModel(this, data)
+  }
+
+  data class ParameterModel(
+    val source: KSValueParameter,
+    val name: String?,
+    val type: TypeName
+  ) : RpcModel() {
+    override fun <D, R> accept(visitor: RpcModelVisitor<D, R>, data: D) =
+      visitor.visitParameterModel(this, data)
+  }
+
+  abstract fun <D, R> accept(visitor: RpcModelVisitor<D, R>, data: D): R
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/rpcmodel/RpcModelVisitor.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/rpcmodel/RpcModelVisitor.kt
new file mode 100644
index 0000000..f4b06f2
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/rpcmodel/RpcModelVisitor.kt
@@ -0,0 +1,17 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.rpcmodel
+
+interface RpcModelVisitor<D, R> {
+  fun visitObjectModel(model: RpcModel.ObjectModel, data: D): R
+  fun visitFunctionModel(model: RpcModel.FunctionModel, data: D): R
+  fun visitParameterModel(model: RpcModel.ParameterModel, data: D): R
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/ArgString.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/ArgString.kt
new file mode 100644
index 0000000..639582b
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/ArgString.kt
@@ -0,0 +1,38 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.util.kotlinpoet
+
+class ArgString constructor(
+  val name: String,
+  vararg val args: Any?
+) {
+  override fun equals(other: Any?): Boolean {
+    if (this === other) return true
+    if (javaClass != other?.javaClass) return false
+
+    other as ArgString
+
+    if (name != other.name) return false
+    if (!args.contentEquals(other.args)) return false
+
+    return true
+  }
+
+  override fun hashCode(): Int {
+    var result = name.hashCode()
+    result = 31 * result + args.contentHashCode()
+    return result
+  }
+
+  override fun toString(): String {
+    return "ArgString(name='$name', args=${args.contentToString()})"
+  }
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/WhenBlockBuilder.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/WhenBlockBuilder.kt
new file mode 100644
index 0000000..a889e2e
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/WhenBlockBuilder.kt
@@ -0,0 +1,44 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.util.kotlinpoet
+
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.buildCodeBlock
+
+class WhenBlockBuilder constructor(
+  private val over: ArgString
+) {
+  private val cases = mutableListOf<Pair<ArgString, CodeBlock>>()
+
+  constructor(name: String, vararg args: Any?) : this(ArgString(name, args))
+
+  fun addCase(condition: ArgString, block: CodeBlock) {
+    cases.add(Pair(condition, block))
+  }
+
+  fun build(): CodeBlock = buildCodeBlock {
+    beginControlFlow("when (${over.name})", over.args)
+    for ((condition, code) in cases) {
+      beginControlFlow("${condition.name} ->", *condition.args)
+      add(code)
+      endControlFlow()
+    }
+    endControlFlow()
+  }
+
+  inline fun addCase(name: String, vararg args: Any?, f: CodeBlock.Builder.() -> Unit) {
+    addCase(ArgString(name, args), buildCodeBlock(f))
+  }
+
+  inline fun buildElse(f: CodeBlock.Builder.() -> Unit) {
+    addCase(ArgString("else"), buildCodeBlock(f))
+  }
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedCallModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/buildWhen.kt
similarity index 54%
rename from libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedCallModel.kt
rename to libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/buildWhen.kt
index e55a86e..888c06c 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedCallModel.kt
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/buildWhen.kt
@@ -8,13 +8,10 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.generator.model
+package de.justjanne.libquassel.generator.util.kotlinpoet
 
-import de.justjanne.libquassel.annotations.ProtocolSide
+import com.squareup.kotlinpoet.CodeBlock
 
-data class SyncedCallModel(
-  val name: String,
-  val rpcName: String?,
-  val side: ProtocolSide?,
-  val parameters: List<SyncedParameterModel>
-)
+inline fun CodeBlock.Builder.buildWhen(name: String, vararg args: Any?, f: WhenBlockBuilder.() -> Unit) {
+  this.add(WhenBlockBuilder(name, args).apply(f).build())
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedCallAnnotationModel.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/withIndent.kt
similarity index 61%
rename from libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedCallAnnotationModel.kt
rename to libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/withIndent.kt
index 536fb79..b5e3d67 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedCallAnnotationModel.kt
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/kotlinpoet/withIndent.kt
@@ -8,11 +8,12 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.generator.annotation
+package de.justjanne.libquassel.generator.util.kotlinpoet
 
-import de.justjanne.libquassel.annotations.ProtocolSide
+import com.squareup.kotlinpoet.CodeBlock
 
-data class SyncedCallAnnotationModel(
-  val name: String?,
-  val target: ProtocolSide?
-)
+fun CodeBlock.Builder.withIndent(f: CodeBlock.Builder.() -> Unit) {
+  indent()
+  f()
+  unindent()
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/asClassName.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/asClassName.kt
new file mode 100644
index 0000000..edae35d
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/asClassName.kt
@@ -0,0 +1,34 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.util
+
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.google.devtools.ksp.symbol.KSType
+import com.squareup.kotlinpoet.ClassName
+
+private fun KSDeclaration.parents(): List<KSDeclaration> {
+  val declarations = mutableListOf(this)
+  var parent = this.parentDeclaration
+  while (parent != null) {
+    declarations.add(parent)
+    parent = parent.parentDeclaration
+  }
+  return declarations.reversed()
+}
+
+fun KSDeclaration.asClassName(): ClassName {
+  return ClassName(
+    packageName.asString(),
+    parents().map { it.simpleName.asString() }
+  )
+}
+
+fun KSType.asClassName(): ClassName = declaration.asClassName()
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/asType.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/asType.kt
similarity index 100%
rename from libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/asType.kt
rename to libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/asType.kt
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/asTypeName.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/asTypeName.kt
new file mode 100644
index 0000000..450c83e
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/asTypeName.kt
@@ -0,0 +1,49 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.util
+
+import com.google.devtools.ksp.symbol.KSDeclaration
+import com.google.devtools.ksp.symbol.KSType
+import com.google.devtools.ksp.symbol.Variance
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.WildcardTypeName
+
+fun KSDeclaration.asTypeName(): TypeName =
+  ClassName(packageName.asString(), simpleName.asString())
+
+fun KSType.asTypeName(): TypeName {
+  val baseType = asClassName()
+  if (arguments.isEmpty()) {
+    return baseType
+  }
+
+  val parameters = arguments.map {
+    val type = it.type?.resolve()
+    when (it.variance) {
+      Variance.STAR ->
+        WildcardTypeName.producerOf(Any::class)
+          .copy(nullable = true)
+      Variance.INVARIANT ->
+        type!!.asTypeName()
+          .copy(nullable = type.isMarkedNullable)
+      Variance.COVARIANT ->
+        WildcardTypeName.producerOf(type!!.asTypeName())
+          .copy(nullable = type.isMarkedNullable)
+      Variance.CONTRAVARIANT ->
+        WildcardTypeName.consumerOf(type!!.asTypeName())
+          .copy(nullable = type.isMarkedNullable)
+    }
+  }
+
+  return baseType.parameterizedBy(parameters)
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/findAnnotationWithType.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/findAnnotationWithType.kt
similarity index 100%
rename from libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/findAnnotationWithType.kt
rename to libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/findAnnotationWithType.kt
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getClassDeclarationByName.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/getClassDeclarationByName.kt
similarity index 100%
rename from libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getClassDeclarationByName.kt
rename to libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/getClassDeclarationByName.kt
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getMember.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/getMember.kt
similarity index 96%
rename from libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getMember.kt
rename to libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/getMember.kt
index 520994a..953a710 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/getMember.kt
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/getMember.kt
@@ -30,7 +30,7 @@ internal inline fun <reified T> KSAnnotation.getMember(name: String): T? {
     is KSType ->
       when {
         T::class.java != ClassName::class.java -> null
-        else -> argValue.toClassName() as T
+        else -> argValue.asTypeName() as T
       }
     else -> null
   }
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/hasAnnotation.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/hasAnnotation.kt
similarity index 100%
rename from libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/hasAnnotation.kt
rename to libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/hasAnnotation.kt
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toEnum.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/toEnum.kt
similarity index 86%
rename from libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toEnum.kt
rename to libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/toEnum.kt
index 115a1f7..dea5dea 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toEnum.kt
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/ksp/toEnum.kt
@@ -12,9 +12,10 @@ package de.justjanne.libquassel.generator.util
 
 import com.google.devtools.ksp.symbol.KSType
 import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.asClassName
 
 internal inline fun <reified T : Enum<T>> KSType.toEnum(): T? {
-  return toClassName().toEnum(T::class.java)
+  return asClassName().toEnum(T::class.java)
 }
 
 internal inline fun <reified T : Enum<T>> ClassName.toEnum(): T? {
@@ -22,7 +23,7 @@ internal inline fun <reified T : Enum<T>> ClassName.toEnum(): T? {
 }
 
 internal fun <T : Enum<T>> ClassName.toEnum(clazz: Class<T>): T? {
-  val enumClassName = clazz.toClassName()
+  val enumClassName = clazz.asClassName()
   return clazz.enumConstants.find {
     this.canonicalName == enumClassName.nestedClass(it.name).canonicalName
   }
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toClassName.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toClassName.kt
deleted file mode 100644
index 4542f82..0000000
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/util/toClassName.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * libquassel
- * Copyright (c) 2021 Janne Mareike Koschinski
- * Copyright (c) 2021 The Quassel Project
- *
- * This Source Code Form is subject to the terms of the Mozilla Public License,
- * v. 2.0. If a copy of the MPL was not distributed with this file, You can
- * obtain one at https://mozilla.org/MPL/2.0/.
- */
-
-package de.justjanne.libquassel.generator.util
-
-import com.google.devtools.ksp.isLocal
-import com.google.devtools.ksp.symbol.KSClassDeclaration
-import com.google.devtools.ksp.symbol.KSType
-import com.squareup.kotlinpoet.ClassName
-
-internal fun KSType.toClassName(): ClassName {
-  val decl = declaration
-  check(decl is KSClassDeclaration)
-  return decl.toClassName()
-}
-
-internal fun KSClassDeclaration.toClassName(): ClassName {
-  require(!isLocal()) {
-    "Local/anonymous classes are not supported!"
-  }
-  val pkgName = packageName.asString()
-  val typesString = qualifiedName!!.asString().removePrefix("$pkgName.")
-
-  val simpleNames = typesString
-    .split(".")
-  return ClassName(pkgName, simpleNames)
-}
-
-fun Class<*>.toClassName(): ClassName {
-  val packageName = `package`.name
-  val names = canonicalName.substring(packageName.length)
-    .trimStart('.')
-    .split('.')
-  return ClassName(packageName, names)
-}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KSDeclarationParser.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KSDeclarationParser.kt
new file mode 100644
index 0000000..8eb0326
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KSDeclarationParser.kt
@@ -0,0 +1,97 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.visitors
+
+import com.google.devtools.ksp.getDeclaredFunctions
+import com.google.devtools.ksp.processing.KSPLogger
+import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.symbol.KSClassDeclaration
+import com.google.devtools.ksp.symbol.KSFunctionDeclaration
+import com.google.devtools.ksp.symbol.KSNode
+import com.google.devtools.ksp.symbol.KSValueParameter
+import com.google.devtools.ksp.visitor.KSEmptyVisitor
+import com.squareup.kotlinpoet.ClassName
+import de.justjanne.libquassel.generator.annotation.RpcFunctionAnnotation
+import de.justjanne.libquassel.generator.annotation.RpcObjectAnnotation
+import de.justjanne.libquassel.generator.rpcmodel.RpcModel
+import de.justjanne.libquassel.generator.util.asTypeName
+
+class KSDeclarationParser(
+  private val resolver: Resolver,
+  private val logger: KSPLogger
+) : KSEmptyVisitor<Unit, RpcModel?>() {
+  override fun visitClassDeclaration(
+    classDeclaration: KSClassDeclaration,
+    data: Unit
+  ): RpcModel.ObjectModel? {
+    val annotation = RpcObjectAnnotation.of(classDeclaration, resolver)
+      ?: return null
+    try {
+      return RpcModel.ObjectModel(
+        classDeclaration,
+        ClassName(
+          classDeclaration.packageName.asString(),
+          classDeclaration.simpleName.asString()
+        ),
+        annotation.name,
+        classDeclaration.getDeclaredFunctions()
+          .mapNotNull { it.accept(this, Unit) }
+          .mapNotNull { it as? RpcModel.FunctionModel }
+      )
+    } catch (t: Throwable) {
+      logger.error("Error processing  ${annotation.name}", classDeclaration)
+      logger.exception(t)
+      throw t
+    }
+  }
+
+  override fun visitFunctionDeclaration(
+    function: KSFunctionDeclaration,
+    data: Unit
+  ): RpcModel.FunctionModel? {
+    val annotation = RpcFunctionAnnotation.of(function, resolver)
+      ?: return null
+    try {
+      return RpcModel.FunctionModel(
+        function,
+        function.simpleName.asString(),
+        annotation.name,
+        annotation.target,
+        function.parameters
+          .mapNotNull { it.accept(this, Unit) }
+          .mapNotNull { it as? RpcModel.ParameterModel }
+      )
+    } catch (t: Throwable) {
+      logger.error("Error processing  ${annotation.name ?: function.simpleName.asString()}", function)
+      logger.exception(t)
+      throw t
+    }
+  }
+
+  override fun visitValueParameter(
+    valueParameter: KSValueParameter,
+    data: Unit
+  ): RpcModel.ParameterModel {
+    try {
+      return RpcModel.ParameterModel(
+        valueParameter,
+        valueParameter.name?.asString(),
+        valueParameter.type.resolve().asTypeName()
+      )
+    } catch (t: Throwable) {
+      logger.error("Error processing  ${valueParameter.name?.asString()}", valueParameter)
+      logger.exception(t)
+      throw t
+    }
+  }
+
+  override fun defaultHandler(node: KSNode, data: Unit): RpcModel? = null
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KotlinSaver.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KotlinSaver.kt
new file mode 100644
index 0000000..29bfc2c
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/KotlinSaver.kt
@@ -0,0 +1,33 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.visitors
+
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import de.justjanne.libquassel.generator.kotlinmodel.KotlinModel
+import de.justjanne.libquassel.generator.kotlinmodel.KotlinModelVisitor
+
+class KotlinSaver : KotlinModelVisitor<CodeGenerator, Unit> {
+  override fun visitFileModel(model: KotlinModel.FileModel, data: CodeGenerator) {
+    data.createNewFile(
+      Dependencies(false, model.source.containingFile!!),
+      model.data.packageName,
+      model.data.name
+    ).bufferedWriter(Charsets.UTF_8).use {
+      model.data.writeTo(it)
+    }
+  }
+
+  override fun visitFunctionModel(
+    model: KotlinModel.FunctionModel,
+    data: CodeGenerator
+  ) = Unit
+}
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/RpcModelProcessor.kt b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/RpcModelProcessor.kt
new file mode 100644
index 0000000..04d9807
--- /dev/null
+++ b/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/visitors/RpcModelProcessor.kt
@@ -0,0 +1,151 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.generator.visitors
+
+import com.squareup.kotlinpoet.ANY
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeSpec
+import com.squareup.kotlinpoet.asTypeName
+import com.squareup.kotlinpoet.buildCodeBlock
+import de.justjanne.libquassel.annotations.ProtocolSide
+import de.justjanne.libquassel.generator.kotlinmodel.KotlinModel
+import de.justjanne.libquassel.generator.rpcmodel.RpcModel
+import de.justjanne.libquassel.generator.rpcmodel.RpcModelVisitor
+import de.justjanne.libquassel.generator.util.kotlinpoet.ArgString
+import de.justjanne.libquassel.generator.util.kotlinpoet.buildWhen
+import de.justjanne.libquassel.generator.util.kotlinpoet.withIndent
+
+class RpcModelProcessor : RpcModelVisitor<ProtocolSide, KotlinModel?> {
+  override fun visitObjectModel(model: RpcModel.ObjectModel, data: ProtocolSide): KotlinModel {
+    val name = ClassName(
+      TYPENAME_INVOKER.packageName,
+      "${model.rpcName}${data.name.toLowerCase().capitalize()}Invoker"
+    )
+    return KotlinModel.FileModel(
+      model.source,
+      FileSpec.builder(name.packageName, name.simpleName)
+        .addImport(
+          TYPENAME_QVARIANT_INTOORTHROW.packageName,
+          TYPENAME_QVARIANT_INTOORTHROW.simpleName
+        )
+        .addType(
+          TypeSpec.objectBuilder(name.simpleName)
+            .addSuperinterface(TYPENAME_INVOKER.parameterizedBy(model.name))
+            .addProperty(
+              PropertySpec.builder(
+                "className",
+                String::class.asTypeName(),
+                KModifier.OVERRIDE
+              ).initializer("\"${model.rpcName}\"").build()
+            )
+            .addFunction(
+              FunSpec.builder("invoke")
+                .addModifiers(KModifier.OVERRIDE, KModifier.OPERATOR)
+                .addParameter(
+                  ParameterSpec.builder(
+                    "on",
+                    TYPENAME_ANY
+                  ).build()
+                ).addParameter(
+                  ParameterSpec.builder(
+                    "method",
+                    String::class.asTypeName()
+                  ).build()
+                ).addParameter(
+                  ParameterSpec.builder(
+                    "params",
+                    TYPENAME_QVARIANTLIST
+                  ).build()
+                )
+                .addCode(
+                  buildCodeBlock {
+                    beginControlFlow("if (on is %T)", model.name)
+                    buildWhen("method") {
+                      for (method in model.methods) {
+                        val block = method.accept(this@RpcModelProcessor, data)
+                          as? KotlinModel.FunctionModel
+                          ?: continue
+                        addCase(ArgString("%S", method.rpcName ?: method.name), block.data)
+                      }
+                      buildElse {
+                        addStatement("throw %T(className, method)", TYPENAME_UNKNOWN_METHOD_EXCEPTION)
+                      }
+                    }
+                    nextControlFlow("else")
+                    addStatement("throw %T(on, className)", TYPENAME_WRONG_OBJECT_TYPE_EXCEPTION)
+                    endControlFlow()
+                  }
+                )
+                .build()
+            )
+            .build()
+        ).build()
+    )
+  }
+
+  override fun visitFunctionModel(model: RpcModel.FunctionModel, data: ProtocolSide) = KotlinModel.FunctionModel(
+    model.source,
+    buildCodeBlock {
+      if (model.parameters.isEmpty()) {
+        addStatement("on.${model.name}()")
+      } else {
+        addStatement("on.${model.name}(")
+        withIndent {
+          val lastIndex = model.parameters.size - 1
+          for ((i, parameter) in model.parameters.withIndex()) {
+            val suffix = if (i != lastIndex) "," else ""
+            addStatement(
+              "${parameter.name} = params[$i].intoOrThrow<%T>()$suffix",
+              parameter.type
+            )
+          }
+        }
+        addStatement(")")
+      }
+    }
+  )
+
+  override fun visitParameterModel(model: RpcModel.ParameterModel, data: ProtocolSide): KotlinModel? = null
+
+  companion object {
+    private val TYPENAME_INVOKER = ClassName(
+      "de.justjanne.libquassel.state.invoker",
+      "Invoker"
+    )
+    private val TYPENAME_UNKNOWN_METHOD_EXCEPTION = ClassName(
+      "de.justjanne.libquassel.state.exceptions",
+      "UnknownMethodException"
+    )
+    private val TYPENAME_WRONG_OBJECT_TYPE_EXCEPTION = ClassName(
+      "de.justjanne.libquassel.state.exceptions",
+      "WrongObjectTypeException"
+    )
+    private val TYPENAME_QVARIANTLIST = ClassName(
+      "de.justjanne.libquassel.protocol.variant",
+      "QVariantList"
+    )
+    private val TYPENAME_QVARIANT_INTOORTHROW = ClassName(
+      "de.justjanne.libquassel.protocol.variant",
+      "intoOrThrow"
+    )
+    private val TYPENAME_ANY = ANY.copy(nullable = true)
+
+    init {
+      System.setProperty("idea.io.use.nio2", "true")
+    }
+  }
+}
diff --git a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt
index d25c661..ce654bf 100644
--- a/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/QVariant.kt
@@ -107,6 +107,8 @@ sealed class QVariant<T> {
   internal inline fun <reified U> withType(): QVariant<U>? =
     withType(U::class.java)
 
+  fun type(): Class<*>? = data?.let { it::class.java }
+
   internal fun serialize(buffer: ChainedByteBuffer, featureSet: FeatureSet) =
     serializer.serialize(buffer, data, featureSet)
 }
@@ -130,6 +132,17 @@ inline fun <reified T> qVariant(data: T, type: QuasselType): QVariant<T> =
 inline fun <reified T> QVariant_?.into(): T? =
   this?.withType<T>()?.data
 
+/**
+ * Extract the content of a QVariant in a type-safe manner
+ * @return value of the QVariant
+ * @throws if the value could not be coerced into the given type
+ */
+inline fun <reified T> QVariant_?.intoOrThrow(): T =
+  this?.withType<T>()?.data ?: throw WrongVariantTypeException(
+    T::class.java.canonicalName,
+    this?.type()?.canonicalName ?: "null"
+  )
+
 /**
  * Extract the content of a QVariant in a type-safe manner
  * @param defValue default value if the type does not match or no value is found
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedParameterModel.kt b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/WrongVariantTypeException.kt
similarity index 60%
rename from libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedParameterModel.kt
rename to libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/WrongVariantTypeException.kt
index e7242ab..83c2084 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedParameterModel.kt
+++ b/libquassel-protocol/src/main/kotlin/de/justjanne/libquassel/protocol/variant/WrongVariantTypeException.kt
@@ -8,11 +8,9 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.generator.model
+package de.justjanne.libquassel.protocol.variant
 
-import com.google.devtools.ksp.symbol.KSType
-
-data class SyncedParameterModel(
-  val name: String?,
-  val type: KSType
-)
+data class WrongVariantTypeException(
+  val expected: String,
+  val actual: String?
+) : Exception("Could not coerce QVariant of type $actual into $expected")
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedObjectModel.kt b/libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/exceptions/UnknownMethodException.kt
similarity index 66%
rename from libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedObjectModel.kt
rename to libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/exceptions/UnknownMethodException.kt
index 04f52dc..592355e 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/model/SyncedObjectModel.kt
+++ b/libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/exceptions/UnknownMethodException.kt
@@ -8,10 +8,9 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.generator.model
+package de.justjanne.libquassel.state.exceptions
 
-data class SyncedObjectModel(
-  val name: String,
-  val rpcName: String?,
-  val methods: List<SyncedCallModel>
-)
+data class UnknownMethodException(
+  val className: String,
+  val methodName: String
+) : Exception()
diff --git a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedObjectAnnotationModel.kt b/libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/exceptions/WrongObjectTypeException.kt
similarity index 69%
rename from libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedObjectAnnotationModel.kt
rename to libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/exceptions/WrongObjectTypeException.kt
index d2ce55a..61a32ab 100644
--- a/libquassel-generator/src/main/kotlin/de/justjanne/libquassel/generator/annotation/SyncedObjectAnnotationModel.kt
+++ b/libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/exceptions/WrongObjectTypeException.kt
@@ -8,8 +8,9 @@
  * obtain one at https://mozilla.org/MPL/2.0/.
  */
 
-package de.justjanne.libquassel.generator.annotation
+package de.justjanne.libquassel.state.exceptions
 
-data class SyncedObjectAnnotationModel(
-  val name: String?
-)
+data class WrongObjectTypeException(
+  val obj: Any?,
+  val type: String
+) : Exception()
diff --git a/libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/invoker/Invoker.kt b/libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/invoker/Invoker.kt
new file mode 100644
index 0000000..89a26df
--- /dev/null
+++ b/libquassel-state/src/main/kotlin/de/justjanne/libquassel/state/invoker/Invoker.kt
@@ -0,0 +1,21 @@
+/*
+ * libquassel
+ * Copyright (c) 2021 Janne Mareike Koschinski
+ * Copyright (c) 2021 The Quassel Project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+package de.justjanne.libquassel.state.invoker
+
+import de.justjanne.libquassel.protocol.variant.QVariantList
+import de.justjanne.libquassel.state.exceptions.UnknownMethodException
+import de.justjanne.libquassel.state.exceptions.WrongObjectTypeException
+
+interface Invoker<out T> {
+  val className: String
+  @Throws(WrongObjectTypeException::class, UnknownMethodException::class)
+  fun invoke(on: Any?, method: String, params: QVariantList)
+}
-- 
GitLab