Grooscript で Canvas アニメーション

G* Advent Calendar 2015 16日目です!(更新前に来てくださった方すいません…)


Groovy を altJS にしてしまった驚異の Grooscript を紹介したいと思います。Groovy をかくとソースコードが JavaScript に変換され、ブラウザや node.js 上で実行できてしまいます。

論よりrunということで、Try online がありますので試してみると良いと思います。

Try groovy to javascript conversion with grooscript

http://grooscript.org/conversions.html

ここでは、ブラウザを Grooscript の実行環境とし、Groovy で Canvas を使ったアニメーションのプログラミングをしてみます。

Grooscript を始めるには Grooscript の Gradle plugin を使うと簡単です。初期コードの作成からファイルウォッチからの変換まで可能です。

build.gradle

plugins {
    id "org.grooscript.conversion" version "1.2.2"
}

これで、

gradle initStaticWeb

とすれば、初期コードを生成してくれます。

grooscript-01

この状態で次のようにすれば、src/main/groovy 配下の *.groovy ファイルの更新を監視して、src/main/webapp/js/app の下に変換した *.js を入れてくれます。

gradle --daemon convertThread

監視を止める場合は gradle のデーモンを停止させればOKです。(Windows, Linux, OS X とも動作するはずです)

gradle --stop

あとは Groovy をかけば完成です。 🙂

ここでは Canvas アニメーションを実装するために、src/main/webapp/index.html を次のようにしました。

<html>
<head>
<title>groovy on browser!</title>
<link rel="stylesheet" href="css/style.css" />
<script type="text/javascript" src="js/lib/grooscript.min.js"></script>
<script type="text/javascript" src="js/app/Presenter.js"></script>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<canvas></canvas>
</body>

src/main/groovy/Presenter.groovy で Canvas に対するアニメーション処理を Groovy でかきました。

ブラウザの window や document などのオブジェクトはそのまま記述できます。

import org.grooscript.asts.GsNative

class Animation {

    static ANIMATION_FPS = 60;

    def canvas
    def context
    def width
    def height

    def actor
    def line

    Animation() {
        window.addEventListener('load', init, false)
        window.addEventListener('resize', init, false)
        window.addEventListener('orientationchange', init, false)
        window.setInterval(update, 1000 / ANIMATION_FPS, false)
        render()
    }

    def init() {
        actor = []
        canvas = document.getElementsByTagName('canvas')[0]
        context = canvas.getContext('2d')
        width = canvas.clientWidth
        height = canvas.clientHeight
        canvas.setAttribute("width" ,width);
        canvas.setAttribute("height" ,height);
        for(def degree = 0 ; degree <= 360; degree += 360/86) {
            this.actor << new Ham("images/ham.png"
                , this.width
                , this.height
                , degree
            )
        }
    }

    def update() {
        actor.each {
            it.update()
        }
    }

    def render() {
        window.requestAnimationFrame(render)
        if(!context) return
        context.fillStyle = "#000"
        context.fillRect(0, 0, width, height)
        actor.each {
            it.draw(context)
        }
    }
}

class Ham extends Actor {

    def swidth
    def sheight
    def degree
    def sx
    def sy
    def sv

    Ham(def url, def w, def h, def d) {
        super(url)
        swidth = w
        sheight = h
        sx = swidth / 2
        sy = sheight / 2
        sv = 1
        degree = d
        width = 48
        height = 48
    }

    @GsNative
    def update() {/*
        var radian = Math.PI / 180 * this.degree
        this.x = Math.cos(radian) * (this.sx / 2 - 24) + this.swidth / 2 - 24
        this.y = Math.sin(radian) * (this.sy / 2 - 24) + this.sheight / 2 - 24
        this.degree += 0.9
        if(this.degree > 360) {
            this.degree = 0
        }
        this.sx += this.sv * 8
        this.sy += this.sv * 8
        if(this.sx >= this.swidth
            || this.sy >= this.sheight
            || this.sx <= (this.swidth * -1) + 48 + 24
            || this.sy <= (this.sheight * -1) + 48 + 24
            ) {
            this.sv = this.sv * -1
        }
        this.width = 48 + (this.sx / 48)
        this.height = 48 + (this.sy / 48)
    */}
}

abstract class Actor {

    def image
    def x = 0
    def y = 0
    def width
    def height

    Actor(def url) {
        image = Resource.getInstance().getImage(url)
    }

    abstract update()

    @GsNative
    def draw(context) {/*
        if(!this.image['loaded']) return
        context.drawImage(this.image['image'], this.x, this.y, this.width, this.height)
    */}
}

class Resource {

    def static resource = new Resource()
    def images = [:]

    private Resource() { }
    
    def static getInstance() {
        return resource
    }

    def getImage(url) {
        if(!images.containsKey(url)) {
            images[url] = [:]
            def image = document.createElement('img')
            image.src = url
            image.addEventListener('load', {
                images[url]['image'] = image
                images[url]['loaded'] = true
                images[url]['width'] = image.naturalWidth
                images[url]['height'] = image.naturalHeight
            }, false)
        }
        return images[url]
    }
}

new Animation()

本当に Groovy です。 😀

一部 @GsNative というアノテーションがついたコメント実装されているメソッドは JavaScript のネイティブコードです。 速度を稼ぎたい部分は、このように(Groovy で変換された周りのコードを考えつつ) JS をそのままかくこともできます。

以下をクリックすると、実際に動いている様子が見ることができます。

ham

変換された JavaScript のコードを見ると面白いと思います。

Grooscript はここ1年で node.js、require.js や React.js の対応が進むなど、開発を見ているのも楽しいです。

ブラウザとのつなぎも簡単ですので、Groovy な方はぜひ試してみてください 🙂

Keep on Groovy-ing!

Groovy + Spring Boot + SWT でクライアントアプリケーションをつくる

G* Advent Calendar 2015 9日目です!

昨日は saba1024 さん「[Groovy] MongoDBを簡単に扱えるイケてる言語Groovy -Groovyの応用編-」でした!


 

職場などで業務改善的なツールをつくりたくなる場合がありますが、案外みんなの PC にスクリプト言語を動かす環境がなかったりします。そんな時は Groovy ! Java の現場であればそのままつくった jar を渡せますし、そうでなくても launch4j などで JRE ごと渡すことができます。

今回は「Groovy + Spring Boot + SWT」という組み合わせで、手軽に高速に GUI Groovy アプリケーションをつくる骨格を紹介してみたいと思います。

珍しい組み合わせかと思いますが、Spring Boot のオートコンフィグレーションと、後述する spring-loaded によるホットデプロイ(GUI 再起動無しで処理を変更できる)と Groovy によるプログラミングの組み合わせは、かなり高速に開発を進めることができると思います。

プロジェクトの構成は以下のような感じになります。

groovy-swt-02

