<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-7028370</id><updated>2024-09-04T20:01:37.477+09:00</updated><category term="Java"/><category term="AspectJ"/><category term="trie"/><category term="Mac"/><category term="AppEngine"/><category term="Wicket"/><category term="通信基盤"/><category term="DoubleArray"/><category term="Eclipse"/><category term="LG3D"/><category term="LOUDS"/><category term="Webサービス"/><category term="pos2wit"/><category term="slim3"/><category term="AJAX"/><category term="AOP"/><category term="IDE"/><category term="JRuby"/><category term="JSON"/><category term="JSR"/><category term="JavaFx"/><category term="Mozilla"/><category term="OpenGL"/><category term="Trie4J"/><category term="Twitter4J"/><category term="WhiteDog"/><category term="Wii"/><category term="ajdt"/><category term="news"/><category term="trac"/><title type='text'>WhiteDog@Blog</title><subtitle type='html'>アスペクト指向プログラミングや通信ライブラリ関連のトピックを取り上げるブログ</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://takao.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default?alt=atom'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default?alt=atom&amp;start-index=26&amp;max-results=25'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>255</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-7028370.post-2504014668212059084</id><published>2013-05-19T17:01:00.001+09:00</published><updated>2013-05-20T11:57:41.164+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="trie"/><category scheme="http://www.blogger.com/atom/ns#" term="Trie4J"/><title type='text'>LZ77によるテキスト圧縮</title><content type='html'>　ちょこちょこと改良を続けている&lt;a href=&quot;http://github.com/takawitter/trie4j/&quot;&gt;Trie4J&lt;/a&gt;での話。&lt;br /&gt;
&lt;br /&gt;
　LOUDSによるTrieが実装できたことで、木構造を保持するのに必要なメモリは大幅に削減されました。そうなると今度は、TAIL配列の大きさが気になってきます。最新のTailLOUDSTrieの各構成要素をファイルに書きだしてサイズをみてみると、こんな感じ。&lt;br /&gt;
&lt;br /&gt;
&lt;table border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;サイズ(KB)&lt;/th&gt;&lt;th&gt;説明&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;木構造(LOUDS)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;756.8&lt;/td&gt;&lt;td&gt;Trie木構造をLOUDSで表現したもの&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;終端フラグ&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;189.3&lt;/td&gt;&lt;td&gt;木の各ノードが文字列の終端となるかどうかを表すフラグ&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ラベル配列&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;3,054.9&lt;/td&gt;&lt;td&gt;木の各ノードに対応するラベル&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TAILIndex配列&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;6,053.9&lt;/td&gt;&lt;td&gt;木の各ノードに対応する、TAIL配列へのインデックス&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TAIL配列&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;9,302.8&lt;/td&gt;&lt;td&gt;全ノードのTAILを繋いだ文字列&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;合計&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;19,329.6&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
※元データは2012年2月20日のWikipedia日本語タイトル(jawiki-20120220-all-titles-in-ns0.gz)。
&lt;br /&gt;
&lt;br /&gt;
　木は十分に小さくなり、ラベル配列以降の文字関連データで95%以上を占める状態になっています。その中でも一番容量を食っているTAIL配列をターゲットに、圧縮方法を考えます。(ちなみにTAILIndex配列は、完備辞書により大幅に圧縮することが可能で、既にTrie4Jで実装していますが、これはまたの機会に)&lt;br /&gt;
&lt;br /&gt;
&lt;iframe align=&quot;right&quot; frameborder=&quot;0&quot; marginheight=&quot;2px&quot; marginwidth=&quot;4px&quot; scrolling=&quot;no&quot; src=&quot;http://rcm-jp.amazon.co.jp/e/cm?lt1=_blank&amp;amp;bc1=000000&amp;amp;IS2=1&amp;amp;bg1=FFFFFF&amp;amp;fc1=000000&amp;amp;lc1=0000FF&amp;amp;t=takaoblogspot-22&amp;amp;o=9&amp;amp;p=8&amp;amp;l=as4&amp;amp;m=amazon&amp;amp;f=ifr&amp;amp;ref=ss_til&amp;amp;asins=4000069748&quot; style=&quot;height: 240px; width: 120px;&quot;&gt;&lt;/iframe&gt;
　いろいろと圧縮方法を調べてみると、少し前に買ってた書籍、「高速文字列解析の世界」 で解説されているLZ-Endという手法が良さそうです。&lt;br /&gt;
&lt;div style=&quot;margin-left: 4px;&quot;&gt;
　LZ-Endは、その論文のタイトル&lt;a href=&quot;http://www.dcc.uchile.cl/~gnavarro/ps/dcc10.1.pdf&quot;&gt;「LZ77-like Compression with Fast Random Access」&lt;/a&gt;からもわかるように、LZ77をベースに、高速にランダムアクセス出来るよう拡張されたアルゴリズムです。少々複雑なので、まずはベースとなったLZ77を見てみます(LZ77も同書籍に説明があります)。&lt;/div&gt;
&lt;br /&gt;
　LZ77では、文字列中の同じパターンを見つけ、より後に表れるものを過去のパターンを参照する情報に置き換えることで、圧縮を行います。具体的には、先頭から1文字ずつ見ていき、一致が無い場合は(-1, 0, 不一致文字)、あった場合は一致する文字数を調べて(先頭位置, 文字数, 不一致文字)を出力します(-1, 0の部分は、不一致を表す値であれば他のものでも構いません)。例えば&lt;br /&gt;
&lt;br /&gt;
&lt;table border=&quot;1&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;pre&gt;hello shallow&lt;/pre&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
　という文字列があった場合の処理と出力結果は、以下のようになります。&lt;br /&gt;
&lt;br /&gt;
&lt;table border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;th nowrap=&quot;true&quot;&gt;処理&lt;br /&gt;
番号&lt;/th&gt;&lt;th&gt;処理内容&lt;/th&gt;&lt;th&gt;出力&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;&#39;h&#39;は以前に現れないので、一致しない。&lt;/td&gt;&lt;td nowrap=&quot;true&quot;&gt;(-1, 0, &#39;h&#39;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;&#39;e&#39;は以前に現れないので、一致しない。&lt;/td&gt;&lt;td&gt;(-1, 0, &#39;e&#39;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;&#39;l&#39;は以前に現れないので、一致しない。&lt;/td&gt;&lt;td&gt;(-1, 0, &#39;l&#39;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;&#39;l&#39;は元文字列の2番目に現れ、1文字だけ一致し、次の&#39;o&#39;は一致しない。&lt;/td&gt;&lt;td&gt;(2, 1, &#39;o&#39;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;&#39; &#39;は以前に現れないので、一致しない。&lt;/td&gt;&lt;td&gt;(-1, 0, &#39; &#39;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;&#39;s&#39;は以前に現れないので、一致しない。&lt;/td&gt;&lt;td&gt;(-1, 0, &#39;s&#39;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;&#39;h&#39;は元文字列の0番目現れ、1文字だけ一致し、次の&#39;a&#39;は一致しない。&lt;/td&gt;&lt;td&gt;(0, 1, &#39;a&#39;)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;&lt;td&gt;&#39;l&#39;は元文字列の2番目に現れ、さらに2文字一致し、次の&#39;w&#39;は一致しない。&lt;/td&gt;&lt;td&gt;(2, 3, &#39;w&#39;)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
　上記の処理番号と、元の文字列の対応は次の通り。処理によって圧縮された部分を赤字にしています。
&lt;br /&gt;
&lt;table border=&quot;1&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;td&gt;5&lt;/td&gt;&lt;td&gt;6&lt;/td&gt;&lt;td&gt;7&lt;/td&gt;&lt;td&gt;8&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;h&lt;/td&gt;&lt;td&gt;e&lt;/td&gt;&lt;td&gt;l&lt;/td&gt;&lt;td&gt;&lt;span style=&quot;color: red;&quot;&gt;l&lt;/span&gt;o&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;s&lt;/td&gt;&lt;td&gt;&lt;span style=&quot;color: red;&quot;&gt;h&lt;/span&gt;a&lt;/td&gt;&lt;td&gt;&lt;span style=&quot;color: red;&quot;&gt;llo&lt;/span&gt;w&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
　処理の結果、(位置, 文字数, 不一致文字)の3つ組は8個出力されたことになります。それぞれ2byteとしても、3*8*2で48byte、元の文字列は13文字で、1文字2byteとすると26byteです。これからわかるように、LZ77は同じパターンがある程度現れないと、効率よく圧縮できません。ただし長いテキストデータにはある程度同じパターンが出てくるものが多く、一定の圧縮効果は期待できます。
&lt;br /&gt;
&lt;br /&gt;
　また、大規模なテキストを考えると、一致を先頭から順に探していては、膨大な時間がかかってしまいます。そのため構築方法に工夫が必要ですが、一般的には
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;一致を検索するために遡る文字数を制限する&lt;/li&gt;
&lt;li&gt;文字の出現位置をハッシュで保持する&lt;/li&gt;
&lt;/ul&gt;
　という方法がとられます。
&lt;br /&gt;
　これらの対応を行い、上記TrieのTAIL配列(9.3MB)に対してLZ77を行なってみると、約85%に圧縮されました。但しTAIL配列は既に先頭一致で重複を省いた後の文字列なので、一般的にはもっと小さくなります。実際、Trieに格納する前のWikipedia日本語タイトル約1千万文字に対して適用すると、約70%になりました。
&lt;br/&gt;　コードは下記を参照して下さい。
&lt;script src=&quot;https://gist.github.com/takawitter/5572607.js&quot;&gt;&lt;/script&gt;

&lt;br/&gt;
　LZ77はシンプルながら強力なアルゴリズムで、その派生がたくさんあります。一般的に使われるのは&lt;a href=&quot;http://ja.wikipedia.org/wiki/LZSS&quot;&gt;LZSS&lt;/a&gt;という拡張のようで、これは常に3つ組を出力するのではなく、一致する場合は(true, 先頭位置, 文字数)、一致しない場合は(false, 不一致文字)を出力する事により、さらにムダを省くものとなっています。しかしこれもランダムアクセスが難しいため、&lt;a href=&quot;http://github.com/takawitter/trie4j&quot;&gt;Trie4J&lt;/a&gt;ではLZSSをさらに拡張するか、LZ-Endを使うかのどちらかにしようと考えています。
</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/2504014668212059084/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/2504014668212059084' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/2504014668212059084'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/2504014668212059084'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2013/05/lz77.html' title='LZ77によるテキスト圧縮'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-3255984053439625321</id><published>2012-08-25T01:11:00.001+09:00</published><updated>2012-09-12T18:05:53.845+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DoubleArray"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="LOUDS"/><category scheme="http://www.blogger.com/atom/ns#" term="trie"/><title type='text'>最新の各種トライ速度比較</title><content type='html'>&lt;p&gt;trie4jの最新の実装で、Wikipedia日本語タイトル127万件を格納する速度(build)、全件を照合する速度(contains)、消費サイズ(size)を測ってみました。
&lt;/p&gt;
&lt;table border=&quot;1&quot;&gt;
&lt;tr&gt;&lt;th&gt;クラス&lt;/th&gt;&lt;th&gt;build(ms)&lt;/th&gt;&lt;th&gt;contains(ms)&lt;/th&gt;&lt;th&gt;size(MB)&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;java.util.HashSet&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;417&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;453&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;160.4&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;java.util.TreeSet&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;402&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;261&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;160.2&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PatriciaTrie&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;442&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;244&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;104.6&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TailPatriciaTrie(SuffixTrieTail)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;1,220&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;271&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;100.8&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TailPatriciaTrie(ConcatTail)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;517&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;241&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;86.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;MultilayerPatriciaTrie&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;704&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;386&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;91.8&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;MultilayerPatriciaTrie(packed)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2,822&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;866&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;82.9&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DoubleArray&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;471&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;106&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;48.5&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TailDoubleArray(SuffixTrieTail)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;3,078&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;175&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;37.3&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TailDoubleArray(ConcatTail)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;2,597&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;157&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;42.0&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LOUDSTrie(SuffixTrieTail)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;777&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;508&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;18.1&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LOUDSTrie(ConcatTail)&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;234&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;483&lt;/td&gt;&lt;td align=&quot;right&quot;&gt;22.9&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;

