tokuhirom's Blog

Java で method_missing したい。

Java で method_missing したいというのは、テストを書いているときによくあるケースである。 Mockito など利用すればいいのだが、その中身が気になってしまうのが、エンジニアの性であろう。

そういうわけで、method_missing のやり方を調べていると、Proxy を利用する例が多々みつかる。 しかしこの方法では満足な結果は得られない。なぜならば Proxy では interface に対するモッキングはできるがクラスにたいするモッキングが出来ないからだ。悲しい。

参考: http://www.javaworld.com/article/2076233/java-se/explore-the-dynamic-proxy-api.html

そういうわけで、mocking には cglib or javassist などのバイトコード生成系ライブラリを利用するのが良い。 http://stackoverflow.com/questions/3291637/alternatives-to-java-lang-reflect-proxy-for-creating-proxies-of-abstract-classes


なお mockito の

when(body).thenReturn()

のような記法で、body がキャプチャできるわけないしキモいなあと思っていたのだが、これは単に DSL としてかっこ良く見せてるだけで、実際には引数としてなにかに利用しているわけではないようだ。つまり以下のように書いても同じなのだが、読みやすくするためにああなっている、ということのようだ。

body; when().thenReturn()

spring の MockMVC で通信結果をダンプしたいとき

mockmvc.perform(get("/"))
    .andDo(log());

とかしとけばいい。

    .andDo(print());

のほうが、確実に出力されるのでいいかも(ロガーの設定によらず出力される)

spring boot でテスト書いてたら 403 になって、csrf filter に引っかかってるぽくていらっとしたときの対策

@Before
public void setUp() throws Exception {
    mockMvc = MockMvcBuilders
            .webAppContextSetup(webApplicationContext)
            .apply(springSecurity())
            .build();
}

みたいな設定してるときに csrf filter かかってきてテストコードが 403 で弾かれてうざいわーって時

mockMvc.perform(post("/register")
              .with(csrf())
              .param("action", "signup"))
     .andExpect(status().isOk());

とかすりゃ、とりあえず csrf token 渡すようになるっぽい。

って stackoverflow に書いてあった。

http://stackoverflow.com/questions/21749781/why-i-received-an-error-403-with-mockmvc-and-junit

Spring Boot で redirect: とかなってるやつを自分で拡張したい

通常の Spring Boot では redirect:/ とか書くと、Moved Temporaly になるけど Moved Permanently 返したいときがある。

そんなとき、以下のような答えがインターネッツを検索していると出てくる。

そういうわけで、redirect301:/hoge/ とかでできるようにしたらいいんじゃないかと思いつく。 この似非 URL みたいなやつは ViewResolver ってやつがハンドリングしている。

ViewResolver は https://github.com/spring-projects/spring-framework/blob/554bf49/spring-webmvc/src/main/java/org/springframework/web/servlet/DispatcherServlet.java#L700 のへんで集められるが、要するに DI で ViewResolver をねじ込めば良い。

Spring Boot の場合、以下のようにすればよい。ViewResolver は複数指定できて、順番に呼び出される感じになっている。apache module のような呼びだされ方を想定しておけば良い。

@Slf4j
@Configuration
public class WebConfig implements ServletContextAware {
    @Bean
    public ViewResolver myViewResolver() {
        return (viewName, locale) -> {
            final String header = "redirect301:";
            if (viewName.startsWith(header)) {
                String redirectUrl = viewName.substring(header.length());
                RedirectView view = new RedirectView(redirectUrl, true, true);
                view.setStatusCode(HttpStatus.MOVED_PERMANENTLY);
                return view;
            } else {
                return null; // DECLINED
            }
        };
    }
}

spring mvc の interceptor のパスマッチャのやつ

Spring mvc の interceptor ってやつ。hook みたいなやつ。以下のようなパスの設定ができる。 excludePathPatterns の設定だけだと動かないとかいう説もあって、注意が必要。