まずは、build.gradle で Spring Boot と SWT を定義してあげます。

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.3.0.RELEASE'
    }
}

apply plugin: 'groovy'
apply plugin: 'spring-boot'

repositories {
    mavenCentral()
    maven { url 'http://maven-eclipse.github.io/maven' }
}

dependencies {
    compile 'org.codehaus.groovy:groovy-all:2.4.5'
    compile 'org.springframework.boot:spring-boot-starter'
    compile "org.eclipse.swt:org.eclipse.swt.win32.win32.x86:4.5.1"
}

springBoot {
    mainClass = 'sample.Application'
}

SWT は Windows x86 のものを選択していますが、他の環境の定義は次の通りです。

    // The Standard Widget Toolkit(SWT)
    // System.getProperty('os.name').toLowerCase().split()[0]
    // System.getProperty("os.arch")
    // 
    // Windows x86
    // compile "org.eclipse.swt:org.eclipse.swt.win32.win32.x86:4.5.1"
    // Windows x64
    // compile 'org.eclipse.swt:org.eclipse.swt.win32.win32.x86_64:4.5.1'
    // Linux GTK+ x86
    // compile 'org.eclipse.swt:org.eclipse.swt.gtk.linux.x86:4.5.1'
    // Linux GTK+ x64
    // compile 'org.eclipse.swt:org.eclipse.swt.gtk.linux.x86_64:4.5.1'
    // OS X Cocoa x64
    // compile 'org.eclipse.swt:org.eclipse.swt.cocoa.macosx.x86_64:4.5.1'

次に Spring Boot の規約に従って(必要なら)ロガー(logback-spring.xml)と Application.yml を定義します。

src/main/resources/logback-spring.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <include resource="org/springframework/boot/logging/logback/base.xml"/>
    <logger name="sample" level="DEBUG"/>
</configuration>

src/main/resources/Application.yml

setting:
    defaultPath: C:\Users

Application.yml は Spring Boot の設定も記述できますが、作成するアプリケーションで外だししたい設定なども書けます。これを読むための、ApplicationSetting は次のようになります。

src/main/groovy/ApplicationSetting.groovy

package sample;

import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.stereotype.Component

@Component
@ConfigurationProperties(prefix = "setting")
public class ApplicationSetting {
    String defaultPath
}

Java でかくと Setter/Getter が必要ですが、Groovy ならこれだけです。.yml のキーと変数名を合わせれば勝手に Spring Boot がバインドしてくれます。

プログラムの起動点となる Application は次のようになります。

src/main/groovy/Application.groovy

package sample

import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.SpringApplication
import sample.gui.Controller

@SpringBootApplication
class Application {

    @Autowired
    Controller controller

    static void main(args) {
        SpringApplication.run(Application.class, args).withCloseable {
            it.getBean(Application.class).controller.start()
        }
    }
}

通常の Java アプリケーションと同様に main を起動してあげると、Spring Boot が main があるパッケージ配下のコンポーネントを自動でスキャンしてクラスロードしてくれます。 @Autowired で GUI の Controller をインジェクションして main から呼び出しました。

呼び出される gui.Controller は次のようなものです。

src/main/groovy/gui/Controller.groovy

package sample.gui

import org.slf4j.*
import org.eclipse.swt.*
import org.eclipse.swt.events.SelectionListener
import org.eclipse.swt.graphics.*
import org.eclipse.swt.layout.*
import org.eclipse.swt.widgets.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import sample.ApplicationSetting
import sample.service.Convert

@Component
class Controller {

    static final logger = LoggerFactory.getLogger(Controller.class);

    @Autowired
    Convert convert

    @Autowired
    ApplicationSetting setting

    Display display
    Shell shell
    ToolItem open
    Table list

    def private init() {
        // Window Setting
        display = new Display()
        shell = new Shell(
            display, SWT.TITLE | SWT.RESIZE | SWT.MIN | SWT.MAX | SWT.CLOSE)
        shell.setSize(640, 480)
        shell.setText("Sample Application")
        shell.setLayout(new GridLayout())
        // Application Icon
        shell.setImage(new Image(display, this.getClass().getResourceAsStream(
            "/images/icon/calculator.32x32.png")))

        // Toolbar
        def toolbar = new ToolBar(shell, SWT.FLAT | SWT.RIGHT)
        // Open
        open = new ToolItem(toolbar, SWT.PUSH)
        open.setText('Open')
        open.setImage(new Image(display, this.getClass().getResourceAsStream(
            "/images/icon/start.32x32.png")))
        // Toolbar Size
        toolbar.pack()

        // List Table
        list = new Table(shell, SWT.FULL_SELECTION | SWT.BORDER)
        list.setHeaderVisible(true)
        list.setLayoutData(new GridData(GridData.FILL_BOTH))
        list.setFocus()

        // Event Listener
        open.addSelectionListener([
            widgetSelected : {  e ->
                def f = new FileDialog(shell, SWT.OPEN)
                f.setFilterPath(setting.defaultPath);
                f.setFilterExtensions(["*.csv"] as String[])
                def login = f.open()
                def message = convert.input(login);
                def box = new MessageBox(shell, SWT.OK | SWT.OK)
                box.setMessage(message)
                box.open()
            }
        ] as SelectionListener)
 
        shell.open()
    }

    public void loop() {
        while(!shell.isDisposed()) {
            if(!display.readAndDispatch()) {
                display.sleep();
            }
        }
        display.dispose()
    }

    def start() {
        try {
            init()
            loop()
        } catch(Exception e) {
            e.printStackTrace()
            def box = new MessageBox(shell, SWT.OK | SWT.ABORT)
            box.setMessage("例外が発生しました。\n" + e.message)
            box.open()
        }
    }
}

SWT の GUI 作成とメインイベントループがあります。

ソース先頭で、サービスクラスにあたる service.Convert と先ほど Application.yml を読むためにつくった ApplicationSetting を DI しています。

    @Autowired
    Convert convert

    @Autowired
    ApplicationSetting setting

ボタンを押したときのイベントでこれらを利用しています。

        // Event Listener
        open.addSelectionListener([
            widgetSelected : {  e ->
                def f = new FileDialog(shell, SWT.OPEN)
                f.setFilterPath(setting.defaultPath);
                f.setFilterExtensions(["*.csv"] as String[])
                def login = f.open()
                def message = convert.input(login);
                def box = new MessageBox(shell, SWT.OK | SWT.OK)
                box.setMessage(message)
                box.open()
            }
        ] as SelectionListener)

サービスクラスはとりあえず。

src/main/groovy/service/Convert.groovy

package sample.service;

import java.io.File;

import org.springframework.stereotype.Component;

@Component
public class Convert {
    def input(file) {
        return "converted!" 
    }
}

というわけで、Application.groovy を IDE から実行するか、./gradlew bootRun するとアプリケーションが起動します。

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.0.RELEASE)

