tokuhirom's blog

「Javaとベンチマーク」というタイトルでJJUG ナイト・セミナー 「ビール片手にLT&納涼会」で話してきたよ~

nanobench という java 用の簡単に使えるベンチマークツールについてお話させていただきました。 ご査収ください。

Created: 2014-08-25 15:34:42
Updated: 2014-08-25 15:34:42

Apache HttpClient で Content-Body を String として得る

public String getContentBody(CloseableHttpResponse res) {
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    res.getEntity().writeTo(stream);
    byte[] bytes = stream.toByteArray();

    ContentType contentType = ContentType.getOrDefault(res.getEntity());
    new String(bytes, contentType.getCharset());
}

としてもいいけど、

EntityUtils.toString(res.getEntity());

とすればいい。

ただこの方法でやる場合、Entity 一回読むと二回は読めないのが不便ではある。 使い勝手のいいゆるふわライブラリを作りたい場合は、Entity 読みだした後でどっかに保存しておいたほうがいいのかも。

Created: 2014-08-25 13:23:20
Updated: 2014-08-25 13:23:20

[Security Notice] Amon2, HTTP::Session2 security updates

Last week, I shipped Amon2 and HTTP::Session2 includes security fix.

[Must] Update "secret" if you are using HTTP::Session2::ClientStore

Amon2::Flavor::* generates the 'secret'. If your are using generated value, You MUST update it.

[Recommended] Update each libraries.

I recommend to update Amon2, HTTP::Session, HTTP::Session2 to the latest version.

[Recommended] Switch HTTP::Session2::ClientStore2 from HTTP::Session2::ClientStore

For reducinng security risk.

[Must] Update HTTP::Session2 if you are using ClientStore

It includes security fix.

[Recommended] Update HTTP::Session2 if you are using ServerStore

It includes security fix.

Created: 2014-08-11 14:55:06
Updated: 2014-08-11 14:55:06

jackson で JDK8 の Optional をうまく扱ってほしい

jackson では JDK8 の Optional にまだ対応していません。 Jackson 自体が JDK6 をサポートしているので、コアのデータバインディングに入ることはないのですが、それにしても JDK8 の Optional に対応していないのは不便ですね。まあ JDK8 はまだあれなので、しょうがないですが。。

しかしまあ、JDK8 使ってると Optional を返したいときはままあるので、はやく jackson-databind-jdk8 が出ることを願ってやみません。

取り急ぎ、https://github.com/FasterXML/jackson-databind/issues/494 この issue に書いてある方法で、対応は可能。これをコピペッペして、使えば動きます。

jersey でカスタマイズした ObjectMapper を使う方法についてはこちらを御覧ください。 https://jersey.java.net/documentation/latest/media.html#json.jackson

package me.geso.mobirc4j;

import java.io.IOException;
import java.util.Optional;

import javax.ws.rs.ext.ContextResolver;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.Version;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.deser.ContextualDeserializer;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;

/**
 * Module for JDK 1.8 types.
 * 
 * <p>
 * https://github.com/FasterXML/jackson-databind/issues/494
 * 
 * @author Gili Tzabari
 */
final class Jdk18Module extends SimpleModule
{
    private static final long serialVersionUID = 1L;

    private static class OptionalSerializer extends StdSerializer<Optional<?>>
    {
        /**
         * Create a new OptionalSerializer.
         */
        OptionalSerializer()
        {
            super(Optional.class, true);
        }

        @Override
        public void serialize(Optional<?> value, JsonGenerator generator,
                SerializerProvider provider)
                throws IOException
        {
            if (value.isPresent())
                generator.writeObject(value.get());
            else
                generator.writeNull();
        }
    }

    private static class OptionalDeserializer extends
            StdDeserializer<Optional<?>>
            implements ContextualDeserializer
    {
        private static final long serialVersionUID = 1L;
        private Class<?> targetClass;

        /**
         * Creates a new OptionalDeserializer.
         */
        OptionalDeserializer()
        {
            super(Optional.class);
        }

        @Override
        public JsonDeserializer<?> createContextual(
                DeserializationContext context,
                BeanProperty property) throws JsonMappingException
        {
            if (property != null)
            {
                // See
                // http://jackson-users.ning.com/forum/topics/deserialize-with-generic-type
                JavaType type = property.getType();
                JavaType ofType = type.containedType(0);
                this.targetClass = ofType.getRawClass();
            }
            return this;
        }

        @Override
        public Optional<?> deserialize(JsonParser parser,
                DeserializationContext context)
                throws IOException, JsonProcessingException
        {
            return Optional.of(parser.readValueAs(targetClass));
        }

        @Override
        public Optional<?> getNullValue()
        {
            return Optional.empty();
        }
    }

    /**
     * Creates a new Jdk18Module.
     */
    public Jdk18Module()
    {
        super("Jdk18Module", new Version(1, 0, 0, null,
                "com.fasterxml.jackson.databind", "jdk18"));
        addSerializer(new OptionalSerializer());
        addDeserializer(Optional.class, new OptionalDeserializer());
    }
}

public class MyObjectMapperProvider implements ContextResolver<ObjectMapper> {
    final ObjectMapper objectMapper;

    public MyObjectMapperProvider() {
        objectMapper = new ObjectMapper();
        objectMapper.registerModule(new Jdk18Module());
    }

