tokuhirom's blog

Regexp::Trie を java に移植した

https://github.com/tokuhirom/regexp-trie

Regexp::Assemble っぽいことをするには graph-expression に入ってるやつを使えばいい、という話は昨日書いた のですが、生成コードがあまり綺麗ではないし、まあ今どき google-collections に依存しているところからも分かる通り、メンテナンスはされていないようです。

というわけで、@dankogai の Regexp::Trie を java に移植してみました。

使い方は以下の様になります。Perl5 とほぼ同じ感じで使えていますね(例は例によって groovy)。

@GrabResolver(name='tokuhirom', root='https://tokuhirom.github.io/maven/releases/')
@Grab('me.geso:regexp-trie:0.0.1')

import me.geso.regexp_trie.RegexpTrie;

def trie = new RegexpTrie();
["foobar", "fooxar", "foozap", "fooza"].forEach {
    trie.add(it);
}
println(trie.regexp()) // → (?:foo(?:bar|xar|zap?))

依存もないし、シンプルなコードなので使いやすいと思います。

必要な JDK バージョンは 7 です。


コードは Perl 版に比べて長くなっているが、読みやすくなっていると思います。 長くなってる感じがするのは、元のコードが dan さんのコードなので、めっちゃ詰めて書いてあるからというのもでかい。

今回、唯一詰まったのは、

TreeMap<String, TreeMap<String, TreeMap<String, ...>

みたいな再帰的な型をどうやって定義しようかな、というところだったのですが、これは

class CharTrie extends TreeMap<String, CharTrie> { }

とすればよいのでした。

あと、当初は Character 型を Trie のキーにしていたのだが、null は TreeMap の key に使えないということに気づいたので、String 型に変更し、terminator は "" で表現することに変更しました。 (本当は Terminator オブジェクトとツリーオブジェクトを定義する方が Java らしいのかもしれないけど、めんどくさいし今動いてるのでいいということにします。もっと Java らしくてかっこいい設計が思いつく人がいたら教えてほしいです。)

Created: 2014-07-24 07:02:03
Updated: 2014-07-24 07:02:03

groovy の grapes がうまくいかないときのデバッグ方法

groovy -Divy.message.logger.level=4 -Dgroovy.grape.report.downloads=true examples/synopsis.groovy

みたいにすると、ivy のログがめっちゃ出るようになるので、なにが原因だか探しやすくなる。


なお、ivy は dependency resolver で、cpanm の依存解決部分だけをやってくれる機能みたいなライブラリです。

Apache ivy は、単体のライブラリとしても使えるし、ivy.jar を使って ivy.xml からダウンロードさせる、みたいなこともできます。ant と組み合わせる場合には ivy を ant の中に埋め込んだりできる。

Maven が、依存関係の解決とビルドシステムを密結合させているのに対し、Ivy は単体のコンポーネントとして再利用可能なところが Cool。

あと、Apache Ivy は、Gradle の依存解決にも使われています。

Created: 2014-07-24 06:31:08
Updated: 2014-07-24 06:31:08

Java で Regexp::Assemble のように効率的な正規表現を文字列のリストから組み立てる

graph-expression ってやつに入ってる RegExpUtils ってのを使うと出来ます。 (サンプルコードは groovy)

#!/usr/bin/env groovy

@GrabResolver(name='mromll', root='http://mromll.googlecode.com/svn/repo/')
@Grab(group='com.myml', module='gexp', version='0.1')

import com.myml.gexp.chunker.common.util.RegExpUtils;

String [] examples = ['a', 'b', 'c', 'cca'];

String optimizedRegexp = RegExpUtils.convertListToRegexp(true, examples);
println(optimizedRegexp);

出力は以下のようになります。

(?:c(?:ca|)|a|b)

これを使うと、Java でも簡単に効果的な正規表現を生成できます。やったね!

ちなみに開発が終わってるっぽいのと、依存してるモジュールがちょっとおおいので、プロダクションコードの依存に入れるのは微妙だと思うけど、 用途的に実行時に使うものじゃないのでまあいいか、という感じです。

Created: 2014-07-23 13:46:25
Updated: 2014-07-23 13:46:25

