Wednesday, May 4, 2011

Gjが更新されました!

"Gj"を今日更新しました。"Gj"は素早く、簡単に、そしてなんの苦労もいらずにGroovyプログラムを配布形式にする為のスクリプトです。 (以前のポストも見てください).

更新内容は以下です:

1. Groovyをバンドルするオプション
-bg/--bundlegroovy オプションが追加されました。Groovyのライブラリを直接指定する代わりにこのオプションを使うことができます。
----
> cat src\PrintHello.groovy
println 'hello'

> groovy Gj.groovy -bg -s src -m PrintHello printhello.jar

> java -jar printhello.jar
hello
----

2. javac用のデフォルトのjavaバージョン
javac用のデフォルトのjavaバージョンがシステムと同じバージョンになるよう変更されています。

3. Gjのバージョンを出力するオプション
-ver/--version オプションが追加されました。このオプションを使ってGjのバージョンを確認することができます。
----
> groovy Gj.groovy -ver
Gj version: 11.05.05
----

新しいバージョンはこちらからダウンロードできます。使ってみてください ;-)

Monday, May 2, 2011

Poison for Groovy

昨夜、Gjスクリプトのテスト中にgroovy.batのバグにぶち当たりました (Gjスクリプトは実行可能なjarファイルを簡単に作るために僕が書いたものです。詳しくは以前のポストを見てください)。groovy.batが * (アスタリスク)を含んだコマンドライン引数を扱えないのです。

例えば、次のコマンドを入力するとgroovy.batは応答しなくなります。
----
groovy Foo.groovy "*"
----
あるいは
----
groovy -e "println '*'"
----

GroovyMainに直接渡した時は動作します。
----
java -cp "%GROOVY_HOME%\embeddable\groovy-all-1.8.0.jar" groovy.ui.GroovyMain -e "println '*'"
*
----

既知バグかどうか探したら、GROOVY-3043を見つけました。幸運なことにこのバグは2年前に修正されていました。よしよし、そういうことなら... って修正済み? いやちょっと、まだバグってますよ。

奇妙なことに修正済みの (不確か、とりあえず動いてるように見える) startGroovy.bat (groovy.batから呼ばれ、コマンドライン引数をパースする)はGROOVY-3043に添付されています。なぜこれがいまだに提供されていないのかはわかりません。

このバグをGROOVY-4805に新しい問題として報告しました。僕が書いたパッチも提供したのでWindows NT系のユーザの方はそれを当てるのもよいと思います。いずれにせよ、できるだけ早くこのバグが修正されることを望みます。

Friday, April 8, 2011

Gj、それはGroovyプログラムを配布する最も簡単な方法

Groovy向けのビルドツールはあります。たぶん素晴らしいツールなんだろうと思います。 でも僕はめんどくさがりやです。たかがjarファイルを作る為だけに大きなツールを覚えたくはありません。

そこで1つのGroovyスクリプトを書きました。名前は'Gj'です。Gjを使えば、簡単に痛みもなくjarファイルを作れます。1つ最も簡単なGjの使用例を見せます。あなたがFoo.groovyという名前のGroovyのソースファイルを、srcdirディレクトリの下に持っていて、それでfoo.jarという実行可能なjarファイルを作りたいとします。その場合、こう入力します:

> groovy Gj.groovy -s srcdir -m Foo foo.jar

たくさんのライブラリを使っている? 問題ありません。引数にそれらのディレクトリを追加すればいいだけです。Gjがそれらを結果のjarファイルにマージします。

> groovy Gj.groovy -s srcdir -m Foo -l libdir1,libdir2,libdir3

もっとヘルプが欲しいということであれば、こちらからGjをダウンロードしてこう入力してください:

> groovy Gj.groovy -h

Monday, February 28, 2011

Groovy Monkeyでより良いEclipseライフを

あなたがEclipseのヘビーユーザなら、Groovy Monkeyを飼うことをお勧めします。この好奇心旺盛なおさるはあなたのEclipseへ機能を簡単に追加する方法を提供してくれます。

まずはおさるをアップデートサイトから手に入れましょう。私の知る限りペットは高いものですが、ラッキーなことにこのおさるは無料です :-)

