Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Meta-Programming with Kotlin - Droidkaigi 2020

Jitin
May 31, 2020

Meta-Programming with Kotlin - Droidkaigi 2020

Kotlin has become go-to language for Android developers all over the world and the language itself has a large number of feature sets.
With greater acceptance of language, we look at how we can leverage power of Kotlin for meta-programming.
Through the talk, we'll introduce what Meta-Programming is and how we do it on day to day basis. We'll see how using Kotlin's internal compiler API, we can parse kotlin files, pull data out of it and go through various applications of this data such as custom documentation for API code and much more.
We'll also look at various approaches of source code generation for Kotlin files and how we can leverage them in projects.

Jitin

May 31, 2020
Tweet

More Decks by Jitin

Other Decks in Technology

Transcript

  1. Source Code Parsers? • Gain insights over large codebase •

    Build static analysis tools • Documentation!
  2. File Class class… Functions Variables • abstract • interface •

    static • …. • private • protected • public • ….
  3. File Classes Functions Variables class… Functions Variables • abstract •

    interface • object • companion • data • …. • private • internal • typealias • ….
  4. val source: String = System.getProperty("user.dir") class Generator : GeneratorConfig {

    private var replaceIfNecessary = false override fun createConfig(config: Config) { } inner class Template { ... } data class Model(...) }
  5. PSI PsiFileFactory .getInstance(project) .createFileFromText(KotlinLanguage.INSTANCE, text) private val project by lazy

    { KotlinCoreEnvironment.createForProduction( Disposer.newDisposable(), CompilerConfiguration(), EnvironmentConfigFiles.JVM_CONFIG_FILES ).project }
  6. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  7. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  8. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  9. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  10. class ArrowView(context: Context, sides: Float, val base: Float) : View(context)

    { KtClass KtSuperTypeEntries } } KtPrimaryConstructor }
  11. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  12. private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context)

    imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView } KtParameter KtModifierKeywordToken KtTypeReference KtBlockExpression } } } } KtExpression
  13. private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context)

    imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView } /** * Generates an imageview dynamically */ KDoc }
  14. ArrowView.kt import android.content.Context import android.graphics.Canvas import android.graphics.Color class ArrowView(context: Context,

    sides: Float, val base: Float) : View(context) { private fun createIconView(drawable: Drawable): ImageView { val imageView = AppCompatImageView(context) imageView.scaleType = ImageView.ScaleType.CENTER ... return imageView }
  15. Source Code as Data Source Code @Serializable data class ParsedFile(

    var name: String, var doc: String? = null, @SerialName("classes") var parsedClasses: List<ParsedClass>? = null, @SerialName("functions") var parsedFunctions: List<ParsedFunction>? = null, @SerialName("properties") var parsedVariables: List<ParsedVariable>? = null )
  16. Source Code as Data Source Code @Serializable data class ParsedFile(

    var name: String, var doc: String? = null, @SerialName("classes") var parsedClasses: List<ParsedClass>? = null, @SerialName("functions") var parsedFunctions: List<ParsedFunction>? = null, @SerialName("properties") var parsedVariables: List<ParsedVariable>? = null )
  17. Source Code Generators? • Avoid manual boilerplate code • Promote

    templating • Create more formalised code base
  18. Writing Source Code • Easy string manipulation with regex and

    indent extensions • Java8 provides better file operations and Streams! • Coroutines for async
  19. Writing Templates val text = """ class MainActivity : AppCompatActivity()

    { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) } } """.trimIndent() Files.write(path, text)
  20. Writing Templates val text = """ class $activityName : BaseActivity()

    { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.$layoutName) } } """.trimIndent() Files.write(path, text)
  21. Schema based Codegen { "sourceClass": "MainActivity.kt", "sourceXml": "activity_main", "adapter": "CountryListAdapter",

    "views": [ { "layoutFileName": "country_list_header.xml", "layoutType": "TextView", "viewHolderName": "CountryListHeaderViewHolder" } ] }
  22. SqlDelight CREATE TABLE Photos ( id INTEGER NOT NULL, title

    TEXT NOT NULL ); insert: INSERT INTO Photos (id, title) VALUES (?, ?); getAll: SELECT * FROM Photos;
  23. SqlDelight interface PhotoQueries : Transacter { fun <T : Any>

    getAll(mapper: ( id: String, title: String ) -> T): Query<T> fun getAll(): Query<Photos> fun insert( id: String, title: String ) }
  24. <T : Closeable?, R> T.use() codeString .lineSequence() .map { line

    -> } Files.find(path, 10, BiPredicate { path, _ -> })
  25. <T : Closeable?, R> T.use() codeString .lineSequence() .map { line

    -> } Files.find(path, 10, BiPredicate { path, _ -> }) Files.write( path, classString.toByteArray(), StandardOpenOption.CREATE_NEW, StandardOpenOption.TRUNCATE_EXISTING)
  26. . ├── app ├── module-1 ├── module-2 ├── … └──

    tools-module java -jar project/build/libs/tools.jar --param