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=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?>
<configuration>
    <include resource=&quot;org/springframework/boot/logging/logback/base.xml&quot;/>
    <logger name=&quot;sample&quot; level=&quot;DEBUG&quot;/>
</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(&quot;Sample Application&quot;)
        shell.setLayout(new GridLayout())
        // Application Icon
        shell.setImage(new Image(display, this.getClass().getResourceAsStream(
            &quot;/images/icon/calculator.32x32.png&quot;)))

        // 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(
            &quot;/images/icon/start.32x32.png&quot;)))
        // 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([&quot;*.csv&quot;] 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(&quot;例外が発生しました。\n&quot; + 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([&quot;*.csv&quot;] 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 制作環境で役立つプラグインが沢山ありますので試してがってん!

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

WireframeSketcher でワイヤーフレームドキュメント作成

以前、サイトを眺めてすごいなぁと思っていたものの、有料($99)だったため試さなかった、WireframeSketcher を本日購入してみました。

用意された部品や操作系を元に、アプリケーション開発時のワイヤーフレームやモックアップなどのドキュメント作成を支援するソフトウェアです。 SI の世界だと、エクセル方眼紙でやっちゃうような、外設の画面設計書をつくるの、で通じるでしょうか(笑)

WireframeSketcher

WireframeSketcher is a wireframing tool that helps designers, developers and product managers quickly create wireframes, mockups and prototypes for desktop, web and mobile applications. It’s a desktop app and a plug-in for any Eclipse IDE.

デザイナー、開発者、プロダクトマネージャさん向け。

Eclipse GEF/EMF ベースでつくられており、Windows、Mac、Linux 対応。ダウンロードしすぐ実行できるオールインワンのスタンドアローン版の他、Eclipse プラグイン版も用意されています。

130422-0007

下のような、描くのも変更するのも大変そうなイラストが、

wp_tree_f

次のテキスト入力だけで作成できてしまう。。

wp-content
-plugins
--otenki
---otenki.php
-themes
--[v] twentyeleven
--[v] twentytwelve

[tegaki]なんてことだ…[/tegaki]

このことだけで、なんでもっと早く試さなかったのだろうと後悔です。。15日のお試し期間中の 1 日目で購入してしまいました。

以下、これまたどう考えても描くの面倒だろうJK、と言われそうな一覧表とフォームの組み合わせも、

130422-0019

こんな感じのテキストでサクッとつくることができます。

130422-0020

あっという間。。 🙂

試しに、このサイト hiromasa.another のモックアップを作成してみました。

130422-0011

出力を手書き風味にできるのがポイントで、(お客さんなどに)まだデザインとかはイメージですよ、というようなことを表現できるとのことです。

ちなみに Screen のプロパティーを Sketch から Clear に設定することで、びしっとすることもできます。

130422-0002

びし。まっすぐ。

130422-0010

WireframeSketcher の操作はパレットビューより部品を選択し、ぺたぺた貼り付け、それぞれの個別属性を指定していく感じです。

部品は PC 用、モバイル、Android、iOS、Web フォームなどなど沢山。 これらは一般的な .svg 形式で、フォーマットに従えば自分で描いた絵も部品に取り込むことができるようです。

130422-0021

ブログのコメントフォームを描いてみたの図。

130422-0022

ドローアプリとしての操作感は、利用開始当初、レイヤー機能がないため若干厳しい部分あるかなと思いましたが、アウトラインビューなどからのZオーダ設定、グルーピング系と、そのグループへの突入(?)、またプロパティーからの位置ロック操作をうまく使えば、問題なく描くことができると感じました。

130422-0023

画面上、オブジェクトの単一選択と矩形選択の切り替えの UI がぱっと分かりませんが、(Fn + ) F3/F4 でそれぞれに切り替わります。クリック to ドラッグの範囲選択において、カーソル位置のオブジェクトを引っ張りたくないケースでは F4 を押すといいと思います。

さて、WireframeSketcher の良いところのひとつは保存形式に XML のテキスト形式が用いられていることで、さらに画像となる .svg ベクターデータと、骨組みとなる .screen データは完全に分離されています。

このため、骨組みの .screen の変更履歴をみると、どの部分が修正されたかが分かります。

130422-0016

スクリーンショットは新旧ファイル差分を出した図。–File Three が増えてその下のアイテムの y 座標が増えたことが分かります。

このように、WireframeSketcher はテキストファイルベースのデータ形式を採っているため、ローカルヒストリーの利用や Subversion、Git などバージョン管理との統合も得意です。

Eclipse マーケットから EGit や Subclipse などを追加することで Git や SVN が使えるようになるハズです。(スタンドアローン版 は Eclipse Juno ベース。自分は Eclipse Juno for JavaEE に WireframeSketcher をプラグインの形で入れました)

さて、ぼくは普段からドキュメントの作成に Eclipse Mylyn の .textile サポートを使っていましたので、今回ワイヤーフレーム作成に WireframeSketche という心強い見方が統合されたことになります。

ワイヤーフレームはもちろん、ちょっと億劫だなと思っていた図形の描画もできるようになってとても嬉しいです。 🙂

130422-0018

ブログ”徹底解析”シリーズを Eclipse で書いているの図。

日本に一人しかいないんじゃないかと思うくらいの Eclipse の利用法ですが、ソースコード見ながら記事を書くことが多いので、この使い方はすごく便利です。(ちなみに 300ページの原稿に耐えてます)

上記スクリーンショット下部は、PlantUML。これもテキストベース図形描画 & Eclipse プラグインあり。 UML を描くツールですが、これがまた色々な用途に使えるのです。

PlantUML についてはプログラマーズ雑記帳さんが非常に参考になります。(とても助かりました!)

PlantUML の使い方 | プログラマーズ雑記帳

テキストから UML を生成する PlantUML についての解説記事を書いてみました。

PlantUML の Eclipse プラグインについては、そのうちまた。 🙂