おさるを手に入れましたか? あなたのEclipseのメニューバーに現れましたか? では彼を押してGroovy Monkeyチームが提供するサンプルプロジェクトを作りましょう。 このプロジェクトにはGroovy Monkeyを理解するための良い例が入っています。







その中からもっとも単純な例を取り上げましょう:
----
/*
 * Menu: Open Dialog > Groovy
 * Script-Path: /GroovyMonkeyScripts/monkey/OpenDialog_Groovy.gm
 * Kudos: ervinja
 * License: EPL 1.0
 * Job: UIJob
 * DOM: http://groovy-monkey.sourceforge.net/update/plugins/net.sf.groovyMonkey.dom
 */

out.println( 'hello world from Groovy' )
org.eclipse.jface.dialogs.MessageDialog.openInformation( window.getShell(),
                                                         'Monkey Dialog',
                                                         'Hello World from Groovy' )
----
とても単純でしょう?

Groovy Monkeyはヘッダにメタデータを持ちます。この例で重要なメタデータはMenuとJobです。Menuはこのスクリプトを実行するメニューアイテムの位置を表します。こんな風に:














Jobは実行するスレッドを表します。 JobにはJob、UIJob、WorkspaceJobの3つがあり、これらはEclipse Jobs APIに定義されています(詳細はこちらを参照してください)。 この例ではUIにアクセスする為UIJobが指定されています。

"out"と"window"がどこから来たのか不思議ですか? これらはDOM (Domain Object Model) と呼ばれる予め定義されているオブジェクトです。"out"はConsole DOMと呼ばれるorg.eclipse.ui.console.MessageConsoleStreamのオブジェクトでGroovy Monkeyが利用するEclipseコンソールにアクセスする為のものです。"window"はWindow DOMと呼ばれるorg.eclipse.ui.IWorkbenchWindowのオブジェクトでアクティブなEclipseウィンドウへアクセスする為のものです。DOMがなければ、この例は次のようになるでしょう:
----
import org.eclipse.ui.*
import org.eclipse.ui.console.*

def console = new MessageConsole('Groovy Monkey', null)
ConsolePlugin.default.consoleManager.with {
addConsoles([console] as IConsole[])
showConsoleView(console)
}
def out = console.newMessageStream()
def window = PlatformUI.workbench.activeWorkbenchWindow

out.println( 'hello world from Groovy' )
org.eclipse.jface.dialogs.MessageDialog.openInformation( window.getShell(),
                                                         'Monkey Dialog',
                                                         'Hello World from Groovy' )
----

Groovy Monkeyは他にもDOMを提供します。 これらはOutline ViewかInstalled DOMs Viewで確認できます。

もう1つ、私が書いた例を紹介しましょう。次の例はファイルのダウンロード機能を追加するもので、Includeメタデータを使ってGroovy Monkeyスクリプトからワークスペースのリソースを使う方法を示しています。この例を試すにはURLConnectionBuilderを取得する必要があります。

----
/*
 * Menu: Download File...
 * Script-Path: /GroovyMonkeyScripts/monkey/DownloadFile.gm
 * Kudos: Nagai Masato
 * License: EPL 1.0
 * Job: UIJob
 * Include: /GroovyMonkeyScripts/toybox/urlconnbuilder-0.0.2.jar
 */

import net.sourceforge.urlconnbuilder.*

def download = { url, filename ->
new URLConnectionBuilder().url(url) {
       connect {
           configure(requestProperties: ['User-Agent': 'George'])
           communicate(input: { conn, stream ->
               new File(filename).newOutputStream() << stream
           })
       }
}
}

shell = jface.shell(text: 'Download File') {
    def urlTxf, filenameTxf
    gridLayout(numColumns: 2)
    label(text: 'From:')
    urlTxf = text() {
        gridData(widthHint: 300)
    }
    label(text: 'To:')
    filenameTxf = text() {
        gridData(widthHint: 300)
    }
    button(text: 'Download') {
        gridData(
            horizontalSpan: 2,
            horizontalAlignment: org.eclipse.swt.layout.GridData.END
        )
        onEvent(type: 'Selection', closure: {
            download(urlTxf.text, filenameTxf.text)
            shell.close()
        })
    }
}
shell.pack()
shell.open()
----