registry.addInterceptor(new ThemeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");

http://docs.spring.io/spring/docs/4.2.4.RELEASE/spring-framework-reference/html/mvc.html#mvc-config-interceptors

ここのパターンは PathMatcher でマッチするのだが、AntPathMatcher が事実上唯一の実装。 https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/AntPathMatcher.html このマッチャで利用可能なメタキャラクタは以下のもののみ。

? matches one character
* matches zero or more characters
** matches zero or more directories in a path

インターセプターで、特定のパスのみ認証スキップとかやってる場合、気をつけないとエラー表示ページとかがそのまま interceptor かかってて謎のリダイレクトかかったりしてだるいです。

以上。

spring boot で、freemarker のテンプレートの読み込みがサーバー再起動しないと読まれなくてだるいって時の対処方法

spring boot はデフォルトでは classpath から読んでるので、まあなんか再読み込みさせるのが難しい。eclipse なら自動でコンパイルされるのでまだマシだが、intellij だとなんか遅いしイライラする。

そこで、build.gradleに以下のように記述し、template の読み込みパスを変更した。

bootRun {
    systemProperty "spring.freemarker.templateLoaderPath", "file:src/main/resources/templates/,classpath:/templates/"
}

freemarker が適当に file:// から直接読んでくれるので、ファイル変更したらすぐ読み込まれて便利。なぜ application.properties or application.yml に書かないのかというと、application.yml 的なものは common module に入れておいて、web module, admin module などがそれに依存するみたいな構成にしたいからで、そのような構成になっている場合、common module に sub project のパスを直ガキできないからです。

まあ、そんな感じ。

Perl5 での Plack に当たる Crust をみんなで作った話

この記事は Perl 6 Advent Calendar 2015 の1日目です。

Perl 6 は、2015年のクリスマスにリリースされることになっており、我々としてはそれを待ち受ける必要があると考えました。

Perl6 がリリースされた暁には、いろいろ遊ぼうかなという気持ちをみなさんお持ちだと思います。 Perl6 には夢が詰まっており、様々な機能が含まれている夢の言語です。

そういった、夢の機能については今後、クリスマスまでの間に、語られていきますが、本稿では、現実的な話をします。


今年のクリスマスに Perl6 がリリースされるぞ、という宣言がでたわけですが、そうなってくると、Perl6 をクリスマスから早速遊びたいなと思うわけですよね。

そして、私は web engineer なので、 Perl6 が出たら早速 web application を書きたいと思うわけです。

しかし、そこで気づいてしまう。Perl6 には Plack のようなものが無いと!

Perl6 に Plack のようなものがないし、当時まともな httpd の実装が存在していなかったのです! (実際には存在はしていたのですが、multi thread 対応していないし、なぜか当時の MoarVM は listen の backlog が 1 にハードコードされているためにまったく実用的ではなかったのです。この listen のバックログがハードコードされている問題は現在では修正されています)

これではいけない。と気づくわけです。まともな httpd が必要だ。 とりあえず fork(2) があれば、listen backlog が 1 にハードコードされているというハードな環境下でもなんとかなるだろうと思って、まず NativeCall ( Perl6 から FFI で C 関数をコールする機能) を使って fork を呼ぶようにしてみました。これはなかなかうまく動作したのですが、Mac で動作しないという問題が発見されました。

なぜか。

MoarVM は内部で libuv を利用しており、あらゆるソケット処理に libuv を利用しています。 libuv は fork をサポートしていません。より具体的に言うと mac の kqueue は fork をサポートしてないので、完全に詰みました。

対策として、ソケットライブラリを全部 NativeCall で実装することも考えたのですが(実装もしたのですが)、完全に本質を見失っている感じがしてきたので、この方針は諦めることにしました。


そうこうしているうちに、IO::Socket::Async ベースならわりと動くっぽいのではないかという話になり、IO::Socket::Async ベースで実装する方針に転換しました。

何度か書き直したのですが、まあそこそこ動くようになりました。

そのそこそこ動く実装が HTTP::Server::Tiny です。これは Perl5 の有名な httpd 実装である kazuhooku 氏による Starlet を参考に実装されており、HTTP/1.1 に対応した高速なサーバーです。 (Perl6 でできる範囲としてはかなり高速になるように実装しています。期待したパフォーマンスが出ていなければ、それは、、)

https://github.com/tokuhirom/p6-HTTP-Server-Tiny/


そういうわけで、HTTP::Server::Tiny ができたので、あとは Plack 相当のレイヤーを実装しようということになりました。

そこで、下記12名の Perl Monger が集まり、Plack を Perl6 に移植するプロジェクトに乗り出しました。

https://github.com/tokuhirom/p6-Crust

結果として、2015年12月1日時点でほぼ Plack の機能を実装しております。 例えばイカのように実行すれば、さくっと httpd が立ち上がります。めっちゃ便利。

crustup -e 'sub { 200, [], ["OK"] }'

具体的にはイカのものが完備されています。


そんなわけで、ウェブアプリケーションをかく準備は整っています。

あとは Perl6 のリリースを待つだけや!!!!

明日は karupanerura さんの 無限リストであそぶ です。

http://qiita.com/advent-calendar/2015/perl6

MoarVM における DESTROY メソッドの呼び出し手順

refcnt 型のインタープリタの場合デストラクタの呼び出しは極めて容易だが、GC の場合はちょっと面倒だ。

Rakudo の場合は、src/Perl6/Metamodel/Finalization.nqp にコードがある。

以下のように、DESTROY メソッドを持っているクラスを MoarVM に settypefinalize している。

role Perl6::Metamodel::Finalization {
    has @!destroyers;

    method setup_finalization($obj) {
        my @mro   := self.mro($obj);
        my int $i := nqp::elems(@mro);
        my @destroyers;
        while --$i >= 0 {
            my $class   := @mro[$i];
            my $destroy := $class.HOW.find_method($class, 'DESTROY', :no_fallback(1));
            if !nqp::isnull($destroy) && $destroy {
                nqp::push(@destroyers, $destroy);
            }
        }
        @!destroyers := @destroyers;
        if @destroyers {
            nqp::settypefinalize($obj, 1);
        }
    }

    method destroyers($obj) {
        @!destroyers
    }
}

settypefinalize の処理は src/core/interp.c の以下の部分で処理される。

            OP(settypefinalize):
                MVM_gc_finalize_set(tc, GET_REG(cur_op, 0).o, GET_REG(cur_op, 2).i64);
                cur_op += 4;
                goto NEXT;

MVM_gc_finalize_set により、クラスに MVM_FINALIZE_TYPE フラグが付与される。 このフラグが付与されているとオブジェクトの新規作成を行う MVM_gc_allocate_object の中で、MVM_gc_finalize_add_to_queue(tc, obj); が呼ばれ、ファイナライズキューに詰められる。

というわけで、ファイナライズ処理が必要になっているものは別枠で処理される、ということでした。

valgrind でメモリリークの誤検出が起きる場合、suppression を指定する

valgrind は、問題がない場合でも誤検出することがママある。 そのような場合、suppressions ファイルを生成すればよい。

たとえば、dlopen() は内部でエラーメッセージ保存用のバッファを calloc で確保するが、このバッファは開放されないが、これはバグではないということになっている。See. https://sourceware.org/bugzilla/show_bug.cgi?id=14015

このような場合、以下のようにして、--gen-suppressions=all として、suppressions 設定をダンプさせることができる。

$ make -j 4 && valgrind --gen-suppressions=all --leak-check=full --show-leak-kinds=all ./a.out 
make: Nothing to be done for `all'.
==829== Memcheck, a memory error detector
==829== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==829== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==829== Command: ./a.out
==829==
==829==
==829== HEAP SUMMARY:
==829==     in use at exit: 32 bytes in 1 blocks
==829==   total heap usage: 437 allocs, 436 frees, 117,923 bytes allocated
==829==
==829== 32 bytes in 1 blocks are still reachable in loss record 1 of 1
==829==    at 0x4C2B974: calloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==829==    by 0x4E3668F: _dlerror_run (dlerror.c:141)
==829==    by 0x4E360C0: dlopen@@GLIBC_2.2.5 (dlopen.c:87)
==829==    by 0x407599: pone_compile_node (main.c:771)
==829==    by 0x407851: main (main.c:845)
==829==
{
    <insert_a_suppression_name_here>
    Memcheck:Leak
    match-leak-kinds: reachable
    fun:calloc
    fun:_dlerror_run
    fun:dlopen@@GLIBC_2.2.5
    fun:pone_compile_node
    fun:main
}
==829== LEAK SUMMARY:
==829==    definitely lost: 0 bytes in 0 blocks
==829==    indirectly lost: 0 bytes in 0 blocks
==829==      possibly lost: 0 bytes in 0 blocks
==829==    still reachable: 32 bytes in 1 blocks
==829==         suppressed: 0 bytes in 0 blocks
==829==
==829== For counts of detected and suppressed errors, rerun with: -v
==829== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

この内容を t/myapp.supp などに保存する。そして、このファイルを指定して起動すると、以下のように、不要な警告をだまらせることができる。

$ make -j 4 && valgrind --suppressions=t/myapp.supp --leak-check=full --show-leak-kinds=all ./a.out
make: Nothing to be done for `all'.
==862== Memcheck, a memory error detector
==862== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==862== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==862== Command: ./a.out
==862==
==862==
==862== HEAP SUMMARY:
==862==     in use at exit: 32 bytes in 1 blocks
==862==   total heap usage: 437 allocs, 436 frees, 117,923 bytes allocated
==862==
==862== LEAK SUMMARY:
==862==    definitely lost: 0 bytes in 0 blocks
==862==    indirectly lost: 0 bytes in 0 blocks
==862==      possibly lost: 0 bytes in 0 blocks
==862==    still reachable: 0 bytes in 0 blocks
==862==         suppressed: 32 bytes in 1 blocks
==862==
==862== For counts of detected and suppressed errors, rerun with: -v
==862== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 2 from 2)

C++ と valgrind

libstdc++ は内部でメモリプールを持っている上に、プロセス終了時に開放しないので、valgrind が勘違いする。

以下のような極めて単純なプログラムでも All heap blocks were freed にならない。

#include <iostream>
int main() {
    return 0;
}

この問題は以下のように公式の FAQ に掲載されている。

My program uses the C++ STL and string classes. Valgrind reports 'still reachable' memory leaks involving these classes at the exit of the program, but there should be none.
First of all: relax, it's probably not a bug, but a feature. Many implementations of the C++ standard libraries use their own memory pool allocators. Memory for quite a number of destructed objects is not immediately freed and given back to the OS, but kept in the pool(s) for later re-use. The fact that the pools are not freed at the exit of the program cause Valgrind to report this memory as still reachable. The behaviour not to free pools at the exit could be called a bug of the library though.

    Using GCC, you can force the STL to use malloc and to free memory as soon as possible by globally disabling memory caching. Beware! Doing so will probably slow down your program, sometimes drastically.

    With GCC 2.91, 2.95, 3.0 and 3.1, compile all source using the STL with -D__USE_MALLOC. Beware! This was removed from GCC starting with version 3.3.

    With GCC 3.2.2 and later, you should export the environment variable GLIBCPP_FORCE_NEW before running your program.

    With GCC 3.4 and later, that variable has changed name to GLIBCXX_FORCE_NEW.

There are other ways to disable memory pooling: using the malloc_alloc template with your objects (not portable, but should work for GCC) or even writing your own memory allocators. But all this goes beyond the scope of this FAQ. Start by reading http://gcc.gnu.org/onlinedocs/libstdc++/faq/index.html#4_4_leak if you absolutely want to do that. But beware: allocators belong to the more messy parts of the STL and people went to great lengths to make the STL portable across platforms. Chances are good that your solution will work on your platform, but not on others.

http://valgrind.org/docs/manual/faq.html#faq.reports

が、上記の方法は最近の gcc だとなぜかうまく動かすことができなかったので、諦めた。

valgrind --leak-check=yes --leak-resolution=high --show-reachable=yes ./a.out

として、qr/ERROR SUMMARY: 0 errors/ になることを確認してそれでよし、ということにした。

