プログラミング Groovy キタ!

楽しみにしていました書籍「プログラミング GROOVY」が Amazon さんより到着しました。 日本初(発)の Groovy 本ということで、著者の方々も Web で Groovy のこと検索すると必ずお世話になる方ばかりですごいす :)。。

てなわけで早速。

groovy20

出たばかりの Groovy 1.8 についても網羅されていますし、 イデオムや GDK の API が一覧できるとても良い本でした。 Groovy の歴史的経緯など背景的なことなども知ることができて楽しく読み終えることができました。 ぼくはまだ全然 Groovy のボキャブラリが少ないので、今後も座右の書として活躍です。 🙂

同僚の人が、仕事で Groovy をやりはじめたようなのでお勧めしておきましょう。(←どうやら貸さないらしい(笑)

最近ぐるぐる言い出したのは、実はふと買ってみた Groovy In Action が面白すぎたせいなのです。 おもしろい技術書というの非常に大切な存在ですね。 In Action 1st は既に絶版(Amazon で 1.5万円くらい。。)ですので、プログラミング Groovy がその役目を果たすのではないかと思います。 🙂

てなわけで(??)、本日は Groovy スクリプトをひとつかいてみました。

以前、shiroica さんがつくられていました XREA/CORESERVER への SSH 許可ホストを登録する Python スクリプトの Groovy 移植版です。 なんとなく移植(笑)

XREAのサーバーに設置したWordPressを自動バックアップする環境を整える

さて、とりあえず手動で登録すればよいことは分かりましたが、登録の有効期限は30日間だけに限られています。また、出先でSSHで繋ぐために別の IPアドレスを登録した場合、上書きされてしまい、再度登録しないと繋がらなくなってしまいます。そのため、ホスト情報の登録も自動化する必要があります。そこでこんなスクリプトをPythonで書いてみました。

Groovy は JVM と Groovy の jar ひとつあれば動作しますので Windows でも動作させやすいのではないかと思います。(下のソースは手元の Ubuntu で動かしていますので1行目にシェバングが入っています)

動作環境のグローバル IP アドレス調べて、レンタルサーバの管理画面に GET で送信してあげるプログラムです。

#!/opt/groovy-1.8.0/bin/groovy
 
// ここを書き換えてください
def server = 'サーバ名'
def username = 'ユーザ名'
def password = 'パスワード'
def ipaddressResponder = 'グローバルIPを教えてくれるPHP等のURL'
 
def adminURL = "http://$server/jp/admin.cgi"
 
if(!(ipaddressResponder.toURL().text ==~
    /\d+\.\d+\.\d+\.\d+/)) {
    println "Failed ($ipaddress)"
    System.exit(1)
}
 
def values =
    [ 'id' : username, 'pass' : password,
    'remote_host' : ipaddress, 'ssh2' : 'SSH登録']
def param = ""
values.each {
    param += 
        "${it.key}=${URLEncoder.encode(it.value, "UTF-8")}&"
}
param = param[0..param.size()-2]
 
if(!("$adminURL?$param".toURL().getText("Shift_JIS") =~
    /データベースに追加しました。/)) {
    println "Failed (unmutch)"
    System.exit(1)
}
 
System.exit(0)

 

Linux で groovy シェルを動かすと Exception したときに echo $? が 1 返してくれるようなので、例外処理は思い切って削除しちゃいました。

ほとんど調べることなくホントにのりでかけますので便利ですねぇ。 こんな感じじゃない?って書き方が普通に動くのはちょっと感動します。 Java だけでなく、Web 制作で PHP とかやられている人にもとっつきやすいのではないでしょうかっ。

さて大抵の OS でいけるこの Groovy スクリプトの簡単な動作のさせ方は、、

  1. とりあえず上のソースの 1行目を消す。 ソース内にあるサーバ設定とかする。
  2. Groovy をダウンロード してきて、アーカイブの中に入っている embeddable\groovy-all-1.8.0.jar を上のソースを保存したファイル(たとえば registhost.groovy)といっしょのフォルダに置く。
  3. コマンドプロンプトやコンソールをそのディレクトリに開いて以下のコマンドを入力。
java -jar groovy-all-1.8.0.jar registhost.groovy

 

Keep on Groovy – ing!

ぐるぐるぐる~び~。

ぐるぐるぐる~び~(挨拶) というわけで、プログラム言語 Groovy。

最近、このブログをみてくれている会社の人から仕事でぐる~び~という言葉を聞いたり、帳票作成 Jasper Reports の標準スクリプトが Groovy に設定されていたり、あなたの生活にそっと忍び寄る Groovy。 ぼくも内部的なツールをつくるときや、エクセルからソースを起こすときなんかに大変便利に使っております。 🙂

さて、Groovy の楽なところの一つにランタイムがポータブルであるというところがあげられると思います。 JVM と Groovy の jar がひとつだけあれば動作。 インストールいらないし、埋め込んで executeble jar にしてしまえばもう Groovy であったことすらわかりません。

$ java -jar groovy-all-1.8.0.jar hoge.groovy

 

カレントに jar 置いておけば、スクリプトファイルもこんなんでコンパイルかかってプログラムが動作してしまいますしとても便利です。

もうひとつ面白いなぁと思うのが、Groovy は Java と同じコメント形式をもちますが、先頭行のシェバングを読み飛ばせること。 というわけで、UNIX 上であれば以下のようにシェルスクリプトっぽい使い方ができるようになっています。

#!/opt/groovy-1.8.0/bin/groovy
 
println "*Groovy"
new File(".").eachFile {
    println it.name
}

 

これもなかなか便利ですね~。 ちなみに String が拡張されていて外部コマンドもすぐ呼び出せますので UNIX コマンドと合わせ技で、

#!/opt/groovy-1.8.0/bin/groovy
 
println "*Unix Command"
def command = "ls -l".execute() | "grep ^-".execute() 
command.text.eachLine {
    println it.split()[7]
}

 

こんなんもできます。 awk 使えというつっこみはなしで(笑)

Process クラスにオーバーロードかかっていて、パイプが | でかけたりして奥が深い。。

というわけで Ubuntu 生活も java-gnome あたりを使って Groovy ということで。 🙂

loader.groovy。

#!/opt/groovy-1.8.0/bin/groovy
 
// java-gnome クラスロード
this.getClass().classLoader.rootLoader.addURL(
    new File("/usr/share/java/gtk.jar").toURL())
 
// 本体スクリプト評価
evaluate(new File('notify.groovy'))

 

notify.groovy。

import org.gnome.gtk.Gtk
import org.gnome.gdk.Pixbuf
import org.gnome.notify.Notify
import org.gnome.notify.Notification
 
// GTK & NotifyOSD 初期化
Gtk.init([] as String[])
Notify.init("notify.groovy")
 
// アイコン取得
def pixbuf = new Pixbuf("./torimaki.png", 40, 40, true)
 
// NotifyOSD 表示設定
def notify = new Notification(
    "γ(@ω@)γ "
    , "私は不変であることすら破壊する。"神の領域を侵犯し私は私であり続ける。ぐるぐる。"
    , null)
notify.setIcon(pixbuf)
 
// 通知!
notify.show()

 

loader.groovy で java-gnome の jar を動的クラスロードして実行してみました。 gtk.jar は apt-get install java-gnome あたりで入ります。(パッケージ名ちがったかも)

groovy02 

まぁこのサンプルは "notify-send".execute() でいけたりするのですが、ちょっとしたプログラムの中から gnome を簡単に呼べるのはいろいろ遊べそうですね。 🙂

というわけでそんな Groovy さんですが、日本初(発)の Groovy 本がでるそうです!

Amazon.co.jp: プログラミングGroovy: 関谷 和愛, 上原 潤二, 須江 信洋, 中野 靖治: 本

GroovyはJavaと抜群の親和性を持つハイブリッド言語です。簡潔で強力な記述力と高い柔軟性を持っており、Javaを補完・強化する究極のパートナーになります。 本書はGroovyの基本的な文法を丁寧に説明するほか、Groovy API、Groovy JDK、Grape、メタプログラミング、ASP変換などの応用的な話題もカバーします。また、GrailsをはじめとするGroovyの関連プロダクトについても解説します。

さっそく予約しました~。

あなたのシステムにも Groovy ひとつ、そっといかがでしょうか。 ぐるぐる。

Groovy + SWT + WebKit + jQuery Mobile でデスクトップアプリケーションをつくる

最近のいわゆる OS 上で直接動作するデスクトップアプリも、iPhone の影響かぴょこぴょこ動くのが多くなってきた気がします。 Linux のアプリでもこの手のがありますが、だいたいは WebKit + Python でやっている模様。 じゃー、Groovy でもやってやろうじゃないかというのがこの企画でありまして、でもって結果 SWT + WebKit はなかなか強力な感じな気がします。 🙂

どういう風にするかというと、まずウインドを作成して WebKit のコンポーネントをぺたっと張ります。 でもって、Groovy 側から処理したいタイミングがあれば、JavaScript 経由で処理結果を WebKit の HTML に描画。 WebKit 側から Groovy を呼びたければ、これまた JavaScript を経由して処理をして値をもらう、みたいな感じです。

Groovy 側では JavaScript では処理できないような、ローカル資源を扱う処理をかいてあげて、WebKit 側にはビューを担当させます。 ここでは HDD にある画像ファイル一覧を読み出して画面に表示するようにしています。 まぁ HTML5 とか使うとこれはできるのかもですが、なんちゃってサンプルということで。  Java のライブラリで EXIF でも読めばそれっぽかったですね。

さて、HTML + JavaScript をビューに使うメリットは多くの派手な画面を描けるライブラリが使えることで、ここでは jQuery + jQuery Mobile を使って iPhone ちっくな画面とアニメにしてみました。 🙂

画像サイズが不揃いでかっこわるくてすいません、すいません。。

webkitgtk02

デバッグ用にツールバーとか、タブとかつけていますが、本来的にはこれはなくなるもので、ミニブラウザと言うべきものがアプリの体裁となります。

webkitgtk01

上の画面から下の拡大画面にアニメで遷移、、ということでこれは動画にて。 😀

 

Groovy – JavaScript インターフェースは、このスクラップコードでいいますと「HTML ソース表示」の部分が Groovy –> JavaScript 呼び出し。 ローカルファイル表示の部分が JavaScript –> Groovy 呼び出しとしています。

Groovy –> JavaScript は簡単で SWT の browser コンポーネントにメソッドが用意されています。

Groovy 側。(タブ切り替えたときのイベント)

tabFolder.addSelectionListener([
  widgetSelected : { SelectionEvent e ->
    if(e.item == tabSource) {
      source.setText(screen.evaluate('return getContentHtml()'))
    }
  },
  widgetDefaultSelected : {}] as SelectionListener)

 

JavaScript 側。

// タブ切替時にソース表示を行う。
function getContentHtml() {
    return $('html').html(); 

}

 

抜粋コードなのであれですが、Groovy から evaluate を発行して JavaScript から返値を string でもらっています。 evaluate の中身に return をかくのを忘れてしばらくはまっていたのはここだけの秘密です(笑)

今度は逆パターン。 (たとえば) jQuery の ready を契機に Groovy のメソッドを呼び出すパターン。

JavaScript 側。 getFileList() がそれです。 値もらって HTML くみたてています。

$(document).ready(function() {
  var fileList = eval("(" + getFileList() + ")");
  for (var name in fileList) {
    $("#filelist").prepend(
      "<li class='ui-li-has-thumb ui-li ui-li-static ui-body-b'>"
      + "<a href='#bar' class='ui-link-inherit'"
      + "onclick='imageChange(\"" + fileList[name] + "\")'>"
      + "<img src='" + fileList[name] + "' class='ui-li-thumb'/>"
      + "<h3 class='ui-li-heading'>" + name + "</h3></li>\n")
      + "</a>";
  }
  $('ul').listview('refresh');
});

 

Groovy 側、 JavaScript とのインターフェースをとるために SWT には BrowserFunction というクラスが用意されています。 これがエロい。

event = new BrowserFunction(screen, "getFileList") {
  public Object function(Object[] arguments) {
    return getFileList(arguments);
  }
}
 
def getFileList(args) {
  def files = [:]
  new File("${imagePath}").eachFileMatch(~/.*\.jpg/) {
    files[it.name] = it.path 
  }
  return new JsonBuilder(files).toString()
  //return [10, 20] as Object[]
}

 

ここで我らが Groovy。 ということで返値に JSON を使っています。 JSON で Map を構築して return して JavaScript 側で eval すれば良い感じでデータの受け渡しができます。

インターフェース的には、org.eclipse.swt.browser.WebKit:convertToJS() あたりをみると分かりますが、String、Boolean、Number、Object[] がやりとりできるようです。 Object[] はこれらの再起です。 上の最後のソースの再下位行が Object[] で返す場合の記述です。

以上、まぁアプリの作りとしては若干強引な気もしますが、既存 GUI コンポーネントを使わずに、特にリキットデザインにしたい UI の場合はこの方式は楽かもしれません。

てなわけで、、シルバーらい子ちゃんをだしておいて、なぜ Silverlight じゃないんだっ、WPF じゃないんだっ。 しかも Windows じゃないし! ゆ、ゆるさないんだからね! となど聞こえてきそうですが、ここはご勘弁ください。。

あぁ、らい子ちゃん・・・。(←はまりすぎ

あ、そうそう SWT + WebKit は Ubuntu 11.04 だと SWT 3.7 の現在 head を使わないと貼り付けられないのでご注意ください。 あとね、、なぜかその WebKit で jQuery Mobile を使うとアプリ終了時に WebKitGTK+ がセグるようです。 なんだろうなぁ。 ちょっと調べてみます。

—-

以下、スクラップソースなので全然アプリケーションの体裁になっていませんが、一応動くコードをおいておきます。

Groovy 側、

import groovy.json.JsonBuilder
import org.eclipse.swt.*
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.BrowserFunction
import org.eclipse.swt.browser.ProgressListener
import org.eclipse.swt.custom.CTabFolder
import org.eclipse.swt.custom.CTabItem
import org.eclipse.swt.events.SelectionEvent
import org.eclipse.swt.events.SelectionListener
import org.eclipse.swt.layout.GridData
import org.eclipse.swt.layout.GridLayout
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text
import org.eclipse.swt.widgets.ToolBar
import org.eclipse.swt.widgets.ToolItem
 
class BrowserApplication {
 
    Display display
    Shell shell
    CTabFolder tabFolder
    CTabItem tabScreen
    CTabItem tabSource
    Browser screen
    Text source
    Label statusBar
    ToolItem toolbarReload
    
    boolean ready
    def event
    def url
    
    def imagePath = "/home/hiromasa/ピクチャ/wallpaper/"
    
    def init() {
        display = new Display()
        
        // シェル
        shell = new Shell(display)
        shell.setSize(540, 640)
        shell.setLayout(new GridLayout())
        shell.setText("シルバーらい子ちゃん")
        
        // ツールバー作成
        ToolBar toolbar = new ToolBar(shell, SWT.NULL)
        toolbarReload = new ToolItem(toolbar, SWT.PUSH)
        toolbarReload.setText("リロード")
 
        // CTabFolder
        tabFolder = new CTabFolder(shell, SWT.BORDER)
        tabFolder.setTabPosition(SWT.BOTTOM)
        tabFolder.setSimple(false)
        tabFolder.setTabHeight(20)
        tabScreen = new CTabItem(tabFolder, SWT.NONE)
        tabScreen.setText("スクリーン")
        tabSource = new CTabItem(tabFolder, SWT.NONE)
        tabSource.setText("ソース")
        tabFolder.setSelection(0)
        // CTabFolder - Layout
        GridData tabLayout = new GridData()
        tabLayout.horizontalAlignment = GridData.FILL
        tabLayout.verticalAlignment = GridData.FILL
        tabLayout.grabExcessHorizontalSpace = true
        tabLayout.grabExcessVerticalSpace = true
        tabFolder.setLayoutData(tabLayout)
        
        // WebKit
        url = new File(".").getAbsolutePath()
        screen = new Browser(tabFolder, SWT.WEBKIT)
        screen.setUrl("file:///${url}/script/screen.html")
        tabScreen.setControl(screen)
 
        // ソース
        source = new Text(tabFolder, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL)
        tabSource.setControl(source)
 
        // ステータスバー
        statusBar = new Label(shell, SWT.LEFT)
        statusBar.setLayoutData(new GridData(GridData.FILL_HORIZONTAL))
    }
    
    def handle() {
        // screen.html 読み込み終了検知
        screen.addProgressListener([
            changed : { },
            completed : {
                ready = true
                statusBar.setText("Ready")
            }] as ProgressListener)
        // リロードボタン
        toolbarReload.addSelectionListener([
            widgetSelected : {
                screen.refresh()
            },
            widgetDefaultSelected : {}] as SelectionListener
        )
        // Groovy からブラウザの JavaScript を呼び出して HTML テキストをもらう。
        tabFolder.addSelectionListener([
            widgetSelected : { SelectionEvent e ->
                if(e.item == tabSource) {
                    source.setText(screen.evaluate('return getContentHtml()'))
                }
            },
            widgetDefaultSelected : {}] as SelectionListener)
        // JavaScript から Groovy を呼び出すための JS 関数を登録する。
        event = new BrowserFunction(screen, "getFileList") {
            public Object function(Object[] arguments) {
                return getFileList(arguments);
            }
        }
    }
    
    // ファイル一覧を取得して JSON 形式で返却
    def getFileList(args) {
        def files = [:]
        new File("${imagePath}").eachFileMatch(~/.*\.jpg/) {
            files[it.name] = it.path 
        }
        return new JsonBuilder(files).toString()
        //return [10, 20] as Object[]
    }
    
    def loop() {
        try {
            shell.open();
            while (!shell.isDisposed()) {
                if (!display.readAndDispatch()) display.sleep();
            }
        } catch (Exception e) {
            // TODO:
            e.printStackTrace()
        } finally {
            display.dispose()
        }
    }
 
    def BrowserApplication() {
        init()
        handle()
        loop()
    }
    
    def static main(def args) {
        new BrowserApplication();
    }
}

 

JavaScript + HTML。

<!DOCTYPE html>
<html>
<head>
<title>jQuery Moblie test</title>
<link rel="stylesheet" href="./css/jquery.mobile-1.0a4.1.css" />
<link rel="stylesheet" href="./css/style.css" />
<script src="./js/jquery-1.6.1.js"></script>
<script src="./js/jquery.mobile-1.0a4.1.js"></script>
<script language="JavaScript">
$(document).ready(function() {
    // 右クリックでコンテキストメニューを表示しない。
    $('body').live('contextmenu', function(e){
        e.preventDefault();
    });
    // ファイル一覧取得
    var fileList = eval("(" + getFileList() + ")");
    for (var name in fileList) {
        $("#filelist").prepend(
            "<li class='ui-li-has-thumb ui-li ui-li-static ui-body-b'>"
            + "<a href='#bar' class='ui-link-inherit'"
            + "onclick='imageChange(\"" + fileList[name] + "\")'>"
            + "<img src='" + fileList[name] + "' class='ui-li-thumb'/>"
            + "<h3 class='ui-li-heading'>" + name + "</h3></li>\n")
            + "</a>";
    }
    $('ul').listview('refresh');
});
 
// 詳細画面に遷移
function imageChange(path) {
    $("#image").attr({src: path});
    return true;
}
 
// タブ切替時にソース表示を行う。
function getContentHtml() {
    return $('html').html(); 
}
 
</script>
</head>
<body>
 
<!-- メインページ -->
<div data-role="page" data-theme="b" class="setting">
    <div data-role="header" data-position="fixed">
        <h1>らい子ちゃん</h1>
    </div>
    <ul data-role="listview" data-theme="b" id="filelist">
    </ul>
    <div data-role="footer" data-id="footer" id="footer" data-position="fixed">
        <h4>ばよえ〜ん</h4>
    </div>
</div>
 
<!-- サブページ -->
<div data-role="page" id="bar" data-theme="b" class="setting">
    <div data-role="header">
        <h1>らい子ちゃん</h1>
    </div>
    <div data-role="content">
        <img id="image" src="" />
    </div>
    <div data-role="footer" data-id="footer" id="footer" data-position="fixed">
        <h4>ぱんなこった〜</h4>
    </div>
</div>
 
</body>
</html>

twitter streaming API への接続

その昔、twitter が Jabber プロトコルをサポートしていたことを覚えている人は何人いるだろうか?(笑)

どうもぼかぁ最新データをとるのにいちいちポーリングするのってのが全般的に好きではなくて twitter も Jabber 復活を心待ちにしていたのですが、果たしていつの日だったかストリーミング API として戻ってきたのでした。 良かった 🙂

というわけで、API 公開からずいぶんずいぶん経ってしまいましたが、何となく思い立って Groovy からつないでみました。

Java には twitter4j というとてもよくサポートされたtwitter ライブラリがありますので、まずはこちらを利用させていただきます!

Twitter4J – A Java library for the Twitter API

Twitter4J は Twitter API の Java ラッパです。 Twitter4J を使うと JSON や HTTP に詳しくなくても容易に Twitter とインタラクトするアプリケーションを書くことが出来ます。

さて、まず twitter につなぐのに面倒なのが oAuth 。。 とりあえず動作確認したいだけなのにアプリの登録とかしなければならなくあれですが、しょうがないのでしますです。

Twitter / アプリケーションを登録

ここでOAuthを使ったアプリケーションを登録することができます。OAuthは新しい外部アプリケーションの認証システムです。

アプリの登録をすると、consumerKey と consumerSecret がもらえますのでメモして、以下の Groovy ソースに入れて実行してみます。

import twitter4j.*
 
def oAuthConsumer = "入れる"
def oAuthConsumerSecret = "入れる"
 
def twitter = new TwitterFactory().getInstance()
twitter.setOAuthConsumer(oAuthConsumer, oAuthConsumerSecret)
 
def requestToken = twitter.getOAuthRequestToken()
def accessToken = ""
 
println "ブラウザで以下の URL にアクセスして表示された PIN コードを入力してください。"
println " ${requestToken.getAuthorizationURL()}"
System.in.withReader {
    print "PIN コード: "
    accessToken = twitter.getOAuthAccessToken(requestToken, it.readLine())
}
 
println "access.token=${accessToken.getToken()}"
println "access.token.secret=${accessToken.getTokenSecret()}"

実行すると URL がでてきますので、ブラウザにコピって入力して先ほど登録したアプリケーションからのアクセスを自分の twitter アカウントに対して許可してあげます。 そうすると PIN コードでてくるので、こっちのアプリに入力してあげると、今度はアクセストークンが2つもらえます。

コンシューマキー 2 つと、アクセストークン 2 つが揃ったらいよいよアプリをかきます。

ここではストリーミング API を使ってリアルタイムに自分のタイムラインをひろってみます。

import twitter4j.*
import twitter4j.conf.*
 
def consumer = "入れる"
def consumer_secret = "入れる"
def access_token = "入れる"
def access_token_secret = "入れる"
 
def builder = new ConfigurationBuilder()
builder.setOAuthConsumerKey(consumer)
builder.setOAuthConsumerSecret(consumer_secret)
builder.setOAuthAccessToken(access_token)
builder.setOAuthAccessTokenSecret(access_token_secret)
 
def config = builder.build()
def twitter = new TwitterFactory(config).getInstance()
def stream =
    new TwitterStreamFactory(config).getInstance(twitter.getOAuthAccessToken())
 
stream.addListener(new UserStreamAdapter() {
    @Override
    public void onStatus(Status status) {
        def user = status.getUser().getScreenName()
        def text = status.getText()
        println "@${user} ${text}"
    }
})
 
println "Start streaming..."
stream.user()
 
System.in.withReader {
    while((line = it.readLine()) && (line=line.trim()));
}
 
println "Stop streaming and cleanup..."
stream.cleanUp()

じゃんじゃんじゃん。 エンター押すと終了です。

ソケットつなぎっぱなしになりますのでリアルタイムにタイムラインが表示されると思います。 Web より速くでるときもあるみたいですね。 😀

これでたとえば、Ubuntu とかだと notify-send で簡単に画面に通知出せますので、画像ファイル取得とかちょこちょこつけると・・・

stream01

あっという間に通知クライアントのできあがり。 よい。

てなわけで oAuth はちょっと面倒ですが、それ以外は twitter4j と Groovy のおかげでとてもお手軽。 クライアントつくってみてはどうでしょう。

そういえば、オープンソースの twitter クライアントって、コンシューマーキーはどうしているんだろう・・・。

う。

Groovy と PS3 Media Server プラグイン

主に PS3 向けの DNLA サーバアプリ PS3 Media Server のプラグインを、言語の勉強がてら Groovy でかいてみました。 🙂

また、pms のプラグインのつくりかたはあまりインターネット上にドキュメントがないみたいなのでメモも兼ねまして。 まぁこんなことしようする人はソースみれば分かる内容ですが(笑)。。。

PS3 Media Server は Java でできていますので、Groovy の jar をクラスパスにいれてあげれば Groovy でプラグインをかくことができます。 Groovy 言語はプリコンパイルでただの .class にできますので Java との相互運用性は非常に高く、こういうときにとても便利です。

とりあえず、pms3 の trunk を落としてきて lib/groovy-all-1.7.10.jar を入れて、ant かけて pms3.jar の本体を差し替え。 あとはプラグインかくだけ。

以下ソースから追ってつくっていますので、間違えがありましたらご容赦を。

まず作成するプラグインを以下のパッケージに配置。

package net.pms.external

 

画面にルートフォルダを一つ追加する場合は、AdditionalFolderAtRoot を implements してあげたクラスを作成。 で、extends VirtualFolder なクラスをつくって addChild して構築したものを getChild() から戻してあげれば初期設定完了です。

class SamplePlugin implements AdditionalFolderAtRoot {
    
    def root
    
    def SamplePlugin() {
        root = new VirtualFolder("Sample", "")
        root.addChild(new SampleDirectory("Child"))
    }
    
    @Override
    public DLNAResource getChild() {
        return root
    }
    
    @Override
    public String name() {
        return "SamplePlugin"
    }
    
    @Override
    public void shutdown() {
        // nothing to do
    }
    
    @Override
    public JComponent config() {
        // no gui
        return new JPanel()
    }
}

 

extends VirtualFolder な discoverChildren() ではそれが持つ子供(さらにフォルダとかファイルとかストリームとか)を addChild(DLNAResiyrce) で追加していきます。このメソッドはクリックされたときに動きます。

class SampleDirectory extends VirtualFolder {
    
    def SampleDirectory(name) {
        super(name, "")
    }
    
    @Override
    public void discoverChildren() {
        addChild(new SampleDirectory("Child"))
        addChild(new WebAudioStream("Station", "http://127.0.0.1/", ""))
        addChild(new RealFile(new File("/tmp/hoge.mp3"))
    }
}

 

簡単あるね。 上のソースは今そらで書いたものなので動かなかったらすいません。。イメージということで。。

最後に plugin というテキストファイルに起動クラス名(この場合は net.pms.external.SamplePlugin)を記述した上で jar にして pms3 の plugins フォルダに格納してサーバ起動すれば動作するはずです。

 icecast_02

trunk 版の起動シェルはプラグインディレクトリにクラスパス通さなくなっている修正がかかっていますのでそちらも併せて。起動メッセージに Found Plugin がでれば成功。 (ちなみに newInstance() しているのは net.pms.external.ExternalFactory.java です。 うまくプラグインが読まれない場合は確認を)

icecast_01

てなわけで、こちらは動作する例ということで Icecast サーバの実装が出力する yp.xml を解析して PS3 上に表示して再生するプラグインです。 ジャンルでサマってフォルダ分けしています。

package net.pms.external
 
import javax.swing.JPanel
import javax.swing.JComponent
 
import net.pms.dlna.DLNAResource
import net.pms.dlna.WebAudioStream
import net.pms.dlna.virtual.VirtualFolder
 
class IcecastPlugin implements AdditionalFolderAtRoot {
    
    def root
    def icecast
    
    def IcecastPlugin() {
        icecast = new Icecast("http://www.example.com/yp.xml")
        
        root = new IcecastDirectory("Icecast Radio", "", {})
        root.addChild(new IcecastDirectory("Genre", "", { c1 ->
            icecast.getGenre().each {
                c1.addChild(new IcecastDirectory(it, it, { c2 ->
                    icecast.getStationByGenre(c2.query).each {
                        c2.addChild(new WebAudioStream(it.value, it.key, ""))
                    }
                }))
            }
        }))
    }
    
    @Override
    public DLNAResource getChild() {
        return root
    }
    
    @Override
    public String name() {
        return "IcecastPlugin"
    }
    
    @Override
    public void shutdown() {
        // nothing to do
    }
    
    @Override
    public JComponent config() {
        // no gui
        return new JPanel()
    }
}
 
class IcecastDirectory extends VirtualFolder {
    
    def explorer
    def query
    
    def IcecastDirectory(name, query, explorer) {
        super(name, "")
        this.query = query
        this.explorer = explorer
    }
    
    @Override
    public void discoverChildren() {
        explorer(this)
    }
}
 
class Icecast {
    
    def yp
    def ypxml
    
    def Icecast(yp) {
        this.yp = yp
    }
    
    def getStationByGenre(genre) {
        parse()
        def stations = [:]
        ypxml.entry.each {
            if(it.genre.text() == genre) {
                stations[it.listen_url.text()] =
                    "${it.server_name.text()} (${it.bitrate.text()}K)"
            }
        }
        return stations
    }
    
    def getGenre() {
        parse()
        def genres = [:]
        ypxml.entry.each {
            genres[it.genre.text()] = ""
        }
        return genres*.key.sort()
    }
    
    def parse() {
        if(ypxml != null && ypxml.entry.size() != 0) return
        ypxml = new XmlParser().parse(yp)
    }
}

 

Groovy の勉強を兼ねていましたので中途半端に遊んでるソースですいません。 木構造でクロージャ渡しの遅延評価を使って一度にツリーを構築しています。 あとは  XML の解析部分が Groovy っぽい部分でしょうか。 ぼくが分からないだけでもっと良い方法はあることでしょう!

さて、こんな感じのものを Java でかいていたらさすがにぼくでもいらっとしそうですが、Groovy なら気楽に短時間でかけますね。

これは動かして調整して、、たぶん…。

このブログ書く方が時間かかっている!

完。

PS3 と PS3 Media Server

大変な地震が起きてしまいなんだか冷静ではいられない日々が続いていますが、無事でいられた人間がマイナス思考しているのもいけないであろう、ということで力をプラスに変換して大好きなブログ、書きたいと思います。

被災された方が早く普段の生活を取り戻せるよう祈っています。 aka さん無事で良かった。 🙂

少し前になるのですがついに我が家に液晶テレビと PS3 がやってきました!

いままであったのは、おそらく最終形態のブラウン管テレビ。。 1080i が写るやつでしたので地デジもちゃんとみれていたのですが、HDMI もついていませんでしたし旧世代感は否めませんでした。

買ったテレビは REGZA さんで、超解像とか LAN 上のディスクに録画できるとか、DNLA で TS みれるとか、RD のときもそうでしたがいろいろ遊べそうなのが選定理由。

追加して PS3 も導入され、ついに 10 年くらいメディアプレイアーになっていた初代 XBOX with XBMC をお休みさせられるときがきました。 PS3 のビデオ再生は噂通り、DVD のアップスキャンも優秀で良い感じです。

ps3001

さて、PS3 も REGZA も XBOX でやっていたように、LAN 内のメディア再生が DNLA により可能です。 ここがキモ。

以前、shiroica さんに教えてもらっていた PS3 Media Player を Cent OS/ Atom サーバに導入していろいろ試していています。 🙂

ps3mediaserver

PS3 Media Server is a DLNA compliant Upnp Media Server for the PS3, written in Java, with the purpose of streaming or transcoding any kind of media files, with minimum configuration. It’s backed up with the powerful Mplayer/FFmpeg packages.

PS3 Media Player は Java でできた DNLA サーバアプリケーションで、各種接続機器設定に従い mplayer / mencoder へのパイプから機種にあったストリームを返却して、いろいろなメディアファイルを再生できるつくりになっているようです。 この動作をしてくれるので flac など PS3 が対応していないファイルも再生できるようになります。

いろいろ動画を再生してみましたが PS3 のアップスキャンも効いて初代 XBOX よりパワーアップすることができました。

続いて XBOX はネットラジオの再生でもずいぶん活躍してくれていたのですが、PS3 Media Server もできるかな、、と少し調べていると WEB.inf でちまちま設定はできるのですが、再生できる局とそうでない局があったりいくぶん調子が悪い。。

またネットラジオのディレクトリを読んで、、という動作もできないため XBOX より使い勝手も落ちてしまう感じです。

違う、そうじゃない… by マーチン

…ないものはつくろう精神はこのブログにも!

PS3 Media Server はプラグインで機能を追加できるようなので、うんうんとソースをよみつつ、あ、そか Groovy 使えるんだと jar をつっこみ苦節数時間。

とりゃ!

ps3002

おれの勝ちだ(笑)

ちょっと API のライセンスの関係でソースとか公開できないのです(はず)が、たぶんやり始めればすぐできると思います。  Groovy 使って150 ステップくらいでできました。

こちらのMedia Monkey の DB を読んで返すプラグインが参考になりました。

PS3Mediaserver plugin

PS3Mediaserver has lots of limitations itself, so it is best to try PS3MediaServer on its own before using the plugin.

PS3 のメディア再生はもう枯れたい域に入っているようで安心して使えますね。

XBMC のほうが動画再生中に動かせたり UI もかっこ良かったりするのですが、こちらは後のお楽しみということで、しばらく PS3 で楽しんでみたいと思います。 😀

Groovy と GUI アプリケーション

たまに作業の関係で OS ネイティブの GUI アプリケーションを作りたくなる時があります。 適当に自分用につくったコマンドラインベースのものが "ウケ” がよくて GUI に昇格させてみんなに使ってもらう、なんてときですね。 🙂

加えてぼくは普段 Linux を使っていますが、折角なのでたまに使う Windows でも同じもの動かしたいなんて要求が加わってくると使う環境は絞られてきて、、 GTK + スクリプト系言語とか Mono とか Java + Swing とかで、、いろいろやってみましたが一番簡単にランタイム環境を用意できて綺麗に動かせるのは Java + SWT ではないかと思います。

というわけで、SWT。

しかも SWT あまり分かっていないのに、これを Groovy の groovy-swt モジュールを使って呼び出してみました。 …分からないことだらけで大変(笑)

Groovy – GroovySWT

GroovySWT is a wrapper around SWT, the eclipse Standard Widget Toolkit. It allows you to easily write Eclipse SWT applications by using Groovy’s builder mechanism.

これが何かといえば、Groovy お得意の動的言語の機能をつかって、GUI のツリーを Groovy 記述で構築してしまおうというものです。

GUI コンポーネントを扱ったことがある方なら分かりますが、これらは親子関係とかいろいろ大変ですのでこれを解決しようという動きです。 XML 定義に Groovy の制御構文が使える、というイメージで扱うのがよさそうです。

ちなみに、GroovySWT のダウンロードですが、現在あろうことかリンクが切れていますのでリポジトリからダウンロードします。

Revision 21442: /trunk/groovy/modules/groovy-swt/dist

groovy-swt-0.5-examples.zip

groovy-swt-0.5-windows-all.jar

groovy-swt-0.5-without-swt.jar

without-swt のほうがうまいこと swt だけ除いてある jar になっていますので、Windows 以外の場合は without と各機種にあった swt をいっしょにクラスパスに通しておきます。

GroovySWT 0.5 時点での SWT 依存は 3.5 になっていましたので以下からダウンロードできます。(trunk のコミットコメントみると 3.5.1 でもよさそうな感じです)

Eclipse Project

swt-3.5-win32-win32-x86.zip
swt-3.5-win32-win32-x86_64.zip
swt-3.5-wpf-win32-x86.zip
swt-3.5-win32-wce_ppc-arm-j2se.zip
swt-3.5-win32-wce_ppc-arm-j2me.zip
swt-3.5-gtk-linux-x86.zip
swt-3.5-gtk-linux-x86_64.zip
swt-3.5-gtk-linux-ppc.zip
swt-3.5-motif-linux-x86.zip
swt-3.5-gtk-solaris-sparc.zip
swt-3.5-gtk-solaris-x86.zip
swt-3.5-motif-solaris-sparc.zip
swt-3.5-motif-hpux-ia64_32.zip
swt-3.5-photon-qnx-x86.zip
swt-3.5-motif-aix-ppc.zip
swt-3.5-cocoa-macosx.zip
swt-3.5-cocoa-macosx-x86_64.zip
swt-3.5-carbon-macosx.zip

各 OS の環境に合わせた x32 とか x64 とか使えばOKです。 Windows は WPF 版なんかが目を引きますね。 🙂

commons-logging-1.1.1.jar も動作時に class not found したので入れました。 swt.jar と groovy-swt-0.5.without-swt.jar とあとは Groovy の jar を入れれば準備OKです。

またこれを使ってプログラムかくにはクラスライブラリのソースコード必須です。 swt と groovy-swt のソースはダウンロードして jar にアタッチしておきましょう。(まずは groovy.swt.SwtBuilder からみると良さそうです。あとは SWT のコンポーネントソース見てプロパティ設定していきます)

ということでこの状態でから始めたのですが、、

どーやってもメニューが追加できない!!

サンプルもうまく動いていない模様。  ここの解明が疲れました。。

どうやら groovy-swt のライブラリに不具合があるようです(たぶん)。

これであっているかどうかな上にひどいコードですが、以下のようにパッチ。。(早く動かしたくて手抜きしたの図)

しかも diff 逆にとってしまったのはご愛嬌。。 + と – がさかさまです(笑)

Index: WidgetFactory.java
===================================================================
--- WidgetFactory.java    (revision 5)
+++ WidgetFactory.java    (revision 2)
@@ -119,19 +119,14 @@
                                 Object[] arguments = { parent, new Integer(style) };
                                 return constructor.newInstance(arguments);
                             }
+                            // lets try to find the constructor with 1
+                            // arguments
+                        } else if (types.length == 1
+                                && types[0].isAssignableFrom(parent.getClass())) {
+                            Object[] arguments = { parent };
+                            return constructor.newInstance(arguments);
                         }
                     }