2015-12-09 00:25:31.353  INFO 6560 --- [           main] sample.Application 

groovy-swt-01

さて、ここで大技です。 spring-loaded を実行時に javaagent で読み込むことで、実行中に修正したクラスをリロードできるようになります。 プロジェクトサイトから jar をダウンロードし、アプリケーション実行時の JVM 引数に以下を加えます。

-javaagent:${project_loc:template-springboot-swt}/lib/springloaded-1.2.5.RELEASE.jar -noverify

“${project_loc:template-springboot-swt}” は Eclipse 的なプロジェクトホームの指定ですので、適宜変更してください。

groovy-swt-04

なお、Eclipse で実行する場合は Application.groovy から Run してください。./gradle bootRun だとリロードされません。これは、Eclipse の場合、自動コンパイルの .class ファイルを置く先が bin/ ディレクトリ配下となり、bootRun した場合は build/ 配下の class ファイルで実行され、ファイルの更新がされないためです。

というわけで、spring-loaded ですが、やってみると異常に快適です。 GUI をつくる場合、対象の処理にたどり着くまでの操作が長くかかることがありますが、spring-loaded を入れておくとリトライが簡単で、なんだか世界が変わります。 🙂

その他、Spring Boot 上であると、H2 の組み込み DB や、JPA や GORM などの O/R マッパーも、gradle の定義だけでオートコンフィグレーションされてすぐ使えるようになりますので、非常に便利です。

今回のサンプルでは、サービスクラスで CSV を H2 に読んで GORM で抽出をかけたかったのですが、出張先にて機材トラブルにより間に合いませんでした。ごめんなさい。。

以上、あまり手間をかけず、ゆるいフレームワーク規約で自由気ままにツールなどの GUI をつくりたいなんて時に、良ければお試しください。

最後に、Apache Groovy おめでとうございます! 今年も Groovy にずいぶん助けてもらいました。 🙂

Keep on Groovy-ing!

Gradle と Brakets でお手軽 Sass 開発

Sass をやってみたいだけなのに、OS に node.js 入れたり grunt 入れたり Ruby 入れたりなかなか大変な思いをしている方が結構いるようですので、「Gradle」を使ったお手軽な導入方法を紹介してみたいと思います!

Gradle は、JVM(Java) の世界で広く用いられているビルド・タスクランナーツールです。node.js でいうところの grunt や glup と似たものだと思えばOKです。

Gradle の良いところのひとつは、Gradle 自体の導入も不要な gradlew というラッパーが用意されていること。また、Java の豊富な資産を活用したさまざまなタスクをポータブルに準備し、手軽に実行することができます。

おそらく Sass のファイルウォッチからのコンパイルできる環境が 15分くらいでできると思いますので、よければ試していただければ。この方法は OS 環境に依存しないので、他の人とプロジェクトを共有するのもとても楽です。(フォルダをそのまま相手に配布するだけでそのまま全部働きます)

Gradle 環境は、Windows、Mac OS X、Linux とも同じ方法で動作させることができます。ここでは Mac での操作を紹介してみます。

(のハズだったのですが、実行に使われる gradle-jruby に Windows で動かす場合の不具合があって 2015/3 月時点では動きません。。すでに修正がでているようなので、なおり次第記事をアップデートします)

Java の導入

環境の構築とビルドに使う Gradle というプロダクトが OS に依存するのは Java の JVM のみです。

ターミナルを起動して「java -version」を入力し以下のような結果がでれば導入済みなので何もする必要はありません。次の「Gradle テンプレートのダウンロード」に進みます。

gradle00

macbookpro:~ hiromasa$ java -version
java version "1.8.0_25"
Java(TM) SE Runtime Environment (build 1.8.0_25-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.25-b02, mixed mode)

OS X で Java が未導入の場合、次のダイアログがでますので、詳しい情報を押下してダウンロードサイトにいきます。

gradle01

「JDK」から「jdk-8u25-macosx-x64.dmg」を探し、上部の「Accept License Agreement」ラジオボタンを選択し、リンククリックでファイルをダウンロードします。(jdk-8u25 の u25 はバージョン番号です。より新しいものがあればそちらを選択してください)

gradle02

.dmg をダブルクリックし、インストーラを起動して JDK を導入します。

gradle03

インストール後、一度ターミナルを開きなおして「java -version」して前述の表示がでれば準備完了です。

Gradle テンプレートのダウンロード

Gradle から Sass をコンパイルするための gradle-compass プラグインの定義ファイルと構成一式を以下からダウンロードします。(構成はぼくがそれなりにつくったものです)

https://github.com/h1romas4/gradle-template-web/archive/master.zip

.zip ファイルを任意の場所に展開して、自分の好みの名前をつけます。このフォルダがそのまま開発用の作業フォルダになります。

gradle10

ここでは「sample」にリネームしました。

gradle12

ターミナルのカレントディレクトリを「sample」に移します。(なお、この時パスに漢字などのマルチバイト文字が含まれるとビルドが不具合を起こすようですので、パスが半角英数字だけの場所にフォルダを配置します)

$ cd /Users/hiromasa/Desktop/sample

カレントディレクトリを移すには、ターミナルに cd スペースと入力したあとフォルダをドロップしてエンターを押すと確実です。

gradle13

できたら以下のコマンドを入力します。

$ ./gradlew compassWatch

gradle14

初回のみ、環境をインターネットからダウンロードするので時間がかかります。 5分ほどで終わりますのでのんびり待ちましょう。

gradle15

起動が無事完了すると、Sass ファイルのコンパイルウォッチ状態になります。これだけで環境構築は全て完了です。 😀

gradle16

作業をやめるときは Ctrl + C を押します。 後日再開する場合は、再び ./gradlew compassWatch してください。(今度はすぐ起動します)

Brakets からのファイル編集

「sample」フォルダ内の「www」フォルダの下に編集用の index.html と .scss ファイルのサンプルが用意してあります。 _sass/style.scss を編集するとウオッチが働いて css/style.css が生成される仕組みです。(ファイルは自由に追加できます)

HTML エディターである Adobe の Brakets でファイルを編集するとリアルタイムプレビューもできて便利です。

Brakets を導入し、「ファイル -> フォルダを開く」から「www」フォルダを選択します。

gradle20

フォルダ選択後、index.html を開き、画面右上の「雷アイコン」をクリックすると、リアルタイムプレビューモードでブラウザが起動します。

gradle21

_sass/style.scss を編集すると、ターミナルでウォッチが働き Sass のコンパイルが行われるとほぼ同時に、ブラウザがオートリロードし更新されます。

gradle22

なお、この状態でブラウザの開発者ツールを開くと、Brakets のリアルタイムプレビューの仕組みで改変された HTML が表示されてしまいます。

gradle23

