u-ryo's blog

various information for coding...

Category: Groovy

Grape on Groovysh

| Comments

groovyshでgrapeを使ってlibraryを使いたい時がありました。

1
2
3
4
5
6
7
groovy:000> :i groovy.grape.*
===> groovy.grape.*
groovy:000> Grape.grab(group:'mysql',module:'mysql-connector-java',version:'8.0.11')
===> null
groovy:000> Grape.grab('mysql:mysql-connector-java:8.0.11')
ERROR java.lang.RuntimeException:
Error grabbing Grapes -- [unresolved dependency: groovy.endorsed#mysql:mysql-connector-java:8.0.11;2.5.0: not found]

versionも文字列でないとならないのに気を付けます。 いつもの'mysql:mysql-connector-java:8.0.11'の形式はダメでした。

@GrabConfig(systemClassLoader=true)はどうやってもダメっぽいので、 結局groovyshからのMySQL accessは諦めましたが。

参考: groovysh で Maven リポジトリにあるライブラリを使う

Drawing Rectangles on an Image for Groovy

| Comments

今まで、「画像を読み込んで線や図形を描く」というのはGroovyFXでやってたんですが、 JavaFXってheadlessで出来ないんですね!? びっくりポンです。 java.awt.headless=trueもそういやAWTなんですね。 うーむ、流石は廃止されるJavaFX、と思ったんですが、 どうせim4java使ってるなら、なんだImageMagickだけで出来るじゃーん、 ということに遅ればせながら気付きました。 ImageMagickってdrawも出来るんですね。

大体、以下の要領です。

  1. import org.im4java.core.*
  2. op = new IMOperation()
  3. op.addImage(...)で画像fileを読み込む
  4. op.fill('rgba(255,100,0,0.5)')等と塗り潰す色を指定
  5. op.stroke('white')等と線の色を指定
  6. op.draw('rectangle 0,10,30,30')で長方形を描画
  7. op.draw('text 0,10 ABCD')で文字を描画
  8. op.quality(80)で圧縮率(品質)指定
  9. もう一度op.addImage(...)で出力画像の名前と形式を指定
  10. new ConvertCmd().run(op)で実行

公式pageにありますけどね。

Groovy Grape Fails

| Comments

時々、「全く同じgroovy scriptなのにあるマシンでだけ 起動に失敗する」ことがあります。 error messageはcaseによりますが、例えば直近では以下のようなものでした。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ groovy webpush.groovy
org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
General error during conversion: Error grabbing Grapes -- [download failed: com.google.guava#guava;19.0!guava.jar(bundle)]

java.lang.RuntimeException: Error grabbing Grapes -- [download failed: com.google.guava#guava;19.0!guava.jar(bundle)]
        at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
        at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
        at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
        at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
        at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:83)
        at org.codehaus.groovy.reflection.CachedConstructor.doConstructorInvoke(CachedConstructor.java:77)
        at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrap.callConstructor(ConstructorSite.java:84)
        at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:59)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:238)
        at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:250)
        at groovy.grape.GrapeIvy.getDependencies(GrapeIvy.groovy:464)
   :
   :

要するに「guava-19.0.jarのdownloadに失敗した」と。 ~/.groovy/grapes/com.google.guava/guava/を見ると、 確かに他のlibrariesのようにjars directoryがありませんし、 当然jar fileもありません。 じゃぁっていうんで、自分でguava-19.0.jarを落としてきて、 jars/作ってその下に置いても、同じ結果です。 Groovyが悪いのかJavaが悪いのかGrapeが悪いのか設定が悪いのか versionが悪いのか、何なのか全然分かりません。 普通に動くマシンでのJava/Groovyとversionを合わせてもダメ、 Grapeする各libraryのversionを上げてもダメ、 Guava 19.0を明示的に@Grabに入れてもダメ、 Groovy - Grab - download failedを見て~/.groovy以下や~/.groovy/grapesを削除してもダメ、 と途方に暮れました。 いつもは面倒なので諦めて、 その動くマシンでやっていつのですけれども、 今回は粘って原因究明してみました。

結局、以下のどちらかの方法で立ち上がるようになりました。

  1. ~/.groovy/grapes/com.google.guava/guava/jarsではなく~/.groovy/grapes/com.google.guava/guava/bundlesにして、その下に落としてきたguava-19.0.jarを置く
  2. 他の多くのlibrariesと同様に~/.groovy/grapes/com.google.guava/guava/jarsに落としてきたguava-19.0.jarを置き、~/.groovy/grapes/com.google.guava/guava/ivy-19.0.xml<publications>下の<artifact name="guava" type="bundle" ext="jar" conf="master"/>type attributeをbundleからjarにする

上手く行っていたマシンでは、 ~/.groovy/grapes/com.google.guava/guava/bundlesが出来ており、 その下にguava-19.0.jarもフツーにありました。 上手く行かないマシンではなぜbundlesが出来ないのか、は未だ不明ですが、 取り敢えず前に進める方法がわかったので記しています。