-                    // lets try to find the constructor with 1
-                    // arguments
-                    for (int i = 0, size = constructors.length; i < size; i++) {
-                        Constructor constructor = constructors[i];
-                        Class[] types = constructor.getParameterTypes();
-                        if (types.length == 1
-                                && types[0].isAssignableFrom(parent.getClass())) {
-                            Object[] arguments = { parent };
-                            return constructor.newInstance(arguments);
-                        }
-                    }
                 }
             }
             return beanClass.newInstance();

SWT の org.eclipse.swt.widgets.Menu は 2引数コンストラクタがあって parent と style が設定できて、parent が Shell オブジェクトで style が SWT.BAR のときにメニューとしてはりつくと思うのですが、パッチ前の処理では 1引数コンストラクタを先に見つけてしまうため、style なしで new されてうまくメニューがでないという不具合のようです。(たぶん他の Widget は 2引数のコンストラクタが上のほうに定義されているのでしょう)

さて、そんなこんなで以下のつくったソースで動作させてみる。

変数定義の位置がおかしいとか大文字小文字が混在しているとかは気にしない方向で。  万が一動かしてみたい奇特な方がいらっしゃいましたら、画像のところはコメントアウトしてください。 🙂

import groovy.swt.SwtBuilder
import org.eclipse.swt.layout.GridData;
 