開発者ツールのインスペクタなどを使いたい時は、Brakets の「雷アイコン」をもう一度押し、リアルタイムプレビューをきった状態にしてください。(プレビューがきれるだけで、ローカルで立ち上がった Web サーバはそのまま残るので、リロードなどの操作をしても問題ありません)

編集中の HTML や CSS がそのまま見えるようになります。 Gradle の Sass のコンパイル時に map ファイルを生成しているので、Firefox などでは対応した style.scss のソースを表示することができます。

gradle24

この制作環境を人に渡したい時は、フォルダごとぽいっとあげればOKです。相手方で同じように「./gradlew compassWatch」してもらえれば動きます。とてもポータブルで便利です。

さて最後に、Gradle でどんなことをやっているか気になったり、コンパイルオプションが気になった方は sample フォルダにある build.gradle を覗いてみてください。

/**
 * compass & sass build setting.
 * 
 * @see https://github.com/robfletcher/gradle-compass
 */
compass {
    cssDir = file("www/css")
    sassDir = file("www/_sass")
    // nested, expanded, compact, compressed
    outputStyle = "expanded"
    sourcemap = true
    noLineComments = true
    debugInfo = false 
}

Gradle はこの他にも Web 制作環境で役立つプラグインが沢山ありますので試してがってん!

(久しぶりのブログでオチが弱い上にどこかの記事とかぶってる…)

baserCMS テーマの github 公開と Gradle による Sass ビルド

baserCMS テーマコンテスト受賞作品の「Cafe Debut」と「basercart」テーマのソースコードを github に公開しました。 🙂

h1romas4/basercart

basercart

h1romas4/cafedebut

cafedebut

普通に baserCMS に導入してみたい方は、baserマーケットから .zip をダウンロードしていただければと思います。「Cafe Debut」は先日 baserCMS 3.0.4 に対応され、プラグイン同梱機能や、新しいコーディングスタンダードに対応した新板になっています。(調整していただきまして、どうもありがとうございました!)

今回公開した github のソースコードは、テーマの修正や開発用を想定したものです。

basercart のソースツリーには開発時に用いた sass ファイルと、gradle によるビルド定義を加えています。( .sass ファイルウォッチから .css への自動コンパイルができるように準備しています)

本テーマの製作時は grunt で sass のコンパイルを行っていたのですが、nodejs や Ruby の環境を導入したり、それらのバージョンを開発メンバー間で合わせたりするのが大変と感じましたので、github 公開版ではビルダーを Gradle に変更しました。

Gradle はウェブ制作の方にはあまり馴染みがないかとは思いますが、最近の Java 界隈で良く採用されているビルドツールです。ここでは、grunt と同じようなタスクランナーとして考えてもらって良いと思います。

Gradle が依存するのは Java の環境だけですので、Windows、Mac、Linux ともにほとんど準備なしに(Java が入ってなければ入れるだけ、nodejs や Ruby、各周辺ツールの導入なしに)動作させられ、いつでもポータブルに同一の結果が得られるのが、grunt から変更したポイントになります。

github に公開されているソースには Gradle から生成できる gradlew と呼ばれる Gradle 自体の環境を自動で準備するラッパープログラムもコミットされています。なので事前に Gradle を導入することすら不要です。

というわけで、github から git clone するか .zip をダウンロードしていただいた後、./gradlew watch するだけで .sass のウォッチ・ビルドが開始できます。(初回起動時のみ、環境をオートでつくるため時間が少々かかります)

cd basercart
./gradlew watch
#Windows の場合は、gradlew.bat watch

簡単あるね。 🙂

build.gradle では、次のプラグインを使わせて頂いています。

A SASS / Compass plugin for Gradle は、JRuby を用いて gem の取得や処理を行い、Sass / Compass のコンパイルを行います。この動きにより、ビルドを行う PC には事前に Ruby の導入が不要になる仕掛けになっています。

その他にも同様な動きで Java に含まれる JavaScript の実装(Rhino)を用い、CoffeeScript をコンパイルするプラグインもあるようです。(TypeScript も同じ実装、早くでないかなぁ。Nashorn だと面白そう :))

Gradle Watch Plugin は Java の NIO を用いて、各 OS のファイル監視 API からの通知を元に変更時のタスクが定義できます。(ちなみに、A SASS / Compass plugin だけでも watch はできそうでした)

Gradle の build.gradle 定義は、Groovy が持つ AST 変換や各種シンタックスシュガーなどの効果で、非常に簡潔にかくことができます。 これに慣れてしまうと、JS の (function() { }) とか .pipe(“”) などがずいぶん冗長に見えてしまいますね。 😛

というわけで、ウェブ制作にも Gradle いかがでしょうか。配布先に環境をつくらなくてよいのは、大きなメリットのように思います。

ちなみに、あんまり関係ないですが Groovy 版 Ruby on Rails の新板である Grails 2.3 からは同梱される Asset Pipeline Plugin により sass、less、coffeescript が標準サポートされるようです。ごくり。

ぐる。

Kindle Fire HD 購入

Kindle Fire の 3000円割引最終日に、Grails2 系の洋書がいくつかでていることに気がついてしまったのが運の尽き・・・。 スマートフォンや 10インチ Android タブレットの Kindle アプリで読んでみたものの、小さすぎ大きすぎ。。

以前から Kindle Paperwhite は持っていて、小説を読むのにこれ以上のものはないと思っていたのですが、技術書を出力するにはちょっとページ送りの速度や、画面の大きさに難があり。小説と違って、ペラペラ戻ったり飛んだりしますゆえ。

というわけで、Kindle Fire HD を買ってしまいました。 割引入って 12,800 円。:)

20130714_111847

手前 Paperwhite、奥 Fire HD。こうしてみると、Paperwhite の紙っぽさはさすがですね。今後も小説はこちらで!

さて、Kindle Fire HD で The Defintive Guide To Grails2 を読んでみている図。良い感じです。

Screenshot_2013-07-14-11-15-03

Kindle 版の Grails 書籍は、紙版よりずいぶん安くなっています。 たぶん、Groovy in Action Second Edition も今後でてくるとと思うので楽しみです。当たり前なんですが、洋書でも送料もかからないのが良いですなぁ。:)

Screenshot_2013-07-14-10-56-07

Kindle Fire は、普通の Android に近いですので .apk ファイルさえあれば Kindle Store にないアプリも(たぶん大抵)動作します。 XBMC を入れてみた図。

Screenshot_2013-07-14-10-53-10

GPU は PowerVR SGX 540。 残念ながら XBMC では動画再生支援が効いていないのかコマ落ちします。 Twitter クライアントや FIrefox などはずいぶん機敏に動作します。 🙂

重量は若干あるものの、カバンのサイドぽけっとに入れておくにも丁度良いです。純正ケースも良い意味で存在感がなく、これは本なんだなって感じがしてお気に入りました。