あぁ、あと、このようにGrapeに失敗すると、 ~/.groovy/grapes/以下にresolved-caller-all-caller-working73.propertiesresolved-caller-all-caller-working73.xmlといったfilesが澱のように溜まっていくので、都度削除した方がいいです。

GroovyFX

| Comments

SwingからJavaのGUIの座を引き継いだとはいえ、 Java11ではJava本体から削除され、 OpenJFXとなるJavaFX、 でもGUI作るならJavaFXだろうなぁ、 簡単に描けるようにGroovyでないかなー、 と思ったら、ありましたGroovyFX。 早速試してみると、 本家に書いてある「Hello, World」すら以下のように失敗しました。

1
2
3
4
5
6
7
8
9
10
11
Caught: java.lang.ExceptionInInitializerError
java.lang.ExceptionInInitializerError
        at java_lang_Class$isAssignableFrom$1.call(Unknown Source)
        at com.sun.proxy.$Proxy8.onClassInfo(Unknown Source)
Caused by: java.lang.IllegalStateException: Toolkit not initialized
        at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:273)
        at com.sun.javafx.application.PlatformImpl.runLater(PlatformImpl.java:268)
        at com.sun.javafx.application.PlatformImpl.setPlatformUserAgentStylesheet(PlatformImpl.java:550)
        at com.sun.javafx.application.PlatformImpl.setDefaultPlatformUserAgentStylesheet(PlatformImpl.java:512)
        at javafx.scene.control.Control.<clinit>(Control.java:87)
        ... 2 more

色々試すと、以下のような最低限のscriptでもaaaを出力もせず 全く同じerror messageを出すので、 もうGrapeでgroovyfxを読み込んでいる時点でダメなんでしょう。

1
2
3
@Grab('org.groovyfx:groovyfx:8.0.0')
import java.lang.*
println('aaa')

scriptに何を書いても無視される(そこまで到達しない)わけなので、 事前にnew javafx.embed.swing.JFXPanel()して JavaFX environmentを初期化する、ということも当然効きません。 Grapeに拘っていたので、 古いversion(@Grab('org.codehaus.groovyfx:groovyfx:0.4.0'))に してnew javafx.embed.swing.JFXPanel()しておけば、 start{...}が動くことがわかりversion落としてやってましたが、 今改めて試してみると、 Grapeじゃなくてgroovy -cp groovyfx-8.0.0.jar test.groovyと Class Pathを指定すれば、 new javafx.embed.swing.JFXPanel()もなしに 「Hello World」もその通り動くことが確認できました。 そっかー、Grape諦めればよかったのかー。 -cpを指定しても足りないところはGrapeも効くので、 そぃでよかったでもんそ。

あと、MySQLからdata読む必要があったので、

1
2
3
4
5
6
7
8
@GrabConfig(systemClassLoader=true)
@Grab('mysql:mysql-connector-java')
import groovy.sql.*

def sql = Sql.newInstance('jdbc:mysql://localhost:3306/database?useSSL=false', 'user', 'password', 'com.mysql.cj.jdbc.Driver')
sql.eachRow('SELECT * FROM table WHERE filename LIKE ?', ['%' + filename]) { r ->
  ...
}

とすればよいと。

今回の目的は、画像を読み込んで、それにDBから読み込んだ値で四角形を描き、 そぃを画像fileとして出力する、ちゅうもんじゃった。 scene.snapshot()してからSwingFXUtils.fromFXImage(scene, null)で 描画画像をbitmap化出来ます。 それをImageIO.write()で一発でfileに書けるがじゃっどん、 そぃだと何か出来た画像が赤みがかっちゃっちょるんですよね。 どげんしたもんか、試行錯誤の挙句、ImageMagickのfrontend、 im4javaを通すとうまく行きました。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.im4java.core.*

color = new Color(0.753, 0.302, 0.314, 0.4)
op = new IMOperation().addImage().quality(100.0).strip().addImage()
convert = new ConvertCmd()

start {
  st = stage(title: 'any words OK', visible: true) {
    s = scene(fill: BLACK, width: w, height: h) {
      imageView(new Image(Paths.get(imageFileName).toUri().toString()))
      rectangles.values().forEach { v ->
        rectangle(x:v[0], y:v[1], width:v[2], height:v[3], fill:color)
      }
    }.snapshot(new WritableImage(w, h))
    convert.run(op, SwingFXUtils.fromFXImage(s, null), imageFileName.replace('.jpg', '_.jpg'))
    // ImageIO.write(SwingFXUtils.fromFXImage(s, null), 'jpg', new File(imageFileName.replace('.jpg', '_.jpg')))
  }
  st.close()
}

最後、stage.close()すればwindowは開かずに済みます。