class Gonswt {
 
   def shell
   def sashForm1
   def builder
   def tab
 
   def run() {
      builder = new SwtBuilder()
      shell = builder.shell (text:'Groovy on SWT', size:[410, 600], location:[300,200]) {
         menu(style:"BAR") {
            menuItem("バインダー" , style:"CASCADE") {
               menu( style:"DROP_DOWN") {
                  menuItem(style:"PUSH", "&Open file")
                  menuSeparator()
                  menuItem(style:"PUSH", "E&xit"){
                     onEvent('Selection'){
                        shell.dispose()
                     }
                  }
               }   
            }
            menuItem("焼きそばん" , style:"CASCADE") {
            }
         }
         gridLayout()
         composite() {
            rowLayout()
            toolBar( style:"FLAT, WRAP, RIGHT") {
               toolItem(style:"push", "再チャレンジ") {
                  image( src:'resource/item1.png' )
               }
               toolItem(style:"push", "里に帰ろう") {
                  image( src:'resource/item2.png' )
               }
            }
         }
         composite(layoutData:gridData(horizontalSpan:3
            , horizontalAlignment:GridData.FILL
            , verticalAlignment:GridData.FILL
            , grabExcessHorizontalSpace:true
            , grabExcessVerticalSpace:true)) {
            fillLayout()
            sashForm1 = sashForm( style:"vertical") {
               tab = cTabFolder( id: 'myTabs', tabHeight:28, simple:false, style:"BORDER") {
                  fillLayout()
                  //gridData( style:'fill_both')
                  cTabItem( 'タイムマシーン', style:'none') {
                     table(linesVisible:true, headerVisible:true, style:"V_SCROLL") {
                        tableColumn("食べたい", width:80)
                        tableColumn("食べちゃいたい", width:60)
                        10.times {
                           tableItem(["", "ほげほげ"]) {
                              image:image( src:'resource/item3.png' )
                           }
                           tableItem(["", "もげもげ"]) {
                              image:image( src:'resource/item4.png' )
                           }
                           tableItem(["", "ほえほえ"]) {
                              image:image( src:'resource/item5.png' )
                           }
                        }
                     }
                  }
                  cTabItem( 'マンション', style:'none') {
                     text( style:'border, multi, v_scroll, h_scroll', 'Content for Tab1' ) 
                  }
                  cTabItem( 'ダイレクトショッピング', style:'none') {
                     text( style:'border, multi', 'Content for Tab2' ) 
                  }
               }
               composite( style:"none" ) {
                  fillLayout()
                  text( style: "border, multi, wrap")
                  }
            }
            // 2つの weight を決める
            sashForm1.weights = [90, 10]
            // デフォルト表示タブ
            tab.setSelection(0)
         }
         composite(style:"BORDER") {
            rowLayout()
            toolBar( style:"FLAT, WRAP, LEFT") {
               toolItem(style:"push", "ラーメンバー")
            }
         }
      }
 
      // fillLayout のときは縮められるので pack を false にする
      shell.doMainloop(false)
   }
 