jcmd が "well-known file is not secure" という IO 例外を上げてくるときの対処

jcmd が "well-known file is not secure" などと意味不明なことを申した場合、この例外は 以下の位置から上がっている。


JNIEXPORT void JNICALL Java_sun_tools_attach_LinuxVirtualMachine_checkPermissions
  (JNIEnv *env, jclass cls, jstring path)
{
    jboolean isCopy;
    const char* p = GetStringPlatformChars(env, path, &isCopy);
    if (p != NULL) {
        struct stat64 sb;
        uid_t uid, gid;
        int res;

        /*
         * Check that the path is owned by the effective uid/gid of this
         * process. Also check that group/other access is not allowed.
         */
        uid = geteuid();
        gid = getegid();

        res = stat64(p, &sb);
        if (res != 0) {
            /* save errno */
            res = errno;
        }

        /* release p here before we throw an I/O exception */
        if (isCopy) {
            JNU_ReleaseStringPlatformChars(env, path, p);
        }

        if (res == 0) {
            if ( (sb.st_uid != uid) || (sb.st_gid != gid) ||
                 ((sb.st_mode & (S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)) != 0) ) {
                JNU_ThrowIOException(env, "well-known file is not secure");
            }
        } else {
            char* msg = strdup(strerror(res));
            JNU_ThrowIOException(env, msg);
            if (msg != NULL) {
                free(msg);
            }
        }
    }
}

この例外はちょっと不親切で、

ときに発生するのだが、実際に上がるケースとしては、sudo jcmd などとしてユーザーのプロセスにアタッチしようとしたなどの理由により発生することが多くて、sudo -uwww-data jcmd ... などとすればすむことが多い。

Perl6 のフィボナッチ数列生成についての解説

mattn ブログで紹介されている Perl6 のフィボナッチ数列が奇妙に見える人が多いようなので、まともな解説。

ref. http://mattn.kaoriya.net/software/lang/perl6/20151026144119.htm

フィボナッチ数列とは以下のような数列です。

1, 1, 2, 3, 5, 8, 13, 21, 34, 55

最初の2つの数字が 1, 1 でして、その後のものは直前2つの数字を足したものです。

よって、Perl5 で記述した場合、先頭10個のフィボナッチ数列を求めるには以下のようになります。

use v5.16.0;

sub fib {
    state %memo; # 一応 memoize ぐらいはしておく

    my $n = shift;
    $memo{$n} //= do {
        if ($n == 0 || $n == 1) {
            1
        } else {
            fib($n-1) + fib($n-2);
        }
    };
}

sub fibs {
    my $n = shift;
    map { fib($_) } 0..$n;
}

say join(', ', fibs(10));

ですが、Perl6 では無限数列を扱う機能が充実しているため、こういったものは非常に単純に記述できます。

Perl6 での書き方を解説しましょう。

まず、先頭の数列を用意します。

1, 1

次に、続きは一つ前の項目と2つ前の項目の和であることを示します。

1, 1, -> $a, $b { $a + $b } ...*

ここで、-> $a, $b { $a + $b } はラムダ式です。これは ES6 などのものと同じですね。 ...* というのは、数列が続くことを示し、* は無限大を表します。こうすることで無限に続く数列を表すことができるのです。

Perl6 では、2項のラムダは * + * のように省略できるのでこれは

1, 1, *+* ...*

と表記することができます。

ここから、先頭10個を取り出すには

my @a = (1, 1, *+* ...*);
say @a[0..9];

また、レンジの右辺は ^10 のようにすれば「10は含まない」つまり「0~9」のレンジを作ることができるので、以下のように表記できます。

my @a = (1, 1, *+* ...*);
say @a[^10];

これをまとめると以下のようになります。

say (1, 1, *+* ...*)[^10];

ま、そんな感じです。

Shipped full featured Plack clone for Perl6 named `Crust`

Hi, i'm tokuhirom, one of the core developer of Plack.

During the past few weeks, I've been hack the Perl6. This is to celebrate the Christmas in the best mood.

I'm a minimalist, and I love a software built on clean and well tested parts. I mean tokuhirom loves CPAN. To write the practical web applications in Perl6, it has released some of the library.

Finally, by using these software, we have succeeded in transplanting the Plack. It's named Crust.

https://github.com/tokuhirom/p6-Crust/

I, and documentation issue(you know, my English is not good)t contains following modules.

You can install it by panda.

panda install Crust.

And there is a crustup command. You can run your P6SGI application by following comamnd:

crustup -e '-> $env { 200, [], ["ok"] }'
crustup app.psgi

We need your contribution for this project. There are some missing parts.

Thanks,

Raw::Socket was retired.

https://github.com/tokuhirom/p6-Raw-Socket/

I removed raw-socket from ecosystem. Because I had misunderstood the behavior of IO::Socket::Async.

IO::Socket::Async's behaviour is good enough to me. tap-ed callback function have each threads. these works concurrently

Ported Cookie::Baker to Perl6

I ported @kazeburo san's great Cookie::Baker to Perl6.

https://github.com/perl6/ecosystem/pull/60

This is my third module for Perl6 ecosystem

formatting moarvm's profiler result in CLI

moarvm outputs profiler result as HTML. It's too heavy to display for large programs. But it can output json too.

I wrote a command line tool to display inclusive/exclusive time.

here is a code: https://github.com/tokuhirom/p6-HTTP-Server-Tiny/blob/master/author/moar-profiler-cli.pl

sample output is here:

42329399        <anon>  gen/moar/m-BOOTSTRAP.nqp        2734
42326077        CALL-ME lib/NativeCall.pm       219
41899557        accept  /home/tokuhirom/.rakudobrew/moar-nom/install/share/perl6/site/lib/Raw/Socket/INET.pm6       136
41836327        p6_socket_accept        native library  -2
39888308        <anon>  /home/tokuhirom/dev/p6-HTTP-Server-Tiny/lib/HTTP/Server/Tiny.pm6        62
2080758 handler /home/tokuhirom/dev/p6-HTTP-Server-Tiny/lib/HTTP/Server/Tiny.pm6        147
2070563 <anon>  /home/tokuhirom/dev/p6-HTTP-Server-Tiny/lib/HTTP/Server/Tiny.pm6        154
1870526 parse-http-request      /home/tokuhirom/dev/p6-HTTP-Server-Tiny/lib/HTTP/Server/Tiny.pm6   238
852769  infix:<ne>      gen/moar/m-CORE.setting 7922
838721  infix:<eq>      gen/moar/m-CORE.setting 7918
733063  sink    gen/moar/m-CORE.setting 10186
729940  sink-all        gen/moar/m-CORE.setting 2806
589297  <anon>  /home/tokuhirom/dev/p6-HTTP-Server-Tiny/lib/HTTP/Server/Tiny.pm6        246
495481  match   gen/moar/m-CORE.setting 8369
473728  list    gen/moar/m-CORE.setting 7536
438270  <anon>  lib/NativeCall.pm       220
417899  ASSIGN-POS      gen/moar/m-CORE.setting 13204
414589  guess_library_name      lib/NativeCall.pm       157
408443  library /home/tokuhirom/.rakudobrew/moar-nom/install/share/perl6/site/lib/Raw/Socket/INET.pm6       9
366499  <anon>  /home/tokuhirom/.rakudobrew/moar-nom/install/share/perl6/site/lib/Raw/Socket/INET.pm6       12
365713  <anon>  /home/tokuhirom/.rakudobrew/moar-nom/install/share/perl6/site/lib/Raw/Socket/INET.pm6       13
328924  infix:<eq>      gen/moar/m-CORE.setting 1713
328914  <anon>  /home/tokuhirom/dev/p6-HTTP-Server-Tiny/lib/HTTP/Server/Tiny.pm6        269
312313  Stringy gen/moar/m-CORE.setting 1264
299129  r       gen/moar/m-CORE.setting 21928
294579  subst-mutate    gen/moar/m-CORE.setting 5729
274877  Str     gen/moar/m-CORE.setting 11385
265843  ASSIGN-POS-SLOWPATH     gen/moar/m-CORE.setting 13225
260310  join    gen/moar/m-CORE.setting 11938
250136  e       gen/moar/m-CORE.setting 21905

