KotlinPoetを使ってコード生成をしたので、触りだけですが、紹介したいと思います。
なおこのエントリーはKotlinPoet v0.6.0を利用しています。
想定読者
KotlinPoetに興味がある人
JavaPoetを触ったこと・勉強したことがある人
書いていないこと
KotlinPoetについて
KotlinPoetはKotlinのコードを生成することを手助けするライブラリです。JavaPoet のKotlin版というイメージです。
以下のコードが
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
val greeterClass = ClassName ( "" , "Greeter" )
val file = FileSpec . builder ( "" , "HelloWorld" )
. addType ( TypeSpec . classBuilder ( "Greeter" )
. primaryConstructor ( FunSpec . constructorBuilder ()
. addParameter ( "name" , String :: class )
. build ())
. addProperty ( PropertySpec . builder ( "name" , String :: class )
. initializer ( "name" )
. build ())
. addFunction ( FunSpec . builder ( "greet" )
. addStatement ( "println(%S)" , "Hello, \$name" )
. build ())
. build ())
. addFunction ( FunSpec . builder ( "main" )
. addParameter ( "args" , String :: class , VARARG )
. addStatement ( "%T(args[0]).greet()" , greeterClass )
. build ())
. build ()
file . writeTo ( System . out )
このコードを出力します。
1
2
3
4
5
6
7
8
9
class Greeter ( val name : String ) {
fun greet () {
println ( "Hello, $name" )
}
}
fun main ( vararg args : String ) {
Greeter ( args [ 0 ]). greet ()
}
KotlinPoetの紹介はKotlinConfの動画 を観ると良いです。
KotlinPoetの考え方
実は上記のコード一点、非常に面白い 点があります。
1
class Greeter ( val name : String )
primary constructorの生成です。KotlinPoetの以下の部分で生成しています。
1
2
3
4
5
6
7
8
val file = FileSpec . builder ( "" , "HelloWorld" )
. addType ( TypeSpec . classBuilder ( "Greeter" )
. primaryConstructor ( FunSpec . constructorBuilder ()
. addParameter ( "name" , String :: class )
. build ())
. addProperty ( PropertySpec . builder ( "name" , String :: class )
. initializer ( "name" )
. build ())
valのname
をprimary constructorに入れるだけなのですが、KotlinPoetでは3回もname
と記述しています。
せっかくなので、一つずつ見ていきましょう。まずPropertyを生成する場合、以下のコードになります。
1
2
3
4
val file = FileSpec . builder ( "" , "HelloWorld" )
. addType ( TypeSpec . classBuilder ( "Greeter" )
. addProperty ( PropertySpec . builder ( "name" , String :: class )
. build ())
以下のコードが生成されます。
1
2
3
class Greeter {
val name : String
}
次にprimary constructorにname
入れたい為、primary constructorの設定を記述します。
1
2
3
4
5
6
7
val file = FileSpec.builder("", "HelloWorld")
.addType(TypeSpec.classBuilder("Greeter")
.primaryConstructor(FunSpec.constructorBuilder()
.addParameter("name", String::class)
.build())
+ .addProperty(PropertySpec.builder("name", String::class)
+ .build())
そうするとこんなコードが生成されます。
1
2
3
class Greeter ( name : String ) {
val name : String
}
最後にprimary constructorとpropertyを連結するため、初期化方法を記述します。
1
2
3
4
5
6
7
8
val file = FileSpec.builder("", "HelloWorld")
.addType(TypeSpec.classBuilder("Greeter")
.primaryConstructor(FunSpec.constructorBuilder()
.addParameter("name", String::class)
.build())
.addProperty(PropertySpec.builder("name", String::class)
+ .initializer("name")
.build())
これでようやく以下のコードが生成されるようになります。
1
class Greeter ( val name : String )
これは、記述されたコードは全て生成する。最適化はKotlinPoetがする。というKotlinPoetの考えからきているそうです。
KotlinConfの動画でも解説されていますので確認して見てください。 https://youtu.be/_obNBSldffw?t=20m40s
若干文法が違ったり、上記のような隠れた癖がある為、JavaPoetに慣れている方は最初戸惑うことがあるかもしれませんので、生成後のコードをしっかり確認した方が良いです。
ちなみにPermissionsDispatcherはKotlinPoetのこの挙動を知らず、誤って外に出てしまったpropertyをコンストラクタに詰める為の修正 をv3.0.1で入れています:joy:
その時の開発者のつぶやきです。
その他の代表的なコードの生成方法
ファイル、クラス、クラスメンバー、プライマリコンストラクター、トップレベルの関数に関しては上記サンプルコードをみてください。
それ以外のよく使いそうなコードの生成方法をメモしておきます。
コメント
kotlinpoetのコード
1
val file = FileSpec . builder ( "com.github.shiraji" , "HelloWorld" ). addComment ( "コメント" ). build ()
生成されるコード
1
2
// コメント
package com.github.shiraji
FileSpec.Builder
のaddComment
を利用している為、ファイル上部にコメントしていますが、Builderの種類(TypeSpec.Builderなど)によりコメント位置が調整されます。
フォーマット
1
2
val message = "ふふふ"
val file = FileSpec . builder ( "com.github.shiraji" , "HelloWorld" ). addComment ( "コメント %L" , message ). build ()
1
2
// コメント ふふふ
package com.github.shiraji
フォーマットはJavaPoetと違い%
を利用します。その他のフォーマットはこちら を参照してください。
これ以降のコードはあまりフォーマットを利用していませんが、本来はこのフォーマットを使う方が良いです。
initメソッド
1
2
3
4
5
6
7
val file = FileSpec . builder ( "com.github.shiraji" , "HelloWorld" )
. addType ( TypeSpec . classBuilder ( "Greeter" )
. addInitializerBlock ( CodeBlock . builder ()
. addStatement ( "val i = 10" )
. build ())
. build ())
. build ()
1
2
3
4
5
6
7
package com.github.shiraji
class Greeter {
init {
val i = 10
}
}
Secondary Constructor
1
2
3
4
5
6
7
8
9
10
11
12
val file = FileSpec . builder ( "com.github.shiraji" , "HelloWorld" )
. addType ( TypeSpec . classBuilder ( "Greeter" )
. primaryConstructor ( FunSpec . constructorBuilder ()
. addParameter ( "name" , String :: class )
. build ())
. addFunction ( FunSpec . constructorBuilder ()
. callThisConstructor ( "name" ) // callSuperConstructorもあります。
. addParameter ( "name" , String :: class )
. addParameter ( "lastname" , String :: class )
. build ())
. build ())
. build ()
1
2
3
4
5
6
7
package com.github.shiraji
import kotlin.String
class Greeter ( name : String ) {
constructor ( name : String , lastname : String ) : this ( name )
}
拡張関数
1
2
3
4
5
6
7
val file = FileSpec . builder ( "com.github.shiraji" , "HelloWorld" )
. addType ( TypeSpec . classBuilder ( "Greeter" )
. addFunction ( FunSpec . builder ( "foo" )
. receiver ( String :: class )
. build ())
. build ())
. build ()
1
2
3
4
5
6
7
8
package com.github.shiraji
import kotlin.String
class Greeter {
fun String . foo () {
}
}
Class修飾子(Dataクラス)
1
2
3
4
val file = FileSpec . builder ( "com.github.shiraji" , "HelloWorld" )
. addType ( TypeSpec . classBuilder ( "Foo" )
. addModifiers ( KModifier . DATA ) // KModifier.ENUMなどもあります
. build ())
1
2
3
package com.github.shiraji
data class Foo
if/else
1
2
3
4
5
6
7
8
9
10
11
12
val file = FileSpec . builder ( "com.github.shiraji" , "HelloWorld" )
. addType ( TypeSpec . classBuilder ( "Greeter" )
. addFunction ( FunSpec . builder ( "foo" )
. beginControlFlow ( "if (true)" )
. addStatement ( "val i1 = 10" )
. endControlFlow ()
. beginControlFlow ( "else" )
. addStatement ( "val i2 = 20" )
. endControlFlow ()
. build ())
. build ())
. build ()
1
2
3
4
5
6
7
8
9
10
11
12
package com.github.shiraji
class Greeter {
fun foo () {
if ( true ) {
val i1 = 10
}
else {
val i2 = 20
}
}
}
when
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val file = FileSpec . builder ( "com.github.shiraji" , "HelloWorld" )
. addType ( TypeSpec . classBuilder ( "Greeter" )
. addFunction ( FunSpec . builder ( "foo" )
. addStatement ( "val i = 10" )
. beginControlFlow ( "when(i)" )
. beginControlFlow ( "10 ->" )
. addStatement ( "println(\"foo\")" )
. endControlFlow ()
. beginControlFlow ( "20 ->" )
. addStatement ( "println(\"foo222\")" )
. endControlFlow ()
. endControlFlow ()
. build ())
. build ())
. build ()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.github.shiraji
class Greeter {
fun foo () {
val i = 10
when ( i ) {
10 -> {
println ( "foo" )
}
20 -> {
println ( "foo222" )
}
}
}
}
インデント
1
val file = FileSpec . builder ( "com.github.shiraji" , "HelloWorld" ). addComment ( "%>コメント" ). build ()
1
2
// コメント
package com.github.shiraji
アンインデントされるまでインデントされ続けます。
アンインデント
1
val file = FileSpec . builder ( "com.github.shiraji" , "HelloWorld" ). addComment ( "%>コメント%<" ). build ()
1
2
// コメント
package com.github.shiraji
APIドキュメント
その他知りたければ、KotlinPoetのAPIドキュメント を確認して下さい。(v0.x系以降のドキュメントはURLが変更されるかも?)
最後に
PermissionsDispatcherのこの辺り を眺めるとKotlinPoetの実装の参考になると思います。