   public static void main(String[] args) {
      new Gonswt().run();
   }
}

とりゃ、実行!

Linux 版。

groovy30

Windows 版。

groovy31

Mac 版。 は、残念ながら手元に機械がないのでできません(笑)

てなことで、いい感じでネイティブ描画されて同じように表示されているのが分かります。 可変ウインドウもばっちりです。 斜めタブは Eclipse でおなじみの CTabFolder 。

自分で使いそうな GUI 部品はだいたい入れてみて動いたので、この後は知恵と勇気でなんとかいけそうです。。 間違ってるかもですが。。

まぁ正直なところ SWTBuilder 使わなくても当然 Groovy からは SWT を直接呼べるのでそっちでもいいのですが、これはお遊びということで。 ちなみに、JFace の Builder もありますので、そちらを使えばコンポーネント・データバインディングを使うことができます。

一応描画ができるようになったので、あとはイベントリスナーとスレッド系の操作が分かればなにかつくれそうです。 Groovy でやると確かにお手軽感あります!

もうちょっとやってみます。 🙂

WordPress 3.1 の新しい XMLRPC API

RC3 まできてもうすぐ出ると思われる WordPress 3.1!。

つらつら changelog をみていますと、XMLRPC の API にメディアライブラリ系の API が追加になっていましたので、ここ hiromasa.another の 3.1 テストもかねまして遊んでみました。 :)。