nanobench のインターフェース変えた

lambda 使わないほうがいいんじゃない?という意見を kazuho さんから頂いたので、使わないようにしてみた。 lambda 使わない方がデバッガで追いやすいというのは一理あるので、それもまたいいのかな、と。

というわけで、以下のように書けばいいという感じになった。

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;

public class ListBenchmark {
    // Benchmarking method must be started with 'bench'.
    public void benchArrayList() {
        List<Integer> l = new ArrayList<>();
        for (int i=0; i<1_000_000; ++i) {
            l.add(i);
        }
    }

    public void benchLinkedList() {
        List<Integer> l = new LinkedList<>();
        for (int i=0; i<1_000_000; ++i) {
            l.add(i);
        }
    }
}

/^bench/ から始まってるメソッドが走っていい感じにベンチマークが取られます。

これを javac でコンパイルして nanobench.jar から実行させればいいという寸法。

> javac ListBenchmark.java
> java -jar nanobench.jar ListBenchmark

Score:

benchArrayList:  1 wallclock secs ( 1.03 usr +  0.10 sys =  1.13 CPU) @ 142.79/s (n=162)
benchLinkedList:  2 wallclock secs ( 1.07 usr +  0.15 sys =  1.21 CPU) @ 146.54/s (n=178)

Comparison chart:

                      Rate  benchArrayList  benchLinkedList
     benchArrayList  143/s              --              -3%
    benchLinkedList  147/s              3%               --

nanobench.jar があれば、どこでも動くので便利。

Created: 2014-07-15 11:53:31
Updated: 2014-07-15 11:53:31

Java 8 で例外を投げていることをテストしたい

特定の条件で例外を投げるコードを書いたときに、それをテストしたい。なんてときがある。 このクラスの例外があがってるってことだけ判断できればいいんだけどな、ぐらいのゆるい条件のとき。

そんな時は以下のようなユーティリティを定義すればいい。

public static void assertThrows(Class<? extends Exception> exceptionClass, Code code) {
    boolean thrown = false;
    try {
        code.run();
    } catch (Exception ex) {
        System.out.println(ex);
        assertTrue(exceptionClass.isInstance(ex));
        thrown = true;
    }
    assertTrue(thrown);
}

@FunctionalInterface
public interface Code {
    public void run() throws Exception;
}

利用法は以下のようになる。

@Test
public void testDelete() throws SQLException, HanaException {
    Member m1 = new Member().setEmail("foo@example.com").insert(conn);
    Assert.assertNotNull(m1);
    Member m2 = new Member().setEmail("foo@example.com").insert(conn);
    Assert.assertNotNull(m2);
    Follow follow = new Follow()
            .setFromMemberId(m1.getId())
            .setToMemberId(m2.getId())
            .insert(conn);

    assertThrows(HanaNoPrimaryKeyException.class, () -> {
        follow.delete(conn);
    });
}

以前の Java ではこのような簡便な書き方はできなかったが、Java8 では lambda expression を利用して、LL に近い表現が可能になっている。


と、ここまで書いたところで、実は JUnit でできるんじゃないかと思って調べてみたところ、以下のような解決策があることがわかった。


Spring Framework の場合は以下のようにできるようだ http://docs.spring.io/spring/docs/2.5.x/api/org/springframework/test/AssertThrows.html

 public class FooTest {
    public void testSomeBusinessLogicBadArgumentPath() {
        new AssertThrows(IllegalArgumentException.class) {
            public void test() {
                new Foo().someBusinessLogic(null);
            }
        }.runTest();
    }
 }

これは素晴らしい。これならなにも問題がない。ただ、lambda を使ってる場合よりちょっと長いけど、 このサンプルは spring の古いバージョンのドキュメントを見ているのでしょうがないのかもしれない。 Spring の新しいバージョンのドキュメントを調べたいところだが、めんどくさいので無理だった。


また、StackOverflow によれば以下のように JUnit の機能で書けるようだ。 http://stackoverflow.com/questions/156503/how-do-you-assert-that-a-certain-exception-is-thrown-in-junit-4-tests