&lt;ul&gt;
&lt;li&gt;補足1 - 今回からbuild, contains共に正味の速度を測るようにした(前回まではWikipediaアーカイブからの要素取得も入っていた)ので、HashSetも前回に比べると速くなっている様に見えている。&lt;/li&gt;
&lt;li&gt;補足2 - TailPatriciaTrie(suffixTrieTail)のサイズが大きいのは、SuffixTrieTailBuilderのトライが消費している分がでかい(約23MB)。TailPatriciaTrieは要素追加に対応するので、SuffixTrieTailBuilderもトライを保持しておく必要がある。&lt;/li&gt;
&lt;li&gt;補足3 - MultilayerPatriciaTrie(packed)は、ラベルを2層目のトライに追い出した状態。追い出しに時間がかかる(2秒超)が、サイズは10MB弱小さくなる。但し以降要素の追加はできない。また、DoubleArray以降は構築に別のトライを必要とし(今回PatriciaTrieを使用)、構築後は要素の追加はできない(未実装)。&lt;/li&gt;
&lt;li&gt;補足4 - DoubleArrayやLOUDSTrieで構築後の要素追加を実装する方法がないわけではないが、実用的な速度で追加するのはかなり難しそう。特にWikipedia級のデータになると1要素ずつ何万件もずらすような操作が発生してしまい、性能が出ない。&lt;/li&gt;
&lt;li&gt;補足5 - DoubleArrayの速度が劇的に改善してるのは、文字へのコード採番方式を変更したため。今までHashMapを使っていたが、TreeSet&lt;Character&gt;(総数カウント、列挙用)とchar[Character.MAX_VALUE](コード変換テーブル)の2つに置き換えた。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
意外とHashSetとTreeSetはがんばってるんだなという印象。Trieの利点であるcommonPrefixSearchやpredictiveSearchは使えないけど。あとメモリ消費はやっぱりでかい。あと、HashSetの方が速いと思ってたんだけど、意外にTreeSetの方が速かった。メモリ消費もこの2つでは変わらない。
&lt;/p&gt;
&lt;p&gt;
MultilayerPatriciaTrieは結構実装がんばってるんだけど、packしない前(要素追加可能)はTailPatriciaTrie(concatTail)に負けてるし、pack後(要素追加不可)はDoubleArray, LOUDSTrieにぼろ負け。結構トリッキーな実装してるし、実装ほんとに苦労したから残念なんだけど、現状存在意義無さそう。experimental状態(src.kitchensink)にしようかな。
&lt;/p&gt;
&lt;p&gt;
あとTailDoubleArrayが遅いのは、DoubleArrayと比較して、空き要素の検索に時間がかかるようになるため。DoubleArrayだと1文字だけのラベル、子供1つだけというノードが大量に存在し、それがbase/checkの隙間を埋めてくれる。一方TailDoubleArrayはラベルをTAIL配列に追い出すことによってそういうノードがなくなるため、空きノードの検索対象が増え、速度が遅くなる。
&lt;/p&gt;
&lt;p&gt;
今のところ、
&lt;ul&gt;
&lt;li&gt;要素動的追加必須 -&gt; TailPatriciaTrie(ConcatTail)&lt;/li&gt;
&lt;li&gt;検索速度重視 -&gt; DoubleArray&lt;/li&gt;
&lt;li&gt;サイズ重視 -&gt; LOUDSTrie(SuffixTrieTail)&lt;/li&gt;
&lt;/ul&gt;
と使い分けるのが良さそうです。
&lt;/p&gt;
</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/3255984053439625321/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/3255984053439625321' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/3255984053439625321'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/3255984053439625321'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2012/08/blog-post.html' title='最新の各種トライ速度比較'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-1124065194877101948</id><published>2012-08-25T00:33:00.002+09:00</published><updated>2012-09-12T18:05:53.841+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="LOUDS"/><category scheme="http://www.blogger.com/atom/ns#" term="trie"/><title type='text'>LOUDSTrie実装してみた</title><content type='html'>よく考えたらblogに載せてなかった(TwitterやGoogle+でつぶやいて載せた気になってた・・)ので、改めて。
&lt;p&gt;
LOUDSはLevel-order Unary Degree Sequenceの略。詳しくは書籍&lt;a href=&quot;http://www.amazon.co.jp/gp/product/4774149934/ref=as_li_ss_il?ie=UTF8&amp;tag=takaoblogspot-22&amp;linkCode=as2&amp;camp=247&amp;creative=7399&amp;creativeASIN=4774149934&quot;&gt;「日本語入力を支える技術」&lt;/a&gt;(amazon)や下記サイトを参照してください。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://d.hatena.ne.jp/takeda25/20120303/1330760254&quot;&gt;情報系修士にもわかるLOUDS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://d.hatena.ne.jp/echizen_tm/20101005/1286291649&quot;&gt;LOUDS(Level-Order Unary Degree Sequence)を調べたのでメモ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;
要は木構造を01のビット列で表そうというデータ構造で、遅いけどかなり小さく圧縮できる。ただでさえJavaではオブジェクトがメモリを食う(何もフィールド無くてもポインタ2個分消費する)ので、木構造がビット列で表現できれば、絶大なサイズ削減効果があります。
&lt;/p&gt;
&lt;p&gt;
既に構築済みのトライからLOUDS Trieを構築するのは結構簡単で、幅優先でトライを辿り、子の数だけ1を追加、最後に0を追加というのを繰り返すだけ。trie4jではこんな感じに実装してます。&lt;/p&gt;
&lt;a href=&quot;https://github.com/takawitter/trie4j/blob/master/trie4j/src/org/trie4j/louds/LOUDSTrie.java#L53&quot;&gt;https://github.com/takawitter/trie4j/blob/master/trie4j/src/org/trie4j/louds/LOUDSTrie.java#L53&lt;/a&gt;
&lt;p&gt;
構築済みのトライを辿るには、ルートから、&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;左端の子ノードの位置を求める(簡潔ビット配列のselect0操作)&lt;/li&gt;
&lt;li&gt;右端の子ノードの位置を求める(簡潔ビット配列のselect0操作)&lt;/li&gt;
&lt;li&gt;左端の子ノードのIDを求める(簡潔ビット配列のrank1操作)&lt;/li&gt;
&lt;li&gt;子ノードから適切なラベルを持つものを探す&lt;/li&gt;
&lt;li&gt;子ノードのTAIL配列(2番目以降のラベル文字列)を得て検索文字列と比較&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;という感じで子ノードの検索、文字列比較を行っていく。これを高速に行うのが結構面倒で、select0やrank1をどう高速化するか、ビット配列の実装が肝になります。&lt;/p&gt;
&lt;p&gt;trie4jでは、select0、rank1共にキャッシュを作って高速化してます。また、上記2の処理専用にnext0という操作を簡潔ビット配列クラス(&lt;a href=&quot;https://github.com/takawitter/trie4j/blob/master/trie4j/src/org/trie4j/util/SuccinctBitVector.java&quot;&gt;SuccinctBitVector&lt;/a&gt;)に追加してます。そこらへんの工夫により、127万件の要素を照合するのに500ms程度と、まずまずの速度を確保してます(もちろんC++で実装されたライブラリに比べれば遅いだろうけど)。&lt;/p&gt;
&lt;p&gt;
trie4jの実装はこちら &lt;a href=&quot;https://github.com/takawitter/trie4j&quot;&gt;https://github.com/takawitter/trie4j&lt;/a&gt;からどうぞ。BuildHive使って、最新のスナップショットtrie4j-SNAPSHOT.jarも常にダウンロードできる状態にしてあります。&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/1124065194877101948/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/1124065194877101948' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/1124065194877101948'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/1124065194877101948'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2012/08/loudstrie.html' title='LOUDSTrie実装してみた'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-3783380596434347965</id><published>2012-06-15T00:54:00.001+09:00</published><updated>2012-09-12T18:05:53.848+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DoubleArray"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="LOUDS"/><category scheme="http://www.blogger.com/atom/ns#" term="trie"/><title type='text'>各種トライの速度比較</title><content type='html'>&lt;p&gt;&lt;em&gt;6/17 GCの影響をできるだけ排除するように実装し、測り直しました。&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;LOUDSTrieの実装が落ち着いてきたので、パトリシアトライ、ダブルアレイ、LOUDSトライの構築、contains速度を測ってみました。&lt;/p&gt;
&lt;p&gt;何回か測ってみたけど、だいたいこんな感じ。&lt;/p&gt;

&lt;table border=&quot;1&quot;&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;構築(ms)&lt;/th&gt;&lt;th&gt;contains(ms)&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;java.util.HashSet&lt;/td&gt;&lt;td&gt;1,042&lt;/td&gt;&lt;td&gt;816&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;PatriciaTrie(UTF-16 char[])&lt;/td&gt;&lt;td&gt;891&lt;/td&gt;&lt;td&gt;662&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;MultilayerPatriciaTrie(多層トライ)&lt;/td&gt;&lt;td&gt;3,538&lt;/td&gt;&lt;td&gt;1,354&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TailCompactionDoubleArray&lt;/td&gt;&lt;td&gt;5,082&lt;/td&gt;&lt;td&gt;858&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;OptimizedTailCompactionDoubleArray&lt;/td&gt;&lt;td&gt;5,760&lt;/td&gt;&lt;td&gt;1,022&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LOUDSTrie&lt;/td&gt;&lt;td&gt;922&lt;/td&gt;&lt;td&gt;1,025&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;CPUはCore i7 2.5GHz。データはWikipedia日本語タイトル127万件。パトリシアトライの実装は&lt;a href=&quot;http://takao.blogspot.jp/2012/04/blog-post.html&quot;&gt;ここ&lt;/a&gt;、ダブルアレイの実装は&lt;a href=&quot;http://takao.blogspot.jp/2012/05/blog-post.html&quot;&gt;ここ&lt;/a&gt;を参照。DoubleArray2つとLOUDSTrieはPatriciaTrieを使って構築してるので、実際の構築時間はPatriciaTrieの分も必要です。
&lt;/p&gt;
&lt;p&gt;意外とLOUDSTrieが構築、検索共に頑張ってる印象。というかDoubleArrayってもっと速いんじゃなかったっけ。実装が悪いのかも知れませんが。
&lt;/p&gt;
&lt;p&gt;実装は&lt;a href=&quot;https://github.com/takawitter/trie4j&quot;&gt;Trie4J(GitHub)&lt;/a&gt;にて公開してます。
&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/3783380596434347965/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/3783380596434347965' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/3783380596434347965'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/3783380596434347965'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2012/06/blog-post.html' title='各種トライの速度比較'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-673764017509964719</id><published>2012-05-16T22:07:00.002+09:00</published><updated>2012-05-17T08:30:19.641+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="trie"/><title type='text'>Wikipedia英語タイトルも格納してみた</title><content type='html'>Wikipedia英語版のタイトル一覧(2012/4/3版。9,310,564エントリ)も格納して、サイズ測ってみました。
&lt;br /&gt;
&lt;table border=&quot;1&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;th&gt;実装&lt;/th&gt;&lt;th&gt;配列要素数(base/check等)&lt;/th&gt;&lt;th&gt;TAIL配列要素数&lt;/th&gt;&lt;th&gt;全体サイズ&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;OptimizedTailCompactionDoubleArray&lt;/td&gt;&lt;td&gt;12,710,311&lt;/td&gt;&lt;td&gt;15,699,525&lt;/td&gt;&lt;td&gt;162,867,250&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;br /&gt;
各種トライの性能を比較した記事「&lt;a href=&quot;http://d.hatena.ne.jp/ny23/20110110/p1&quot;&gt;2011-01-10 marisa-trie 強いな&lt;/a&gt;」と比較してみると、エントリ数が1.5倍弱になってることを考慮して、darts-cloneに近いメモリ効率をたたき出してると思う。ただdarts-cloneはTAILをDAWG(Directed Acyclic Word Graph)で保持してるようなので、今のトライ使ったTAIL圧縮だと勝てないかも知れない。あと文字をcharで保持してたり、違いはいろいろ。まぁこっちはJavaなので既に速度的にはだいぶ水開けられてるし(Corei72.5GHzで24秒程度)、元よりあまり有効な比較にはならないかも知れない。構築途中で1.3GBくらいメモリ消費するしねー。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/673764017509964719/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/673764017509964719' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/673764017509964719'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/673764017509964719'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2012/05/wikipedia.html' title='Wikipedia英語タイトルも格納してみた'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-6912570707594881585</id><published>2012-05-16T21:48:00.000+09:00</published><updated>2012-09-12T18:05:42.303+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DoubleArray"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="trie"/><title type='text'>ダブルアレイのサイズ比較</title><content type='html'>今勉強＆実装してるダブルアレイのサイズを測ってみました。詳しい話はまた今度(というつつパトリシアトライの解説もまだ途中だけど・・・)。&lt;br /&gt;
&lt;br /&gt;
データはパトリシアトライと同じく、Wikipedia日本語タイトルの127万エントリ。文字列は全てcharで保持してます。また、予めパトリシアトライを構築し、それをもとにダブルアレイを構築してます。&lt;br /&gt;
&lt;br /&gt;
以下の表にまとめます。&lt;br /&gt;
&lt;table border=&quot;1&quot;&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;th&gt;実装&lt;/th&gt;&lt;th&gt;配列要素数(base/check等)&lt;/th&gt;&lt;th&gt;TAIL配列要素数&lt;/th&gt;&lt;th&gt;全体サイズ&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;DoubleArray&lt;sup&gt;*1&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;5,668,704&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt;48,547,151&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TailDoubleArray&lt;sup&gt;*2&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;1,679,616&lt;/td&gt;&lt;td&gt;4,651,339(capacity: 4,718,590)&lt;/td&gt;&lt;td&gt;31,947,268&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;TailCompactionDoubleArray&lt;sup&gt;*3&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;1,679,616&lt;/td&gt;&lt;td&gt;1,797,866(capacity: 2,359,294)&lt;/td&gt;&lt;td&gt;27,226,058&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;OptimizedTailCompactionDoubleArray&lt;sup&gt;*4&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;1,519,679&lt;/td&gt;&lt;td&gt;1,797,866&lt;/td&gt;&lt;td&gt;21,177,535&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
*1 - シンプルなダブルアレイ実装。空き要素の位置を記録して構築高速化、孫ノードの多い子ノードを先に登録してメモリ利用効率向上、TAIL無し。
&lt;br /&gt;
*2 - TAILあり。
&lt;br /&gt;
*3 - 接尾辞トライによるTAIL圧縮。
&lt;br /&gt;
*4 - 構築終了時に配列サイズをtrim。checkの要素をintではなくcharにする。
&lt;br /&gt;
&lt;br /&gt;
パトリシアトライの時に苦労していたのが嘘のように、シンプルな実装でいきなり48MBを叩きだしてます。前回Javaではオブジェクト数大事、と連呼してましたが、トライを保持する方法がノードオブジェクトをポインタでつなぐ方法から、int配列2つに変わったことで、TAIL圧縮すらしてないのに約34MB減りました。&lt;br /&gt;
&lt;br /&gt;
結論: &lt;b&gt;やっぱりオブジェクト数大事&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
さらにTAIL配列、TAIL配列圧縮等工夫していくことで、最終的に21MBまで小さくなっています。これ以上は、ダブルアレイの仕組みそのものを変えないと大幅な圧縮は期待できず、行き着く先は、トライの保持をビットベクタで行うLOUDS(Level Order Unary Degree Sequence。&lt;a href=&quot;http://d.hatena.ne.jp/takeda25/20120421/1335019644&quot;&gt;参考&lt;/a&gt;)です。なので今後はLOUDSを勉強&amp;実装してく予定です。&lt;br /&gt;
&lt;br /&gt;
ダブルアレイの実装について少し。最初のDoubleArrayクラスでは、教科書通りの実装(&lt;a href=&quot;http://nanika.osonae.com/DArray/dary.html&quot;&gt;ここ&lt;/a&gt;とか参照)に加えて、少し効率化を図ってます。一つは構築速度の効率化として行なっている、空き要素位置の保持です。これは最初の空き要素位置を保持する変数を1個儲け、2番目以降の空き要素位置はcheck配列内に保持してます。ただし真面目にやると大変(時間がかかる)なので、実際見に行って空き要素ではなければ、そこから1つずつ空き要素を探すようにしています。もう一つは孫ノードの多い子ノードを先に登録することで充填率が上がり、結果配列のサイズが小さくなります。これはまぁデータ依存なところはありますが。
&lt;br /&gt;
&lt;br /&gt;
TailDoubleArrayクラスは、DoubleArrayにTAIL配列(&lt;a href=&quot;http://nanika.osonae.com/DArray/dary.html&quot;&gt;ここ&lt;/a&gt;のMinimal Prefix Double-Arrayあたり参照)を導入したものです。一つしか子持たないノードが続く区間は、ラベルを文字配列内にコピーすることで、メモリ利用効率を上げます。
&lt;br/&gt;&lt;br/&gt;
TailCompcationDoubleArrayクラスは、TAIL配列を可能な限り共有することで、さらに効率を上げます。&quot;world&quot;がTAILに格納されている状態で&quot;javaworld&quot;を格納する場合に&quot;world\0java\1(0)&quot;というように、既に接尾辞の一部が格納されている場合はそれを再利用するという工夫も行なってます。これは、多層トライで使ったテクニックを応用して、TAILを逆順に格納したトライをつくることで実現しています。このあたりはまた別途詳しく書く予定。
&lt;br/&gt;&lt;br/&gt;
OptimizedTailCompactionDoubleArrayクラスは、構築終了時にbase配列やcheck配列の末尾にある使用していない領域を縮小したり、TAIL配列(中身はStringBuilder)をtrimしたりしてます。また、check配列をintではなくcharにしています。文字の種類が32,767を超えると格納できなくなりますし、次の空き要素までの距離が32,768を超えても格納できなくなります。現実的にはどちらもまず起こらない(Wikipedia日本語タイトルでも、文字種は7,564)ので、実用上問題ないんじゃないかと思います(でも念のため別クラスとして実装)。
&lt;br/&gt;&lt;br/&gt;
実装は今までどおり、&lt;a href=&quot;http://github.com/takawitter/trie4j&quot;&gt;GitHub&lt;/a&gt;で公開してます。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/6912570707594881585/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/6912570707594881585' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/6912570707594881585'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/6912570707594881585'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2012/05/blog-post.html' title='ダブルアレイのサイズ比較'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-6357729433344490388</id><published>2012-04-19T23:30:00.001+09:00</published><updated>2012-04-20T00:10:44.780+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="trie"/><title type='text'>パトリシアトライのサイズ比較</title><content type='html'>&lt;p&gt;　記事が前後しますが。気になったのでちょっと調べてみました。トライの内部形式をchar[]からbyte[]にすることでどれくらいインパクトがあるのか。byte[]にする際、UTF-8、Shift-JIS、EUC、JIS(ISO2022JP)でどのような違いがあるのか。
&lt;/p&gt;
&lt;p&gt;
以下の表にまとめます。
&lt;/p&gt;

&lt;table border=&quot;1&quot;&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th colspan=&quot;2&quot;&gt;トライのノード&lt;/th&gt;&lt;th colspan=&quot;2&quot;&gt;文字配列数&lt;/th&gt;&lt;th&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;th&gt;エンコーディング(内部形式)&lt;/th&gt;&lt;th&gt;数&lt;/th&gt;&lt;th&gt;サイズ&lt;/th&gt;&lt;th&gt;数&lt;/th&gt;&lt;th&gt;サイズ&lt;/th&gt;&lt;th&gt;全体サイズ&lt;/th&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;ISO-2022-JP(byte[])&lt;/td&gt;&lt;td&gt;1,763,967&lt;/td&gt;&lt;td&gt;58,210,911&lt;/td&gt;&lt;td&gt;1,764,142&lt;/td&gt;&lt;td&gt;41,189,910&lt;/td&gt;&lt;td&gt;123,904,937&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;UTF-8(byte[])&lt;/td&gt;&lt;td&gt;1,736,910&lt;/td&gt;&lt;td&gt;57,318,030&lt;/td&gt;&lt;td&gt;1,737,083&lt;/td&gt;&lt;td&gt;40,404,315&lt;/td&gt;&lt;td&gt;123,095,316&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Shift_JIS(byte[])&lt;/td&gt;&lt;td&gt;1,639,064&lt;/td&gt;&lt;td&gt;54,089,112&lt;/td&gt;&lt;td&gt;1,639,229&lt;/td&gt;&lt;td&gt;35,103,790&lt;/td&gt;&lt;td&gt;112,335,651&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;EUC_JP(byte[])&lt;/td&gt;&lt;td&gt;1,633,865&lt;/td&gt;&lt;td&gt;53,917,545&lt;/td&gt;&lt;td&gt;1,634,031&lt;/td&gt;&lt;td&gt;35,089,059&lt;/td&gt;&lt;td&gt;112,013,060&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;UTF-16(char[])&lt;/td&gt;&lt;td&gt;1,513,469&lt;/td&gt;&lt;td&gt;48,431,008&lt;/td&gt;&lt;td&gt;1,515,346&lt;/td&gt;&lt;td&gt;35,043,030&lt;/td&gt;&lt;td&gt;103,472,496&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;UTF-16(char[])+フィールド最適化&lt;sup&gt;(*1)&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;1,513,469&lt;sup&gt;(*3)&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;39,664,136&lt;sup&gt;(*4)&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;1,515,338&lt;/td&gt;&lt;td&gt;34,688,312&lt;/td&gt;&lt;td&gt;91,774,563&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;UTF-16(char[])+フィールド最適化&lt;sup&gt;(*1)&lt;/sup&gt;+多層トライ&lt;sup&gt;(*2)&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;1,995,303&lt;sup&gt;(*3)&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;55,082,824&lt;sup&gt;(*4)&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;483,714&lt;sup&gt;(*5)&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;10,345,546&lt;sup&gt;(*5)&lt;/sup&gt;&lt;/td&gt;&lt;td&gt;82,870,660&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;p&gt;
*1 - 子ノードを持たない/1つだけ/2つ以上、終端である/ないの組み合わせでそれぞれ最適なフィールドを持つノードクラスを使用する。
&lt;br/&gt;
*2 - ラベルを逆順に持つ圧縮用トライを用意(参考: &lt;a href=&quot;http://d.hatena.ne.jp/s-yata/20101223/1293143633&quot;&gt;多層トライの実験結果 - やた＠はてな日記&lt;/a&gt;)。本来のトライのノードもその圧縮用トライのノードをフィールドに持つものを使用。
&lt;br/&gt;
*3 - 全ノードの合計数。
&lt;br/&gt;
*4 - 全ノードの合計サイズ。
&lt;br/&gt;
*5 - 文字配列は圧縮用トライ内のもの。本来のトライには文字配列を持たない。
&lt;/p&gt;
&lt;p&gt;
いずれもWikipedia日本語タイトル127.3万件を格納した結果です。フィールド最適化+多層トライのデータでは、圧縮用トライのノードや文字配列のサイズも加算されています。
&lt;/p&gt;
&lt;p&gt;こうして見てみると、多層トライがいかに圧縮率が高いかと、オブジェクト数の増減がいかにインパクトがあるかがわかります。せっかく多層トライで圧縮したメモリサイズ(24MB)も、ノードオブジェクト数の増加でその効果は半減しています。&lt;/p&gt;
&lt;p&gt;結論: &lt;strong&gt;Javaではオブジェクト数大事&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;多層トライの実装は&lt;a href=&quot;http://github.com/takawitter/trie4j&quot;&gt;GitHub&lt;/a&gt;で公開してます。org.trie4j.trie.patricia.multilayer.MultilayerPatriciaTrieがフィールド最適化と多層トライの実装で、全タイトル挿入後のデータ、挿入してからpack(), morePack()を呼び出した後のデータが表の下2つです。packで多層トライでの圧縮、morePackで多層トライのノードのフィールド最適化を行います(morePack遅いです)。&lt;/p&gt;
&lt;p&gt;下記にVisualVMでの主要なオブジェクトのスナップショットを。まずはUTF-16のフィールド最適化のみ。&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiUG-bbHGCSQCTu8l6teKNSO8dmdPZK2Q-rhYXPzmPRFXt4puWZK9a6VMLV4QzHQgfQ8N5Q-a2iYK0vw-vUfujEgjA7xaiN53-nbFO7aS1e9l8Ht5l1xNCzXniB-e4eNXmoWHM/s1600/hist1.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img border=&quot;0&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiUG-bbHGCSQCTu8l6teKNSO8dmdPZK2Q-rhYXPzmPRFXt4puWZK9a6VMLV4QzHQgfQ8N5Q-a2iYK0vw-vUfujEgjA7xaiN53-nbFO7aS1e9l8Ht5l1xNCzXniB-e4eNXmoWHM/s400/hist1.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;次にフィールド最適化+多層トライ(もフィールド最適化)。&lt;/p&gt;

&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-9POgOlwSF5OLqyrapws2enk6rtwwBQhhym-GWKdXRkjOV4B3IW3MDUzb31t_rzUXeAUamAExZSDhBdTty1jgRfaU1QCc01p73sqPtov3g4ZC456I4UW0DYwL4kOYtYEiONkP/s1600/hist2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left:1em; margin-right:1em&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;82&quot; width=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-9POgOlwSF5OLqyrapws2enk6rtwwBQhhym-GWKdXRkjOV4B3IW3MDUzb31t_rzUXeAUamAExZSDhBdTty1jgRfaU1QCc01p73sqPtov3g4ZC456I4UW0DYwL4kOYtYEiONkP/s400/hist2.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;サイズをオブジェクト数で割るとわかるけど、1オブジェクトで24とか32バイト消費してます。フィールド最適化を行わない場合、40バイト消費することもあります(64bit環境で。32bitなら半分になります。)&lt;/p&gt;
&lt;p&gt;結論: &lt;strong&gt;Javaではオブジェクト数大事&lt;/strong&gt;(2回目)&lt;/p&gt;
&lt;p&gt;しかしこの画像の小ささ何とかならんかな。&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/6357729433344490388/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/6357729433344490388' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/6357729433344490388'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/6357729433344490388'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2012/04/blog-post.html' title='パトリシアトライのサイズ比較'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjiUG-bbHGCSQCTu8l6teKNSO8dmdPZK2Q-rhYXPzmPRFXt4puWZK9a6VMLV4QzHQgfQ8N5Q-a2iYK0vw-vUfujEgjA7xaiN53-nbFO7aS1e9l8Ht5l1xNCzXniB-e4eNXmoWHM/s72-c/hist1.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-4973838445674941110</id><published>2012-04-13T03:11:00.002+09:00</published><updated>2012-04-13T03:11:25.065+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="trie"/><title type='text'>Trie4J - Java Patricia Trie Implementation</title><content type='html'>ちょこちょことまとめているパトリシアトライのJava実装ですが、現状のソースをGitHubにあげました。
&lt;p&gt;&lt;/p&gt;
&lt;a href=&quot;https://github.com/takawitter/trie4j&quot;&gt;https://github.com/takawitter/trie4j&lt;/a&gt;
&lt;p&gt;&lt;/p&gt;
記事では簡単のためラベルをString, 子ノードをList&amp;lt;String&amp;gt;で管理しているものを紹介していますが、この実装では
&lt;ul&gt;
&lt;li&gt;ラベルをchar[]、子ノードをNode[]で管理する、org.trie4j.patricia.simple.PatriciaTrie&lt;/li&gt;
&lt;li&gt;ノードの状態(終端かどうか、子ノードを持つかどうかなど)に応じてクラスを変更し、かつ多層トライによるサイズ圧縮に対応する、org.trie4j.patricia.multilayer.MultilayerPatriciaTrie&lt;/li&gt;
&lt;/ul&gt;
の2つが含まれています(もう一つありますが、あまり効果無いのでそのうち消します)。
詳しい解説はまた後ほど(predictive search書けてから)。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/4973838445674941110/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/4973838445674941110' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/4973838445674941110'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/4973838445674941110'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2012/04/trie4j-java-patricia-trie.html' title='Trie4J - Java Patricia Trie Implementation'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-4734433917276241940</id><published>2012-04-13T02:30:00.001+09:00</published><updated>2012-05-17T08:29:30.483+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="trie"/><title type='text'>JavaでPatriciaTrieで各種検索</title><content type='html'>&lt;a href=&quot;http://takao.blogspot.jp/2012/03/patriciatrie.html&quot;&gt;前回&lt;/a&gt;からの続きで、今回は検索メソッドを実装してみます。実装するのはこの2つ。
&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;common prefix search&lt;/li&gt;
&lt;li&gt;predictive search&lt;/li&gt;
&lt;/ul&gt;
common prefix searchは、クエリ&quot;東京国際フォーラム&quot;が与えられた時に、&quot;東&quot;、&quot;東京&quot;、&quot;東京国&quot;、&quot;東京国際フォーラム&quot;をトライから列挙するという検索です(実際これらの単語はWikipedia日本語版にあります)。

&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhncucfrGEVznKASn_UXmGXu5ofXVo6VS4CDoFBhBJXPKQlDcz3sDsrK1-rdPOtL9vg3BzfI1uJkk2XOyINSPGCwLadECJ_HWGmSG4ULDpwlVjylwspayWuWYXgqEuHL9xN4QNy/s1600/tokyoforum.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;139&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhncucfrGEVznKASn_UXmGXu5ofXVo6VS4CDoFBhBJXPKQlDcz3sDsrK1-rdPOtL9vg3BzfI1uJkk2XOyINSPGCwLadECJ_HWGmSG4ULDpwlVjylwspayWuWYXgqEuHL9xN4QNy/s320/tokyoforum.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
上図で赤枠で示したノードをたどりながら、終端ノードを列挙していく処理になります。先頭からノードをたどり、ノードのラベルとクエリを比較し、ノードのラベルを比較し終えたら、終端ノードであれば列挙、そして子ノードから残りのクエリの比較を行う候補を探し、そのノードに対しても同じ処理をしていきます。一方向に辿っていくだけなので再帰は必要なく、単純なループで実現できます。コードを下記に示します。

&lt;br /&gt;
&lt;pre class=&quot;brush: java&quot;&gt;public Iterable&amp;lt;String&amp;gt; commonPrefixSearch(String query) {
 List&amp;lt;String&amp;gt; ret = new ArrayList&amp;lt;String&amp;gt;(); // 結果配列
 int cur = 0; // query内の照合位置
 Node node = root;
 while(node != null){
  String letters = node.getLetters();
  if(letters.length() &amp;gt; (query.length() - cur)) return ret; // 残りの文字列よりノードのラベルのほうが長ければマッチしないので終了
  for(int i = 0; i &amp;lt; letters.length(); i++){
   if(letters.charAt(i) != query.charAt(cur + i)) return ret;
  }
  if(node.isTerminated()){
   ret.add(query.substring(0 , cur + letters.length()));
  }
  cur += letters.length();
  if(query.length() == cur) return ret;
  node = node.getChild(query.charAt(cur));
 }
 return ret;
}
&lt;/pre&gt;
単純なwhileループで、着目するノード(変数node)を書き換えながら処理を行なっています。
&lt;br /&gt;
predictive searchは、指定された文字列(prefix)を前方一致検索します。例えば&quot;東京国&quot;が指定されれば、&quot;東京国&quot;、&quot;東京国税局&quot;、&quot;東京国際フォーラム&quot;、&quot;東京国際マラソン&quot;が列挙されます。

&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi255MLIttY77cLpoKKUY65xokqutarM33Zkg5dnajGu-ZV-Tc9SPY1Lz-8E5CB13YVtvTUdWAzd4dAV41Xvg5vDfyU3qnA-DUKv6XCZLYYRT2PvOBNmAE9xLXCZjvz3t7BJLIk/s1600/tokyoforum2.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;142&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi255MLIttY77cLpoKKUY65xokqutarM33Zkg5dnajGu-ZV-Tc9SPY1Lz-8E5CB13YVtvTUdWAzd4dAV41Xvg5vDfyU3qnA-DUKv6XCZLYYRT2PvOBNmAE9xLXCZjvz3t7BJLIk/s320/tokyoforum2.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
上図赤枠のノードが列挙対象になります。まず指定されたprefixを含むノードを探し、見つかったノード以降のノードを全て列挙します。この列挙は部分木全部が対象になるので、再帰で実装するのが楽です(再帰が嫌な場合はQueue使ってもOK)。コードはこんな感じ。

&lt;br /&gt;
&lt;pre class=&quot;brush: java&quot;&gt;/**
 * 部分木の列挙をする再帰関数。
 */
private static void enumLetters(Node node, String prefix, List&amp;lt;String&amp;gt; letters){
 List&amp;lt;node&amp;gt; children = node.getChildren();
 if(children == null) return;
 for(Node child : children){
  String text = prefix + child.getLetters();
  if(child.isTerminated()) letters.add(text);
  enumLetters(child, text, letters);
 }
}

public Iterable&amp;lt;String&amp;gt; predictiveSearch(String prefix) {
 char[] prefixChars = prefix.toCharArray();
 int cur = 0; // prefixChars内の照合位置
 Node node = root;
 while(node != null){
  String letters = node.getLetters();
  int n = Math.min(letters.length(), prefixChars.length - cur); // 共通部分の比較のためnは短い方に合わせる。
  for(int i = 0; i &amp;lt; n; i++){
   if(letters.charAt(i) != prefixChars[cur + i]){
    return Collections.emptyList();
   }
  }
  cur += n;
  if(prefixChars.length == cur){ // prefixCharsの比較が終われば、あとは部分木の列挙
   List&amp;lt;String&amp;gt; ret = new ArrayList&amp;lt;String&amp;gt;();
   prefix += letters.substring(n);
   if(node.isTerminated()) ret.add(prefix);
   enumLetters(node, prefix, ret);
   return ret;
  }
  // 比較がまだ終わって無ければ、子ノードを探して検索を継続
  node = node.getChild(prefixChars[cur]);
 }
 return Collections.emptyList();
}
&lt;/string&gt;&lt;/node&gt;&lt;/string&gt;&lt;/pre&gt;</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/4734433917276241940/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/4734433917276241940' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/4734433917276241940'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/4734433917276241940'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2012/04/javapatriciatrie.html' title='JavaでPatriciaTrieで各種検索'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhncucfrGEVznKASn_UXmGXu5ofXVo6VS4CDoFBhBJXPKQlDcz3sDsrK1-rdPOtL9vg3BzfI1uJkk2XOyINSPGCwLadECJ_HWGmSG4ULDpwlVjylwspayWuWYXgqEuHL9xN4QNy/s72-c/tokyoforum.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-1875162271592801730</id><published>2012-03-15T02:06:00.003+09:00</published><updated>2012-04-13T01:39:08.363+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="trie"/><title type='text'>JavaでPatriciaTrieを実装してみた</title><content type='html'>書籍「&lt;a href=&quot;http://www.amazon.co.jp/dp/4774149934&quot;&gt;日本語入力を支える技術&lt;/a&gt;」を読んでテンション上がったので、Javaでパトリシアトライを実装してみました(本当はビットベクタを実装したかったけどとりあえずダブル配列からやろうと思ってそれでも挫折してPatriciaTrieから入門した次第)。また、このエントリで紹介するコードはパトリシアトライの説明用で、全然最適化が行われていないので、メモリ効率も速度も良くないです。そのあたりを最適化したコードは、別のエントリで紹介する予定です。&lt;br /&gt;
&lt;div align=&quot;right&quot;&gt;
(↓※アソシエイトIDあり)&lt;br /&gt;
&lt;iframe align=&quot;right&quot; frameborder=&quot;0&quot; marginheight=&quot;0&quot; marginwidth=&quot;0&quot; scrolling=&quot;no&quot; src=&quot;http://rcm-jp.amazon.co.jp/e/cm?lt1=_blank&amp;amp;bc1=000000&amp;amp;IS2=1&amp;amp;bg1=FFFFFF&amp;amp;fc1=000000&amp;amp;lc1=0000FF&amp;amp;t=takaoblogspot-22&amp;amp;o=9&amp;amp;p=8&amp;amp;l=as4&amp;amp;m=amazon&amp;amp;f=ifr&amp;amp;ref=ss_til&amp;amp;asins=4774149934&quot; style=&quot;height: 240px; width: 120px;&quot;&gt;&lt;/iframe&gt;&lt;/div&gt;
ちなみにPatriciaはPractical Algorithm to Retrieve Information Coded in Alphanumericの略らしい(参考: wikipedia:&lt;a href=&quot;http://ja.wikipedia.org/wiki/%E5%9F%BA%E6%95%B0%E6%9C%A8&quot;&gt;基数木&lt;/a&gt;)。構造は至ってシンプルで、いろいろと実装例があります。&lt;br /&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://www.geocities.jp/m_hiroi/light/pyalgo09.html&quot;&gt;Algorithms with Python: トライ (trie) とパトリシア (patricia tree)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://code.google.com/p/patricia-trie/&quot;&gt;PATRICIA in Java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://blog.livedoor.jp/dankogai/archives/51766842.html&quot;&gt;404 Blog Not Found: Patricia Trie (Radix Trie) を JavaScript で&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;a href=&quot;http://ja.wikipedia.org/wiki/%E5%9F%BA%E6%95%B0%E6%9C%A8&quot;&gt;wikipediaのパトリシアトライの説明&lt;/a&gt;から図を借りると、その構造はこんな感じ:&lt;br /&gt;
&lt;img src=&quot;http://upload.wikimedia.org/wikipedia/commons/thumb/a/ae/Patricia_trie.svg/320px-Patricia_trie.svg.png&quot; /&gt;&lt;br /&gt;
ちょっとわかりにくいので、形を変えてみると:&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWPz2zWFz2c22CmFTeGdDSRk_xuaO8azD2cUxXj0PiUnKHYGe73HhUtgmxwFk9Vc6_ERSTKVE-zx8bCfEUtlQKSmwz_BPhwC3rbjGb20b72xrn9rAvCQ_rY9n4QWOp6IHalca2/s1600/patriciatrie.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;249&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWPz2zWFz2c22CmFTeGdDSRk_xuaO8azD2cUxXj0PiUnKHYGe73HhUtgmxwFk9Vc6_ERSTKVE-zx8bCfEUtlQKSmwz_BPhwC3rbjGb20b72xrn9rAvCQ_rY9n4QWOp6IHalca2/s320/patriciatrie.png&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
空間効率的には、上図右側の各文字列の灰色で示した部分が他の文字列と共有されるので、かなりデータ量は小さくて済みます(wikipedia日本語タイトルデータで50%程度の効率化)。また、検索効率は、検索したい文字列を先頭から順に探して行くだけなので、最悪O(k)で済みます(kは文字列長)。実際は子ノードの探索方法によっては検索速度に影響が出るけど、それはまた後で。&lt;br /&gt;
&lt;br /&gt;
構造がわかったところで、それを表現するクラスを定義します。&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;brush: java&quot;&gt;public class Node{
 // コンストラクタ、getter、setterは割愛
 private String letters;
 private List&amp;lt;node&amp;gt; children;
 private boolean terminated;
}&lt;/node&gt;&lt;/pre&gt;
&lt;br /&gt;
各ノードで保持する文字列は変数lettersに、子ノードは変数childrenに格納します。加えて、そのノードで文字列が終わっているかどうかを保持する変数terminatedを設けます。これは、例えば上図の文字列に加え、&quot;rom&quot;や&quot;rubic&quot;が加わった場合、ツリーの途中で文字列が終わることになるので、それに対応するため。上図に、文字列の終端という概念を加えて、あとツリーの途中で文字列が終わるケースを示すために&quot;rom&quot;と&quot;rubic&quot;も挿入されたとして、終端のノード(terminated==true)を太枠で表すと、下図のようになります。&lt;br /&gt;
&lt;br /&gt;
&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;
&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqYBohcEWq08vicXeW1fKRroMGNG0fZE0OgIzv1w79l_5Zy1yqL5o892INZahhWEzKo65DKNJOljmFDlZmH0z1zV-XudhpyxkHb6XO5yNUc0r1PSG6S5ipKb_aCHEHO573IVie/s1600/patriciatrie2.png&quot; imageanchor=&quot;1&quot;&gt;&lt;img border=&quot;0&quot; height=&quot;200&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiqYBohcEWq08vicXeW1fKRroMGNG0fZE0OgIzv1w79l_5Zy1yqL5o892INZahhWEzKo65DKNJOljmFDlZmH0z1zV-XudhpyxkHb6XO5yNUc0r1PSG6S5ipKb_aCHEHO573IVie/s200/patriciatrie2.png&quot; width=&quot;162&quot; /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;br /&gt;
終端については、終端文字列を設けて(&#39;$&#39;とか)、それを終端となるノードにぶら下げるのが一般的のようなんですが、コードをシンプルにしたかったので、上記実装にしました(実際はいくつか工夫の余地はあり、今できるだけメモリを使わない方法をトライ中なんですが、それは完成した後に詳しく書く予定)。&lt;br /&gt;
&lt;br /&gt;
さて、まずはある文字列がトライに含まれるかどうかを判定する、containsメソッドを実装してみます。考え方としては、&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;自ノードに文字列が設定されている場合、検索文字列との比較を行う。&lt;/li&gt;
&lt;li&gt;自ノードの文字列だけでは比較が終わらない場合、子ノードを探す。&lt;/li&gt;
&lt;li&gt;子ノードが見つかった場合、子ノードに対して同じ操作を行う&lt;/li&gt;
&lt;/ol&gt;
という感じで、検索対象文字列の先頭から、ノード内の文字列との比較を行い、どんどん子ノードに降りて行きます。コードは下記。&lt;br /&gt;
&lt;br /&gt;
&lt;pre class=&quot;brush: java&quot;&gt;boolean contains(String word){
 int rest = word.length();
 int n = letters.length();
 if(n &amp;gt; rest) return false; // 自ノードの文字列のほうが長ければ比較失敗。
 for(int i = 0; i &amp;lt; n; i++){
  if(letters.charAt(i) != word.charAt(i)) return false;
 }
 if(n == rest) return terminated; // 文字列が同じであれば、終端かどうかを返す。
 word = word.substring(n); // 比較済み文字列を捨てる。
 if(children != null){
  char c = word.charAt(0); // 子ノードを探す。(*1)
  for(Node n : children){
   if(n.letters.charAt(0) == c){
    return n.contains(word); // 見つかれば、containsを呼び出す。
   }
  }
 }
 return false;
}&lt;/pre&gt;
自ノードの文字列(letters)、子ノード(children)の順で処理を行なっています。子ノードの検索(*1)は線形探索していて、当然遅いので、実用で使うには、頻度順に並べるとか、childrenはソートしておいて2分探索を使うとか、ハッシュを使うとかする必要がありますが、今は割愛。データ量が膨大になる場合は、各ノードでハッシュを持つとメモリ消費が問題になる可能性があるので、2分探索か頻度順が現実的かと思います(そのぶん構築コストは高いけど)。実際wikipediaの日本語タイトルを挿入してみると、線形探索と2分探索では、処理にかかる時間の桁が違いました(50万件の検索で線形探索1762ms, 2分探索201ms)。&lt;br /&gt;
&lt;br /&gt;
ちなみに2分探索だと、コードは下記のようになります。&lt;br /&gt;
&lt;pre class=&quot;brush: java&quot;&gt;boolean contains(String word){
 int rest = word.length();
 int n = letters.length();
 if(n &gt; rest) return false; // 自ノードの文字列のほうが長ければ比較失敗。
 for(int i = 0; i &lt; n; i++){
  if(letters.charAt(i) != word.charAt(i)) return false;
 }
 if(n == rest) return terminated; // 文字列が同じであれば、終端かどうかを返す。
 word = word.substring(n); // 比較済み文字列を捨てる。
 if(children != null){
  char c = word.charAt(0); // 2分探索で子ノードを探す
  int end = children.size();
  int start = 0;
  while(start &lt; end){
   int i = (start + end) / 2;
   Node child = children.get(i);
   int d = c - child.getLetters().charAt(0);
   if(d == 0){
    return child.contains(word);
   }
   if(d &lt; 0){
    end = i;
   } else if(start == i){
    break;
   } else{
    start = i;
   }
  }
 }
 return false;
}&lt;/pre&gt;
&lt;br /&gt;
次に要素の挿入です。おおまかに以下の流れで実装しました。
&lt;ol&gt;
&lt;li&gt;ノード内の文字列(thisLetters)か比較対象の文字列(letters)の短い方の文字数分比較&lt;/li&gt;
&lt;li&gt;全て比較成功&lt;ol&gt;
 &lt;li&gt;thisLettersとlettersの長さが同じであれば、ノードを終端にして(terminated = true)終了(a)&lt;/li&gt;
 &lt;li&gt;thisLettersの方が長ければ、ノードを分割して終了(b)&lt;/li&gt;
 &lt;li&gt;lettersの方が長ければ、比較済み文字列を捨てる&lt;/li&gt;
 &lt;li&gt;新しいlettersの先頭文字とマッチする先頭文字を持つ子ノードを探す&lt;/li&gt;
 &lt;li&gt;存在すれば、その子ノードに対してinsertChildを呼び出して終了(c)&lt;/li&gt;
 &lt;li&gt;存在しなければ、新しい子ノードを作成して追加し終了(d)&lt;/li&gt;
&lt;/ol&gt;&lt;/li&gt;
&lt;li&gt;途中で比較が止まった場合、その位置でノードを分割し、lettersの残りも子ノードとして追加する。(e)&lt;/li&gt;
&lt;/ol&gt;
コードは下記のようになります。
&lt;pre class=&quot;brush: java&quot;&gt;public void insertChild(String letters){
   int n = Math.min(letters.length(), this.letters.length());
   int i = 0;
   while(i &amp;lt; n &amp;&amp; (letters.charAt(i) == this.letters.charAt(i))) i++;
   if(i == n){
    if(letters.length() == this.letters.length()){
     terminated = true; // (a)
     return;
    }
    if(letters.length() &amp;lt; this.letters.length()){
     Node node = new Node( // (b)
       this.letters.substring(letters.length())
       , this.children, this.terminated);
     this.letters = letters;
     this.children = new ArrayList&amp;lt;Node&amp;gt;(1);
     this.children.add(node);
     ((ArrayList&amp;lt;Node&amp;gt;)this.children).trimToSize();
     this.terminated = true;
     return;
    }
    letters = letters.substring(i);
    int index = 0;
    for(Node child : children){
     int c = letters.charAt(0) - child.letters.charAt(0);
     if(c &lt; 0) break;
     if(c == 0){
      child.insertChild(letters); // (c)
      return;
     }
     index++;
    }
    this.children.add(index, new Node(letters)); // (d)
    ((ArrayList&amp;lt;Node&amp;gt;)this.children).trimToSize();
    return;
   }
   String newLetter1 = this.letters.substring(0, i); // (e)
   String newLetter2 = this.letters.substring(i);
   String newLetter3 = letters.substring(i);
   List&amp;lt;Node&amp;gt; newChildren = new ArrayList&amp;lt;Node&amp;gt;(2);
   if(newLetter2.charAt(0) &amp;lt; newLetter3.charAt(0)){
    newChildren.add(new Node(newLetter2, this.children, this.terminated));
    newChildren.add(new Node(newLetter3));
   } else{
    newChildren.add(new Node(newLetter3));
    newChildren.add(new Node(newLetter2, this.children, this.terminated));
   }
   this.letters = newLetter1;
   this.terminated = false;
   this.children = newChildren;
  }&lt;/pre&gt;
&lt;br /&gt;
なお、子ノードは全て昇順でソートされるようにして、探索は線型探索を使っています(ここも2分探索にすれば高速化できる)。また、メモリ利用量を抑えるため、子ノードの配列は正味のサイズだけ確保するようにしています(その分メモリリアロケーションが増えて速度は落ちているはず)。&lt;br /&gt;
&lt;br /&gt;
ちなみに、上記のコードでは、VMの最大メモリ容量が128MBだと、Wikipedia日本語タイトル(127万件)はメモリに載りません(180MB程度必要です。ちなみにlettersをchar[]に、childrenをArrayListではなくNode[]に変えれば載ります)。ですが、まぁ一応機能はするので、次はTrieの特徴であるcommon prefix searchを実装してみます。
&lt;br /&gt;&lt;br /&gt;</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/1875162271592801730/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/1875162271592801730' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/1875162271592801730'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/1875162271592801730'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2012/03/patriciatrie.html' title='JavaでPatriciaTrieを実装してみた'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgWPz2zWFz2c22CmFTeGdDSRk_xuaO8azD2cUxXj0PiUnKHYGe73HhUtgmxwFk9Vc6_ERSTKVE-zx8bCfEUtlQKSmwz_BPhwC3rbjGb20b72xrn9rAvCQ_rY9n4QWOp6IHalca2/s72-c/patriciatrie.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-3850113944984304598</id><published>2011-02-02T01:01:00.034+09:00</published><updated>2011-12-22T22:59:07.106+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine"/><category scheme="http://www.blogger.com/atom/ns#" term="JSON"/><category scheme="http://www.blogger.com/atom/ns#" term="slim3"/><title type='text'>Slim3 JSON機能の説明(非公式)</title><content type='html'>&lt;i&gt;(2/7更新)&lt;/i&gt;&lt;br /&gt;
&lt;i&gt;(2/26更新)&lt;/i&gt;&lt;br /&gt;
&lt;i&gt;(12/22更新 @Jsonはフィールド直接指定になりました)&lt;/i&gt;&lt;br /&gt;
&lt;br /&gt;
Slim3 JSON機能のドキュメントを書き始めました。まだ非公式なものですが、ひと通り書き終わったら公式へのマージを提案する予定です。でも、公式は英語なんですよね。どうしたものか。まぁ、いきなり下手な英語で書くより、まずは日本語でちゃんと書いたほうが良いはず。&lt;br /&gt;
&lt;br /&gt;
というわけで、下記ドキュメントの草案です。若干改行が変ですが、evernoteに書きなぐってexportしたものを貼っつけてるためだと思います。これにあとJSON入出力のカスタマイズ方法を書いて一段落とする予定です。(書きました!)&lt;br /&gt;
&lt;br /&gt;
&lt;div&gt;&lt;font size=&quot;5&quot;&gt;&lt;span style=&quot;font-size: 18px;&quot;&gt;概要&lt;/span&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;　Slim3のJSON機能は、モデル(@org.slim3.datastore.Modelアノテーションが付加されたクラス)のJSON変換機能を提供します。JSON変換は、org.slim3.datastore.ModelMeta&amp;lt;M&amp;gt;に定義される以下のメソッドを呼び出すことにより行われます。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;モデルのJSON文字列への変換:&lt;/div&gt;&lt;ul&gt;&lt;li&gt;String modelToJson(Object model);&lt;/li&gt;
&lt;li&gt;String modelToJson(Object model, int maxDepth);&lt;/li&gt;
&lt;li&gt;String modelsToJson(Object[] models);&lt;/li&gt;
&lt;li&gt;String modelsToJson(Object[] models, int maxDepth);&lt;/li&gt;
&lt;/ul&gt;JSON文字列のモデルへの変換:&lt;br /&gt;
&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;M jsonToModel(String json);&lt;/li&gt;
&lt;li&gt;M jsonToModel(String json, int maxDepth);&lt;/li&gt;
&lt;li&gt;M[] jsonToModels(String json);&lt;/li&gt;
&lt;li&gt;M[] jsonToModels(String json, int maxDepth);&lt;/li&gt;
&lt;/ul&gt;&lt;div&gt;　例えばTestModelというモデルに対してJSON変換機能を使用するコードは、次のようになります。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;brush: java&quot;&gt;TestModel m = new TestModel();
m.setValue(1);
String json = TestModelMeta.get().modelToJson(m);
System.out.println(json); // {&amp;quot;value&amp;quot;: 1}
TestModel m2 = TestModelMeta.get().jsonToModel(json);
Assert.assertEquals(m.getValue(), m2.getValue());
&lt;/pre&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;font size=&quot;5&quot;&gt;&lt;span style=&quot;font-size: 18px;&quot;&gt;JSON変換の制御&lt;/span&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;　フィールドにJSONアノテーション(@org.slim3.datastore.json.JSON)を指定することで、JSON変換の振る舞いを変更できます。以下にJSONアノテーションで指定できるパラメータを示します。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;table border=&quot;1&quot; width=&quot;100%&quot; cellspacing=&quot;0&quot; cellpadding=&quot;2&quot;&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;パラメータ名&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;型&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;デフォルト&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;説明&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;ignore&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;boolean&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;false&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;このアトリビュートを無視します。JSON出力に含まれず、JSON入力時にも読み込まれません。&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;ignoreNull&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;boolean&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;true&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;アトリビュートがnullの場合、何も出力しません。falseの場合は、&amp;quot;null&amp;quot;が出力されます。&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;alias&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;String&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;空文字列&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;アトリビュートのJSON文字列内での名前を指定します。&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;coder&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;Class&amp;lt;? extends JsonCoder&amp;gt;&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;Default.class&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;JSON入出力を行うJsonCoderのクラスを指定します。ここに独自のクラスを指定することで、JSON入出力のカスタマイズを行うことができます(後述)。&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;　例えばプロパティを無視したり、エイリアスを設定したりする場合、以下のように記述します。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;brush: java&quot;&gt;@Model
class TestModel{
  ...
  @Json(ignore=true)
  private String ignoredValue;
  @Json(alise=&amp;quot;foo&amp;quot;)
  private String bar;
}&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;font size=&quot;5&quot;&gt;&lt;span style=&quot;font-size: 18px;&quot;&gt;対応する値型&lt;/span&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;　Slim3のJSON変換機能は、以下の値型に対応します。また、後述するカスタマイズを行えば、これ以外の型に対応したり、入出力方法を変更する事ができます。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div style=&quot;overflow:scroll&quot;&gt;&lt;table border=&quot;1&quot; width=&quot;100%&quot; cellspacing=&quot;0&quot; cellpadding=&quot;2&quot;&gt;&lt;tbody&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;Javaクラス&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;JSON表現&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;例&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;java.lang.String, &lt;span style=&quot;border-collapse: separate; color: rgb(0, 0, 0); font-family: HiraKakuPro-W3; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: 2; text-align: -webkit-auto; text-indent: 0px; text-transform: none; white-space: normal; widows: 2; word-spacing: 0px; -webkit-border-horizontal-spacing: 0px; -webkit-border-vertical-spacing: 0px; -webkit-text-decorations-in-effect: none; -webkit-text-size-adjust: auto; -webkit-text-stroke-width: 0px; font-size: medium;&quot;&gt;&lt;span style=&quot;font-family: Arial, Verdana, sans-serif; font-size: 13px;&quot;&gt;com.google.appengine.api.datastore.Text&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&lt;span style=&quot;font-family: Arial, Verdana, sans-serif;&quot;&gt;文字列。cipher=trueの場合、JSON内では暗号化されます。&lt;/span&gt;&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;text&amp;quot;:&amp;quot;hello&amp;quot;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;byte[], com.google.appengine.api.datastore.ShortBlob, com.google.appengine.api.datastore.Blob&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;バイト列のBase64文字列。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;blob&amp;quot;:&amp;quot;mMB4qZAgtBKJq0d1LBGTCA==&amp;quot;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;boolean, java.lang.Boolean&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;ブール値(true or false)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;value&amp;quot;:true&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;short, java.lang.Short, int, java.lang.Integer, long, java.lang.Long&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;整数。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;value&amp;quot;:100&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;float, java.lang.Float, double, java.lang.Double&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;小数。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;value&amp;quot;:1.0&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;java.util.Date&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;整数(Date.getTime()が返す値)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;value&amp;quot;:10233400&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;java.lang.Enum&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;文字列(Enum.name()が返す値)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;value&amp;quot;:&amp;quot;MONDAY&amp;quot;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;com.google.appengine.api.users.User&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;ネストしたJSON文字列(User.getEmail()等の値をJSONエンコード)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;user&amp;quot;:{&amp;quot;authDomain&amp;quot;:&amp;quot;authDomain&amp;quot;,&amp;quot;email&amp;quot;:&amp;quot;user@test.com&amp;quot;}&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;com.google.appengine.api.datastore.Key&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;文字列(KeyFactory.keyToString(Key)の実行結果)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;key&amp;quot;:&amp;quot;aglzbGltMy1nZW5yCwsSBHRlc3QY6AcM&amp;quot;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;com.google.appengine.api.datastore.Category&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;文字列(Category.getCategory()の値)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;category&amp;quot;:&amp;quot;partOfSpeech&amp;quot;&lt;br /&gt;
&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;com.google.appengine.api.datastore.Email&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;文字列(Email.getEmail()の値)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;mail&amp;quot;:&amp;quot;user@domain.tld&amp;quot;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;com.google.appengine.api.datastore.GeoPt&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;ネストしたJSON文字列(GeoPt.getLatitude()及びGeoPt.getLongitude()の値をJSONエンコード)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;geopt&amp;quot;:{&amp;quot;latitude&amp;quot;:10.0,&amp;quot;longitude&amp;quot;:10.0}&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;com.google.appengine.api.datastore.IMHandle&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;ネストしたJSON文字列(IMHandle.getAddress()及びIMHandle.getProtocol()の値をJSONエンコード)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;handle&amp;quot;:{&amp;quot;address&amp;quot;:&amp;quot;handle&amp;quot;,&amp;quot;protocol&amp;quot;:&amp;quot;xmpp&amp;quot;}&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;com.google.appengine.api.datastore.Link&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;文字列(Link.getValue()の値)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;link&amp;quot;:&amp;quot;linkValue&amp;quot;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;com.google.appengine.api.datastore.PhoneNumber&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;文字列(PhoneNumber.getNumber()の値)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;phone&amp;quot;:&amp;quot;000-000-000&amp;quot;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;com.google.appengine.api.datastore.PostalAddress&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;文字列(PostalAddress.getAddress()の値)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;address&amp;quot;:&amp;quot;Tokyo, Japan&amp;quot;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;com.google.appengine.api.datastore.Rating&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;整数(Rating.getRating()の値)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;rating&amp;quot;:100&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;com.google.appengine.api.blobstore.BlobKey&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;文字列(BlobKey.getKeyString()の値)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;blobkey&amp;quot;:&amp;quot;Q3PqkweYlb4iWpp0BVw&amp;quot;&lt;/td&gt; &lt;/tr&gt;
&lt;tr&gt; &lt;td valign=&quot;top&quot;&gt;org.slim3.datastore.ModelRef&amp;lt;M&amp;gt;&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;文字列(モデルのkeyに対してKeyFactory.keyToString(key)した結果)。&lt;br /&gt;
&lt;/td&gt; &lt;td valign=&quot;top&quot;&gt;&amp;quot;ref&amp;quot;:{&amp;quot;key&amp;quot;:&amp;quot;lskfo2ijalefkwejfwlke&amp;quot;,value:100}&lt;/td&gt; &lt;/tr&gt;
&lt;/tbody&gt; &lt;/table&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;　また上記サポートする型のコレクションにも対応しています。対応するコレクションクラスはjava.util.List, java.util.Set, java.util.SortedSetで、JSON文字列からモデルを作成する際は、それぞれjava.util.ArrayList、java.util.HashSet、java.util.TreeSetをnewして要素を追加したものがセットされます。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;font size=&quot;5&quot;&gt;&lt;span style=&quot;font-size: 18px;&quot;&gt;ModelRef&amp;lt;M&amp;gt;の展開&lt;/span&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;　デフォルトではJSONアノテーションのcoderパラメータにorg.slim3.datastore.json.Defaultが指定されたものとして扱われ、このクラスは、ModelRefを参照先のモデルのキーに変換します。参照先のモデルの内容を展開したい場合、org.slim3.datastore.json.Expandedを指定して下さい。このクラスは、参照先のモデルの内容を展開し、そのモデルがさらにModelRefを持っている場合も展開します。展開のネストの深さは、modelToJsonメソッドやjsonToModelメソッドの、maxDepth引数で制限できます。&lt;/div&gt;&lt;div&gt;　以下にコード例を示します。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;brush: java&quot;&gt;@Model
class TestModel{
  ...
  @Json(coder=Expanded.class)
  private ModelRef&amp;lt;TestModel&amp;gt; ref = new ModelRef&amp;lt;TestModel&amp;gt;(TestModel.class);
}
...
TestModel m = new TestModel();
TestModelMeta.get().modelToJson(m, 3);  // 3階層まで展開
&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;font size=&quot;5&quot;&gt;&lt;span style=&quot;font-size: 18px;&quot;&gt;JSON入出力のカスタマイズ&lt;/span&gt;&lt;/font&gt;&lt;/div&gt;&lt;div&gt;　JSONアノテーションのcoderパラメータにJSON入出力を行うクラスを指定することにより、JSON変換の振る舞いを変更することができます。デフォルトではorg.slim3.datastore.json.Defaultが指定されており、slim3が対応するデータ型のJSON入出力が行われます。slim3ではもう一つ、org.slim3.datastore.json.Expandedが用意されていて、これをModelRef&amp;lt;M&amp;gt;型のフィールドに対して使用すると、ModelRef&amp;lt;M&amp;gt;が参照しているオブジェクトも展開します。&lt;/div&gt;&lt;div&gt;　slim3が対応していないデータ型を使う場合や入出力方法を変更したい場合は、org.slim3.datastore.json.Defaultから継承したクラスを作成し、encode/decodeメソッドをオーバーライドして下さい。&lt;/div&gt;&lt;div&gt;　下記にjava.awt.Pointの入出力をサポートするクラスの例を示します。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;brush: java&quot;&gt;class CustomCoder extends org.slim3.datastore.json.Default{
  public void encode(JsonWriter writer, Object value){
    if(value instanceof java.awt.Point){
      java.awt.Point pt = (java.awt.Point)value;
      writer.beginObject();
      writer.writeValueProperty(&amp;quot;x&amp;quot;, pt.x);
      writer.writeValueProperty(&amp;quot;y&amp;quot;, pt.y);
      writer.endObject();
    } else{
      super.encode(writer, value);
  }
  public &amp;lt;T&amp;gt; T decode(JsonReader reader, T defaultValue, Class&amp;lt;T&amp;gt; clazz){
    if(java.awt.Point.isAssignableFrom(clazz)){
      try{
        int x = Integer.parseInt(reader.readProperty(&amp;quot;x&amp;quot;);
        int y = Integer.parseInt(reader.readProperty(&amp;quot;y&amp;quot;);
        return clazz.cast(new Point(x, y));
      } catch(NumberFormatException e){
      }
    }
    return defaultValue;
  }
}&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;　java.awt.PointクラスのフィールドをJSON入出力の対象にするには、上記のCustomCoderをcoderパラメータに指定します。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;brush: java&quot;&gt;import java.awt.Point;

@Model
class TestModel{
  public Key getKey(){
    return key;
  }

  public void setKey(Key key){
    this.key = key;
  }

  public Point getPoint(){
    return point;
  }

  public void setPoint(Point point){
    this.point = point;
  }

  @Attribute(primariKey=true)
  private Key key;

  @Attribute(persistent=false)
  @Json(coder=CustomCoder.class)
  private Point point;

}&lt;/pre&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;　上記のように指定すると、java.awt.Point型のpointフィールドのJSON入出力がおこなえるようになります。&lt;/div&gt;&lt;div&gt;&lt;br /&gt;
&lt;/div&gt;&lt;div&gt;&lt;pre class=&quot;brush: java&quot;&gt;TestModel m = new TestModel();
m.setPoint(new Point(10, 20));
String json = TestModelMeta.get().modelToJson(m); // {&amp;quot;point&amp;quot;:{&amp;quot;x&amp;quot;:10,&amp;quot;y&amp;quot;:20}}
TestModel m2 = TestModelMeta.get().jsonToModel(json);
Assert.assertEquals(m.x, m2.x);
Assert.assertEquals(m.y, m2.y);&lt;/td&gt; &lt;/tr&gt;
&lt;/pre&gt;&lt;br /&gt;
&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/3850113944984304598/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/3850113944984304598' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/3850113944984304598'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/3850113944984304598'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2011/02/slim3-json.html' title='Slim3 JSON機能の説明(非公式)'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-7433986854589201978</id><published>2011-01-02T14:45:00.004+09:00</published><updated>2011-01-05T22:45:14.033+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="pos2wit"/><title type='text'>AppEngine Java で絵文字入りメールを送信する</title><content type='html'>Google AppEngineで絵文字メールを送る方法を紹介します。AppEngineでは送信メールのエンコーディングを指定できない( issueへのリンク)ため、絵文字を送ることはできないと思っていたんですが、 &lt;a href=&quot;https://twitter.com/tmurakami&quot;&gt;@tmurakami&lt;/a&gt;さんに、送る方法を教えてもらいました。ただし、今のところこの方法で動作確認が出来ているのは、auのW62T, W63H, W64Kのみです。また、AppEngineの振る舞いに依存しているため、今後も動くかどうかの保証はありません。また、メールをWebメール等に転送している場合、Webメール側(au oneとgmailで確認されています)ではメール全体が(絵文字部分だけでなく)正常に表示されないようです(ヘッダにあるエンコーディング情報とメールボディのエンコーディングが違うので無理も無いですが)。&lt;br /&gt;
&lt;br /&gt;
方法は至ってシンプルで、JISエンコード済みのテキストを無理やりStringに入れるというものです。例えばスマイルマーク(ucs: ☺, au: &lt;img style=&quot;padding:0px;border:0px;margin:0px&quot; width=&quot;8&quot; src=&quot;http://mail.google.com/mail/e/ezweb_ne_jp/336&quot;&gt;)であれば、auでの文字コードは0x7656なので、以下のようにします。&lt;br /&gt;
&lt;pre class=&quot;brush: java&quot;&gt;String text = new String(
   new byte[]{0x1b, &#39;$&#39;, &#39;B&#39;, 0x76, 0x56, 0x1b, &#39;(&#39;, &#39;B&#39;}
   , &quot;ISO8859-1&quot;);
&lt;/pre&gt;0x1b, &#39;$&#39;, &#39;B&#39;はJIS漢字を開始するエスケープシーケンス、0x1b, &#39;(&#39;, &#39;B&#39;はアスキー文字を開始するエスケープシーケンスです。こうやって作った文字列を、メールの本文に設定します。&lt;br /&gt;
&lt;pre class=&quot;brush: java&quot;&gt;MailService.Message m = new MailService.Message();
 m.setTextBody(text);
 // set sender, recipients and subject
 MailServiceFactory.getMailService().send(m);
&lt;/pre&gt;ここでは触れませんが、JavaMailApiでは、ByteArrayDataSourceを使うと送れるそうです。&lt;br /&gt;
&lt;br /&gt;
AppEngineはメールを送信する際、含まれているキャラクタを見て、エンコーディングを決定します。日本語が含まれていればISO2022JPを選択しますし、中国語であればBig5、UTF8になる場合もあります。上記のように制御コードが混じっていると、ISO8859-1として、そのまま送信するようです。そして実機ではエンコーディングを無視してISO2022JPとして表示するため、絵文字が表示できる、という仕組みです。ただしこのAppEngineの振る舞いは規定されているものではないので、今後も動き続ける保証はありません。&lt;br /&gt;
&lt;br /&gt;
&lt;a href=&quot;http://www.pos2wit.com/&quot;&gt;pos2wit&lt;/a&gt;ではこの方法を使って、絵文字の送信機能を実装しました(今のところauのみ)。ダッシュボードから有効にできるので、au携帯でお使いの方は是非試してみてください。また、今後他社の携帯も調査して、可能であれば対応する予定です。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/7433986854589201978/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/7433986854589201978' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/7433986854589201978'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/7433986854589201978'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2011/01/appengine-java.html' title='AppEngine Java で絵文字入りメールを送信する'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-8440407626042518834</id><published>2010-11-20T01:06:00.000+09:00</published><updated>2010-11-20T01:06:23.154+09:00</updated><title type='text'>JSON変換機能、slim3にマージ中</title><content type='html'>以前のエントリで書いたslim3モデルのJSON変換機能ですが、slim3のコミッターになってマージ中です！&lt;br /&gt;
&lt;br /&gt;
ロードマップはこんな感じ。&lt;br /&gt;
&lt;br /&gt;
1st step (完了).&lt;br /&gt;
・modelToJsonメソッドの追加。基礎的な仕組みの実装。&lt;br /&gt;
・short, int, long, float, double及びそれらのラッパークラスのJSON出力実装。&lt;br /&gt;
・StringのJSON出力実装(cipher=trueを考慮)。&lt;br /&gt;
・ラッパークラスとStringのList, Set, SortedSetのJSON出力実装。&lt;br /&gt;
&lt;br /&gt;
2nd step (完了).&lt;br /&gt;
・AppEngine固有の型(Blob, Category, Email, GeoPt, IMHandle, Link,&lt;br /&gt;
PhoneNumber, PostalAddress, Rating, ShortBlob, Text, UserのJSON出力実装。&lt;br /&gt;
・Date, EnumのJSON出力実装。&lt;br /&gt;
・上記の型のList, Set, SortedSetのJSON出力実装。&lt;br /&gt;
&lt;br /&gt;
3rd step以降.&lt;br /&gt;
・リファクタリング&lt;br /&gt;
・ModelRef対応。&lt;br /&gt;
・シリアライズ対応(おそらくシリアライズしてBase64)&lt;br /&gt;
・アノテーションによる制御の検討(フィールド名のエイリアスとか、JSON変換時は無視するとか)&lt;br /&gt;
・jsonToModelメソッド追加(と上記の型の対応)。&lt;br /&gt;
&lt;br /&gt;
実装の途中経過は、適宜&lt;a href=&quot;http://sites.google.com/site/slim3appengine/discussion-groups&quot;&gt;slim3のDiscussion Group&lt;/a&gt;でお知らせしてます。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/8440407626042518834/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/8440407626042518834' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/8440407626042518834'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/8440407626042518834'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2010/11/jsonslim3.html' title='JSON変換機能、slim3にマージ中'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-790178579088980114</id><published>2010-11-09T23:44:00.002+09:00</published><updated>2010-11-10T00:50:58.210+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="slim3"/><title type='text'>slim3を改造して、モデルのJSON変換機能を追加する</title><content type='html'>Google App Engine for Java用のフレームワークと言えばslim3です。シンプルで非常に使いやすいフレームワークですが、その特徴はアノテーションプロセッサとして実装されている、Metaクラスジェネレータ(slim3-gen)にも当てはまります。拡張性が考慮されていて、簡単に機能を拡張することが出来るようになっていて、実際にアノテーションの情報を元にソースを生成するサンプルも公開されています(&lt;a href=&quot;http://code.google.com/p/slim3/source/browse/#svn/trunk&quot;&gt;リポジトリ&lt;/a&gt;の、slim3-gen-extention-sample)。&lt;br /&gt;
&lt;br /&gt;
そのサンプルを参考に、slim3-genが生成するメタクラスにjson変換を行うメソッドを追加する拡張、slim3-gen-jsonicを作ってみたので、紹介します。&lt;br /&gt;
&lt;br /&gt;
&lt;h2&gt;使い方&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;slim3-gen-jsonicのバイナリを、&lt;a href=&quot;http://code.google.com/p/whitedog/downloads/list&quot;&gt;ここ&lt;/a&gt;からダウンロードして下さい(現在最新版はslim3-gen-jsonic-1.0.5.jar)。&lt;br /&gt;
&lt;li&gt;次に、jsonicのバイナリを&lt;a href=&quot;http://jsonic.sourceforge.jp/&quot;&gt;jsonicのサイト&lt;/a&gt;からダウンロードして下さい(現在最新版はjsonic-1.2.4.jar)。&lt;/ol&gt;slim3と使う場合と同様にプロジェクトを作成、セットアップし(&lt;a href=&quot;http://sites.google.com/site/slim3documentja/getting-started&quot;&gt;slim3のスタートガイド&lt;/a&gt;参照)、jsonicのjarをビルドパスに追加、slim3-genのjar(現在最新はslim3-gen-1.0.5.jar)の代わりにslim3-gen-jsonicのjarを使用すると、モデルごとに生成されるメタクラスに以下のメソッドが追加されます。 &lt;ul&gt;&lt;li&gt;public String modelToJson(Object model)&lt;br /&gt;
&lt;li&gt;public ModelClass jsonToModel(String json)&lt;/ul&gt;ModelClassは、各モデルのクラスに置き換わります。 &lt;p&gt;modelToJsonでモデルをJSON文字列に変換し、jsonToModelでその逆、JSON文字列をモデルに変換します。jsonToModelは、内部で&lt;a href=&quot;http://jsonic.sourceforge.jp/&quot;&gt;JSONIC&lt;/a&gt;を使っています。使い方は簡単、Metaクラスからこれらのメソッドを呼び出すだけです。  &lt;pre class=&quot;brush: java&quot;&gt;public void test(){
  TestModelMeta m = TestModelMeta.get();
  TestModel model = new TestModel();
  String json = m.modelToJson(model); // model -&gt; JSON
  TestModel model2 = m.jsonToModel(json); // JSON -&gt; model
}
&lt;/pre&gt;&lt;h2&gt;対応している型&lt;/h2&gt;現在、以下の型に対応しています。 &lt;ul&gt;&lt;li&gt;String(cipher=trueの場合、JSON出力時に暗号化されます), List&amp;lt;String&gt;, Set&amp;lt;String&gt;, SortedSet&amp;lt;String&gt;&lt;br /&gt;
&lt;li&gt;short, Short, List&amp;lt;Short&gt;, Set&amp;lt;Short&gt;, SortedSet&amp;lt;Short&gt;&lt;br /&gt;
&lt;li&gt;int, Integer, List&amp;lt;Integer&gt;, Set&amp;lt;Integer&gt;, SortedSet&amp;lt;Integer&gt;&lt;br /&gt;
&lt;li&gt;long, Long, List&amp;lt;Long&gt;, Set&amp;lt;Long&gt;, SortedSet&amp;lt;Long&gt;&lt;br /&gt;
&lt;li&gt;float, Float, List&amp;lt;Float&gt;, Set&amp;lt;Float&gt;, SortedSet&amp;lt;Float&gt;&lt;br /&gt;
&lt;li&gt;double, Double, List&amp;lt;Double&gt;, Set&amp;lt;Double&gt;, SortedSet&amp;lt;Double&gt;&lt;br /&gt;
&lt;/ul&gt;&lt;h2&gt;ソースからのビルド&lt;/h2&gt;EclipseとGoogle Plugin for Eclipse、Subversionクライアント(Subclipse or subversive)が必要です。 &lt;p&gt;まず、&lt;a href=&quot;http://code.google.com/p/slim3/source/browse/#svn/trunk&quot;&gt;slim3のリポジトリ&lt;/a&gt;から、slim3とslim3-genをチェックアウトします。 &lt;p&gt;次に、&lt;a href=&quot;http://code.google.com/p/whitedog/source/browse/#svn/slim3-jsonic&quot;&gt;slim3-gen-jsonicのリポジトリ&lt;/a&gt;から、slim3-gen-jsonic、slim3-gen-jsonic-testをチェックアウトします。 &lt;p&gt;slim3-gen-jsonicに含まれているbuild.xmlを使ってビルドすると、slim3-gen-jsonicプロジェクトのtargetディレクトリに、jarファイルが生成されます(slim3-genの全クラスもこのjarに含まれています)。 &lt;p&gt;slim3-gen-jsonic-testプロジェクトは、slim3プロジェクトを参照し、slim3-gen-jsonicプロジェクトが生成するjarをannotation processorとして参照しるので、このプロジェクト内にモデルクラスを作成すると、JSON変換機能を簡単に試せます。また、メタクラスはsrc_generatedディレクトリ内に生成されるようになっているので、実際にどんなメソッドが生成されているか確認できます。

&lt;h2&gt;jsonic単体と比べた利点&lt;/h2&gt;今のところ持っている機能だと、文字列の暗号化対応以外、jsonicそのものを使っても同じ効果が得られます。slim3-genに統合する形で機能を追加していく利点としては、
&lt;ul&gt;&lt;li&gt;リフレクションを使わないので速い(但しある程度大きなモデルでも、数十ミリ秒の差でしょう)&lt;br /&gt;
&lt;li&gt;App Engine固有の型に対応しやすい(固有の型には今後対応していく予定です)&lt;br /&gt;
&lt;/ul&gt;というところが挙げられます。まぁ、あまり大きな利点は無いですし、DatastoreのモデルとJSONとしてクライアントに返すデータのモデルが同じものとも限らない(むしろ別々に設計したほうがよさそう)ですが、Metaを使ってモデルを手軽に変換できるだけでも便利なのではないかと思います。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/790178579088980114/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/790178579088980114' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/790178579088980114'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/790178579088980114'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2010/11/slim3json.html' title='slim3を改造して、モデルのJSON変換機能を追加する'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-1445213037444746502</id><published>2010-11-08T23:00:00.008+09:00</published><updated>2010-11-10T10:53:45.241+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine"/><category scheme="http://www.blogger.com/atom/ns#" term="pos2wit"/><title type='text'>pos2wit - メールでTwitterを利用できる無料サービス</title><content type='html'>&lt;a onblur=&quot;try {parent.deselectBloggerImageGracefully();} catch(e) {}&quot; href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqOmsrdq03jNBO6frib-N_evdjXbR06kSdJ2XUygXc_wlwOIbt9N0a6Je2etQLMbmDkHyoEjusPgsxEK85VbWdrQPp_10s31Ox4HVfn0eWg493f5FPrH9Ng9OsdaWenM1vuseG/s1600/DSCF5687.JPG&quot;&gt;&lt;img style=&quot;float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 240px;&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqOmsrdq03jNBO6frib-N_evdjXbR06kSdJ2XUygXc_wlwOIbt9N0a6Je2etQLMbmDkHyoEjusPgsxEK85VbWdrQPp_10s31Ox4HVfn0eWg493f5FPrH9Ng9OsdaWenM1vuseG/s320/DSCF5687.JPG&quot; border=&quot;0&quot; alt=&quot;&quot;id=&quot;BLOGGER_PHOTO_ID_5537192002211572482&quot; /&gt;&lt;/a&gt;&lt;br /&gt;
&lt;br /&gt;
趣味でpos2witの開発を始めて、気づけばもう1年が経ちました。当初予定していたよりも多くの機能が実装できて、かなり便利に使えるツールになってきました。このスマートフォン全盛の時代、あえてガラケーのみで勝負する人のためのサービスです :-)&lt;br /&gt;
&lt;br /&gt;
pos2witはメールでTwitterを利用出来るサービスです。特に携帯電話のメール機能に最適化してあり、携帯電話のメール機能を使って、Twitterの機能を一通り使うことが出来ます。写真の添付や他のユーザが投稿した写真のサムネイル取得にも対応しています。&lt;br /&gt;
&lt;br /&gt;
ガラケーしか持っていない人、特にメール無料プランを契約している人に最適です(私もガラケーでメール無料プランです!)。通勤途中や休憩時間の暇つぶしなど、ちょっとした時間に是非ご活用ください!&lt;br /&gt;
&lt;br /&gt;
&lt;small&gt;&lt;b&gt;注意事項: pos2witはGoogle App Engine上で動作しており、Twitterにアクセスします。そのため、App EngineのメンテナンスやTwitterのAPI制限に影響され、機能が正常に使えないことがあります。多くの機能は失敗することは殆どありませんが、検索については、TwitterのAPI制限のため、一定時間使えない状態になっていることがあります。そのため、検索結果の受信には、数分から数時間かかることがあります。&lt;/b&gt;&lt;/small&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;ユーザ登録&lt;/h3&gt;&lt;br /&gt;
ユーザ登録は、PCからの登録(oAuth)と、メール経由の登録(xAuth)の2種類に対応しています。&lt;br /&gt;
&lt;br /&gt;
PCから登録する場合、&lt;a href=&quot;http://www.pos2wit.com/&quot;&gt;www.pos2wit.com&lt;/a&gt;にある、&quot;Signin with Twitter&quot;と書かれたボタンをクリックしてください。Twitterアカウントへのアクセスを許可する画面が表示されます。&quot;許可する&quot;を選ぶと、ユーザ登録が完了しますので、利用するメールアドレスを登録してください。登録後送られる確認メールに返信すると、メールが登録され、pos2witが利用出来るようになります。&lt;br /&gt;
&lt;br /&gt;
メール経由で登録する場合、1行目にTwitterのIDを、2行目にパスワードを書いたメールを、reg@pos2wit.com に送信してください。ユーザ登録とメール登録が一度に行われ、pos2witが利用出来るようになります。(このとき送られたID, パスワードは、Twitterへのアクセス情報の取得にのみ使われ、取得後破棄されます。pos2wit登録後にパスワードを変更しても、動作に問題はありません。)&lt;br /&gt;
&lt;br /&gt;
&lt;font color=&quot;red&quot;&gt;&lt;b&gt;※迷惑メールフィルタを利用されている場合、pos2wit.comから送信されるメールを受信できるように設定しておいてください。&lt;/b&gt;&lt;/font&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;投稿機能&lt;/h3&gt;&lt;br /&gt;
内容にTwitterに投稿したいツイートを記入して p@pos2wit.com にメールを送ると、Twitterに書き込むことが出来ます(件名は無視されます)。この際、写真が添付されていると、&lt;a href=&quot;http://www.twitpic.com/&quot;&gt;TwitPic&lt;/a&gt;に写真が投稿され、URLがツイートの末尾に付加されます。投稿先の画像サービスは、PCから pos2wit にログインした際に表示される、ダッシュボード画面で変更できます。&lt;br /&gt;
&lt;br /&gt;
また、画像を投稿する際に、回転や反転などのフィルターを適用できます。件名にrlで左回転、rrで右回転を行ないます(このエントリ末尾に詳細)。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;タイムライン取得&lt;/h3&gt;&lt;br /&gt;
h@pos2wit.com に空メールを送ると、ホームタイムラインが取得できます。メールの受信可能サイズ制限のため、通常取得できるツイートは10〜20件程度です。sh@pos2wit.com に空メールを送ると、装飾のほとんどない、簡易フォーマットでホームタイムラインが取得できます。この場合、40〜50件程度のツイートが取得できます。&lt;br /&gt;
&lt;br /&gt;
ユーザのタイムラインやステータス、お気に入り、リツイート、返信、ハッシュタグの検索、画像のサムネイル取得、イベント概要(ATND, TweetVite)取得、グルーポン概要取得など、ホームタイムラインの結果には様々な操作を行うためのメールアドレスへのリンクが貼られます。これはツイートの内容を解析することで実現されていて、短縮URLも展開して解析しています。&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;h3&gt;発表(App Engine Java Night Kansai #3)&lt;/h3&gt;&lt;br /&gt;
先月京都リサーチパークで行われたAJNK3で発表しました! ustと資料はこちらからどうぞ(途中までだけど)&lt;br /&gt;
&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;http://www.ustream.tv/recorded/10234795&quot;&gt;http://www.ustream.tv/recorded/10234795&lt;/a&gt;&lt;br /&gt;
&lt;li&gt;&lt;a href=&quot;http://www.kyoto-gtug.org/home/docs/ajnk3-pos2wit-20101016.pdf?attredirects=0&amp;d=1&quot;&gt;資料&lt;/a&gt;&lt;br /&gt;
&lt;/ul&gt;
また、AJNK3では、slim3のひがやすをさん、teamsteamのpirosukeさんも発表されています。
&lt;ul&gt;&lt;li&gt;&lt;a href=&quot;http://www.ustream.tv/recorded/10233982&quot;&gt;ひがやすをさんのust&lt;/a&gt;&lt;br /&gt;
&lt;li&gt;&lt;a href=&quot;http://www.kyoto-gtug.org/home/docs&quot;&gt;AJNKの資料一覧&lt;/a&gt;&lt;br /&gt;
&lt;/ul&gt;


&lt;h3&gt;各種機能の詳細&lt;/h3&gt;
&lt;b&gt;画像投稿先として対応しているサービス&lt;/b&gt;
&lt;ul&gt;&lt;li&gt;twitpic&lt;br /&gt;
&lt;li&gt;twitgoo&lt;br /&gt;
&lt;li&gt;plixi&lt;br /&gt;
&lt;li&gt;yfrog&lt;br /&gt;
&lt;li&gt;img.ly&lt;br /&gt;
&lt;li&gt;picasa&lt;br /&gt;
&lt;/ul&gt;
&lt;b&gt;画像投稿時のフィルタ(件名に記入)&lt;/b&gt;
&lt;ul&gt;&lt;li&gt;rl - 左回転(90℃)&lt;br /&gt;
&lt;li&gt;rr - 右回転(90℃)&lt;br /&gt;
&lt;li&gt;rp - 半回転(180℃)&lt;br /&gt;
&lt;li&gt;fh - 上下反転&lt;br /&gt;
&lt;li&gt;fv - 左右反転&lt;br /&gt;
&lt;li&gt;ifh - 自動輝度調整&lt;br /&gt;
&lt;li&gt;sNN - 縮小(例: s30 - 30%に縮小)&lt;br /&gt;
&lt;/ul&gt;
&lt;b&gt;サムネイル取得に対応しているサービス&lt;/b&gt;
&lt;ul&gt;&lt;li&gt;twitpic&lt;br /&gt;
&lt;li&gt;twitgoo&lt;br /&gt;
&lt;li&gt;plixi&lt;br /&gt;
&lt;li&gt;yfrog&lt;br /&gt;
&lt;li&gt;flickr&lt;br /&gt;
&lt;li&gt;brizzly&lt;br /&gt;
&lt;li&gt;owly&lt;br /&gt;
&lt;li&gt;携帯百景&lt;br /&gt;
&lt;li&gt;mobypic&lt;br /&gt;
&lt;/ul&gt;</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/1445213037444746502/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/1445213037444746502' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/1445213037444746502'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/1445213037444746502'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2010/09/pos2wit-twitter.html' title='pos2wit - メールでTwitterを利用できる無料サービス'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjqOmsrdq03jNBO6frib-N_evdjXbR06kSdJ2XUygXc_wlwOIbt9N0a6Je2etQLMbmDkHyoEjusPgsxEK85VbWdrQPp_10s31Ox4HVfn0eWg493f5FPrH9Ng9OsdaWenM1vuseG/s72-c/DSCF5687.JPG" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-6648158364116616831</id><published>2010-07-08T23:58:00.009+09:00</published><updated>2010-07-09T01:21:56.931+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="Twitter4J"/><title type='text'>Twitter4J 2.1.2を改造してGoogle AppEngineからTwitPicにOAuthEchoで画像を投稿する。</title><content type='html'>Twitter4J2.1.2では(執筆時はまだ開発中)、画像の投稿機能が提供されます。現在実装されているのは、YFrogへの投稿機能(OAuthEcho, Basic認証)とTwitPicへの投稿機能(Basic認証のみ)です。&lt;br /&gt;&lt;a href=&quot;http://www.pos2wit.com/&quot;&gt;pos2wit&lt;/a&gt;ではTwitPicへのOAuthEchoによる投稿を行いたかったので、できるように改造してみました。&lt;br /&gt;&lt;br /&gt;まず、&lt;a href=&quot;http://twitter4j.org/&quot;&gt;Twitter4Jのサイト&lt;/a&gt;から、バージョン2.1.2をダウンロードして展開して下さい。この中に含まれる、twitter-core内のソースを改造しますので、このソースが改造できる環境を整えて下さい(僕はEclipse上にプロジェクトを作成し、ソースをコピーしました。)。環境を整えてコンパイルすると、twitter4j.internal.logging内のいくつかのソースでコンパイルエラーが起こります。参照しているクラスがないためで、ここは何も考えずにCommonsLogging, Log4J, SLF4J関連のソースを削除します(それぞれLoggerとLoggerFactoryがあるのでそれらを削除)。&lt;br /&gt;&lt;br /&gt;次に&lt;a href=&quot;http://github.com/yusuke/twitter4j/tree/master/twitter4j-core/src/main/java/twitter4j/util/&quot;&gt;githubのtwitter4j.util&lt;/a&gt;から、ImageUpload.javaをダウンロードし、twitter4j.util内にコピーします。このファイル内で、画像投稿機能が実装されています。&lt;br /&gt;コピーするとコンパイルエラーが出るので、まずはこれを潰します。&lt;br /&gt;&lt;br /&gt;twitter4j.http.OAuthAuthorizationに、次のメソッドを追加します(これはgit内のソースには実装されています)。&lt;br /&gt;&lt;pre style=&quot;overflow: auto; border-style: dotted&quot;&gt;&lt;br /&gt;    public List&amp;lt;HttpParameter&gt; generateOAuthSignatureHttpParams (String method, String url) {&lt;br /&gt;        long timestamp = System.currentTimeMillis() / 1000;&lt;br /&gt;        long nonce = timestamp + RAND.nextInt();&lt;br /&gt;        &lt;br /&gt;        List&amp;lt;HttpParameter&gt; oauthHeaderParams = new ArrayList&amp;lt;HttpParameter&gt;(5);&lt;br /&gt;        oauthHeaderParams.add(new HttpParameter(&quot;oauth_consumer_key&quot;, consumerKey));&lt;br /&gt;        oauthHeaderParams.add(OAUTH_SIGNATURE_METHOD);&lt;br /&gt;        oauthHeaderParams.add(new HttpParameter(&quot;oauth_timestamp&quot;, timestamp));&lt;br /&gt;        oauthHeaderParams.add(new HttpParameter(&quot;oauth_nonce&quot;, nonce));&lt;br /&gt;        oauthHeaderParams.add(new HttpParameter(&quot;oauth_version&quot;, &quot;1.0&quot;));&lt;br /&gt;        if (null != oauthToken) {&lt;br /&gt;            oauthHeaderParams.add(new HttpParameter(&quot;oauth_token&quot;, oauthToken.getToken()));&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        List&amp;lt;HttpParameter&gt; signatureBaseParams = new ArrayList&amp;lt;HttpParameter&gt; (oauthHeaderParams.size());&lt;br /&gt;        signatureBaseParams.addAll(oauthHeaderParams);&lt;br /&gt;        parseGetParameters (url, signatureBaseParams);&lt;br /&gt;        &lt;br /&gt;        StringBuffer base = new StringBuffer (method).append(&quot;&amp;&quot;)&lt;br /&gt;                .append(encode(constructRequestURL(url))).append(&quot;&amp;&quot;);&lt;br /&gt;        base.append(encode (normalizeRequestParameters(signatureBaseParams)));&lt;br /&gt;&lt;br /&gt;        String oauthBaseString = base.toString();&lt;br /&gt;        String signature = generateSignature (oauthBaseString, oauthToken);&lt;br /&gt;&lt;br /&gt;        oauthHeaderParams.add (new HttpParameter(&quot;oauth_signature&quot;, signature));&lt;br /&gt;        &lt;br /&gt;        return oauthHeaderParams;&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;次に、ImageUpload.java内にTwitPic用のOAuth Echo対応ImageUploadを実装します。&lt;br /&gt;同ファイル内に、下記のソースをコピーします。下記ソースは、同ファイル内のYFrogOAuthUploaderをコピーしてTwitPic用に修正したものです。&lt;br /&gt;&lt;br /&gt;&lt;pre style=&quot;overflow: auto; border-style: dotted&quot;&gt;&lt;br /&gt;// import部分&lt;br /&gt;import java.io.InputStream;&lt;br /&gt;import java.io.UnsupportedEncodingException;&lt;br /&gt;import java.net.URLEncoder;&lt;br /&gt;import twitter4j.conf.Configuration;&lt;br /&gt;import twitter4j.conf.ConfigurationContext;&lt;br /&gt;import twitter4j.internal.org.json.JSONException;&lt;br /&gt;import twitter4j.internal.org.json.JSONObject;&lt;br /&gt;&lt;br /&gt;// TwitPicOAuthUploader&lt;br /&gt;    public static class TwitPicOAuthUploader extends ImageUpload {&lt;br /&gt;     private String apiKey;&lt;br /&gt;        private OAuthAuthorization auth;&lt;br /&gt;&lt;br /&gt;        // uses the secure upload URL, not the one specified in the YFrog FAQ&lt;br /&gt;        private static final String TWITPIC_UPLOAD_URL = &quot;http://api.twitpic.com/2/upload.json&quot;;&lt;br /&gt;        private static final String TWITTER_VERIFY_CREDENTIALS = &quot;https://api.twitter.com/1/account/verify_credentials.json&quot;;&lt;br /&gt;&lt;br /&gt;        public TwitPicOAuthUploader(String apiKey, OAuthAuthorization auth) {&lt;br /&gt;            this.apiKey = apiKey;&lt;br /&gt;            this.auth = auth;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public String upload(File image) throws TwitterException{&lt;br /&gt;         throw new UnsupportedOperationException();  // AppEngineではFileInputStreamが使えない。&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        public String upload(String message, String fileName, InputStream fileBody) throws TwitterException {&lt;br /&gt;            // step 1 - generate verification URL&lt;br /&gt;//            String signedVerifyCredentialsURL = generateSignedVerifyCredentialsURL();&lt;br /&gt;&lt;br /&gt;            // step 2 - generate HTTP parameters&lt;br /&gt;            HttpParameter[] params = {&lt;br /&gt;                    new HttpParameter(&quot;key&quot;, apiKey)&lt;br /&gt;                 , new HttpParameter(&quot;message&quot;, message, true)&lt;br /&gt;                    , new HttpParameter(&quot;media&quot;, fileName, fileBody)&lt;br /&gt;                    };&lt;br /&gt;&lt;br /&gt;            // step 3 - upload the file&lt;br /&gt;            Configuration config = ConfigurationContext.getInstance();&lt;br /&gt;            config.getRequestHeaders().put(&quot;X-Auth-Service-Provider&quot;, TWITTER_VERIFY_CREDENTIALS);&lt;br /&gt;            StringBuilder b = new StringBuilder();&lt;br /&gt;            b.append(&quot;OAuth realm=\&quot;http://api.twitter.com/\&quot;&quot;);&lt;br /&gt;            for(HttpParameter p : auth.generateOAuthSignatureHttpParams(&quot;GET&quot;, TWITTER_VERIFY_CREDENTIALS)){&lt;br /&gt;             if(p.getName().equals(&quot;oauth_signature&quot;)){&lt;br /&gt;                    try {&lt;br /&gt;                        b.append(&quot;, &quot;).append(p.getName()).append(&quot;=\&quot;&quot;).append(&lt;br /&gt;                                URLEncoder.encode(p.getValue(), &quot;ISO8859_1&quot;)&lt;br /&gt;                                ).append(&quot;\&quot;&quot;);&lt;br /&gt;                    } catch (UnsupportedEncodingException e) {&lt;br /&gt;                        throw new TwitterException(e);&lt;br /&gt;                    }&lt;br /&gt;             } else{&lt;br /&gt;                    b.append(&quot;, &quot;).append(p.getName()).append(&quot;=\&quot;&quot;).append(p.getValue()).append(&quot;\&quot;&quot;);&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;            config.getRequestHeaders().put(&quot;X-Verify-Credentials-Authorization&quot;, b.toString());&lt;br /&gt;            System.out.println(&quot;X-Auth-Service-Provider: &quot; + TWITTER_VERIFY_CREDENTIALS);&lt;br /&gt;            System.out.println(&quot;X-Verify-Credentials-Authorization: &quot; + b.toString());&lt;br /&gt;            HttpClientWrapper client = new HttpClientWrapper(config);&lt;br /&gt;            HttpResponse httpResponse = client.post(TWITPIC_UPLOAD_URL, params);&lt;br /&gt;&lt;br /&gt;            // step 4 - check the response&lt;br /&gt;            int statusCode = httpResponse.getStatusCode();&lt;br /&gt;            if (statusCode != 200) {&lt;br /&gt;                throw new TwitterException(&quot;Twitpic image upload returned invalid status code&quot;, httpResponse);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            try{&lt;br /&gt;             String response = httpResponse.asString();&lt;br /&gt;             return new JSONObject(response).getString(&quot;url&quot;);&lt;br /&gt;            } catch(JSONException e){&lt;br /&gt;                throw new TwitterException(&quot;Unknown Twitpic response&quot;, httpResponse);&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;上記のソースをペーストすると、twitter4j.internal.http.HttpParameterに必要なメソッドが無いためにコンパイルエラーが発生します。下記のフィールド及びメソッドを、HttpParameterクラスに追加して下さい。&lt;br /&gt;&lt;br /&gt;&lt;pre style=&quot;overflow: auto; border-style: dotted&quot;&gt;&lt;br /&gt;    // フィールドを追加&lt;br /&gt;    private boolean noUrlEncode = false;&lt;br /&gt;    private InputStream fileBody = null;&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    // コンストラクタを追加&lt;br /&gt;    public HttpParameter(String name, String value, boolean noUrlEncode) {&lt;br /&gt;        this.name = name;&lt;br /&gt;        this.value = value;&lt;br /&gt;        this.noUrlEncode = noUrlEncode;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public HttpParameter(String name, String fileName, InputStream fileBody) {&lt;br /&gt;        this.name = name;&lt;br /&gt;        this.file = new File(fileName);&lt;br /&gt;        this.fileBody = fileBody;&lt;br /&gt;    }&lt;br /&gt;    ...&lt;br /&gt;&lt;br /&gt;    // メソッドを追加&lt;br /&gt;    public boolean isUrlEncodeUnnecessary(){&lt;br /&gt;     return this.urlEncodeUnnecessary;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public boolean hasFileBody(){&lt;br /&gt;     return null != fileBody;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    public InputStream getFileBody(){&lt;br /&gt;     return fileBody;&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;これでコンパイルエラー自体は消えますが、追加されたパラメータに対応してリクエスト作成機能を修正する必要があります。リクエスト作成機能はtwitter4j.internal.http.HttpClientImpl.java内にあるので、下記のように改造します(&lt;font color=&quot;red&quot;&gt;赤字&lt;/font&gt;が改造部分)。&lt;br /&gt;&lt;br /&gt;&lt;pre style=&quot;overflow: auto; border-style: dotted&quot;&gt;                                // 241行目あたり&lt;br /&gt;                                if(param.isFile()){&lt;br /&gt;                                    write(out, boundary + &quot;\r\n&quot;);&lt;br /&gt;                                    write(out, &quot;Content-Disposition: form-data; name=\&quot;&quot; + param.getName() + &quot;\&quot;; filename=\&quot;&quot; + param.getFile().getName() + &quot;\&quot;\r\n&quot;);&lt;br /&gt;                                    write(out, &quot;Content-Type: &quot; + param.getContentType() + &quot;\r\n\r\n&quot;);&lt;br /&gt;                                    BufferedInputStream in = new BufferedInputStream(&lt;br /&gt;                                      &lt;font color=&quot;red&quot;&gt;param.hasFileBody() ? param.getFileBody() : new FileInputStream(param.getFile())&lt;/font&gt;&lt;br /&gt;                                      );&lt;br /&gt;                                    int buff = 0;&lt;br /&gt;                                    ...&lt;br /&gt;&lt;br /&gt;                                // 258行目あたり&lt;br /&gt;                                    logger.debug(param.getValue());&lt;br /&gt;                                    &lt;font color=&quot;red&quot;&gt;if(param.isUrlEncodeUnnecessary()){&lt;br /&gt;                                     out.write(param.getValue().getBytes(&quot;UTF-8&quot;));&lt;br /&gt;                                    } else{&lt;br /&gt;                                     out.write(encode(param.getValue()).getBytes(&quot;UTF-8&quot;));&lt;br /&gt;                                    }&lt;/font&gt;&lt;br /&gt;                                    write(out, &quot;\r\n&quot;);&lt;br /&gt;                                    ...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;この修正が必要なのは、&lt;ul&gt;&lt;li&gt;オリジナルのコードではFileInputStreamを使っているが、これはAppEngineでは使えない。&lt;br /&gt;&lt;li&gt;オリジナルのコードでは常にパラメータをURLEncodeしているが、メッセージをURLEncodeするとTwitPicで復元されない(URLEncodeされた状態で記録される)&lt;/ul&gt;&lt;br /&gt;という問題に対処するためです。&lt;br /&gt;&lt;br /&gt;これでTwitPicに画像を投稿できるようになります。サンプルコードはこんな感じ。投稿にはTwitPicのAPIKEYが必要なので、&lt;a href=&quot;http://dev.twitpic.com/apps/new&quot;&gt;ここ&lt;/a&gt;からアプリケーションを登録し、APIKEYを入手して下さい。&lt;br /&gt;&lt;pre style=&quot;overflow: auto; border-style: dotted&quot;&gt; public void test() throws Exception{&lt;br /&gt;  String apiKey = &lt;font color=&quot;red&quot;&gt;TwitPicのAPI KEY&lt;/font&gt;;&lt;br /&gt;  Twitter t = new TwitterFactory().getOAuthAuthorizedInstance(new AccessToken(&lt;br /&gt;    &lt;font color=&quot;red&quot;&gt;Twitterユーザのaccess_token&lt;/font&gt;&lt;br /&gt;    , &lt;font color=&quot;red&quot;&gt;Twitterユーザのaccess_token_secret&lt;/font&gt;&lt;br /&gt;    ));&lt;br /&gt;  OAuthAuthorization auth = (OAuthAuthorization)t.getAuthorization();&lt;br /&gt;  String message = &quot;投稿するメッセージ。&quot;;&lt;br /&gt;  String url = new ImageUpload.TwitpicOAuthUploader(apiKey, auth).upload(&lt;br /&gt;    message, &quot;duke.jpg&quot;, getClass().getResourceAsStream(&quot;duke-on-gae.jpg&quot;)&lt;br /&gt;    );&lt;br /&gt;  // twitterには、別途投稿する必要がある(uploadでは写真がTwitPicに投稿されるのみ)。&lt;br /&gt;  t.updateStatus(message + &quot; &quot; + url);&lt;br /&gt; }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;だいぶ汚い改造になってしまいましたが、とりあえず投稿することはできるようになりました。もう少し整理して、Twitter4Jにうまく取り込んでもらえるよう働きかける予定です。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/6648158364116616831/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/6648158364116616831' title='2 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/6648158364116616831'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/6648158364116616831'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2010/07/twitter4j-213google-appenginetwitpicoau.html' title='Twitter4J 2.1.2を改造してGoogle AppEngineからTwitPicにOAuthEchoで画像を投稿する。'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-1257661728939677984</id><published>2010-07-08T23:16:00.001+09:00</published><updated>2010-07-08T23:30:34.673+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ajdt"/><category scheme="http://www.blogger.com/atom/ns#" term="AspectJ"/><category scheme="http://www.blogger.com/atom/ns#" term="Eclipse"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><title type='text'>AJDT2.1.0 Released</title><content type='html'>AJDT2.1.0がリリースされました。&lt;a href=&quot;http://www.eclipse.org/ajdt/whatsnew210/&quot;&gt;リリースノート&lt;/a&gt;に書かれている変更点をざくっと紹介します。主にITD(Inter Type Definition)関連の機能が強化されました。&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;ITD-Aware Search&lt;br /&gt;アスペクトによる定義の追加を考慮したJava Searchが正しく行われるようになりました(例: ITD定義、String Person.getName()に対して参照の検索を行うと、ITDされたメソッドを使用しているコードがヒットする)。&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;ITD rename refactoring&lt;br /&gt;名前変更のリファクタリング機能がITDに対応しました。アスペクトのITDでの名称変更(例: String Person.getName()をString Person.getFirstName()に)を行うと、影響を受ける定義(例: Personの派生クラスでのgetNameメソッド(オーバーライド))も名称変更されます。&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;ITD-aware renaming of getters and setters&lt;br /&gt;フィールドの名称変更を行う際に、ITDによって実装されていたそのフィールドのgetter及びsetterの名称変更も行えるようになりました。nameをfirstNameに変更した場合に、ITDで実装されたgetNameがgetFirstNameに、setNameがsetFirstNameに変更されませす。&lt;font color=&quot;red&quot;&gt;&lt;small&gt;(但し、Personの派生クラスでgetNameをオーバーライドしていた場合、そのメソッドは変更されません(あくまでgetter/setterのみ)。個人的にはこれも変更されて欲しいところ。常に@Override付けといた方が良さそうですね。)&lt;/small&gt;&lt;/font&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Pull-out refactoring&lt;br /&gt;フィールドやメソッドを、ITDとして特定のアスペクトに移動するリファクタリング。パッケージエクスプローラでフィールドやメソッドを選んで右クリック -&gt; Refactory -&gt; Pull Out ITD... と選択すると、既存のアスペクトに切り出せます。&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Aspect-aware type renaming&lt;br /&gt;クラス名の変更を行う際に、それを参照しているアスペクト内での定義(ITD)も適切に変更されるようになりました。&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;@Test and Intertype declarations&lt;br /&gt;ITDによって@Testが付加されたメソッドを追加した場合、そのクラスをテストすると、@Testが付加されたメソッドもテストの対象になります。&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;AspectJ-aware PDE Build (Helios only)&lt;br /&gt;AspectJをPDEに統合したビルドが提供されるようになったそうです。AspectJに依存したプラグインを作る際に、Javaプラグインと同じウィザード等を使えるようになりました。詳しくは&lt;a href=&quot;http://contraptionsforprogramming.blogspot.com/2010/03/ajdt-pde-builds-redux.html&quot;&gt;こちら&lt;/a&gt;。&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Modernize the build server&lt;br /&gt;ビルドサーバーをモダンなものに変えたそうです。詳しくは&lt;a href=&quot;https://bugs.eclipse.org/bugs/show_bug.cgi?id=298913&quot;&gt;こちら&lt;/a&gt;。&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;AspectJ 1.6.9&lt;br /&gt;最新のAspectJ 1.6.9がバンドルされています。readmeは&lt;a href=&quot;http://eclipse.org/aspectj/doc/released/README-169.html&quot;&gt;こちら&lt;/a&gt;。&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Bug Fixes&lt;br /&gt;その他、&lt;a href=&quot;https://bugs.eclipse.org/bugs/buglist.cgi?query_format=advanced;bug_status=RESOLVED;bug_status=VERIFIED;bug_status=CLOSED;product=AJDT;target_milestone=2.1.0&quot;&gt;いくつかのバグ&lt;/a&gt;が修正されています。&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;最初ITDがサポートされるようになった頃は、IDE上はコンパイルエラーになっていましたが、バージョンを重ねるにつれて洗練され、遂にリファクタリングまで正常に動くようになりました。今回の機能強化で、ITDされたメソッド・フィールドも元からクラスに定義されていたものとほぼ同じように扱われるようになり、心おきなく使えるようになったのではないでしょうか。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/1257661728939677984/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/1257661728939677984' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/1257661728939677984'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/1257661728939677984'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2010/07/ajdt210-released.html' title='AJDT2.1.0 Released'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-7118639790335887500</id><published>2010-06-19T02:00:00.023+09:00</published><updated>2010-06-21T00:41:56.159+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="Wicket"/><title type='text'>wicketのoauth認証サンプルをappengineに載せる</title><content type='html'>&lt;p&gt;&lt;em&gt;6/20追記 index.htmlの削除と、エラー処理の修正。&lt;/em&gt;&lt;/p&gt;&lt;br /&gt;矢野 勉さんの記事、&lt;a href=&quot;http://gihyo.jp/dev/feature/01/wicket/&quot;&gt;Wicketで始めるオブジェクト指向ウェブ開発&lt;/a&gt;の&lt;a href=&quot;http://gihyo.jp/dev/feature/01/wicket/0009&quot;&gt;第9回　WicketによるOAuth認証&lt;/a&gt;を、Google App Engineに載せてみました。appengine上のURLは&lt;a href=&quot;http://wicket-sample-oauth.latest.sample.appspot.com/&quot;&gt;こちら&lt;/a&gt;。&lt;br /&gt;&lt;br /&gt;ソースコードの修正は、appengineに載せるための修正と、Twitter4Jの最新版を使うための修正です。&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;1. ソースコードの入手&lt;/h3&gt;&lt;br /&gt;第9回の&lt;a href=&quot;http://image.gihyo.co.jp/assets/files/dev/feature/01/wicket/0009/wicket-sample-oauth.zip&quot;&gt;ソースコード&lt;/a&gt;をダウンロードし、解凍する。&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;2. AppEngineプロジェクトの作成&lt;/h3&gt;&lt;br /&gt;Eclipseで新しいAppEngineプロジェクトを作成。プラグインの入手方法やプロジェクトの作成方法は&lt;a href=&quot;http://codezine.jp/article/detail/3835&quot;&gt;こちら(山下さんの記事)&lt;/a&gt;。作成する際に下記の設定を行う。&lt;ul&gt;&lt;li&gt;プロジェクト名はwicket-sample-oauth&lt;br /&gt;&lt;li&gt;パッケージは&quot;jp&quot;&lt;br /&gt;&lt;li&gt;User Google Web Toolkitはオフ&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;作成後、下記のファイルを削除。(&lt;em&gt;6/20 追記&lt;/em&gt;)&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;war/index.html&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;3. ライブラリの入手&lt;/h3&gt;&lt;br /&gt;下記のライブラリをダウンロードし、war/WEB-INF/lib内に必要なjarを置いて、ビルドパスに追加。&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href=&quot;http://wicket.apache.org/&quot;&gt;Apache Wicket&lt;/a&gt;&lt;br /&gt;・wicket-1.4.9.jar&lt;br /&gt;&lt;li&gt;&lt;a href=&quot;http://www.slf4j.org/&quot;&gt;SLF4J&lt;/a&gt;&lt;br /&gt;・slf4j-api-1.6.0.jar&lt;br /&gt;・slf4j-jdk14-1.6.0.jar(又はお好みでLog4J用やcommons-logging用を)&lt;br /&gt;&lt;li&gt;&lt;a href=&quot;http://twitter4j.org/ja/index.html&quot;&gt;Twitter4J&lt;/a&gt;&lt;br /&gt;・twitter4j-core-2.1.2.jar&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;4. ソースコード上書き&lt;/h3&gt;&lt;br /&gt;解答したソースを、下記のように上書き。&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;src/main/javaをプロジェクトのsrcに。&lt;br /&gt;&lt;li&gt;src/webapp/WEB-INF/web.xmlをプロジェクトのwar/WEB-INFに。&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;5. ソースコード修正&lt;/h3&gt;&lt;br /&gt;上書きしたソースの、下記の部分を修正(&lt;font color=&quot;red&quot;&gt;赤&lt;/font&gt;が変更点)。&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;jp.gihyo.wicket.TwitterClientを削除。このクラスは、当時のTwitter4Jがシリアライズに対応していないために用意されたクラス。最新版ではシリアライズに対応しているため、不要です。&lt;br /&gt;&lt;li&gt;jp.gihyo.wicket.AppSession#login&lt;br&gt;&lt;br /&gt;下記は、Twitter4Jの最新版を使うための修正です。&lt;br /&gt;&lt;pre style=&quot;overflow: auto; border-style: dotted&quot;&gt;    public boolean login(String pin, Response response) {&lt;br /&gt;        try {&lt;br /&gt;            if(requestToken == null) {&lt;br /&gt;                throw new IllegalStateException(&quot;requestToken is missing.&quot;);&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            &lt;font color=&quot;red&quot;&gt;&lt;b&gt;Twitter client = new TwitterFactory().getInstance();&lt;/b&gt;&lt;/font&gt;&lt;br /&gt;            client.setOAuthConsumer(consumerKey, consumerSecret);&lt;br /&gt;            &lt;br /&gt;            AccessToken accessToken = client.getOAuthAccessToken(requestToken, pin);&lt;br /&gt;            &lt;font color=&quot;red&quot;&gt;&lt;b&gt;client = new TwitterFactory().getOAuthAuthorizedInstance(&lt;br /&gt;              consumerKey, consumerSecret, accessToken);&lt;/b&gt;&lt;/font&gt;&lt;br /&gt;            this.twitterSession = client;&lt;br /&gt;            dirty();&lt;br /&gt;            return true;&lt;br /&gt;        } catch(TwitterException ex) {&lt;br /&gt;            LOGGER.error(&quot;Can not setup OAuth Access Token to Twitter object.&quot;, ex);&lt;br /&gt;            return false;&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;jp.gihyo.wicket.AppSession#getTwitterSession&lt;br&gt;&lt;br /&gt;下記は、Twitter4Jの最新版を使うための修正です。&lt;br /&gt;&lt;pre style=&quot;overflow:auto; border-style: dotted&quot;&gt;    public Twitter getTwitterSession(Request request) {&lt;br /&gt;        if(request == null) throw new IllegalArgumentException(&quot;&#39;request&#39; is missing&quot;);&lt;br /&gt;&lt;br /&gt;        //既にTwitterオブジェクトを作成済みなら，認証の必要はない。&lt;br /&gt;        if(twitterSession != null) return twitterSession;&lt;br /&gt;&lt;br /&gt;        //AccessTokenをDBなどに永続化している場合は，ここで永続化したAccessTokenを使って&lt;br /&gt;        //Twitterオブジェクトを再作成して返却すればよい。&lt;br /&gt;        //このサンプルではAccessTokenを永続化していないので，すぐにOAuth認証に入る。&lt;br /&gt;&lt;br /&gt;        //OAuth認証開始。RequestTokenの取得を試みる。&lt;br /&gt;        Twitter client = &lt;font color=&quot;red&quot;&gt;&lt;b&gt;new TwitterFactory().getInstance();&lt;/b&gt;&lt;/font&gt;&lt;br /&gt;        client.setOAuthConsumer(consumerKey, consumerSecret);&lt;br /&gt;        ...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;jp.gihyo.wicket.WicketApplication&lt;br&gt;&lt;br /&gt;以下のメソッドを追加。Wicketアプリケーションをappengineに載せるための設定です。何故これが必要かは&lt;a href=&quot;http://d.hatena.ne.jp/t_yano/20090412/1239498549&quot;&gt;ここ&lt;/a&gt;に解説があります。&lt;br /&gt;&lt;pre style=&quot;overflow:auto; border-style: dotted&quot;&gt; @Override&lt;br /&gt; protected ISessionStore newSessionStore() {&lt;br /&gt;  return new HttpSessionStore(this);&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; @Override&lt;br /&gt; public String getConfigurationType() {&lt;br /&gt;     return Application.DEPLOYMENT;&lt;br /&gt; }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;jp.gihyo.wicket.AppSession#getTwittersession(&lt;em&gt;6/20 追記&lt;/em&gt;)&lt;br&gt;&lt;br /&gt;以下の修正を行う。Twitter呼出時の例外を修正。Twitterがエラーを返した場合の処理を改善します。&lt;br /&gt;&lt;pre style=&quot;overflow:auto; border-style: dotted&quot;&gt;&lt;br /&gt;    public Twitter getTwitterSession(Request request) &lt;font color=&quot;red&quot;&gt;&lt;b&gt;throws TwitterException&lt;/b&gt;&lt;/font&gt;{&lt;br /&gt;        if(request == null) throw new IllegalArgumentException(&quot;&#39;request&#39; is missing&quot;);&lt;br /&gt;&lt;br /&gt;        //既にTwitterオブジェクトを作成済みなら，認証の必要はない。&lt;br /&gt;        if(twitterSession != null) return twitterSession;&lt;br /&gt;&lt;br /&gt;        ...&lt;br /&gt;&lt;br /&gt;        RequestToken token = null;&lt;br /&gt;        try {&lt;br /&gt;            token = client.getOAuthRequestToken(RequestUtils.toAbsolutePath(&quot;login&quot;));&lt;br /&gt;        } catch(TwitterException ex) {&lt;br /&gt;            throw &lt;font color=&quot;red&quot;&gt;&lt;b&gt;ex&lt;/b&gt;&lt;/font&gt;;&lt;br /&gt;        }&lt;br /&gt;        ...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;6. Twitterにアプリケーションを登録&lt;/h3&gt;&lt;br /&gt;&lt;a href=&quot;https://twitter.com/apps&quot;&gt;アプリページ&lt;/a&gt;へ行き、アプリケーションを登録する。&lt;br /&gt;登録方法は、&lt;a href=&quot;http://gihyo.jp/dev/feature/01/wicket/0009&quot;&gt;第9回　WicketによるOAuth認証&lt;/a&gt;の記事内にあります。&lt;br /&gt;&lt;br /&gt;登録後、consumer-keyとconsumer-secretをweb.xml内に設定。&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;7. セッションを有効にする&lt;/h3&gt;&lt;br /&gt;war/WEB-INF/appengine-web.xml内に、次の1行を追加する。&lt;br /&gt;&lt;pre style=&quot;border-style: dotted&quot;&gt;&lt;br /&gt; &amp;lt;sessions-enabled&gt;true&amp;lt;/sessions-enabled&gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;以上の設定・変更を行った上で配備すると、appengineでサンプルが動作するようになります。&lt;br /&gt;動作例は&lt;a href=&quot;http://wicket-sample-oauth.latest.sample.appspot.com/&quot;&gt;こちら&lt;/a&gt;</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/7118639790335887500/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/7118639790335887500' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/7118639790335887500'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/7118639790335887500'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2010/06/wicketoauthappengine.html' title='wicketのoauth認証サンプルをappengineに載せる'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-4127167884673222491</id><published>2009-11-27T21:46:00.006+09:00</published><updated>2010-06-21T00:31:58.723+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AppEngine"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><title type='text'>AppEngineでメール送受信とか</title><content type='html'>メール受信に関しては、&lt;a href=&quot;http://d.hatena.ne.jp/hidemon/20091015/1255557396&quot;&gt;http://d.hatena.ne.jp/hidemon/20091015/1255557396&lt;/a&gt;や&lt;a href=&quot;http://d.hatena.ne.jp/nowokay/20091024&quot;&gt;http://d.hatena.ne.jp/nowokay/20091024&lt;/a&gt;が参考になります。&lt;br /&gt;&lt;br /&gt;送信に関しては&lt;a href=&quot;http://codezine.jp/article/detail/3917&quot;&gt;http://codezine.jp/article/detail/3917&lt;/a&gt;に載っているコードがシンプルです。&lt;br /&gt;送信時の注意点は、アプリケーションの開発者のアカウントか、アプリケーションにログイン注のGoogleアカウントしか送信者になれないこと。自分のアカウントを使いたくない場合は、別途Googleアカウントを取得し、それを開発者に加えます(&lt;a href=&quot;http://code.google.com/intl/ja/appengine/docs/java/mail/overview.html&quot;&gt;http://code.google.com/intl/ja/appengine/docs/java/mail/overview.html&lt;/a&gt;ここのメールメッセージの節参照)。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;http://pos2wit.appspot.com/&quot;&gt;pos2wit&lt;/a&gt;では、実際に別のGoogleアカウントを取得して、それをアプリケーションの開発者に加え、送信者として設定してます。加えて、そのアカウントに返信されたメールをpos2witで処理するために、フィルタを設定してpos2witの指定アドレスに転送してます。&lt;storike&gt;折角メール受信機能があるんだから、APPID.appspotmail.com上のアドレスも送信者として指定できればいいのに。&lt;/storike&gt;(6/20 追記: 数ヶ月前から、APPID.appspotmail.com上のアドレスも指定できることが確認できてます。英語のドキュメントにもそれが可能であることが触れられています。但し日本語のドキュメントにはまだその記述はありません。)</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/4127167884673222491/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/4127167884673222491' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/4127167884673222491'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/4127167884673222491'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2009/11/appengine.html' title='AppEngineでメール送受信とか'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-8733462998175026027</id><published>2009-11-23T01:29:00.003+09:00</published><updated>2009-11-23T01:47:36.314+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="Wicket"/><title type='text'>NavigationToolbarのカスタマイズ</title><content type='html'>コーディングでスタイルを変える方法もあるけど、htmlが用意されているコンポーネントの場合は、それを上書きしてしまうことでもカスタマイズできる(但し動的な部分には向かない)。&lt;br /&gt;以下はAjaxFllbackDefaultDataTableのナビゲータである、AjaxNavigationToolbarの例。　&lt;br /&gt;&lt;br /&gt;(AjaxNavigationToolbar.html)&lt;br /&gt;&lt;pre style=&quot;overflow:scroll&quot;&gt;&amp;lt;wicket:panel&gt;&lt;br /&gt; &amp;lt;tr class=&quot;navigation&quot;&gt;&lt;br /&gt;  &amp;lt;td wicket:id=&quot;span&quot;&gt;&lt;br /&gt;   &amp;lt;div style=&quot;float: &lt;font color=&quot;red&quot;&gt;right&lt;/font&gt;;&quot; class=&quot;navigatorLabel&quot;&gt;&amp;lt;span wicket:id=&quot;navigatorLabel&quot;&gt;[navigator-label]&amp;lt;/span&gt;&amp;lt;/div&gt;&lt;br /&gt;   &amp;lt;div style=&quot;text-align: &lt;font color=&quot;red&quot;&gt;center&lt;/font&gt;;&quot; class=&quot;navigator&quot;&gt;&amp;lt;span wicket:id=&quot;navigator&quot;&gt;[navigator]&amp;lt;/span&gt;&amp;lt;/div&gt;&lt;br /&gt;  &amp;lt;/td&gt;&lt;br /&gt; &amp;lt;/tr&gt;&lt;br /&gt;&amp;lt;/wicket:panel&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;font color=&quot;red&quot;&gt;赤字&lt;/font&gt;の部分を書き換えて、本来ラベル(全何件のうち何件目から何件目かを表示)が左でナビゲータ(&amp;lt; &amp;lt; 1 2 3てやつ)が右なのを、それぞれ右、中央に変えてる。&lt;br /&gt;このファイルをプロジェクトのsrc/org/apache/wicket/extensions/ajax/markup/html/repeater/data/table に置いておけば、本来のファイルより優先して読み込まれる。&lt;br /&gt;&lt;br /&gt;Wicketのカスタマイズは、コードで、propertyファイルで(これには国際化も絡む)、html置き換え、と複数の方法があってもの凄く柔軟。反面どれでやればいいのかわかりにくいこともありそうだけど。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/8733462998175026027/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/8733462998175026027' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/8733462998175026027'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/8733462998175026027'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2009/11/navigationtoolbar.html' title='NavigationToolbarのカスタマイズ'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-5994401276160987326</id><published>2009-11-22T00:23:00.004+09:00</published><updated>2009-11-22T00:40:10.975+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="Wicket"/><title type='text'>AjaxFallbackDefaultDataTableのカラム幅指定(2)</title><content type='html'>やっぱり簡単な方法があった。&lt;br /&gt;&lt;pre style=&quot;overflow:scroll&quot;&gt;public Page(){&lt;br /&gt; List&amp;lt;IColumn&amp;lt;Score&gt;&gt; columns = new ArrayList&amp;lt;IColumn&amp;lt;Score&gt;&gt;();&lt;br /&gt; columns.add(new PropertyColumn&amp;lt;Score&gt;(new Model&amp;lt;String&gt;(&quot;打順&quot;), &quot;order&quot;){&lt;br /&gt;  &lt;font color=&quot;red&quot;&gt;public String getCssClass() {&lt;br /&gt;   return &quot;orderClass&quot;;&lt;br /&gt;  }&lt;/font&gt;&lt;br /&gt; });&lt;br /&gt; add(new AjaxFallbackDefaultDataTable&amp;lt;Score&gt;(&lt;br /&gt;  &quot;scores&quot;, columns&lt;br /&gt;  , new ScoreDataProvider(), 10&lt;br /&gt;  ));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;こんな感じで、カラムのgetCssClassをオーバーライドして適当な値を返せばthタグのclassとして出力されるので、スタイルシートで&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; th.orderClass{ width: 20%}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;ってやっとけば幅を指定できる。前回のコードからわかるように、Columnは実際にはthやtdの下に位置するんだけど、getCssClassが返す結果はthやtdに反映される。&lt;br /&gt;前回のと同じ結果ではないけど、はるかにシンプルにできるので、わざわざ前回のコンポーネント階層を辿るより全然良い。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/5994401276160987326/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/5994401276160987326' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/5994401276160987326'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/5994401276160987326'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2009/11/ajaxfallbackdefaultdatatable2.html' title='AjaxFallbackDefaultDataTableのカラム幅指定(2)'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-7709475291815135063</id><published>2009-11-15T00:26:00.002+09:00</published><updated>2009-11-15T00:32:27.818+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="Wicket"/><title type='text'>AjaxFallbackDefaultDataTableのカラム幅指定</title><content type='html'>AjaxFallbackDefaultDataTableが作ってくれるテーブルの、各カラムのサイズを指定したくていろいろ調べてみたけど、なかなか良い方法が見つからなかった。一応実現できたけど、むちゃくちゃ強引な気がする。&lt;br /&gt;&lt;pre style=&quot;overflow:scroll&quot;&gt;&lt;br /&gt;public Page(){&lt;br /&gt; DataTable&lt;ReceivedMail&gt; table = new AjaxFallbackDefaultDataTable&lt;ReceivedMail&gt;( ... );&lt;br /&gt;&lt;br /&gt; MarkupContainer headers = (MarkupContainer)get(table, &quot;topToolbars/2/toolbar/headers&quot;);&lt;br /&gt; if(headers != null){&lt;br /&gt;  get(headers, &quot;1/header&quot;).add(new AttributeAppender(&quot;width&quot;, new Model&lt;String&gt;(&quot;20%&quot;), &quot;&quot;));&lt;br /&gt;  get(headers, &quot;2/header&quot;).add(new AttributeAppender(&quot;width&quot;, new Model&lt;String&gt;(&quot;20%&quot;), &quot;&quot;));&lt;br /&gt;  get(headers, &quot;3/header&quot;).add(new AttributeAppender(&quot;width&quot;, new Model&lt;String&gt;(&quot;60%&quot;), &quot;&quot;));&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private static Component get(MarkupContainer root, String path){&lt;br /&gt; String[] paths = path.split(&quot;\\/&quot;);&lt;br /&gt; MarkupContainer current = root;&lt;br /&gt; for(String p : paths){&lt;br /&gt;  Component c = current.get(p);&lt;br /&gt;  if(c == null) return null;&lt;br /&gt;  if(!(c instanceof MarkupContainer)) return null;&lt;br /&gt;  current = (MarkupContainer)c;&lt;br /&gt; }&lt;br /&gt; return current;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;こんなんでいいのかね。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/7709475291815135063/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/7709475291815135063' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/7709475291815135063'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/7709475291815135063'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2009/11/ajaxfallbackdefaultdatatable.html' title='AjaxFallbackDefaultDataTableのカラム幅指定'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-4800280373352982520</id><published>2009-11-04T10:43:00.005+09:00</published><updated>2009-11-04T11:22:07.810+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="Wicket"/><title type='text'>Wicket勉強中(DropDownChoice)</title><content type='html'>&lt;a href=&quot;http://wicket.apache.org/&quot;&gt;Apache Wicket&lt;/a&gt;を再勉強中。&lt;a href=&quot;http://wicket.apache.org/docs/1.4/index.html?org/apache/wicket/markup/html/form/DropDownChoice.html&quot;&gt;DropDownChoice&lt;/a&gt;を使う最もシンプルなコードを作ってみた。&lt;br /&gt;&lt;pre style=&quot;overflow:scroll&quot;&gt;&lt;br /&gt;public class Sample extends WebPage{&lt;br /&gt; public Sample() {&lt;br /&gt;  Form&lt;WebPage&gt; form = new Form&lt;WebPage&gt;(&quot;form&quot;);&lt;br /&gt;  form.add(new DropDownChoice&lt;String&gt;(&quot;result&quot;&lt;br /&gt;   , new PropertyModel&lt;String&gt;(this, &quot;resultChoiceSelection&quot;)&lt;br /&gt;   , Arrays.asList(&quot;エラー&quot;, &quot;安打&quot;, &quot;二塁打&quot;, &quot;三塁打&quot;, &quot;ホームラン&quot;)&lt;br /&gt;   ));&lt;br /&gt;  form.add(new Button(&quot;submit&quot;));&lt;br /&gt;  add(form);&lt;br /&gt; }&lt;br /&gt; private String resultChoiceSelection;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;a href=&quot;http://wicket.apache.org/docs/1.4/index.html?org/apache/wicket/model/PropertyModel.html&quot;&gt;PropertyModel&lt;/a&gt;の行削ったり、変わりに&lt;a href=&quot;http://wicket.apache.org/docs/1.4/index.html?org/apache/wicket/model/util/ListModel.html&quot;&gt;ListModel&lt;/a&gt;だったりすると、submit時に例外が起こる。これってバグと言っていいと思うけどなぁ。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/4800280373352982520/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/4800280373352982520' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/4800280373352982520'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/4800280373352982520'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2009/11/wicketdropdownchoice.html' title='Wicket勉強中(DropDownChoice)'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-5261331968391578177</id><published>2008-12-17T22:22:00.000+09:00</published><updated>2008-12-17T22:22:05.182+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AspectJ"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><title type='text'>AJDTやAspectJをベースにしたツールの開発</title><content type='html'>&lt;a href=&quot;http://wiki.eclipse.org/index.php/Developer%27s_guide_to_building_tools_on_top_of_AJDT_and_AspectJ&quot;&gt;Developer&#39;s guide to building tools on top of AJDT and AspectJ&lt;/a&gt;&lt;br /&gt;AJDTやAspectJをベースにしたツールの開発ガイド。&lt;br /&gt;&lt;br /&gt;EclipseプラグインからAJDTを利用する方法や、AspectJのパーザ(.ajファイルパーザ)を利用する方法が解説されてる。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/5261331968391578177/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/5261331968391578177' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/5261331968391578177'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/5261331968391578177'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2008/12/ajdtaspectj.html' title='AJDTやAspectJをベースにしたツールの開発'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-7028370.post-3754142540971727103</id><published>2008-12-17T22:13:00.000+09:00</published><updated>2008-12-17T22:13:44.761+09:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Eclipse"/><category scheme="http://www.blogger.com/atom/ns#" term="Java"/><category scheme="http://www.blogger.com/atom/ns#" term="Mac"/><title type='text'>Java6 on MacOS X では Eclipse は動作しない</title><content type='html'>どうせならEclipseもJava6上で動かしてやろうと、Info.plistを編集してJava6を指定し、起動するとエラーが出た。&lt;br /&gt;&lt;br /&gt;調べてみると、MacのJava6自体は64bitで実装されているが、SWTが32bitにしか対応していないためらしい。&lt;br /&gt;&lt;br /&gt;&lt;a href=&quot;https://bugs.eclipse.org/bugs/show_bug.cgi?id=239301&quot;&gt;https://bugs.eclipse.org/bugs/show_bug.cgi?id=239301&lt;/a&gt;によると、現在SWTをCarbonからCocoaに移植してる(CocoaSWT)最中で、それが終わってから、Eclipse3.5くらいで64bit対応できるといいなぁって程度らしい。&lt;br /&gt;&lt;br /&gt;ちなみにCocoaSWTの移植が終わるのはSWT3.5のM6、March 13, 2009。&lt;br /&gt;64bit版はいつになることやら。</content><link rel='replies' type='application/atom+xml' href='http://takao.blogspot.com/feeds/3754142540971727103/comments/default' title='コメントの投稿'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/7028370/3754142540971727103' title='0 件のコメント'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/3754142540971727103'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/7028370/posts/default/3754142540971727103'/><link rel='alternate' type='text/html' href='http://takao.blogspot.com/2008/12/java6-on-macos-x-eclipse.html' title='Java6 on MacOS X では Eclipse は動作しない'/><author><name>Takao Nakaguchi</name><uri>http://www.blogger.com/profile/11311961225256792260</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>