新しく追加になったのは、wp.getMediaLibrary、wp.getMediaItem、wp.getPostFormats で前2つはメディアライブラリに関するもの、最後は投稿フォーマットに関するものです。

というわけで、せっかくですので練習もかねて Groovy  からレッツアクセス。 😀

import groovy.net.xmlrpc.*
 
class WPXMLRPCProxy {
    def proxy
    def user
    def passwd
    def currentArgs
    
    def methods = [
        getMediaLibrary :
            { [blog_id, this.@user, this.@passwd, struct]},
        getMediaItem :
            { [blog_id, this.@user, this.@passwd, attachment_id] },
        getPostFormats :
            { [blog_id, this.@user, this.@passwd] }
    ]
    
    def invokeMethod(String method, Object args) {
        currentArgs = args[0]
        def wpargs = methods[(method)]()
        method = "wp." + method
        proxy."$method"(wpargs)
    }
    
    def getProperty(String name) { currentArgs[name] }
}
 
def wp = new WPXMLRPCProxy(
    proxy : new XMLRPCServerProxy("http://another.maple4ever.net/xmlrpc.php")
    , user : ""
    , passwd : "")
 
wp.getMediaLibrary(blog_id:0
    , struct:[number: 1, mime_type:"image/jpeg"]).each {
    println it
}
 