perl6 で TSV を SQL に変換する

もはやこういったものは Perl6 で書いた方が楽かもしれない。

my @header = lines.shift.split(/\t/);

say "INSERT INTO member ({@header.map({ "`$_`" }).join(",")}) VALUES";
lines.map({
    "(" ~ .split(/\t/).map({ qq!"$_"! }).join(",") ~ ")"
}).join(",\n").say;
say ";";

これを moarvm で動かすと想像以上に速く動く。便利である。

個人的には Perl5 よりも Ruby よりも書いていて気持ちがいい。 読んでいて気持ちがいいかは知らない。

groovy で ant task を記述したい

ant でロジックを記述するのはなかなか大変であるし、今となっては潰しのきかないスキルでもあり、未来ある若者に ant を使わせるべきではない。

しかし、世の中には ant を利用することを強いられている若者もいると聞く。

そういった中で、タスクを groovy でこなす方法を紹介しよう。

groovy でタスクを定義できれば、コードの可読性があがりハッピーになれるはずだ。

groovy でタスクを定義するためには groovy 本体を自動で取得する必要があるが、これは maven ant tasks で行う。maven ant tasks は、ant から maven の依存関係解決を呼べるようにしたライブラリである。

maven ant tasks はすでにインストール済みである場合も多いと思うが、入っていない場合は以下のようにすればよい。

mkdir ~/.ant/lib/
wget http://ftp.meisei-u.ac.jp/mirror/apache/dist/maven/ant-tasks/2.1.3/binaries/maven-ant-tasks-2.1.3.jar -P ~/.ant/lib/

さて、準備は整った。あとは以下のように記述すればよいだけである。 極めて簡単である。

<project name="demo" default="dist" xmlns:artifact="antlib:org.apache.maven.artifact.ant">

    <target name="dist">
        <!-- groovy-all を依存として宣言 -->
        <artifact:dependencies pathId="groovy.classpath">
            <dependency groupId="org.codehaus.groovy" artifactId="groovy-all" version="2.4.4" scope="compile"/>
        </artifact:dependencies>

        <!-- groovy-all を利用して groovy タグを有効化 -->
        <taskdef name="groovy" classname="org.codehaus.groovy.ant.Groovy" classpathref="groovy.classpath"/>

        <!-- build.groovy を実行 -->
        <groovy src="build.groovy"/>
    </target>

</project>

以上、ant から groovy を呼び出す方法を紹介した。

groovy スクリプトを実行できるようになったのはいいが、普通に groovy script で記述していくのは辛い。しかし ant から呼ばれた場合、ant の機能をつめこんだ AntBuilder といオブジェクトが渡されてくる。これを利用して、ant の機能を利用してコードを書いていけば良い。

AntBuilder についての解説はこのへんを見れば良い。 http://docs.groovy-lang.org/latest/html/documentation/ant-builder.html

良いのだが、これを直接利用するのは辛いので、ヘルパークラスを定義してこれを利用する。

以下は基本的なオペレーションをラップしている。直接 Ant を利用するのではなく、このヘルパクラスを利用して操作を行っていく。

import groovy.util.AntBuilder
import groovy.xml.NamespaceBuilder 
import org.apache.tools.ant.BuildException
import org.codehaus.groovy.ant.AntProjectPropertiesDelegate

public abstract class AbstractDeployer {
    AntBuilder ant
    AntProjectPropertiesDelegate props
    def mvn

    def AbstractDeployer(AntBuilder ant, AntProjectPropertiesDelegate props) {
        this.ant = ant
        this.props = props
        // Maven Ant Tasks
        this.mvn = NamespaceBuilder.newInstance(ant, 'antlib:org.apache.maven.artifact.ant') 
    }

    /**
     * Copy files from {@code src} to {@code dst} recursively.
     */
    def copyRecursive(String src, String dst) {
        if (new File(src).exists()) {
            ant.copy(todir:dst, overwrite:"true") {
                fileset(dir:src)
            }
        } else {
            echo("There is no ${dst}")
        }
    }

    /**
     * Get property. If there's no value for the key, this method throws Exception.
     */
    String propOrDie(String key) {
        def val = props[key]
        if (val == null) {
            throw new BuildException("No such property: '${key}'")
        }
        return val
    }

    /**
     * Get property value for {@code key}.
     */
    String prop(String key) {
        return props[key]
    }

    /**
     * Fetch {@code groupId}:{@code artifactId}:{@code version} and extract it into {@code webappOutput}.
     */
    def fetchAndUnwar(String groupId, String artifactId, String version, String webappOutput) {
        // fetch war
        mvn.dependencies(
            filesetId:'dependency.fileset.war',
            versionsId:'dependency.versions.war',
            useScope:'compile') {
            dependency(
                groupId:groupId,
                artifactId:artifactId,
                version:version,
                type:'war',
                scope:'compile')
        }

        ant.copy(todir:'target/artifacts') {
            fileset(refid:"dependency.fileset.war")
            chainedmapper {
                mapper(
                    classname:"org.apache.maven.artifact.ant.VersionMapper",
                    from:'${dependency.versions.war}')
                mapper(type:'flatten')
            }
        }

        // extract war file
        ant.unwar(
            src:"target/artifacts/${artifactId}.war",
            dest:webappOutput)
    }

    /**
     * Display {@code message}.
     */
    def echo(String message) {
        ant.echo(message:message)
    }

    /**
     * load properties from {@code srcFile}.
     */
    def loadProps(String srcFile) {
        if (new File(ant.project.baseDir, srcFile).exists()) {
            echo("loading ${srcFile}")
            ant.loadproperties(srcFile:srcFile)
        } else {
            echo("There is no ${srcFile}")
        }
    }
}

実際のビルドスクリプトは以下のような形式でやればよろしい。本来ならば、もっと DSL っぽく記述したほうがよいのだが、結局のところコンパイル時にある程度チェックできたほうが生産性が高まるので、@CompileStatic を付けられることを重視してクラスを定義している。もっと綺麗に書く方法があれば教えていただきたい。

このコードを見れば、なにがどういう手順で行われるかは一目瞭然であり、カスタマイズも簡単である。

import groovy.transform.CompileStatic
import groovy.util.AntBuilder
import org.codehaus.groovy.ant.AntProjectPropertiesDelegate

import AbstractDeployer

@CompileStatic
class Deployer extends AbstractDeployer {
    def Deployer(AntBuilder ant, AntProjectPropertiesDelegate properties) {
        super(ant, properties)
    }

    def run() {
        echo("building...")

        fetchAndUnwar("org.glassfish.admingui", 'war', '10.0-b28', 'target/webapp')
        // overwrite files
        copyRecursive("resources", "target/webapp/resources")
    }
}

new Deployer(ant, properties).run()

以上、簡単ですが ant から groovy を用いてコードを記述する方法の紹介でした。

Java 8 の DateTimeFormatter の曜日等のフォーマットについて

DateTimeFormatter を利用して曜日などのフォーマットを行おうとすると、実際どういうふうにフォーマットされるのかよくわからんということになる。