ちなみに、洋書を買っていますが英語が読めません。(←壮大なオチ

ダウンロードしたツイート CSV を Groovy + OrientDB で抽出

ワンショットではあるものの大きめのデータを処理する場合、データベースに入れたらいいなぁとおもいつつも、データベースサーバ起動するのが面倒だったり create table するのが面倒だったりすることがよくあります。

そんな時は組み込み可能な DB ってことで、こんなことに使ったら怒られそうではありますが OrientDB と Groovy の組み合わせ。タイムリーにも Twitter の自分の全ツイートが取得可能となったということで抽出をかけてみました。 🙂

OrientDB Graph-Document NoSQL dbms

OrientDB is an Open Source NoSQL DBMS with both the features of Document and Graph DBMSs. It’s written in Java and it’s amazing fast: can store up to 150,000 records per second on common hardware.

#もちろん OrientDB はサーバモードもありますです。

Twitter の全ツイートダウンロードですが、「ユーザ情報」の下の方にあるリクエストボタンをクリックするとメールリンクで CSV が送られてくるようです。

130326-0003

とりあえず Eclipse にファイルやライブラリを配置して・・・。(./file/Tweets は OrientDB によってつくられた DB の物理ファイルです)

130326-0001

まずは CSV2OrientDB のローダをかきました。 TweetsLoader.groovy

import au.com.bytecode.opencsv.CSVReader
import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx
import com.orientechnologies.orient.core.record.impl.ODocument

// define CSV header (exclude expanded_urls)
def header = [
    "tweet_id"
    ,"in_reply_to_status_id"
    ,"in_reply_to_user_id"
    ,"retweeted_status_id"
    ,"retweeted_status_user_id"
    ,"timestamp"
    ,"source"
    ,"text"]

// load CSV
CSVReader reader = new CSVReader(new FileReader("./file/tweets.csv"))
List myEntries = reader.readAll()
reader.close()

// create OrientDB
ODatabaseDocumentTx db =
    new ODatabaseDocumentTx("local:./file/Tweets").create()

// import CSV
myEntries.each {
    ODocument doc = new ODocument("Tweet");
    def index = 0
    header.each { name ->
        doc.field(name, it[index])
        index++
    }
    doc.save()
}

// close OrientDB
db.close();

これを実行すると .csv が “local:./file/Tweets” DB に読み込まれます。スキーマレスなので適当にロードできます。お手軽。 🙂

でもって、できたデータベースにクエリーを発行します。 Groovy が入っているつぶやきを単純に like にて。 TweetsQuery.groogy

import com.orientechnologies.orient.core.db.document.ODatabaseDocumentTx
import com.orientechnologies.orient.core.record.impl.ODocument
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery

// open OrientDB
ODatabaseDocumentTx db =
    new ODatabaseDocumentTx(
        "local:./file/Tweets").open("admin", "admin");

// query
List<ODocument> result = db.query(
    new OSQLSynchQuery<ODocument>(
        "select * from Tweet where text like '%Groovy%'"));

// output
result.each {
    println it.field("text")
}

// close OrientDB
db.close();

手抜きコード申し訳ない(笑)

という感じで実行結果です。 あとは煮るなり焼くなりできますね。

130326-0002

なんだか懐かしいツイートがいっぱいでてきました。。

ちなみに5万弱のレコード数でしたが、それぞれの処理は待つ暇なく終わっています。

ワンショットとはいえ、普通のサーバ型の RDBMS を使ってしまうとプロジェクトだけでアプリを管理できずポータブル性がなくなってしまいますので組み込みDBの形式は便利ですね。サーバの起動にやきもきすることもありません。

また、OrientDB はドキュメント型ということで、スキーマレスで扱えるのもこういった用途に気軽で良いです。 このソースでは手抜きしていますが、POJO へのマッピングもできますのできちんとかくこともできます。

なんだかものすごいプロダクトをこんなことに使っていいんだろうか、、とも思ってしまいますが、、使い方のひとつということでお許しを。。 🙂

Amazon EC2 と Groovy と QuartzScheduler と commons-daemon

2012/11/11 のポッキーの日に「AWSで動かすWordPressとその高速化」セミナーに伺いまして、Amazon Web Service を堪能して参りました。 🙂

AWSで動かすWordPressとその高速化

アマゾン ウェブ サービス(AWS) にはウェブデザイナーにとっても魅力的なサービスがたくさんあります!
今回はAmazon Web Servicesのテクニカルエバンジェリストである堀内さんをお招きし、すぐにでも使えるサービスを実際に体験しながら(ハンズオンで学びならが)その魅力たっぷりのサービスについてを学びます。

また、AWS上でWordPressを使えるようにし、さらに高速化する方法について、 株式会社デジタルキューブ の小賀さんをお招きし、実際に体験しながらその方法についてを学びます!

tenpura さんや小賀さんにも久しぶりにお会いできまして楽しいひとときでした。

仕事柄、仮想化基盤には慣れてはいるものの、超高速チューンされた WordPress である「AMI 網元」がほとんどワンクリックでデプロイされる様子や、ストレージのスナップショットバックアップはやはり魅力的なフューチャーでした。

WordPress AMI – 超高速 WordPress AMI 網元

EC2のMicro Instance(無償枠あり)を利用すれば、圧倒的にハイパフォーマンスなWordPress環境をコンパネ操作のみで誰でも簡単に構築できます。WordPressの高速化対策、スマートフォン対策に効果的です。

現在のクラウドと呼ばれるサービスの根幹をなしているのは間違いなく仮想化技術です。 そして WordPress を実用的にクラウドにのせる試みはすごく面白いものでした。:)

AMI 綱元も使える Amazon EC2 のマイクロインスタンス(アカウント登録から1年無料とのこと)。

EC2 は root がもらえる IaaS。