println wp.getMediaItem(blog_id:0, attachment_id: 1394).metadata
 
println wp.getPostFormats(blog_id:0)

 

わざわざこんなことしなくてもいいかもですが、invokeMethod と getProperty の練習がてら Groovy に備わるリフレクションの仕組みでメソッドとプロパティを動的に生成して WordPress の API と同じ形で呼べるようにしています。

フィールドの methods マップにメソッド名と引数クロージャを追加していけば、wp.* 系の API がそのまま呼べるはずです。(今は新 API だけ入れています)

実行!

wp.getMediaLibrary

[title:DVC00169.jpg, thumbnail:http://another.maple4ever.net/wp-content/uploads/2011/01/DVC00169-150x150.jpg, date_created_gmt:Sun Jan 23 09:33:11 JST 2011, description:, link:http://another.maple4ever.net/wp-content/uploads/2011/01/DVC00169.jpg, parent:1396, caption:, metadata:[sizes:[thumbnail:[height:150, file:DVC00169-150x150.jpg, width:150], medium:[height:225, file:DVC00169-300x225.jpg, width:300]], height:308, file:2011/01/DVC00169.jpg, width:410, hwstring_small:height='96' width='128', image_meta:[focal_length:0, title:DVC00169, shutter_speed:0, iso:0, camera:SH906i, created_timestamp:1295293049, caption:copy="NO" SH906i, copyright:, credit:, aperture:0]]]

wp.getMediaItem

[sizes:[thumbnail:[height:150, file:DVC00169-150x150.jpg, width:150], medium:[height:225, file:DVC00169-300x225.jpg, width:300]], height:308, file:2011/01/DVC00169.jpg, width:410, hwstring_small:height='96' width='128', image_meta:[focal_length:0, title:DVC00169, shutter_speed:0, iso:0, camera:SH906i, created_timestamp:1295293049, caption:copy="NO" SH906i, copyright:, credit:, aperture:0]]

wp.getPostFormats

audio:Audio, standard:Standard, status:Status, gallery:Gallery, quote:Quote, link:Link, image:Image, chat:Chat, aside:Aside, video:Video

なーるほどざ、にゃんこ。 🙂

あとはちょいちょいプログラムかけば、条件指定してクライアントアプリから画像ひっぱるとか簡単ですね。

xmlrpc01 

よいよい、簡単。 これは swing ですが次は SWT バインディングに挑戦してみたいです。 😀

最近のスクリプト言語

といっても全然最近ではないのですが。 🙂

前のエントリでも書きましたが恥ずかしながら Ruby とかやったことがなく、実はいつだったか Ruby のソースをみたときに処理は分かれど、ライブラリの中身がどうやっているのか理解できず、ショックで引退を考えたことがありましたので最近ようやく勉強を始めました(笑)

ちょっと仕事に近いところというところで、Ruby じゃなくて Groovy でやってます。

DVC00169

Groovy In Action は絶版になっているのか Amazon ですごい値段がついていましたが、ジュンク堂で無事ゲット。 2008 年の本なのでぼくがみても現行バージョンと変わっているなと思う点もありますが(HashMap が LinkedHashMap になっている等)、それほど困ることはなさそうです。 この本は技術書としてもとても面白かったです。 🙂

Groovy はマップ・リスト操作、動的系の機能とクロージャなどのシンタックスを限りなく最小限に抑えている他、JDK にリフレクションで追加する形でクロージャを用いて処理を差し込むパターンのメソッドが多数用意してあるため、ソースから冗長な記述を削除できすっきりとしたソースをかくことができるようです。

というわけで Groovy In Action を読んで仕掛けが何となく分かったので、試しに RSS から画像を取得してファイル保存するプログラムをかいてみました。

// @Grab(group='org.codehaus.groovy.modules.http-builder', module='http-builder', version='0.5.1')
import groovyx.net.http.HTTPBuilder
import static groovyx.net.http.Method.*
import static groovyx.net.http.ContentType.*
 
rssUrl = "http://feedblog.ameba.jp/rss/ameblo/nakagawa-shoko/rss20.xml"
pUrl  = /<img [^>]*src\s*=\s*[\"']?([^\"'> ]+.jpg)/
pFile = /([^\/]+?\.jpg)$/
 
new HTTPBuilder(rssUrl).request(GET, XML) {
   response.success = { res, rss ->
      rss.channel.item.each {
         it.description.text().eachMatch(pUrl) {
            filename = it[1].toURI().path =~ pFile
            new HTTPBuilder(it[1]).request(GET, BINARY) {
               response.success = { res2, image ->
                  new File("./file/${filename[0][0]}").bytes = image.bytes
               }
            }
         }
      }
   }
}

もうちょっと短く書く方法もあるとは思うですが、例えば PHP でかいた場合と違って、http アクセス失敗とか File のオープンクローズとかの処理がソース上に現れていないにもかかわらず、これでうまくいってしまうであろうところが良いですね。

XML パースにはダイナミックランゲージの機能が使われているのが分かります。 あとは String の eachMatch にクロージャ渡せるとか便利です。

  • メソッドの引数の括弧は省略できる。 また、一番外側の引数がクロージャだった場合は括弧にいれなくてOK。
  • インスタンスのフィールドは getter/setter が勝手に生成され、プロパティみたいにフィールド名かけばアクセスできる。

他にもコンストラクタにマップ渡すとフィールドを key/value で初期化できるとか、その際のマップの記述は [] つけなくていいとか、なんだか知らないとぱっと見分からないことでいっぱいだったりするのですが、慣れると便利そうです。

動的型付け、動的言語機能は IDE がほぼ無力になるのでドキュメントとにらめっこするのがちょっと大変ですし、ランタイムエラーをやっぱり出しがちですが、どこで苦労するかのバランスは悪くないように思いました。

というわけで実行。

groovy10 

わーい。 とれた。

…引退します。。(笑)

Groovy のスクラップコードと Eclipse 3.6 Helios

コンピュータでの作業中に、定型的なテキストファイルをつくったり、加工したりなどの効率化を図りたいときがあります。

こんなとき手元でぱっと動くプログラム言語があると便利です。 Linux なんか使っていると Perl とか Ruby とかすぐ動くのでそちらという手もありますし、Windows なら Excel VBA とか?

そんな一つの方法で、Eclipse 立ち上げて仕事しているのなら Groovy が便利かもしれません、、というわけで使いそうな Groovy のプログラム断片をメモしておきます。

Groovy 言語は Java を拡張して連携できるようなスクリプト言語です。

Groovy – Wikipedia

GroovyはJVM上で動作する言語処理系および言語の名称であり、Javaとの直接的な連携を特徴とする。例えばGroovyからすべてのJava SE APIや、Javaで書かれた任意のサードパーティ製のコンパイル済みのライブラリなどを呼び出すことができる。言語の記述能力としては、Javaで記述できることは、無名内部クラスの定義など一部の例外を除き基本的にGroovyでも記述することができる。逆に言うとJavaで記述できない機能は記述できないが、Javaと同様にC言語などで書かれたネイティブメソッドなどは呼び出すことができる。

groovy-eclipse という Eclipse 用のプラグインが提供されており、これ入れておけば実行環境が簡単につくることができます。 最終的には JVM で動くコードができるので、隣の人につくったプログラム動かしてもらうのも簡単だという利点もあるですね。

Eclipse向けのGroobvy開発プラグイン「Groovy-Eclipse 2.0」 登場 – SourceForge.JP Magazine  オープンソースの話題満載

Groovy Eclipseプロジェクトは1月15日、「Eclipse」向けプラグイン「Groovy-Eclipse 2.0」を公開した。Eclipse環境でGroovy開発を構築するもので、EclipseのJava開発機能に匹敵するレベルの支援機能を提供するという。

というわけで、使いそうな断片集です。 ぼくはその場に応じて適当につかっているだけなので、間違っていたらごめんなさい。 メモレベルです。

作業用に、Eclipse のプロジェクトをひとつつくっておいています。

groovy10 

まずは文字列操作編。

// 部分文字列取得
println "テストです。"[3..-1]
 
// 文字列長さ取得
println "Lengthのテストです。".length()
 
// 文字列分割
"split1,split2,split3".split(",").each { str ->
    println str
}

 

結果は上から、

です。
13
split1
split2
split3

 

となります。 日本語も思った通り処理されます。

次は文字列マッチ系。

// 正規表現マッチ判定(完全一致)
if("hiromasa" ==~ /.+masa$/) {
    println "mutch!"
}
 
// 正規表現マッチ判定(部分一致)
if("hiromasa" =~ /masa$/) {
    println "mutch!"
}
 
// 正規表現マッチ取得
"stronger, stranger, walker, hiromasa".eachMatch(/(\w+?er),/) { all, match ->
    println match;
}
 
// 正規表現マッチ置換
println "stronger, stranger, walker, hiromasa".replaceAll(/\w+?er/, { match ->
    match[0].toUpperCase() + ((match.size() > 1) ? match[1..-1] : '')
})

 

結果は、

mutch!
mutch!
stronger
stranger
walker
Stronger, Stranger, Walker, hiromasa

 

こんな方向で。

次は、リストとマップ。

// リスト
def lists = ["test1", "test2", "test3"]
lists.each { value ->
    println value
}
 
// マップ
def maps = ["key1":"apple", "key2":"orange", "key3":"mango"]
maps.each { key, value ->
    println key + " = " + value
}

 

こんな感じでまわせます。 結果はご想像通りの方向で(笑)

次はファイル処理系。

// ファイル読み込み
new File("file/readme.txt").eachLine { line ->
    println line
}
 
// ファイル書き出し
new File("file/writer.txt").withWriter { line ->
    line.println("hoge")
    line.println("moge")
}

 

読み書きともクロージャ渡す感じで。 オープンクローズ、異常系を勝手にやってくれます。 便利あるね。

最後に、便利そうなの。

// 回数指定ループ
(0..2).each { counter ->
    println counter
}
 
// 埋め込み文字列
def lines = ["line1", "line2", "line3"]
def number = 1
lines.each { value ->
    println "${number} : ${value}"
    number++
}
 
// 長い文字列
def gstr = """\
改行できますぞ。
改行できますぞ。
改行できますぞ。\
"""
println gstr

 

文字列系がいろいろ拡張されているようです。

これくらいあればあとは知恵と勇気でなんとかなるかと。 困ったら Java そのまま呼べちゃうので、それでパッと回避するのが吉かもしれません。 ライブラリ jar 入れれば、すぐその機能を使えるのも楽ですね。

てなわけで、Groovy でした。 🙂

さて、先日でました Eclipse 3.6 Helios をちょっと評価がてら最近使っております。

helios01

事務所では Windows XP / JavaEE 版をつかっていますが特に不具合はなさそうです。 最近流行のプログラムのマーケット的なところからプラグインの導入ができるようになりました。

helios02 

毎回、Subclipse のアップデート探していたりしていたので、便利かもしれません。 ぼくの使い方では、あとは JavaEE では劇的に便利になるのはないかな。。

家では、PHP for Developer 版を導入してみました。

Windows 7 対応がされているハズなのですが、うちの環境だとジャンプリストとかがうまく動作しませんでした。 右クリックが無反応でした。(うちだけかな)

Linux 版もいれて、3.5 ガニメデ で存在していた「ボタンが押せなくなる」 SWT の不具合が解消しているのを確認しました。 ただ、PHP のでかいコード表示すると、無反応になった後ヒープエラーがでることも確認。。 パーサがバグってるのかも。

というわけで、全般的にはうちでは 3.6 はまだ様子見という感じです。 引き続きウォッチしていこうと思います。 🙂