https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html をちらっと見ると、以下のように書いてあって、各シンボルが何を表すのかよくわからない。

  Symbol  Meaning                     Presentation      Examples
  ------  -------                     ------------      -------
   G       era                         text              AD; Anno Domini; A
   u       year                        year              2004; 04
   y       year-of-era                 year              2004; 04
   D       day-of-year                 number            189
   M/L     month-of-year               number/text       7; 07; Jul; July; J
   d       day-of-month                number            10

   Q/q     quarter-of-year             number/text       3; 03; Q3; 3rd quarter
   Y       week-based-year             year              1996; 96
   w       week-of-week-based-year     number            27
   W       week-of-month               number            4
   E       day-of-week                 text              Tue; Tuesday; T
   e/c     localized day-of-week       number/text       2; 02; Tue; Tuesday; T
   F       week-of-month               number            3

   a       am-pm-of-day                text              PM
   h       clock-hour-of-am-pm (1-12)  number            12
   K       hour-of-am-pm (0-11)        number            0
   k       clock-hour-of-am-pm (1-24)  number            0

   H       hour-of-day (0-23)          number            0
   m       minute-of-hour              number            30
   s       second-of-minute            number            55
   S       fraction-of-second          fraction          978
   A       milli-of-day                number            1234
   n       nano-of-second              number            987654321
   N       nano-of-day                 number            1234000000

   V       time-zone ID                zone-id           America/Los_Angeles; Z; -08:30
   z       time-zone name              zone-name         Pacific Standard Time; PST
   O       localized zone-offset       offset-O          GMT+8; GMT+08:00; UTC-08:00;
   X       zone-offset 'Z' for zero    offset-X          Z; -08; -0830; -08:30; -083015; -08:30:15;
   x       zone-offset                 offset-x          +0000; -08; -0830; -08:30; -083015; -08:30:15;
   Z       zone-offset                 offset-Z          +0000; -0800; -08:00;

   p       pad next                    pad modifier      1

   '       escape for text             delimiter
   ''      single quote                literal           '
   [       optional section start
   ]       optional section end
   #       reserved for future use
   {       reserved for future use
   }       reserved for future use

GGG などのように、同じ文字列を複数回続けることによって任意の形式で出力することができるのだが、この様式は以下のようになっている。

Date fields: Pattern letters to output a date.

  Pattern  Count  Equivalent builder methods
  -------  -----  --------------------------
    G       1      appendText(ChronoField.ERA, TextStyle.SHORT)
    GG      2      appendText(ChronoField.ERA, TextStyle.SHORT)
    GGG     3      appendText(ChronoField.ERA, TextStyle.SHORT)
    GGGG    4      appendText(ChronoField.ERA, TextStyle.FULL)
    GGGGG   5      appendText(ChronoField.ERA, TextStyle.NARROW)

    u       1      appendValue(ChronoField.YEAR, 1, 19, SignStyle.NORMAL);
    uu      2      appendValueReduced(ChronoField.YEAR, 2, 2000);
    uuu     3      appendValue(ChronoField.YEAR, 3, 19, SignStyle.NORMAL);
    u..u    4..n   appendValue(ChronoField.YEAR, n, 19, SignStyle.EXCEEDS_PAD);
    y       1      appendValue(ChronoField.YEAR_OF_ERA, 1, 19, SignStyle.NORMAL);
    yy      2      appendValueReduced(ChronoField.YEAR_OF_ERA, 2, 2000);
    yyy     3      appendValue(ChronoField.YEAR_OF_ERA, 3, 19, SignStyle.NORMAL);
    y..y    4..n   appendValue(ChronoField.YEAR_OF_ERA, n, 19, SignStyle.EXCEEDS_PAD);
    Y       1      append special localized WeekFields element for numeric week-based-year
    YY      2      append special localized WeekFields element for reduced numeric week-based-year 2 digits;
    YYY     3      append special localized WeekFields element for numeric week-based-year (3, 19, SignStyle.NORMAL);
    Y..Y    4..n   append special localized WeekFields element for numeric week-based-year (n, 19, SignStyle.EXCEEDS_PAD);

    Q       1      appendValue(IsoFields.QUARTER_OF_YEAR);
    QQ      2      appendValue(IsoFields.QUARTER_OF_YEAR, 2);
    QQQ     3      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT)
    QQQQ    4      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL)
    QQQQQ   5      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW)
    q       1      appendValue(IsoFields.QUARTER_OF_YEAR);
    qq      2      appendValue(IsoFields.QUARTER_OF_YEAR, 2);
    qqq     3      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.SHORT_STANDALONE)
    qqqq    4      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.FULL_STANDALONE)
    qqqqq   5      appendText(IsoFields.QUARTER_OF_YEAR, TextStyle.NARROW_STANDALONE)

    M       1      appendValue(ChronoField.MONTH_OF_YEAR);
    MM      2      appendValue(ChronoField.MONTH_OF_YEAR, 2);
    MMM     3      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT)
    MMMM    4      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL)
    MMMMM   5      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW)
    L       1      appendValue(ChronoField.MONTH_OF_YEAR);
    LL      2      appendValue(ChronoField.MONTH_OF_YEAR, 2);
    LLL     3      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.SHORT_STANDALONE)
    LLLL    4      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.FULL_STANDALONE)
    LLLLL   5      appendText(ChronoField.MONTH_OF_YEAR, TextStyle.NARROW_STANDALONE)

    w       1      append special localized WeekFields element for numeric week-of-year
    ww      1      append special localized WeekFields element for numeric week-of-year, zero-padded
    W       1      append special localized WeekFields element for numeric week-of-month
    d       1      appendValue(ChronoField.DAY_OF_MONTH)
    dd      2      appendValue(ChronoField.DAY_OF_MONTH, 2)
    D       1      appendValue(ChronoField.DAY_OF_YEAR)
    DD      2      appendValue(ChronoField.DAY_OF_YEAR, 2)
    DDD     3      appendValue(ChronoField.DAY_OF_YEAR, 3)
    F       1      appendValue(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH)
    E       1      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
    EE      2      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
    EEE     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
    EEEE    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
    EEEEE   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
    e       1      append special localized WeekFields element for numeric day-of-week
    ee      2      append special localized WeekFields element for numeric day-of-week, zero-padded
    eee     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT)
    eeee    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL)
    eeeee   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW)
    c       1      append special localized WeekFields element for numeric day-of-week
    ccc     3      appendText(ChronoField.DAY_OF_WEEK, TextStyle.SHORT_STANDALONE)
    cccc    4      appendText(ChronoField.DAY_OF_WEEK, TextStyle.FULL_STANDALONE)
    ccccc   5      appendText(ChronoField.DAY_OF_WEEK, TextStyle.NARROW_STANDALONE)
 
Time fields: Pattern letters to output a time.

  Pattern  Count  Equivalent builder methods
  -------  -----  --------------------------
    a       1      appendText(ChronoField.AMPM_OF_DAY, TextStyle.SHORT)
    h       1      appendValue(ChronoField.CLOCK_HOUR_OF_AMPM)
    hh      2      appendValue(ChronoField.CLOCK_HOUR_OF_AMPM, 2)
    H       1      appendValue(ChronoField.HOUR_OF_DAY)
    HH      2      appendValue(ChronoField.HOUR_OF_DAY, 2)
    k       1      appendValue(ChronoField.CLOCK_HOUR_OF_DAY)
    kk      2      appendValue(ChronoField.CLOCK_HOUR_OF_DAY, 2)
    K       1      appendValue(ChronoField.HOUR_OF_AMPM)
    KK      2      appendValue(ChronoField.HOUR_OF_AMPM, 2)
    m       1      appendValue(ChronoField.MINUTE_OF_HOUR)
    mm      2      appendValue(ChronoField.MINUTE_OF_HOUR, 2)
    s       1      appendValue(ChronoField.SECOND_OF_MINUTE)
    ss      2      appendValue(ChronoField.SECOND_OF_MINUTE, 2)

    S..S    1..n   appendFraction(ChronoField.NANO_OF_SECOND, n, n, false)
    A       1      appendValue(ChronoField.MILLI_OF_DAY)
    A..A    2..n   appendValue(ChronoField.MILLI_OF_DAY, n)
    n       1      appendValue(ChronoField.NANO_OF_SECOND)
    n..n    2..n   appendValue(ChronoField.NANO_OF_SECOND, n)
    N       1      appendValue(ChronoField.NANO_OF_DAY)
    N..N    2..n   appendValue(ChronoField.NANO_OF_DAY, n)
 
Zone ID: Pattern letters to output ZoneId.

  Pattern  Count  Equivalent builder methods
  -------  -----  --------------------------
    VV      2      appendZoneId()
    z       1      appendZoneText(TextStyle.SHORT)
    zz      2      appendZoneText(TextStyle.SHORT)
    zzz     3      appendZoneText(TextStyle.SHORT)
    zzzz    4      appendZoneText(TextStyle.FULL)
 
