No Programming, No Life

プログラミング関連の話題や雑記

GroovyでMarkdownパーサーを作ろう

GroovyでMarkdownパーサーを作ってみよう。

Headers(見出し)の実装 (予定)

Blockquotes(引用)の実装 (予定)

Lists(リスト)の実装 (予定)

Code Blocks(ソースコードを表現)の実装 (予定)

Horizontal Rules(罫線)の実装 (予定)

Links(リンク)の実装 (予定)

Emphasis(強調)の実装 (予定)

Code(ソースコードの記述)の実装 (予定)

Images(画像)の実装 (予定)

Automatic Links(自動リンク)の実装 (予定)

Backslash Escapes(バックスラッシュ(円記号)による変換回避)の実装 (予定)

Re:3年間の進歩(10分でコーディング)

Groovy! (挨拶)

はじめに

id:uehajさんが、3年間の進歩 - uehaj's blog で楽しそうなお題を解いていたので私もやってみました。こういう三年越しの計画とかっていいですね。昔自分で解いたお題を今の知識で再トライするとか素敵です。

お題

あなたはこれからトランプを配っていきます。
あなたにはトランプを配る人数、
そしてトランプが渡されます。
今回はとても簡単なので例題で説明します。

例)
2つの引数がもらえます。

3
"123123123"

最初の3はプレイヤーの人数を示しています。
"123123123" はトランプの並びを示しています。あなたはこのなかのトランプを
配っていかなければなりません。
この場合、あなたのプログラムは
{"111","222","333"}
を返さなければなりません。
"111"は一番めのプレイヤーが受け取るトランプです。
"222"が2番目のプレイヤーが受け取るトランプです。
"333"が2番目のプレイヤーが受け取るトランプです。
ところが、以下のような場合もあります。
すべてのプレイヤーは同じ数だけのトランプを受け取らなければなりません。
ですので

4
"123123123"

この場合、あなたのプログラムは
{"12","23","31","12"}
を返さなければなりません。
{"123","23","31","12"} は駄目です。
では、以下にもうすこし例をのせます。

例1)
6
"012345012345012345"
Returns: {"000", "111", "222", "333", "444", "555" }

例2)
4
"111122223333"
Returns: {"123", "123", "123", "123" }

例3)
1
"012345012345012345"
Returns: {"012345012345012345" }

例4)
6
"01234"
Returns: {"", "", "", "", "", "" }

例5)
2
""
Returns: {"", "" }

クラス名、などは以下のとおりです。
Class:    Cards
Method:    deal
Parameters:  int, String
Returns:   String
Method signature: String
deal(int numPlayers, String deck)

10分でコーディング|プログラミングに自信があるやつこい!!

回答

v1.8.6から追加された #collate を使いたくて調べてたら10分を軽々オーバーしてしまった罠です。*1

追記 (2013-01-27)

#collate は こっち の第二引数にboolean keepRemainderを指定する方を利用したほうが効率的だったので、コードを修正しました。

おわりに

こういう時間を制限されてるお題は本来、アルゴリズムは極シンプルなものにしておくベキだとは思うんですが、
今回はやっぱり #collate が使いたかったので、時間をかけてでもコーディングを楽しみました。

Enjoy! Coding.

*1:結局30分くらいかかった

JLineの最小サンプル in Groovy

はじめに

Groovy! (挨拶)

突然ですが、
航海日誌: 2012-01-09: [Scala][Java] JLineの最小サンプル
をGroovyでも書いてみました。

JLineは、コンソールアプリケーションにおいてユーザの入力の編集と読み込みを行うJavaライブラリです。 タブ補間、コマンド履歴、パスワードマスキング、キーバインドのカスタマイズ、他のコンソールアプリケーションをつなぐパススルー処理などの機能があります。

JLine - Java console input library プロジェクト日本語トップページ - SourceForge.JP

Download