Saturday, February 5, 2011

Groovy URLConnectionBuilder 0.0.2 が出ました

URLConnectionBuilder 0.0.2 をリリースしました。URLConnectionBuilderは初めてという方は先に以前の投稿を見てください。以下が変更点です。


1. パッケージ名の変更
新しいパッケージ名はnet.sourceforge.urlconnbuilderです。


2. プロキシへのより良いサポート
----
URLConnectionBuilder connBuilder = new URLConnectionBuilder()
connBuilder.url('http://groovy.codehaus.org/') {
    connect(proxy: 'http://proxyhost:proxyport') {
        configure(
            // configure URLConnection
        ) 
        communicate(
            input: { conn, stream ->
                println stream.text
            }
        )
    }
}
----


URL版:
----
proxy: new URL('http://proxyhost:proxyport')
----


URI版:
----
proxy: URI.create('socks5://proxyhost:proxyport')
----


3. 認証へのサポート
----
URLConnectionBuilder connBuilder = new URLConnectionBuilder()
connBuilder.url('http://groovy.codehaus.org/') {
    connect(authentication: 'username:password') {
        configure(
            // configure URLConnection
        ) 
        communicate(
            input: { conn, stream ->
                println stream.text
            }
        )
    }
}
----


List版:
----
authentication: [ 'username', 'password' ]
----


Map版:
----
authentication: [ username: 'username', password: 'password' ]
----

      

Tuesday, January 25, 2011

インサイドGDK

GroovyにはJava Development Kit(JDK)を拡張するGroovy Development Kit(GDK)があります。GDKのおかげで、私達はJDKクラスと一緒に多くの便利なメソッドを使えます。ではそれらのメソッドはどこに定義されているのでしょうか? どうやってJDKのクラスに追加しているのでしょうか?


1つ目の疑問の答えは"org.codehaus.groovy.runtimeパッケージ下のクラス内"です。 そのクラスのうちいくつかを紹介します。


 - DefaultGroovyMethods: 汎用メソッドを定義します。但しいくつか非汎用メソッドも定義します。初期のバージョンでは、ほとんどのGDKメソッドがここに定義されており、Groovyチームはまだこれらの移動を完全には終わらせていません。
 - DateGroovyMethods: 日付/時間関連メソッドを定義します。
 - ProcessGroovyMethods: プロセス管理関連メソッドを定義します。
 - SwingGroovyMethods: Swing関連メソッドを定義します。


これらのメソッドには全てpublic static修飾子がついています。例えば、DefaultGroovyMethodsはdump()メソッドを次のように定義しています(dump()メソッドは過去のポスト、"Groovyによる汎用toString()"で紹介しました):
----
public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
    ...
    public static String dump(Object self) {
        ...
    }
    ...
}
----

2つ目の疑問の答えは少し複雑です。なぜなら正確には追加されているわけではないからです。ではどのように呼び出されているのでしょうか? 例を出して説明します。次のようにjava.net.URLクラスのオブジェクトに対しdump()メソッドを呼ぶとします:
----
def url = new URL('http://groovy.codehaus.org/')
url.dump()
----

この場合、Groovyは次の2つのステップでdump()メソッドを呼びます:


1. DefaultGroovyMethods.dump()のメタデータ(groovy.lang.MetaMethod)を持つorg.codehaus.groovy.runtime.callsite.MetaMethodSiteクラスのオブジェクトを生成します。
2. そのMetaMethodSiteオブジェクトを経由し、MetaMethodオブジェクトのinvoke()メソッドをURLオブジェクト引数とともに呼び出します。


つまり、簡単に言うとGroovyはバイトコードの生成時にurl.dump()をDefaultGroovyMethod.dump(url)に置き換えるわけです。


このMetaMethodSiteクラスはGroovyランタイム専用のクラスなので直接使うことはできません。でもがっかりしないでください。Categoriesを使えば同じことができます:
----
class MyMethods {
    public static String myDump(Object self) {
        ...
    }
}


use (MyMethods) {
    def url = new URL('http://groovy.codehaus.org/')
    url.myDump()
}
----

      