Zone offset: Pattern letters to output ZoneOffset.

  Pattern  Count  Equivalent builder methods
  -------  -----  --------------------------
    O       1      appendLocalizedOffsetPrefixed(TextStyle.SHORT);
    OOOO    4      appendLocalizedOffsetPrefixed(TextStyle.FULL);
    X       1      appendOffset("+HHmm","Z")
    XX      2      appendOffset("+HHMM","Z")
    XXX     3      appendOffset("+HH:MM","Z")
    XXXX    4      appendOffset("+HHMMss","Z")
    XXXXX   5      appendOffset("+HH:MM:ss","Z")
    x       1      appendOffset("+HHmm","+00")
    xx      2      appendOffset("+HHMM","+0000")
    xxx     3      appendOffset("+HH:MM","+00:00")
    xxxx    4      appendOffset("+HHMMss","+0000")
    xxxxx   5      appendOffset("+HH:MM:ss","+00:00")
    Z       1      appendOffset("+HHMM","+0000")
    ZZ      2      appendOffset("+HHMM","+0000")
    ZZZ     3      appendOffset("+HHMM","+0000")
    ZZZZ    4      appendLocalizedOffset(TextStyle.FULL);
    ZZZZZ   5      appendOffset("+HH:MM:ss","Z")
 
Modifiers: Pattern letters that modify the rest of the pattern:

  Pattern  Count  Equivalent builder methods
  -------  -----  --------------------------
    [       1      optionalStart()
    ]       1      optionalEnd()
    p..p    1..n   padNext(n)

これは、 https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatterBuilder.html#appendPattern-java.lang.String- のコピペである。DateTimeFormatter のフォーマットの仕方が DateTimeFormatterBuilder の方にあるので、たどりつくのが難しい。

実際どのようにフォーマットされるのかを確認する

実際にどのようにフォーマットされるのかを確認しておけば、後々便利であろう。


package datetimeformattersample;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

/**
 *
 * @author tokuhirom
 */
public class DateTimeFormatterSample {

  /**
   * @param args the command line arguments
   */
  public static void main(String[] args) {
    String[][] patterns = new String[][]{
      new String[]{"G", "appendText(ChronoField.ERA,"},
      new String[]{"GG", "appendText(ChronoField.ERA,"},
      new String[]{"GGG", "appendText(ChronoField.ERA,"},
      new String[]{"GGGG", "appendText(ChronoField.ERA,"},
      new String[]{"GGGGG", "appendText(ChronoField.ERA,"},
      new String[]{"", ""},
      new String[]{"u", "appendValue(ChronoField.YEAR,"},
      new String[]{"uu", "appendValueReduced(ChronoField.YEAR,"},
      new String[]{"uuu", "appendValue(ChronoField.YEAR,"},
      new String[]{"uuuu", "appendValue(ChronoField.YEAR,"},
      new String[]{"y", "appendValue(ChronoField.YEAR_OF_ERA,"},
      new String[]{"yy", "appendValueReduced(ChronoField.YEAR_OF_ERA,"},
      new String[]{"yyy", "appendValue(ChronoField.YEAR_OF_ERA,"},
      new String[]{"yyyy", "appendValue(ChronoField.YEAR_OF_ERA,"},
      new String[]{"Y", "append"},
      new String[]{"YY", "append"},
      new String[]{"YYY", "append"},
      new String[]{"YYYY", "append"},
      new String[]{"", ""},
      new String[]{"Q", "appendValue(IsoFields.QUARTER_OF_YEAR);"},
      new String[]{"QQ", "appendValue(IsoFields.QUARTER_OF_YEAR,"},
      new String[]{"QQQ", "appendText(IsoFields.QUARTER_OF_YEAR,"},
      new String[]{"QQQQ", "appendText(IsoFields.QUARTER_OF_YEAR,"},
      new String[]{"QQQQQ", "appendText(IsoFields.QUARTER_OF_YEAR,"},
      new String[]{"q", "appendValue(IsoFields.QUARTER_OF_YEAR);"},
      new String[]{"qq", "appendValue(IsoFields.QUARTER_OF_YEAR,"},
      new String[]{"qqq", "appendText(IsoFields.QUARTER_OF_YEAR,"},
      new String[]{"qqqq", "appendText(IsoFields.QUARTER_OF_YEAR,"},
      new String[]{"qqqqq", "appendText(IsoFields.QUARTER_OF_YEAR,"},
      new String[]{"", ""},
      new String[]{"M", "appendValue(ChronoField.MONTH_OF_YEAR);"},
      new String[]{"MM", "appendValue(ChronoField.MONTH_OF_YEAR,"},
      new String[]{"MMM", "appendText(ChronoField.MONTH_OF_YEAR,"},
      new String[]{"MMMM", "appendText(ChronoField.MONTH_OF_YEAR,"},
      new String[]{"MMMMM", "appendText(ChronoField.MONTH_OF_YEAR,"},
      new String[]{"L", "appendValue(ChronoField.MONTH_OF_YEAR);"},
      new String[]{"LL", "appendValue(ChronoField.MONTH_OF_YEAR,"},
      new String[]{"LLL", "appendText(ChronoField.MONTH_OF_YEAR,"},
      new String[]{"LLLL", "appendText(ChronoField.MONTH_OF_YEAR,"},
      new String[]{"LLLLL", "appendText(ChronoField.MONTH_OF_YEAR,"},
      new String[]{"", ""},
      new String[]{"w", "append"},
      new String[]{"ww", "append"},
      new String[]{"W", "append"},
      new String[]{"d", "appendValue(ChronoField.DAY_OF_MONTH)"},
      new String[]{"dd", "appendValue(ChronoField.DAY_OF_MONTH,"},
      new String[]{"D", "appendValue(ChronoField.DAY_OF_YEAR)"},
      new String[]{"DD", "appendValue(ChronoField.DAY_OF_YEAR,"},
      new String[]{"DDD", "appendValue(ChronoField.DAY_OF_YEAR,"},
      new String[]{"F", "appendValue(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH)"},
      new String[]{"E", "appendText(ChronoField.DAY_OF_WEEK,"},
      new String[]{"EE", "appendText(ChronoField.DAY_OF_WEEK,"},
      new String[]{"EEE", "appendText(ChronoField.DAY_OF_WEEK,"},
      new String[]{"EEEE", "appendText(ChronoField.DAY_OF_WEEK,"},
      new String[]{"EEEEE", "appendText(ChronoField.DAY_OF_WEEK,"},
      new String[]{"e", "append"},
      new String[]{"ee", "append"},
      new String[]{"eee", "appendText(ChronoField.DAY_OF_WEEK,"},
      new String[]{"eeee", "appendText(ChronoField.DAY_OF_WEEK,"},
      new String[]{"eeeee", "appendText(ChronoField.DAY_OF_WEEK,"},
      new String[]{"c", "append"},
      new String[]{"ccc", "appendText(ChronoField.DAY_OF_WEEK,"},
      new String[]{"cccc", "appendText(ChronoField.DAY_OF_WEEK,"},
      new String[]{"ccccc", "appendText(ChronoField.DAY_OF_WEEK,"},
      new String[]{"a", "appendText(ChronoField.AMPM_OF_DAY,"},
      new String[]{"h", "appendValue(ChronoField.CLOCK_HOUR_OF_AMPM)"},
      new String[]{"hh", "appendValue(ChronoField.CLOCK_HOUR_OF_AMPM,"},
      new String[]{"H", "appendValue(ChronoField.HOUR_OF_DAY)"},
      new String[]{"HH", "appendValue(ChronoField.HOUR_OF_DAY,"},
      new String[]{"k", "appendValue(ChronoField.CLOCK_HOUR_OF_DAY)"},
      new String[]{"kk", "appendValue(ChronoField.CLOCK_HOUR_OF_DAY,"},
      new String[]{"K", "appendValue(ChronoField.HOUR_OF_AMPM)"},
      new String[]{"KK", "appendValue(ChronoField.HOUR_OF_AMPM,"},
      new String[]{"m", "appendValue(ChronoField.MINUTE_OF_HOUR)"},
      new String[]{"mm", "appendValue(ChronoField.MINUTE_OF_HOUR,"},
      new String[]{"s", "appendValue(ChronoField.SECOND_OF_MINUTE)"},
      new String[]{"ss", "appendValue(ChronoField.SECOND_OF_MINUTE,"},
      new String[]{"", ""},
      new String[]{"SSSS", "appendFraction(ChronoField.NANO_OF_SECOND,"},
      new String[]{"A", "appendValue(ChronoField.MILLI_OF_DAY)"},
      new String[]{"AAAA", "appendValue(ChronoField.MILLI_OF_DAY,"},
      new String[]{"n", "appendValue(ChronoField.NANO_OF_SECOND)"},
      new String[]{"nnnn", "appendValue(ChronoField.NANO_OF_SECOND,"},
      new String[]{"N", "appendValue(ChronoField.NANO_OF_DAY)"},
      new String[]{"NNNN", "appendValue(ChronoField.NANO_OF_DAY,"},
      new String[]{"VV", "appendZoneId()"},
      new String[]{"z", "appendZoneText(TextStyle.SHORT)"},
      new String[]{"zz", "appendZoneText(TextStyle.SHORT)"},
      new String[]{"zzz", "appendZoneText(TextStyle.SHORT)"},
      new String[]{"zzzz", "appendZoneText(TextStyle.FULL)"},
      new String[]{"O", "appendLocalizedOffsetPrefixed(TextStyle.SHORT);"},
      new String[]{"OOOO", "appendLocalizedOffsetPrefixed(TextStyle.FULL);"},
      new String[]{"X", "appendOffset(\"+HHmm\",\"Z\")"},
      new String[]{"XX", "appendOffset(\"+HHMM\",\"Z\")"},
      new String[]{"XXX", "appendOffset(\"+HH:MM\",\"Z\")"},
      new String[]{"XXXX", "appendOffset(\"+HHMMss\",\"Z\")"},
      new String[]{"XXXXX", "appendOffset(\"+HH:MM:ss\",\"Z\")"},
      new String[]{"x", "appendOffset(\"+HHmm\",\"+00\")"},
      new String[]{"xx", "appendOffset(\"+HHMM\",\"+0000\")"},
      new String[]{"xxx", "appendOffset(\"+HH:MM\",\"+00:00\")"},
      new String[]{"xxxx", "appendOffset(\"+HHMMss\",\"+0000\")"},
      new String[]{"xxxxx", "appendOffset(\"+HH:MM:ss\",\"+00:00\")"},
      new String[]{"Z", "appendOffset(\"+HHMM\",\"+0000\")"},
      new String[]{"ZZ", "appendOffset(\"+HHMM\",\"+0000\")"},
      new String[]{"ZZZ", "appendOffset(\"+HHMM\",\"+0000\")"},
      new String[]{"ZZZZ", "appendLocalizedOffset(TextStyle.FULL);"},
      new String[]{"ZZZZZ", "appendOffset(\"+HH:MM:ss\",\"Z\")"}
    };

    ZonedDateTime zonedDateTime = LocalDateTime
            .parse("2015-07-13T12:27:04")
            .atZone(ZoneId.of("Asia/Tokyo"));
    System.out.printf("%-7s : %-19s\t%-19s // %s\n", "Pattern", "English", "Japanese", "Comment");
    for (String[] pattern : patterns) {
      try {
      DateTimeFormatter enFormatter = DateTimeFormatter.ofPattern(pattern[0], Locale.ENGLISH);
      String en = zonedDateTime.format(enFormatter);

      DateTimeFormatter jpFormatter = DateTimeFormatter.ofPattern(pattern[0], Locale.JAPAN);
      String jp = zonedDateTime.format(jpFormatter);

        System.out.printf("%-7s : %-19s\t%-19s // %s\n", pattern[0], en, jp, pattern[1]);
      } catch (java.time.DateTimeException e) {
        System.err.printf("cannot format '%s': %s", pattern[0], e.getMessage());
      }
    }
  }

}