Groovyの場合、@GrabがあるからDownloadは不要。ここMaven Repositoryの定義が書いてあるので、これをもとにスクリプトの先頭にGrab定義を書けばスクリプト実行時に自動でダウンロードしクラスパスも通してくれた状態で実行できます。

@GrabResolver(name='jline', root='http://jline.sourceforge.net/m2repo')
@Grab(group='jline', module='jline', version='0.9.9')

今回はこんな感じ。

Hello

ただ単に入力プロンプトを出力し、入力したコマンド文字を再表示するサンプルです。

ソース

実行結果
$ groovy helloJLine.groovy 
> aaa
--> [aaa]
> bbb
--> [bbb]
> exit
$ groovy helloJLine.groovy 
> ccc
--> [ccc]
(Ctrl + D)
$

whileのあたりが元記事の実装と少し違うけど

ConsoleReaderを生成して、そいつにプロンプトを表示させ入力待ちにする(ConsoleReader#readLine)。
入力文字がnullか"exit"なら終了し、そうでなければ表示するというだけ。

という動作は同じです。
履歴も手繰れて便利です。

メタプログラミングGroovy入門

Groovy!(挨拶)

最近Groovyであまり遊べていないfumokmmです。G* AdventCalender2012の10日目ということで、久々に記事を書かせていただいております。
せっかくの機会なので、Groovyでメタプログラミングする際のとっかかり部分をまとめてみました。自分の理解が至らないところがあると思いますので、変なところがあったらツッコミよろしくお願いします。では早速スタートです。

Groovyでメタプログラミング

GroovyではあらゆるクラスにExpandoMetaClassと呼ばれる特別なクラス(メタクラス)が提供されていて、メソッドやプロパティを利用する際にこのメタクラスを経由して様々な力を得ることができます。
たとえば、メタクラスに実行時、動的にメソッドを定義してあげれば、あたかも初めから存在していたかのようにそのメソッドが利用できるようになるという寸法です。メソッドの定義にはクロージャを、プロパティの定義にはクロージャ以外の値を利用します。

強力なメタプログラミングが可能なRubyと比較した場合、良くも悪くもGroovyはクラスに縛られる*1ため(Rubyはクラス定義自体を何度もオープンして書き直すことができる)、メタクラスというフィルタを通して、メタプログラミングを実現しているようですね。

先ほど、あらゆるクラスにメタクラスが提供されていると書きました。もう少し具体的に書くと、すべての java.lang.Class に metaClass プロパティが提供されているということになります。このプロパティを使ってExpandoMetaClassを利用することができます。

1. クラスにインスタンスメソッドを追加する

まぁ、言葉でダラダラ書くよりもコードで示したほうが分かりやすいと思うで、早速いってみようと思います。
以下に示すコードは

Groovy Version: 2.0.5 JVM: 1.7.0_05 Vendor: Oracle Corporation OS: Mac OS X

にて動作確認をしました。

まずはクラスにインスタンスメソッドを追加する例です。クラスにインスタンスメソッドを追加する時は、クラスからメタクラスを取得してメソッドを定義する形でクロージャを定義します。

例えば、文字列にその文字列を二倍(2回繰り返す)して返すメソッド #twice を実装すると以下のような感じになります。

String.metaClass.twice = { ->
  delegate * 2
}

assert '大事な事なので2回言いました'.twice() == '大事な事なので2回言いました大事な事なので2回言いました'

たぶんこれが一番基本形になると思うので、もういっちょサンプルを。
今度は文字列の大文字と小文字を入れ替えるメソッド #swapCase を実装してみています。

String.metaClass.swapCase = { ->
  def sb = '' << ''
  delegate.each {
    sb << (Character.isUpperCase(it as char) ?
             Character.toLowerCase(it as char) :
             Character.toUpperCase(it as char) )
  }
  sb.toString()
}

assert 'abcDe'.swapCase() == 'ABCdE'

ref. http://groovy.codehaus.org/ExpandoMetaClass


まず、出だしが String.metaClass で始まっています。これでStringクラスのメタクラスが取得できました。そして、そのメタクラスにメソッド名を指定し、 = { /* クロージャ定義 */ } と続きます。

備考1:メソッド名の動的定義

ちなみに、このメソッド名は、GStringを使って動的に解決してもよいので、以下はすべて同じ意味になります。

String.metaClass.twice = { -> delegate * 2 }
String name = 'twice'
String.metaClass."${name}" = { -> delegate * 2 }
String name = 'twice'
String.metaClass[name] = { -> delegate * 2 }
備考2:delegate

delegate は クロージャ内で利用できる暗黙変数で、この文脈ではStringのインスタンスを指しています。この場合、クラス内で自分自身を参照する時は this を使うのと同じニュアンスで利用して問題ないと思います。

2. クラスにコンストラクタを追加する

メソッドの中でもコンストラクタは特別で、constructor という名前で追加することができます。

// コンストラクタがないクラス
class Book {
  String title
}
Book.metaClass.constructor = { String title ->
  new Book(title:title)
}

def b = new Book('本')
assert b.title == '本'

ref. http://groovy.codehaus.org/ExpandoMetaClass+-+Constructors


コンストラクタを追加する場合は無限ループに注意しましょう。たとえば、以下のようなコードは無限ループに陥ります。

class Book {
  String title
}
// java.lang.StackOverflowError 発生!
Book.metaClass.constructor = { new Book() }

コンストラクタの中で、コンストラクタ自身を呼び出し続けることになりますので、当然ですよね。
これを回避するには、Groovy外部で解決してもらうしかないそうです。たとえば、SpringのBeanUtilsを使うなど。

@Grab('org.springframework:spring-beans:latest.integration')
class Book {
  String title
}
Book.metaClass.constructor = {
  org.springframework.beans.BeanUtils.instantiateClass(Book)
}
def b = new Book()
assert b.title == null

3. クラスにスタティックメソッドを追加する

今度はスタティックメソッドです。基本的にはインスタンスメソッドと同じですが、違うのは、metaClassの後に .static が挟まる部分です。こうすることでスタティックメソッドとして定義することができます。(ちなみに、ここでは定義に = ではなく、 << (左シフト) を利用していますが、これに下の備考を参照下さい)

class Book {
  String title
}
Book.metaClass.static.create << { String title ->
  new Book(title: title)
}
def b = Book.create('本')
assert b.title == '本'

ref. http://groovy.codehaus.org/ExpandoMetaClass+-+Static+Methods

備考3:<< と = の違い

メタクラスにメソッド定義する際には、= と<<が利用できます。両者の違いは、<<は既存メソッドが既に存在する場合、例外がスローされ、=の場合、例外は発生せず、上書きする動作となります。これはあくまでクラスに定義されている既存のメソッドだけですので、動的に追加したメソッドに関しては何度 << しても例外は発生しません。

class Book {
  String title
  int price = 2000
  def getPrice() { price }
}

// 例外発生!
//Book.metaClass.toString << { -> title } // Object#toString
//Book.metaClass.getPrice << { -> 10000 } // Book#getPrice

// これならOK
Book.metaClass.toString = { -> title }
Book.metaClass.getPrice = { -> "\\${delegate.@price}" }

def b = new Book(title: '本')
assert b.toString() == '本'
assert b.price == '\\2000'

// ただし、動的に追加したメソッドに関しては << でも例外が発生しない
Book.metaClass.getBookTitle << {-> "本のタイトルは${title}です。" }
Book.metaClass.getBookTitle << {-> "本のタイトルは${title}です。" }
assert b.bookTitle == '本のタイトルは本です。'

4. クラスにプロパティを追加する

今度はプロパティです。そう、フィールドですね。このフィールドを定義するには大体2つの方法があります、

シンプルな方法

まずはシンプルな方法からですが、こちらはmetaClassにメソッドを定義する容量で、クロージャではない値を指定するだけです。簡単ですね。この例では、times がそれに当たります。

String.metaClass.times = 1
String.metaClass.loop = { delegate * times }

def str = 'str'
assert str.loop() == 'str'
str.times = 3
assert str.loop() == 'strstrstr'
println str.loop()

この方法で定義したプロパティは読み書きが可能となります。

getter, setterを利用してプロパティを定義する

もう一つは、getterとsetterを利用する方法です。Groovyではget〜、set〜というメソッドはプロパティになるんでしたね。getterのみ定義した場合は、読み取り専用、setterのみ定義した場合は書き込み専用となります。

{->
  int times = 1
  String.metaClass.setLoopTimes = { int t -> times = t }
  String.metaClass.getLoop = { delegate * times }
}()

def str = 'str'
assert str.loop == 'str'

str.loopTimes = 3
assert str.loop == 'strstrstr'

ここでは、loopTimes プロパティは書き込み専用、受け取った値をローカル変数(メタメソッド定義時のスコープのみ見える)timesに
格納しているloopプロパティは読み込み専用となり、先ほど格納したtimesの値分、繰り返した文字列にして返却しています。

5. インタフェースにメソッドを追加する

メタクラスを使えば、普通ならメソッドを追加できないはずのインタフェースにもメソッドを追加することができてしまいます。
ここでは、サイズを2倍のサイズを取得するメソッドをコレクションに追加しています。

Collection.metaClass.sizeDoubled = { ->
  delegate.size() * 2
}

assert [1, 2, 3].sizeDoubled() == 6
assert ([1, 2] as Set).sizeDoubled() == 4

コレクションに追加しているので、リスト(ArrayList)でもセット(Set)でも利用できるようになっています。これはトレイトっぽいですね。

6. 個別のインスタンスにメソッドを追加する

今までがすべてクラスについてメソッドやプロパティを追加していたのですが、Groovyではnewしたインスタンスにもそれぞれ個別にメソッドやプロパティを追加できます。各インスタンスもそれぞれがメタクラスを持っているため、そこに今までと同様の方法でクロージャを定義してあげれば完了です。

String.metaClass.getSym = { delegate.intern() }
'+'.sym.metaClass.getOp = { -> {a, b -> a + b} }
'*'.sym.metaClass.getOp = { -> {a, b -> a * b} }

assert (1..10).inject('+'.sym.op) == 55
assert (1..10).inject('*'.sym.op) == 3628800

ref. http://groovy.codehaus.org/Per-Instance+MetaClass


一行目のgetSymは常に同じ文字列を指すため、internするメソッドを定義しているのみです。ここで大事なのは、二行目、三行目のgetOpの部分です。'+' という文字列と '*' という文字列にそれぞれgetOpというメソッドを定義し、クロージャを返却するようにしています。ここで取得されるクロージャは#injectなどに渡すことができますので、assertしているように読みやすい記述が可能となりますね。

もう一つサンプルです。

class Hoge {}
def a = new Hoge()
def b = new Hoge()
a.metaClass.who = { 'A' }
b.metaClass.who = { 'B' }

assert [a, b]*.who() == ['A', 'B']

インスタンス毎に違った挙動をしているのがわかりますね。

7. 動的メソッドの探索

動的に追加されたメソッドは普通にリフレクションを利用しても取得できないので、以下のメタメソッド探索用のメソッドを利用して取得します。

  • hasMetaMethod
  • getMetaMethod
  • hasMetaProperty
  • getMetaProperty
//------getMetaMethod/hasMetaMethod
assert String.metaClass.hasMetaMethod('toString', null)

assert !String.metaClass.hasMetaMethod('hello', null) // まだ#helloはない
assert !String.metaClass.hasMetaMethod('hello', [String] as Class[]) // まだ#helloはない

String.metaClass.hello = { "Hello $delegate" }
String.metaClass.hello = { name -> "Hello $name! $delegate" }

assert String.metaClass.hasMetaMethod('hello', null) // もう#helloはある
assert String.metaClass.hasMetaMethod('hello'      ) // もう#helloはある
assert String.metaClass.hasMetaMethod('hello', [String] as Class[]) // もう#helloはある

def hello  = String.metaClass.getMetaMethod('hello', null)
def hello2 = String.metaClass.getMetaMethod('hello'      )
def hello3 = String.metaClass.getMetaMethod('hello', [String] as Class[])
assert hello.invoke('World!!') == 'Hello World!!'
assert hello2.invoke('World!!') == 'Hello World!!'
assert hello3.invoke('World!!', 'fumo') == 'Hello fumo! World!!'

//------getMetaProperty/hasMetaProperty
assert String.metaClass.hasMetaProperty('empty')

assert !String.metaClass.hasMetaProperty('test') // まだtestはない

String.metaClass.test = "テスト"

assert String.metaClass.hasMetaProperty('test') // もうtestはある

def test = String.metaClass.getMetaProperty('test')
assert test.getProperty('abc') == 'テスト'

ref. http://groovy.codehaus.org/ExpandoMetaClass+-+Runtime+Discovery


取得したメタメソッドは#invokeで、メタプロパティは#getProperyで呼び出すことができます。

8. メタクラスDSL

上記までで色々とやってきましたが、なんとGroovyにはこれらを簡単に定義するためのDSLが用意されています。使わない手はないですね。
メタクラスDSLを利用するには、metaClass()メソッドを利用します。このメソッドは、クロージャを引数に1つだけとります。
なので、例えばBookクラスに対して利用する場合は以下のようになります。

class Book {
  String title
}
Book.metaClass {
  price = 2000
  showTitle = { -> println title }
  'static' {
    create << { String title ->
      new Book(title: title)
    }
  }
}

def b = Book.create('本')
assert b.title == '本'
assert b.price == 2000

b.showTitle() // => '本'

このDSLは、複数のプロパティやメソッドを一気に定義したい場合に特に有用です。それぞれの定義について、以下に解説します。

メタクラスDSLでインスタンスメソッドを定義する

インスタンスメソッドを書く場合は、変数に代入するように書きます。

Book.metaClass {
  showTitle = { -> println title }
}

これで、

def b = new Book()
b.showTitle() 

のような感じで使えます。

メタクラスDSLでスタティックメソッドを定義する

スタティックメソッドを書く場合は、'static' という文字列に対してクロージャを渡すように書きます。

Book.metaClass {
  'static' {
    create << { String title ->
      new Book(title: title)
    }
}

def b = Book.create('本')
assert b.title == '本'
メタクラスDSLでプロパティを定義する

プロパティは普通に変数っぽく定義するだけでよいです。

Book.metaClass {
   price = 2000
}
def b = Book.create('本')
assert b.price == 2000
もちろんインスタンスにも使えます
class Hoge {}
def a = new Hoge()
def b = new Hoge()

a.metaClass {
  who = { 'A' }
}
b.metaClass {
  who = { 'B' }
}

assert [a, b]*.who() == ['A', 'B']

ref. http://groovy.codehaus.org/ExpandoMetaClass+Domain-Specific+Language


慣れて来たらDSLを利用するのが便利だと思います。是非使ってみて下さい。

応用例

せっかくなので、何か作ってみようか…ということで、動的なプロパティ読み込みクラスを定義するサンプルを書いてみました。
長くなってしまったのでソースはgistをご参照下さい。

解説

これは、クラス名を使って同じ名前のプロパティファイルを読み取り、キー一覧を動的メソッド(get + キー名)としてクラスのコンストラクト時に追加してしまおうというサンプルです。

/** Fumo.properties読み込みクラス */
class Fumo extends StringProps {}
def fumo = new Fumo()
assert fumo.name == 'fumokmm'
assert fumo.occupation == 'System Engineer'

/** CalcOperator.properties読み込みクラス */
class CalcOperator extends ClosureProps {}
def op = new CalcOperator()
assert op.plus(1, 2) == 3
assert (1..5).inject(op.multiply) == 120

Fumo.propertiesは

name=fumokmm
occupation=System Engineer

CalcOperator.propertiesは

plus={ a, b -> a + b }
minus={ a, b -> a - b }
multiply={ a, b -> a * b }
div={ a, b -> a / b }

のような定義となっており、それぞれ、文字列として評価、クロージャとして評価するようにしています。

おわりに

今回ご紹介した部分はGroovyのメタプログラミングのほんのさわりでしかありません。しかし、ひとまず、今回ご紹介した例だけでも知っていると、既存のクラスにちょっとした機能追加を行ったり修正を加えたりすることが容易になると思います。もしご興味を持っていただけましたら、ぜひ色々と遊んでみて下さい。
ここまで長々とお読みいただき、ありがとうございました。

さて、お次は、@さんです!よろしくお願いします。

*1:最終的にはコンパイルされた.classは実行時に変更できない、といったニュアンス。

お題:package-info.javaの一括生成

プログラミングお題の一覧はこちら
※みなさんもこのお題をお気に入りの言語で解いてみて下さい。解いたらこの記事にトラックバックをお願いします。

説明

(このお題の元ネタはこちら)

JavaDocのパッケージ内容を記述するpackage-info.javaを生成するプログラムを作成せよ。

要件は以下
・Javaソースファイルを配置する起点となるパスを指定する
・指定した起点のパスの配下にあるすべてのパッケージにpackage-info.javaを作成する
・package-info.javaの中身は以下
/**
 * "パッケージのパス"パッケージ。
 *
 * <pre>
 * // TODO パッケージ内容の詳細を記述してください
 * </pre>
 * 
 */
package パッケージのパス;
例 以下のようなディレクトリ構成を持っていた場合に、"./src" を起点として実行した場合、
./src
./src/jp
./src/jp/co
./src/jp/co/a
./src/jp/co/a/a1
./src/jp/co/a/a2
./src/jp/co/a/a3
以下のような結果が得られる。
./src/jp/package-info.java
./src/jp/co/package-info.java
./src/jp/co/a/package-info.java
./src/jp/co/a/a1/package-info.java
./src/jp/co/a/a2/package-info.java
./src/jp/co/a/a3/package-info.java
./src/jp/co/a/a2/package-info.javaの内容
/**
 * jp.co.a.a2パッケージ。
 *
 * <pre>
 * // TODO パッケージ内容の詳細を記述してください
 * </pre>
 * 
 */
package jp.co.a.a2;

GroovyでJavaのpackage-info.javaを一括生成するスクリプト(二番煎じ)

はじめに

id:tbpgさんのRuby記事を見て、Groovyで解こうかなと思っていたら、id:irofさんに先を越された!
けどめげずに私もGroovyで書いてみたら、大体一緒になった。

実行結果

$ groovy generatePackageInfo.groovy 
出力完了。出力結果
./src/jp/co/a/a1/package-info.java
./src/jp/co/a/a2/package-info.java
./src/jp/co/a/a3/package-info.java
./src/jp/co/a/package-info.java
./src/jp/co/b/b1/package-info.java
./src/jp/co/b/b2/package-info.java
./src/jp/co/b/package-info.java
./src/jp/co/package-info.java
./src/jp/package-info.java
$ cat ./src/jp/co/a/a1/package-info.java 
/**
 * jp.co.a.a1パッケージ。
 *
 * <pre>
 * // TODO パッケージ内容の詳細を記述してください
 * </pre>
 * 
 */
package jp.co.a.a1;
$ 

おわりに