d.sunnyone.org
sunnyone.org

ページ

2015-08-04

groovyshでJavaのクラス情報を参照して適当にコード片を生成する

IDEがやってくれるような一般的なコード生成ではないが、規則のあるコードをちょちょいと生成したりしたいときがある。Annotation Processingでアノテーション見ながらというのが王道だろうが、そこまででもない使い捨てな感じにコードをGroovyを使って生成する方法。

groovyshの起動

GroovyのREPLであるところのgroovyshをまず起動する。追記: なお、IntelliJ IDEAであればTools > Groovy Consoleで起動できるGroovyで処理できるので、いきなり次の作業に飛んで問題ない。

単純なケースであれば、クラスパスを指定して、groovyshを起動する。
$ groovysh -cp out/production/javaclasstest

普通はさらさらっと指定できるほど単純ではないと思うが、Gradleからgradle-groovysh-pluginを使ってgroovyshを起動すれば、必要なものをロードしておいてくれる。

基本的には、以下の変更をbuild.gradleに加えた後「./gradlew -q --no-daemon shell」で起動すればOK。
  • buildscriptのdependenciesにcom.tkruse.gradle:gradle-groovysh-pluginを追加する。
  • 'java'と'com.github.tkruse.groovysh'をapply pluginする。
例としてはこんな感じ。

apply plugin: 'java'
apply plugin: 'com.github.tkruse.groovysh'

buildscript {
  repositories {
      jcenter()
  }

  dependencies {
      classpath 'com.tkruse.gradle:gradle-groovysh-plugin:1.0.7'
  }
}


ただし、gradle.propertiesに「org.gradle.daemon=true」が書いてあると「Do not run with gradle daemon (use --no-daemon)」と言われて起動できない。--no-daemonをつけろと言うのだが、つけてもダメ。多分チェック方法が間違っている。Issueに登録しておいたが、直るまでは実行するときはファイルにtrueを記述していない状態にするしかない。

[2015/8/26追記] warningが出つつも起動するようにしてもらえたので、ちゃんと-q --no-daemonで起動すればOK。

リフレクションを使って適当に作る

あとはjava.lang.Classjava.lang.reflect.Methodを見ながら、適当にコードを生成する。GroovyなのでgetHogeはhogeでいいし、Rubyちっくに適当につなげれば生成できるのでラク。

たとえば、こんな感じ。
$ ./gradlew shell -q
(略)
groovy:000> cls = com.example.HelloJavaBuilder
===> class com.example.HelloJavaBuilder
groovy:000> cls.methods.findAll { it.declaringClass == cls }.collect { ".${it.name}()" }.sort().join("\n")
===> .build()
.fuga()
.hige()
.hoge()
groovy:000> 

ポイントとしては、import なんちゃらと打つとパッケージ名の補完が効くので、いったんimportしちゃうなり、補完までしてimportを書き換えるなどするとパッケージ/クラス名の入力が楽。

Tips: GroovyのMetaClassを活用する

setterのsetを外したところを取りたい、みたいな話だと、普通にやるとこんな感じだと思う。
groovy:000> cls.methods.findAll { it.declaringClass == cls && it.name =~ /set/ }.sort().
collect { it.name.replaceAll(/set/, '').replaceAll(/^./) { it.toLowerCase() } }

しかし、GroovyのmetaClassを見ると、setter/getterはset/getを外した形でpropertiesに入っているので、こっちを使うとかなり楽になる(classもあるけど、そこはうまいこと外す)
groovy:000> cls.metaClass.properties.collect{ it.name }

もし役に立つ機会があればどうぞ。

0 件のコメント:

コメントを投稿