出力例は以下のようになる。

Pattern : English            	Japanese            // Comment
G       : AD                 	西暦                  // appendText(ChronoField.ERA,
GG      : AD                 	西暦                  // appendText(ChronoField.ERA,
GGG     : AD                 	西暦                  // appendText(ChronoField.ERA,
GGGG    : Anno Domini        	西暦                  // appendText(ChronoField.ERA,
GGGGG   : A                  	A                   // appendText(ChronoField.ERA,
        :                    	                    // 
u       : 2015               	2015                // appendValue(ChronoField.YEAR,
uu      : 15                 	15                  // appendValueReduced(ChronoField.YEAR,
uuu     : 2015               	2015                // appendValue(ChronoField.YEAR,
uuuu    : 2015               	2015                // appendValue(ChronoField.YEAR,
y       : 2015               	2015                // appendValue(ChronoField.YEAR_OF_ERA,
yy      : 15                 	15                  // appendValueReduced(ChronoField.YEAR_OF_ERA,
yyy     : 2015               	2015                // appendValue(ChronoField.YEAR_OF_ERA,
yyyy    : 2015               	2015                // appendValue(ChronoField.YEAR_OF_ERA,
Y       : 2015               	2015                // append
YY      : 15                 	15                  // append
YYY     : 2015               	2015                // append
YYYY    : 2015               	2015                // append
        :                    	                    // 
Q       : 3                  	3                   // appendValue(IsoFields.QUARTER_OF_YEAR);
QQ      : 03                 	03                  // appendValue(IsoFields.QUARTER_OF_YEAR,
QQQ     : Q3                 	Q3                  // appendText(IsoFields.QUARTER_OF_YEAR,
QQQQ    : 3rd quarter        	第3四半期               // appendText(IsoFields.QUARTER_OF_YEAR,
QQQQQ   : 3                  	3                   // appendText(IsoFields.QUARTER_OF_YEAR,
q       : 3                  	3                   // appendValue(IsoFields.QUARTER_OF_YEAR);
qq      : 03                 	03                  // appendValue(IsoFields.QUARTER_OF_YEAR,
qqq     : 3                  	3                   // appendText(IsoFields.QUARTER_OF_YEAR,
qqqq    : 3                  	3                   // appendText(IsoFields.QUARTER_OF_YEAR,
qqqqq   : 3                  	3                   // appendText(IsoFields.QUARTER_OF_YEAR,
        :                    	                    // 
M       : 7                  	7                   // appendValue(ChronoField.MONTH_OF_YEAR);
MM      : 07                 	07                  // appendValue(ChronoField.MONTH_OF_YEAR,
MMM     : Jul                	7                   // appendText(ChronoField.MONTH_OF_YEAR,
MMMM    : July               	7月                  // appendText(ChronoField.MONTH_OF_YEAR,
MMMMM   : J                  	J                   // appendText(ChronoField.MONTH_OF_YEAR,
L       : 7                  	7                   // appendValue(ChronoField.MONTH_OF_YEAR);
LL      : 07                 	07                  // appendValue(ChronoField.MONTH_OF_YEAR,
LLL     : 7                  	7                   // appendText(ChronoField.MONTH_OF_YEAR,
LLLL    : 7                  	7                   // appendText(ChronoField.MONTH_OF_YEAR,
LLLLL   : 7                  	7                   // appendText(ChronoField.MONTH_OF_YEAR,
        :                    	                    // 