    @Override
    public ObjectMapper getContext(Class<?> type) {
        return objectMapper;
    }
}

としておいて、以下のようにリソースコンフィグに登録すればよいです。

ResourceConfig resourceConfig = new ResourceConfig();
resourceConfig.property(ServerProperties.BV_SEND_ERROR_IN_RESPONSE, true);
resourceConfig.property(ServerProperties.BV_DISABLE_VALIDATE_ON_EXECUTABLE_OVERRIDE_CHECK, true);
resourceConfig.register(MyObjectMapperProvider.class);
resourceConfig.packages(ServerController.class.getPackage().getName());
resourceConfig.register(JacksonFeature.class);

ちなみに、こういう方法いれないと、単に OpionalLong#getLong() とかをガッツリ呼んじゃって、例外があがって悲しみにくれることになります。

Created: 2014-08-02 07:44:13
Updated: 2014-08-02 07:44:13

cpan-outdated 的なことを maven でやりたい

mvn versions:display-dependency-updates

でできる。コアなやつなので、pom.xml をいじらなくても使える。便利。

Created: 2014-08-02 06:58:23
Updated: 2014-08-02 06:58:23

Karabiner の private.xml を設定した

Realforce 買ったので private.xml を書いた。

private.xml を書くと、MBP 本体のキーボードと外付けキーボードとの間で別の設定ができるようになるし、設定を複数の環境で共有するのが楽になるから、書いたほうがよさそう。

今回かいたのは以下の様な感じである。

<?xml version="1.0"?>
<root>
    <appdef>
        <appname>TERMINAL</appname>
        <equal>com.googlecode.iterm2</equal>
    </appdef>

  <devicevendordef>
    <vendorname>RealForce_VendorID</vendorname>
    <vendorid>0x0853</vendorid>
  </devicevendordef>
  <deviceproductdef>
    <productname>RealForce_ProductID</productname>
    <productid>0x0111</productid>
  </deviceproductdef>
  <devicevendordef>
    <vendorname>MBP_VendorID</vendorname>
    <vendorid>0x05ac</vendorid>
  </devicevendordef>
  <deviceproductdef>
    <productname>MBP_ProductID</productname>
    <productid>0x0262</productid>
  </deviceproductdef>

  <item>
    <name>RealForce用セッティング</name>
    <appendix>Swap COMMAND and OPTION</appendix>
    <identifier>private.for_RealForce</identifier>
    <block>
      <device_only>
        DeviceVendor::RealForce_VendorID,
        DeviceProduct::RealForce_ProductID
      </device_only>
      <!-- 左コマンドを左OPTIONに -->
      <autogen>__KeyToKey__ KeyCode::COMMAND_L, KeyCode::OPTION_L</autogen>
      <!-- 右コマンドを右ALTに -->
      <autogen>__KeyToKey__ KeyCode::COMMAND_R, KeyCode::OPTION_R</autogen>
      <!-- 左ALTを左コマンドに、空打ちでEISUU -->
      <autogen>__KeyOverlaidModifier__ KeyCode::OPTION_L, KeyCode::COMMAND_L, KeyCode::JIS_EISUU</autogen>
      <!-- 右ALTを右コマンドに、空打ちでKANA -->
      <autogen>__KeyOverlaidModifier__ KeyCode::OPTION_R, KeyCode::COMMAND_R, KeyCode::JIS_KANA</autogen>
    </block>
  </item>
  <item>
    <name>MBP用セッティング</name>
    <appendix>Swap COMMAND and OPTION</appendix>
    <identifier>private.for_mbp</identifier>
    <block>
      <device_only>
        DeviceVendor::MBP_VendorID,
        DeviceProduct::MBP_ProductID
      </device_only>
      <!-- 左Command空打ちで英数 -->
      <autogen>__KeyOverlaidModifier__ KeyCode::COMMAND_L, KeyCode::COMMAND_L, KeyCode::JIS_EISUU</autogen>
      <!-- 右コマンドを空打ちでかな -->
      <autogen>__KeyOverlaidModifier__ KeyCode::COMMAND_R, KeyCode::COMMAND_R, KeyCode::JIS_KANA</autogen>
    </block>
  </item>

    <item>
        <name>Control+J to Kana</name>
        <identifier>option.kana.control_j</identifier>
        <autogen>__KeyToKey__ KeyCode::J, ModifierFlag::CONTROL_L, KeyCode::JIS_KANA</autogen>
    </item>

</root>
Created: 2014-08-01 17:19:42
Updated: 2014-08-01 17:19:42

Split Amon2::Plugin::Web::CSRFDefender from core distribution.

1. Amon2::Plugin::Web::CSRFDefender was removed from Amon2 core distribution.

Amon2::Plugin::Web::CSRFDefender is no longer default CSRF defender module in Amon2. I suggest to use HTTP::Session2.

If you still use Amon2::Plugin::Web::CSRFDefender, you need to write dependency explicitly in your cpanfile.

2. Latest Amon2::Plugin::Web::CSRFDefender is bit secure.

@mala says Amon2::Util::random_string is bit unsecure for token generation. I change the default token generation algorithm in Amon2::Plugin::Web::CSRFDefender.

I think it's not a critical security issue. But you can update it.

Created: 2014-07-30 17:03:20
Updated: 2014-07-30 17:03:20

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