Tuesday, January 11, 2011

Groovyにはアクセスコントロールがない、ではどうするか?

バージョン1.7.5現在、Groovyではアクセス修飾子が機能しません。これらはコメント同様、注意を促す役割しか持ちません。次の例を見てください。アクセス修飾子がその名を偽っていないのなら、このクラスの全てのフィールドとメソッドへのアクセスは制限されるはずです。
----
package foo

class Foo {
    protected protectedField
    @PackageScope packagePrivateField
    private privateField

    protected protectedMethod() { }
    private privateMethod() { }
}
----

Groovyが期待どおりにGroovyコードをJavaバイトコードにコンパイルしているか確認する為に、classファイルを逆アセンブルしてみましょう。
----
javap -private Foo
----

結果はこうなりました。問題ありません。完璧です。期待どおり。
----
public class Foo extends java.lang.Object implements groovy.lang.GroovyObject{
    protected java.lang.Object protectedField;
    java.lang.Object packageScopeField;
    private java.lang.Object privateField;
    :
    protected java.lang.Object protectedMethod();
    private java.lang.Object privateMethod();
}
----

しかしこれらへのアクセスは制限されません。次の例のようにどこからでもアクセスできます。
----
package bar

def foo = new Foo()
foo.protectedField
foo.packagePrivateField
foo.privateField
foo.protectedMethod()
foo.privateMethod()
----

http://jira.codehaus.org/browse/GROOVY-1875によればこのバグは2.0までに直るようです。では今できることは?

命名規則はどうでしょうか? 事故から守るにはこれでほぼ充分かもしれません。例えばPythonやPerlのようにアンダースコアのプレフィクスを使います。
----
protected _protectedField
@PackageScope __packageScopeField
private ___privateField

protected _protectedMethod()
private ___privateMethod()
----

汚いし、複雑です。使いたくありません。

クロージャはどうでしょうか? もし必要となるのがprivateスコープだけなら、クロージャを使うことで実現できます。
----
package foo

class SecretiveFoo {
  
    SecretiveFoo() {
        def privateField;
        getPrivateField = {
            privateField  
        }
        setPrivateField = { newValue ->
            privateField = newValue
        }
      
        def privateMethod = {
            println 'private method called'
        }
        publicMethod = {
            privateMethod.call()
            // do others
        }
    }
  
    public getPrivateField
    public setPrivateField
    public publicMethod
}
----

----
package bar

def secretiveFoo = new SecretiveFoo()
secretiveFoo.setPrivateField(1)
println secretiveFoo.getPrivateField()
secretiveFoo.publicMethod()
----

結果はこうなります。
----
1
private method called
----

ただし、このやり方には高いコストがかかります。次のプログラムを動かすとわかります。Transparentは通常のGroovyクラスです。Serectiveはこのやり方をシミュレートするクラスで、指定された数だけprivateフィールドにアクセスするクロージャを生成します。これらを1000回インスタンス化するコストを計測します。
----
class Secretive {
  
    Secretive(n) {
        def privateField = 0
        actions = []
        (0..n).each {
            actions << { privateField }  
        }
    }
  
    def actions
}

class Transparent {
  
    private privateField = 0
  
}

def maxmem = Runtime.runtime.maxMemory()
def memBefore = maxmem - Runtime.runtime.freeMemory()
def timeBefore = System.nanoTime()
def list = []
(0..<1000).each {
    list << new Secretive(10)
    // list << new Transparent()
}
def timeAfter = System.nanoTime()
def memAfter = maxmem - Runtime.runtime.freeMemory()
def diff = memAfter - memBefore
println "time usage: ${ (timeAfter - timeBefore) * 0.000001 } ms"
println "memory usage: ${ (memAfter - memBefore) * 0.001 } kb"
----

次の結果が出ました。重すぎます。使えません。

Transparent
Secretive(10)
Serective(20)
Serective(30)
Average Time Usage (ms)
17.26
62.83
75.66
87.15
Average Memory Usage (kb)
1133.67
2706.19
3720.96
5073.95

2.0のリリースをひたすら待つしかないんでしょうか。

誰か案あります?