w       : 29                 	29                  // append
cannot format 'DD': Field DayOfYear cannot be printed as the value 194 exceeds the maximum print width of 2
ww      : 29                 	29                  // append
W       : 3                  	3                   // append
d       : 13                 	13                  // appendValue(ChronoField.DAY_OF_MONTH)
dd      : 13                 	13                  // appendValue(ChronoField.DAY_OF_MONTH,
D       : 194                	194                 // appendValue(ChronoField.DAY_OF_YEAR)
DDD     : 194                	194                 // appendValue(ChronoField.DAY_OF_YEAR,
F       : 6                  	6                   // appendValue(ChronoField.ALIGNED_DAY_OF_WEEK_IN_MONTH)
E       : Mon                	月                   // appendText(ChronoField.DAY_OF_WEEK,
EE      : Mon                	月                   // appendText(ChronoField.DAY_OF_WEEK,
EEE     : Mon                	月                   // appendText(ChronoField.DAY_OF_WEEK,
EEEE    : Monday             	月曜日                 // appendText(ChronoField.DAY_OF_WEEK,
EEEEE   : M                  	月                   // appendText(ChronoField.DAY_OF_WEEK,
e       : 2                  	2                   // append
ee      : 02                 	02                  // append
eee     : Mon                	月                   // appendText(ChronoField.DAY_OF_WEEK,
eeee    : Monday             	月曜日                 // appendText(ChronoField.DAY_OF_WEEK,
eeeee   : M                  	月                   // appendText(ChronoField.DAY_OF_WEEK,
c       : 2                  	2                   // append
ccc     : Mon                	月                   // appendText(ChronoField.DAY_OF_WEEK,
cannot format 'AAAA': Field MilliOfDay cannot be printed as the value 44824000 exceeds the maximum print width of 4
cccc    : Monday             	月曜日                 // appendText(ChronoField.DAY_OF_WEEK,
ccccc   : 1                  	1                   // appendText(ChronoField.DAY_OF_WEEK,
a       : PM                 	午後                  // appendText(ChronoField.AMPM_OF_DAY,
cannot format 'NNNN': Field NanoOfDay cannot be printed as the value 44824000000000 exceeds the maximum print width of 4
h       : 12                 	12                  // appendValue(ChronoField.CLOCK_HOUR_OF_AMPM)
hh      : 12                 	12                  // appendValue(ChronoField.CLOCK_HOUR_OF_AMPM,
H       : 12                 	12                  // appendValue(ChronoField.HOUR_OF_DAY)
HH      : 12                 	12                  // appendValue(ChronoField.HOUR_OF_DAY,
k       : 12                 	12                  // appendValue(ChronoField.CLOCK_HOUR_OF_DAY)
kk      : 12                 	12                  // appendValue(ChronoField.CLOCK_HOUR_OF_DAY,
K       : 0                  	0                   // appendValue(ChronoField.HOUR_OF_AMPM)
KK      : 00                 	00                  // appendValue(ChronoField.HOUR_OF_AMPM,
m       : 27                 	27                  // appendValue(ChronoField.MINUTE_OF_HOUR)
mm      : 27                 	27                  // appendValue(ChronoField.MINUTE_OF_HOUR,
s       : 4                  	4                   // appendValue(ChronoField.SECOND_OF_MINUTE)
ss      : 04                 	04                  // appendValue(ChronoField.SECOND_OF_MINUTE,
        :                    	                    // 
SSSS    : 0000               	0000                // appendFraction(ChronoField.NANO_OF_SECOND,
A       : 44824000           	44824000            // appendValue(ChronoField.MILLI_OF_DAY)
n       : 0                  	0                   // appendValue(ChronoField.NANO_OF_SECOND)
nnnn    : 0000               	0000                // appendValue(ChronoField.NANO_OF_SECOND,
N       : 44824000000000     	44824000000000      // appendValue(ChronoField.NANO_OF_DAY)
VV      : Asia/Tokyo         	Asia/Tokyo          // appendZoneId()
z       : JST                	JST                 // appendZoneText(TextStyle.SHORT)
zz      : JST                	JST                 // appendZoneText(TextStyle.SHORT)
zzz     : JST                	JST                 // appendZoneText(TextStyle.SHORT)
zzzz    : Japan Standard Time	日本標準時               // appendZoneText(TextStyle.FULL)
O       : GMT+9              	GMT+9               // appendLocalizedOffsetPrefixed(TextStyle.SHORT);
OOOO    : GMT+09:00          	GMT+09:00           // appendLocalizedOffsetPrefixed(TextStyle.FULL);
X       : +09                	+09                 // appendOffset("+HHmm","Z")
XX      : +0900              	+0900               // appendOffset("+HHMM","Z")
XXX     : +09:00             	+09:00              // appendOffset("+HH:MM","Z")
XXXX    : +0900              	+0900               // appendOffset("+HHMMss","Z")
XXXXX   : +09:00             	+09:00              // appendOffset("+HH:MM:ss","Z")
x       : +09                	+09                 // appendOffset("+HHmm","+00")
xx      : +0900              	+0900               // appendOffset("+HHMM","+0000")
xxx     : +09:00             	+09:00              // appendOffset("+HH:MM","+00:00")
xxxx    : +0900              	+0900               // appendOffset("+HHMMss","+0000")
xxxxx   : +09:00             	+09:00              // appendOffset("+HH:MM:ss","+00:00")
Z       : +0900              	+0900               // appendOffset("+HHMM","+0000")
ZZ      : +0900              	+0900               // appendOffset("+HHMM","+0000")
ZZZ     : +0900              	+0900               // appendOffset("+HHMM","+0000")
ZZZZ    : GMT+09:00          	GMT+09:00           // appendLocalizedOffset(TextStyle.FULL);
ZZZZZ   : +09:00             	+09:00              // appendOffset("+HH:MM:ss","Z")

この一覧表を参考にしながらコーディングすれば、簡単にフォーマットできる。と思う。

先日公開した、Java8 Date and Time の cheat sheet にも追記した。 https://gist.github.com/tokuhirom/0b7441684653cb85cefa

apache bench(ab) が OSX で刺さった夜は

ある日、OSX Yosemite で apache bench でベンチマークを使っていると、なぜか stuck して困っていた。

ab -c 10 -n 16500 http://127.0.0.1:5000/

どうも、対向サーバーを jetty にしても plack にしてもダメなので、なんなのかな~と。。


ab そのものを疑ってみる

いろいろ調べてみると、対向サーバーを変えても同じような感じで刺さるのでどうやら ab の側に問題があるのかな、と思って調べてみたところ、OSX Lion の時代にそのような問題が話題になっていたようだ。

最新版の ab は以下のようにして入れることができる。

wget http://ftp.tsukuba.wide.ad.jp/software/apache//httpd/httpd-2.4.12.tar.bz2
tar xzvf httpd-2.4.12.tar.bz2
cd httpd-2.4.12
sudo ln -s /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain /Applications/Xcode.app/Contents/Developer/Toolchains/OSX10.10.xctoolchain
cd support/
make ab
cp ab /usr/local/bin/
↑このへんかな、と思ってしらべていたが、最新のリビジョンでは修正されているし、問題がなかった。また、該当のエントリで指摘されている問題は r1351737 で修正されており、yosemite にバンドルされている ab は r1604373 なので、すでに修正済みである。

osx のプロセスあたりのファイルデスクリプタ数制限を疑ってみる

kazuho さんが、「osx のプロセスあたりのファイルデスクリプタ数制限にぶちあたってるだけではないか」というアドバイスをくれたので調べてみたが、よくよく考えると -c 10 とかでやってるので枯渇することはなさそう。

-c 1000 とかだとありえる。

結論としては epehemeral port の枯渇でした、と

ephemeral port とは

An ephemeral port is a short-lived transport protocol port for Internet Protocol (IP) communications allocated automatically from a predefined range by the IP software.

のようなもののことですが、こいつが枯渇していた。

OSX では 49152 to 65535 = 16383 なので、ちょうどそのぐらいで枯渇してエラーになると。 kazuho さんによると「16325 みたいな数値をみたら、あー ephemeral」ってわかるものだそうです。僕にはわからなかった!

linux では同様の条件でやっても再現しないので困っていたのだが、単に linux の場合 ephemeral port の範囲がデフォルトで 32768 to 61000 = 28232 と広いため、-n 17000 とかでやっても再現していなかった。

まとめ

ab は腐ってない。