むふふ..(←脱線したようだ

というわけで、Groovy と QuartzSchedulercommons-daemon を使って怪しい(?)デーモンを動かしてみることにしました。 😛

デーモンは処理(スレッド)の途中終了をつくるのが結構大変だったりしますが、QuartzScheduler にそのへんを任せると定期実行する常駐ものが簡単につくれます。

以下の例は 20秒おきにログをはくデーモンです。実際には CronScheduleBuilder.cronSchedule で時間を決め、QuartzJob::execute() に好きな処理を実装します。

QuartzDaemon.groovy

package net.maple4ever.daemon

import org.apache.commons.daemon.*
import org.quartz.*
import org.quartz.impl.*
import org.apache.log4j.Logger

class QuartzDaemon implements Daemon {

    def static logger = Logger.getLogger(QuartzDaemon.class)
    def static daemon = new QuartzDaemon()
    def static sched = (new StdSchedulerFactory()).getScheduler();

    @Override
    public void init(DaemonContext arg0)
        throws DaemonInitException, Exception {
        // every 20 seconds
        def job = JobBuilder.newJob(QuartzJob.class)
            .withIdentity("job1", "group1")
            .build();
        def trigger = TriggerBuilder.newTrigger()
            .withIdentity("trigger1", "group1")
            .withSchedule(
                CronScheduleBuilder.cronSchedule("0/20 * * * * ?"))
            .build();
        sched.scheduleJob(job, trigger);
    }

    @Override
    public void start() throws Exception {
        logger.info("START QuartzDaemon")
        sched.start()
    }

    @Override
    public void stop() throws Exception {
        logger.info("STOP QuartzDaemon")
        sched.shutdown(true)
    }

    @Override
    public void destroy() {
        logger.info("DESTROY QuartzDaemon")
    }

    static main(args) {
        daemon.init(null)
        daemon.start()
        System.in.withReader {
            print  'Hit any key to exit.'
            println it.readLine()
        }
        daemon.stop()
    }
}

class QuartzJob implements Job {

    def static logger = Logger.getLogger(QuartzJob.class)

    def QuartzJob() {
    }

    @Override
    public void execute(JobExecutionContext context)
        throws JobExecutionException {
        // execute job
        JobKey jobKey = context.getJobDetail().getKey()
        logger.info("QuartzJob says: " + jobKey + " executing at " + new Date())
    }
}

commons-daemon のインターフェースに従ってクラスを定義して、init で QuartzScheduler のジョブ定義。 commons-daemon からのの start、stop をそのままQuartzSchedulerにパスしてあげればOKです。

また、この例のように static main メソッドをつくっておくとデバッグ時にデーモンではなくそのまま実行できるので便利です。

できたら groovyc でコンパイルしたクラスファイルを、commons-daemon の jsvc を次のようなシェルから起動してあげます。

setenv.sh

#!/bin/sh

SCRIPT_HOME=/opt/daemon
JSVC_USER=hiromasa

JAVA_HOME=/usr/lib/jvm/java

GROOVY_LIB=/opt/groovy-2.0.5/embeddable/*
LIB_HOME=$SCRIPT_HOME/lib
LIB_PATH=$GROOVY_LIB:$LIB_HOME/commons-daemon/*:$LIB_HOME/log4j/*:$LIB_HOME/quartz/*
LIB_PATH=$LIB_PATH:$SCRIPT_HOME/script
LIB_PATH=$LIB_PATH:$LIB_HOME/twitter4j/*:$LIB_HOME/javamail/*

#for command line(ex. $GG Foo.groovy)
export GG="java -cp $LIB_PATH groovy.ui.GroovyMain"

# twitter4j とか javamail とかは必要ありません。(ぼくの別デーモンで使われています 🙂

export している $GG はスクリプト作成時の実行用で、シェルから . ./setenv.sh などとして環境をもってきた後に、$GG QuartzDaemon.groovy とするとコンパイルせずに static main から実行できるようなっています。(UNIX 上の vi などで直接 .groovy をかくときに便利です)

ディレクトリ構成と lib は以下のような感じです。

[hiromasa@]$ ls -laF
合計 28
drwxr-xr-x 7 hiromasa hiromasa 4096 11月 18 08:57 2012 ./
drwxr-xr-x 5 root     root     4096 11月 18 09:38 2012 ../
drwxrwxr-x 2 hiromasa hiromasa 4096 11月 18 09:45 2012 bin/
drwxrwxr-x 7 hiromasa hiromasa 4096 11月 18 01:53 2012 lib/
drwxrwxr-x 2 hiromasa hiromasa 4096 11月 18 09:48 2012 log/
drwxrwxr-x 4 hiromasa hiromasa 4096 11月 18 10:56 2012 script/
drwxrwxr-x 2 hiromasa hiromasa 4096 11月 18 09:48 2012 tmp/
[hiromasa@]$ ls -laF lib/*
lib/commons-daemon:
合計 32
drwxrwxr-x 2 hiromasa hiromasa  4096 11月 17 11:43 2012 ./
drwxrwxr-x 7 hiromasa hiromasa  4096 11月 18 01:53 2012 ../
-rw-rw-r-- 1 hiromasa hiromasa 24242  2月 23 23:22 2012 commons-daemon-1.0.10.jar

lib/log4j:
合計 488
drwxrwxr-x 2 hiromasa hiromasa   4096 11月 17 12:01 2012 ./
drwxrwxr-x 7 hiromasa hiromasa   4096 11月 18 01:53 2012 ../
-rw-rw-r-- 1 hiromasa hiromasa 489883  5月  6 11:01 2012 log4j-1.2.17.jar

lib/quartz:
合計 1212
drwxrwxr-x 2 hiromasa hiromasa   4096 11月 18 07:15 2012 ./
drwxrwxr-x 7 hiromasa hiromasa   4096 11月 18 01:53 2012 ../
-rw-r--r-- 1 hiromasa hiromasa 608376  5月 20 11:12 2011 c3p0-0.9.1.1.jar
-rw-rw-r-- 1 hiromasa hiromasa 578548  8月  6 13:28 2012 quartz-all-2.1.6.jar
-rw-r--r-- 1 hiromasa hiromasa  25496  5月 19 20:22 2011 slf4j-api-1.6.1.jar
-rw-r--r-- 1 hiromasa hiromasa   9753  5月 19 21:02 2011 slf4j-log4j12-1.6.1.jar

quartz.sh

#/bin/sh

# setup
. /opt/daemon/bin/setenv.sh

RUN=net.maple4ever.daemon.QuartzDaemon

# check args
if [ $# -ne 1 ]
then
	echo "usage: quartz.sh [start|stop]"
	exit 1
fi

ret=9
case $1 in
	start )
		echo "starting daemon..."
		$SCRIPT_HOME/bin/jsvc \
			-pidfile $SCRIPT_HOME/tmp/$RUN.pid \
			-user $JSVC_USER \
			-home $JAVA_HOME \
			-cp $LIB_PATH \
			$RUN
		ret=$?
		;;
	stop )
		echo "stoping daemon..."
		$SCRIPT_HOME/bin/jsvc \
			-stop \
			-pidfile $SCRIPT_HOME/tmp/$RUN.pid \
			$RUN
		ret=$?
		;;
esac

exit $ret

手抜きシェルですが、./quartz.sh start|stop でデーモンの常駐制御ができます。

hiromasa 24923     1  0 09:48 ?        00:00:00 jsvc.exec -pidfile /opt/daemon/tmp/net.maple4ever.daemon.QuartzDaemon.pid -user hiromasa -home /usr/lib/jvm/java -cp /opt/groovy-2.0.5/embeddable/*:/opt/daemon/lib/commons-daemon/*:/opt/daemon/lib/log4j/*:/opt/daemon/lib/quartz/*:/opt/daemon/script:/opt/daemon/lib/twitter4j/*:/opt/daemon/lib/javamail/* net.maple4ever.daemon.QuartzDaemon
hiromasa 24924 24923  0 09:48 ?        00:00:06 jsvc.exec -pidfile /opt/daemon/tmp/net.maple4ever.daemon.QuartzDaemon.pid -user hiromasa -home /usr/lib/jvm/java -cp /opt/groovy-2.0.5/embeddable/*:/opt/daemon/lib/commons-daemon/*:/opt/daemon/lib/log4j/*:/opt/daemon/lib/quartz/*:/opt/daemon/script:/opt/daemon/lib/twitter4j/*:/opt/daemon/lib/javamail/* net.maple4ever.daemon.QuartzDaemon

このように jsvc プロセスが 2つできれば成功です。 🙂

commons-daemon と QuartzScheduler を使えば、cron などを使うのと違って簡単に無理なく状態遷移を制御できますので良いのではないかと思います。

さぁ、Amazon EC2 のマイクロインスタンスでどんなデーモン動かしましょう。 いろいろアイディアがでてきますが、ネットワークの中で生き続けるプログラムってやっぱり面白いですね。 😀

VPS にデーモンを常駐させて遊ぶ

さくらインターネットさんが値段を下げてにわかに活気づく VPS。

VPS のよいところは、root 権限があることで、もう Web サーバやら PHP やら MySQL やらの束縛からは解放されて、好きな言語や環境でインターネットのアプリケーションがつくれる環境が整いました。 🙂

たぶん VPS で動かして一番面白いのは、デーモンとして常駐してリアルタイムで処理を行うアプリケーション。 じゃーってことで、Groovy でやってみることにしましょう。

まずは UNIX Daemon のつくりかた。 C でやれば fork して tty 切り離してみたいな感じになるとおもいますが、ここは Groovy ってことで、JVM 上のアプリを daemon 扱いにしてシグナルをとばしてくれる Apache Commons の commons-daemon を使うとよいです。

Daemon : Java based daemons or services

Daemon is made of 2 parts. One written in C that makes the interface to the operating system and the other in Java that provides the Daemon API.

commons-daemon には jsvc という UNIX Daemon と Java を取り持つバイナリのソースが入っています。 まずはこれを VPS の環境なりでコンパイルします。 Java のホームディレクトリを指定して configure をかけるのがポイントです。

src/unix$ ./configure --with-java=/usr/lib/jvm/java-6-sun

make すると jsvc のバイナリができます。

次に Java でデーモン用のソースをかきます。 ここでは Groovy で。

org.apache.commons.daemon.Daemon を implements してシグナルハンドラと結びつく規定のインターフェースを実装してあげればOKです。 サービスクラスはスレッドにして、ちゃんと途中でとまるようにつくる感じになるでしょう。

ここではおなじみ twitter4j のストリーミング API をサービスクラスにしています。 VPS とは関係ないですが、ストリーミング API でうけたメッセージを Ubuntu の NotifyOSD で通知するサンプルです。 (画面ならデーモンじゃなくて UI スレッドでやれって感じですが、サンプルということで…)

package net.maple4ever.daemon import java.security.MessageDigest import org.apache.commons.daemon.* import org.apache.log4j.Logger import org.gnome.gtk.Gtk import org.gnome.gdk.Pixbuf import org.gnome.notify.Notify import org.gnome.notify.Notification import twitter4j.Status import twitter4j.TwitterFactory import twitter4j.TwitterStream import twitter4j.TwitterStreamFactory import twitter4j.User import twitter4j.UserStreamAdapter import twitter4j.conf.ConfigurationBuilder class TwitterDaemon implements Daemon { private static TwitterDaemon daemon = new TwitterDaemon() private static Logger logger = Logger.getLogger(TwitterDaemon.class) private TwitterStream stream = null // twitter oAuth 設定(取得して入れます) def consumer = "" def consumerSecret = "" def accessToken = "" def accessTokenSecret = "" @Override public void init(DaemonContext arg0) throws DaemonInitException, Exception { // twitter 認証設定 def builder = new ConfigurationBuilder() builder.setOAuthConsumerKey(consumer) builder.setOAuthConsumerSecret(consumerSecret) builder.setOAuthAccessToken(accessToken) builder.setOAuthAccessTokenSecret(accessTokenSecret) def config = builder.build() // GTK 初期化 Gtk.init([] as String[]) Notify.init("notify.groovy") // Strinming API 取得 def twitter = new TwitterFactory(config).getInstance() stream = new TwitterStreamFactory(config).getInstance( twitter.getOAuthAccessToken()) // Strinming API イベント登録 stream.addListener(new UserStreamAdapter() { @Override public void onStatus(Status status) { User user = status.getUser() def url = user.getProfileImageURL() def screenname = user.getScreenName() def text = status.getText() try { // twitter アイコン取得 MessageDigest md5 = MessageDigest.getInstance("MD5") def md5str md5.with { md5str = new BigInteger( 1, digest(new File(url.file).name.bytes)) .toString(16).padLeft(32, '0') } def filename = "/tmp/" + md5str File file = new File(filename) if(!file.exists()) { file << url.getBytes() } def pixbuf = new Pixbuf( filename, 40, 40, true) // NotifyOSD 通知 def notify = new Notification( "@${screenname}", "${text}", null) notify.setIcon(pixbuf) notify.show() } catch (e) { logger.error(e.message) } } }) } @Override public void start() throws Exception { stream.user() } @Override public void stop() throws Exception { stream.cleanUp() } @Override public void destroy() { } static main(args) { daemon.init(null) daemon.start() } }

# こんなの動かす人いないと思いますが、GTK-Java のライブラリ jar は sudo apt-get install libjava-gnome-java で /usr/share/java かその辺に入ると思います。

デーモンではなく、通常実行でも動作させてデバッグしやすいように static main(args) もいれています。

で、できたら次のように jsvc にクラスを渡してデーモン化してもらいます。

/opt/twitter-daemon/bin/jsvc-ubuntu \ -pidfile /opt/twitter-daemon/bin/TwitterDaemon.pid \ -user hiromasa \ -home /usr/lib/jvm/java-6-sun/ \ -cp /opt/twitter-daemon/lib/*:/opt/twitter-daemon/classes \ -Dlog.home=/opt/twitter-daemon/logs/TwitterDaemon.log \ -Dstream.user.repliesAll=false \ net.maple4ever.daemon.TwitterDaemon

次のように Java のプロセスが 2つ常駐すれば成功です。

hiromasa 6975 1 0 23:09 ? 00:00:00 jsvc.exec -pidfile hiromasa 6976 6975 17 23:09 ? 00:00:01 jsvc.exec -pidfile

NotifyOSD で twitter の投稿がリアルタイムに通知されてきます。 🙂

moz-notify

停止は以下のようにします。 start / stop/ restart 用のシェルをつくることになるでしょう。

/opt/twitter-daemon/bin/jsvc-ubuntu \ -stop \ -pidfile /opt/twitter-daemon/bin/TwitterDaemon.pid \ net.maple4ever.daemon.TwitterDaemon

ここではソースのサンプルとして画面通知にしてしまっていますが、たとえば twitter 関係で VPS で動かすようなものなら、 bot 的なものも cron とかの無理な方法をとることなく簡単に扱いやすいアプリケーションが作れると思います。

常時インターネットにつながっている VPS でリアルタイム処理ができるのは、いろいろ夢が広がりますね。

GroovyClassLoader をうまくつかって Java と Groovy のインターフェースをつくってあげれば、スクリプトテキストファイルのまま、コンパイルすることなくデーモンをつくるようなことも可能です。

daemon

え、最後のそれ、らい子ちゃんを載せたかっただけだろって、、、。はい。 orz

ぐるぐる Groovy。。

記事書いていませんでしたが、最近も Groovy くんにはだいぶお世話になっております。

テストデータ作成や小物ツールをつくるのに非常に便利な Groovy。

開発環境も Eclipse にプラグイン入れるだけでフルセットそろいますし、テキストエディタだけでも jar ひとつでいけますし、実行環境も Java が入っていればいいだけなので、人にも渡しやすく非常に重宝しております。

ということで、ちょこちょこいじっている訳なんですが最近気がついたこと。

ふと Groovy 1.8 系で下のコードを。

import org.codehaus.groovy.runtime.NullObject

// Groovy だとどちらも普通(Java だと上だけ通る)
assert(Integer.valueOf(127) == Integer.valueOf(127));
assert(Integer.valueOf(128) == Integer.valueOf(128));

// なるほど
assert(null == null)
assert(null.equals(null))
// なんとなく Groovy が不思議
assert(null != new NullObject())
assert(!null.equals(new NullObject()))

Groovy の == は equals の演算子オーバーロード。

というわけで、上の2つの assert は普通に通ります。 ちなみに Java だと両方通らない感じですが、上は通ります。 結構謎ですね(笑)

で、したの null のほう。 null もオブジェクトっぽいのですが、最後の2つの assert が不思議な感じ。

なんとなく別件ですが、このパッチがそれっぽい…。

[#GROOVY-4985] null and .with{} – jira.codehaus.org

Currently this fails:

null.with { assert it == null }

単純にぼくがなんかぼけてるのか、、、まぁなんだってわけでもないのですがふと(笑)

あ、そうそう、壁紙変えました。 🙂

Screenshot20111210

は、張る画像がなかったわけじゃないんだからね!

Groovy + XLSBeans でエクセルから HTML に流し込み

Java のエクセルを読むライブラリに XLSBeans さんがありますが、こちらを Groovy と組み合わせて使うと非常に便利ですのでメモがてらご紹介です。 実は以前も書いていたりしたのですが改めて。

Groovy + XLSBeans でエクセルでできた設計書からソースコードを作成したり、なにかテキストファイルを作るとき等に簡易的な DB としてエクセルが利用できたりします。 ここでは後者、サンプル的にエクセルで書いた日記を HTML に出力するのをやってみます。 🙂

というわけで、まずは XLSBeans。

XLSBeans – Project Amateras

ExcelとJavaBeansをJava 5のアノテーションでマッピングするためのライブラリです。

Java のライブラリですので普通に Groovy から呼び出せます。 ここでつくるようなワンショットプログラムは Groovy と非常に相性が良いです。

まずは元データとなるエクセルファイルを作成します。 うちは Linux なので OOo にて。

xlsbeans01

こんな感じに元データをつくります。 できたらこれを bean にマッピングしてあげます。

@Sheet(name="Sheet1")
public class Nikki {
  @HorizontalRecords(tableLabel="日記", recordClass=NikkiLine.class)
  public List<NikkiLine> lines;
}
 
class NikkiLine {
    @Column(columnName="日付")
    public String date
    @Column(columnName="天気")
    public String tenki
    @Column(columnName="コンテンツ")
    public String content
}

アノテーションでシートの構造を指定してあげます。 注目は X/Y 系の位置情報がないところで、これが非常に便利です。 数えなくていいですし、列が増えたとしてもそのままで大丈夫。 🙂

で、bean の定義ができたらエクセルファイルをプログラムから読んであげます。 Groovy なら .groovy ファイルのトップレベルに上のソースと以下をかくだけ。

Nikki nikki = (new XLSBeans()).load(
    new FileInputStream("xls/日記.xls"), Nikki.class)

引き続きやりたいことをプログラムしていきます。 ここでのサンプルでは先ほどのエクセルから .html ファイルを生成していきます。 Groovy お得意の Builder をつかって HTML タグ文字列を意識せず生成できますぞ。

全体の .groovy はこんな感じになります。

import groovy.xml.MarkupBuilder
import net.java.amateras.xlsbeans.*;
import net.java.amateras.xlsbeans.annotation.*;
 
Nikki nikki = (new XLSBeans()).load(
    new FileInputStream("xls/日記.xls"), Nikki.class)
 
for(NikkiLine line : nikki.lines) {
    def date = new Date("$line.date")
    def writer =
        new File("html/${date.format('yyyyMMdd')}.html").newPrintWriter('UTF-8')
    def html = new MarkupBuilder(writer)
    def name = "${date.format('yyyy/MM/dd(E)')}の日記"
 
    html.html('lang':'ja') {
        head {
            meta('http-equiv': 'Content-Type', content: 'text/html; charset=UTF-8')
            title(name)
        }
        body {
            h1(name)
            p("今日は${line.tenki}")
            p("${line.content}")
        }
    }
 
    writer.close()
}
 
@Sheet(name="Sheet1")
public class Nikki {
  @HorizontalRecords(tableLabel="日記", recordClass=NikkiLine.class)
  public List<NikkiLine> lines;
}
 
class NikkiLine {
    @Column(columnName="日付")
    public String date
    @Column(columnName="天気")
    public String tenki
    @Column(columnName="コンテンツ")
    public String content
}

日付のフォーマットやマークアップビルダー、ファイル処理など Groovy パワーをいただきつつ、ちょいちょい 10 分でかけますね。 たぶん VBA でやったらいらっとする類のプログラムです(笑)

できたっ

xlsbeans02

先ほどのエクセルからこんな感じの(複数の) HTML が生成されます。

<html lang='ja'>
  <head>
    <meta http-equiv='Content-Type' content='text/html; charset=UTF-8' />
    <title>2011/08/01(月)の日記</title>
  </head>
  <body>
    <h1>2011/08/01(月)の日記</h1>
    <p>今日は晴れ</p>
    <p>今日は何事もなかった。</p>
  </body>
</html>

Groovy からお手軽にエクセル読めてそれをもとに処理ができるのはかなり適応範囲が広いと思います。 最近もかなり助けられました。。ありがとうございますっ 🙂

てなわけで、Keep on groovy – ing !