@Test(expected=IndexOutOfBoundsException.class)
public void testIndexOutOfBoundsException() {
    ArrayList emptyList = new ArrayList();
    Object o = emptyList.get(0);
}

しかしこれは、テストの条件を Annotation に出してしまっていて、非常にわかりづらい。 しらない人が見てもこの annotation のパラメータがテスト条件だとは気づかないだろう。

Created: 2014-07-14 09:49:29
Updated: 2014-07-14 09:49:29

cv ってコマンドが面白い

procfs で他のプロセスの seek 位置をみることで、進捗状況を外からモニタリングするツールとのこと。

https://github.com/Xfennec/cv

Created: 2014-07-14 09:29:02
Updated: 2014-07-14 09:29:02

Lingua::EN::Inflect みたいなのを Java でやるには Evo Inflector を使う

https://github.com/atteo/evo-inflector

    System.out.println(English.plural("word")); // == "words"

とかやればいいので楽。

Created: 2014-07-08 17:43:27
Updated: 2014-07-08 17:43:27

Java8 Stream API では collector とかを複数回呼ぶと実行時エラーになる

以下のようなコードは、実行時エラーになる。

Stream<String> ss = Arrays.asList("hoge", "fuga").stream();
System.out.println(ss.count());
System.out.println(ss.count());

スタックトレースは以下のようになる。

stream has already been operated upon or closed
java.lang.IllegalStateException
    at java.util.stream.AbstractPipeline.<init>(AbstractPipeline.java:203)
    at java.util.stream.LongPipeline.<init>(LongPipeline.java:91)
    at java.util.stream.LongPipeline$StatelessOp.<init>(LongPipeline.java:572)
    at java.util.stream.ReferencePipeline$5.<init>(ReferencePipeline.java:221)
    at java.util.stream.ReferencePipeline.mapToLong(ReferencePipeline.java:220)
    at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)
    at StreamSampleTest.testStreamTwice(StreamSampleTest.java:21)
    ...

実行時エラーになるのはつらい。慣れてないときはこういうの書いちゃう人大そう。

というようなことがあるので、Stream に対する代入は今後、IDEが警告するようになるんじゃないか?

という話を Java についてカジュアルに語るチャンネルである #java-casual@freenode でしていたというメモ。

Created: 2014-07-08 17:10:22
Updated: 2014-07-08 17:10:22

JPA2 の @GeneratedValue について

JPA2 ではすべての entity に id がふられている。その id の採番方式は選択することができる。

@GeneratedValue(strategy=GeneratedValue.IDENTITY)

MySQL の AUTOINCREMENT を使いたいならこれを使えばいい。 我々は MySQL の AUTOINCREMENT による採番を行いたいので、通常この方式をとっておけばいい。

そのほかのものについては http://www.developerscrappad.com/408/java/java-ee/ejb3-jpa-3-ways-of-generating-primary-key-through-generatedvalue/ このブログが詳しい。 mysql 以外のデータベースでも動くようにしたいとか、そういう場合には他の GeneratedType を採用する必要があるが、現状我々の業務では必要がなさそうだ。

Created: 2014-07-02 10:07:09
Updated: 2014-07-02 10:07:09

Dropwizard+Hibernate で発行したSQLをログに出力する

DBIx::QueryLog のように、発行したSQLを直接みたいな、と思ったりしたわけですが、それは以下のように行えばよいようだ。

database.properties に hibernate.properties に書く内容を書いて hibernate の設定を行うのが dropwizard 流。

database:
  driverClass: com.mysql.jdbc.Driver
  user: root
  password: 
  url: jdbc:mysql://localhost:3306/mattn
  properties:
    charSet: UTF-8
    hibernate.dialect: org.hibernate.dialect.MySQLDialect
    hibernate.show_sql: true # ←
logging:
  level: INFO
  loggers:
    io.dropwizard: INFO
    org.hibernate.SQL: DEBUG # ←
    org.hibernate.type: TRACE # ←
  appenders:
    - type: console

DBIx::QueryLog みたいに、特定のスコープでだけログ出力を出せるようになってると良さそうだけど、どうやるのかよくわからないな。

Created: 2014-07-02 08:39:45
Updated: 2014-07-02 08:39:45