Google Protocol Buffersしてみた

これ Developer Guide  |  Protocol Buffers  |  Google Developers

まずは、GitHub - protocolbuffers/protobuf: Protocol Buffers - Google's data interchange formatからコンパイラとソースをダウンロード。
で、protoc.exeを「protobuf-2.0.0beta/src」にコピーして 「protobuf-2.0.0beta/java/README.txt」の記述どおりにMavenでごにょごにょっとjarを作る。

次にXMLでいうDTDみたいなものを作成。こんなかんじ。とりあえず、ファイル名は「rss_test.proto」としました。

package prototest;

option java_package ="koyane.prototest";
option java_outer_classname = "RssTest";

message Item {
  required string title = 1;
  required string link = 2;
  optional string description =3;
  required string creator = 4;
  required string date = 5;
  optional string subject = 6;

}

message RssChannel {
  required string title = 1;
  required string link = 2;
  optional string description =3;

  repeated Item items =4;
}

なんとなーくjavaとかC++ぽい表記。リファレンスよまなくても何だかわかりそう。雰囲気で。

「option java_package」は生成されるJavaクラスのパッケージ。指定されてなければ「package」の記述が適用されるらしい。

「option java_outer_classname」は生成されるJavaクラスの名称。指定されてなければファイル名がクラスの名前に使われるみたい。この例でいくと、「rss_test.proto」だから、「RssTest」みたいな。

message XXX{
 ...
}

の部分は生成されるJavaクラスのインナークラスに該当してました。
この例ではstringしか使ってないけどint32(int)とかint64(long)とかbool(boolean)とかあります。

required は必須フィールド
optional は必須じゃないフィールド
repeated はまあListになる感じ

試してないけどmessageを宣言する順序は関係ありそう。サンプル見る限り。

で、.protoファイルを作成したら、コンパイラ protoc.exe の出番

protoc --java_out=/ rss_test.proto

でとりあえず、ファイルシステムのルートにJavaソースコードが生成されます。この例だと、koyane.prototest.RssTest.java
ふー。やっと準備完了。ここからが読み書きです。

まずはインスタンスを生成してファイルに保存

package koyane.prototest;

import java.io.FileOutputStream;
import java.util.Iterator;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;

import koyane.prototest.RssTest.Item;
import koyane.prototest.RssTest.RssChannel;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class TestWrite {
    public static void main(String[] args) throws Exception{
        Document doc = read("http://d.hatena.ne.jp/koyane/rss");
        Element root = doc.getDocumentElement();
        XPathFactory factory = XPathFactory.newInstance();
        XPath xpath = factory.newXPath();
        xpath.setNamespaceContext(new NamespaceContext(){
            @Override
            public String getNamespaceURI(String prefix) {
                if(prefix.equals("dc")){
                    return "http://purl.org/dc/elements/1.1/";
                }else{
                    return XMLConstants.NULL_NS_URI;
                }
            }
            @Override
            public String getPrefix(String namespaceURI) {
                throw new UnsupportedOperationException();
            }
            @Override
            public Iterator<?> getPrefixes(String namespaceURI) {
                throw new UnsupportedOperationException();
            }
        });
        Node cNode = (Node)xpath.evaluate("channel", root, XPathConstants.NODE);
        
        //ここ。ここがProtocol Buffers。
        RssChannel.Builder channel = RssChannel.newBuilder();
        channel.setTitle(xpath.evaluate("title/text()", cNode));
        channel.setLink(xpath.evaluate("link/text()", cNode));
        channel.setDescription(xpath.evaluate("description/text()", cNode));

        NodeList iList = (NodeList)xpath.evaluate("item", root, XPathConstants.NODESET);

        for (int i=0;i<iList.getLength();i++) {
            Node iNode = iList.item(i);
            //あとここ。ここがProtocol Buffers。
            Item.Builder item = Item.newBuilder();
            item.setTitle(xpath.evaluate("title/text()", iNode));
            item.setLink(xpath.evaluate("link/text()", iNode));
            item.setDescription(xpath.evaluate("description/text()", iNode));
            item.setCreator(xpath.evaluate("creator/text()", iNode));
            item.setDate(xpath.evaluate("date/text()", iNode));
            item.setSubject(xpath.evaluate("subject/text()", iNode));
            
            channel.addItems(item.build());
        }
        //で、ファイルに書き出し。
        FileOutputStream output = new FileOutputStream("c:/rss_test_result");
        channel.build().writeTo(output);
        output.close();
    }
    
    private static Document read(String url) throws Exception{
        HttpClient httpclient = new HttpClient();
        GetMethod method = new GetMethod(url);
        httpclient.executeMethod(method);
        
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document doc = builder.parse(method.getResponseBodyAsStream());

        method.releaseConnection();
        
        return doc;
    }
}

うりゃーーーっと、はてなRSSを読み込んで保存。ひでーこーどだ。Protocol Buffersのところは超簡単。

次にこのファイルを復元。

package koyane.prototest;

import java.io.FileInputStream;

import koyane.prototest.RssTest.Item;
import koyane.prototest.RssTest.RssChannel;

public class TestRead {
    public static void main(String[] args) throws Exception{
        RssChannel channel = RssChannel.parseFrom(new FileInputStream("c:/rss_test_result"));
        System.out.println("title : " + channel.getTitle());
        System.out.println("link : " + channel.getLink());
        System.out.println("description : " + channel.getDescription());
        
        for (Item item : channel.getItemsList()) {
            System.out.println();
            System.out.println("title : " + item.getTitle());
            System.out.println("link : " + item.getLink());
            System.out.println("description : " + item.getDescription());
            System.out.println("creator : " + item.getCreator());
            System.out.println("date : " + item.getDate());
            System.out.println("subject : " + item.getSubject());
        }
    }
}

おお、できた。
今のところ私に使い道はないけど、面白いなあ。