java
Up one levelJavaでdelegate
grep -iv lru_languages java が空文字列で悲しいですこんにちは。先日javaではinterfaceを一々定義しないとdelegateが使えないという衝撃的な事実を知りました。理論的には言語レベルで適切な変換をかければ互換性を保ちつつC#のdelegateのような書き方ができるはずなんだけどと脳内で愚痴りつつ対策を考えてみました。もちろん僕は必要になるごとにinterfaceをちまちま書くような丁寧なプログラマではないので、汎用的に使えるdelegate interfaceを実装することになります。それで出来上がったのが以下。
Delegate.java:
public interface Delegate<T, R> { public R invoke(T arg); }
言語的に適切にマングリングかければ互換性を保持しつつC++みたいなtemplate汎用性を実現できるのになと脳内で愚痴りながら、2引数版。
Delegate2.java:
public interface Delegate2<T1, T2, R> { public R invoke(T1 arg1, T2 arg2); }
それでたとえばC++でいうfind_ifをリストコンテナに書く場合は、
public Foo[] findIf(Delegate<Foo, Boolean> pred) { List<Foo> result = new ArrayList<Foo>(); for (Foo child: children) { if (pred.invoke(child)) { result.add(child); } } return (Foo[])result.toArray(new Foo[0]); }
となります。使うには、
MyList list = ...; Foo[] found = list.findIf(new Delegate<Foo, Boolean>() { public Boolean invoke(Foo f) { return f.name.equals("Bar"); } });
です。ただdelegate内部で外部変数を使いたいというときに、この方法では独自にクラスを定義しなくてはいけないので面倒です(というか本末転倒)。ということで以下のようにClosureを定義します。
Closure.java:
public abstract class Closure<D, T, R> implements Delegate<T, R> { protected D data; public Closure(D data) { this.data = data; } public abstract R invoke(T arg); }
Closure2.java:
public abstract class Closure2<D, T1, T2, R> implements Delegate2<T1, T2, R> { protected D data; public Closure(D data) { this.data = data; } public abstract R invoke(T1 arg1, T2 arg2); }
これでたとえば先のfindIfの例だと、
MyList list = ...; String name = "Bar"; Foo[] found = list.findIf(new Closure<String, Foo, Boolean>(name) { public Boolean invoke(Foo f) { return f.name.equals(data); } });
といった具合に書けるのです。ちなみに標準でこういうinterfaceが含まれているかもしれないと1.4(Tigerじゃないと駄目?)内を探してみましたが、無いようです。以前どこかで見たような気がするんですが。まあいいでしょう。javaもそろそろtemplate overloadingあるいはdefault template valueをサポートしてくれないかな、無いとtupleも書けないしつらすぎる。
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/delegate_on_java/tbping
RhinoのScriptableObject.defineClass(Scriptable, Class, Boolean, Boolean)で死なないためのパッチ
RhinoのScriptableObject.defineClass(Scriptable scope, Class clazz, boolean sealed, boolean mapInheritence)でInstantiatationExceptionになる人はScriptableObject.javaを以下のようにいじくってみるといいかも。
ScriptableObject.java:850付近:
-if (ScriptableRuntime.ScriptableClass.isAssignableFrom(superClass)) { +if (ScriptableRuntime.ScriptableClass.isAssignableFrom(superClass) && !superClass.isAssignableFrom(ScriptableObject.class)) {
どうやらScriptableオブジェクトを作るのにabstractなクラスのScriptableObjectで実装しているとScriptableObjectのnewInstanceで死んでしまうらしい。上のパッチはScriptableObject以上のクラス階層をprototype展開しないようにする適当コード。ScriptableObjectで実装しなければたぶんこの問題はでてこないだろうけど、
http://www.mozilla.org/rhino/apidocs/org/mozilla/javascript/ScriptableObject.html
ここを見る限りではScriptableObjectを使ったほうがよさ気な雰囲気をだしている。
http://lxr.mozilla.org/mozilla/source/js/rhino/examples/Counter.java
これでも使ってるし。おそらくこのサンプルクラスもmapInheritenceありでdefineClassしたら死ぬんじゃないかと。CVSの最新でもいまだに直ってないような雰囲気。やる気ないのかな。
- Category(s)
- java
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/rhino-scriptableobject-defineclass-patch/tbping
Re:RhinoのScriptableObject.defineClass(Scriptable, Class, Boolean, Boolean)で死なないためのパッチ
DOMのEventsは遅い?
DOMのイベントはノードが深くなればなるほど処理に時間がかかるという不思議なスペックになっています。キャンセレーションの機能はあるものの、イベントがノードにディスパッチされた時点で、ディスパッチ処理がドキュメントからそのノードへ遷移 していくキャプチャフェーズの処理はどうしても避けることができません。つまりノードからparentNodeをn回くりかえしてドキュメントまで辿りつく処理は避けられないのです。もちろんclickとかmousedown程度では気にするほどの処理ではないのですが、たとえばmousemoveを処理しようとすると、マウスを動かすたびにイベントをディスパッチして例のキャプチャフェーズに加えて付加的にですがバブルフェーズまでもが実行されます。これの気持ち悪いところは先にも言ったとおりノードが深くなるにつれて一個のイベントの処理にかかる時間も増加するというところにあります。ちなみにHTMLのmousemoveはcancelableです。たぶんそのことを考慮したのだと思います。
それで、先日そのことが少し問題になったので対策を考えてみました。避けられないキャプチャフェーズの遷移をなんとかする対策。おそらくこれの根本的な対策はひとつしかなくて、つまりイベントをディスパッチしないという方法です。例えば
node.dispatchEvent(evt);
と確実にディスパッチしているところを、
if (!node.getAttribute(evt.getType() + "Event").equals("false")) { node.dispatchEvent(evt); }
として、mousemoveEvent="false"というアトリビュートを追加しておきます(node implements Element, EventTarget)。getAttributeの処理が多少重そうですが、これはおよそ一定時間で終了するのでよしとしましょう。ただこの対策は最終手段です。可能ならば
document.addEventListener('mousemove', function (e) { e.stopPropagation(); }, true);
として、できるだけイベントハンドリングの処理を省いてしまう方法を使うほうがいくらか健全です。どちらにしても強引なことには変わりありませんが。あとcancelableじゃないイベントは効果ないので、手当たり次第cancelableなイベントとして実装してしまうか、
function EventCanceller(e) { e.initEvent(e.type, false, true): e.stopPropagation(); } document.addEventListener('mousemove', EventCanceller, true);
のように無理矢理cancelableにしてからキャンセルしてしまいましょう。もちろんこの方法は実装に依存します。が、普通の実装をしていれば動くはずです(DHTMLじゃうごかなさそうだけど)。ちなみにinitEvent系は二回以上呼べます(もちろん実装依存)。これに関連したもうすこし現実的な対策は、documentにaddEventListenerするのではなくて、ノードを保持する構造としてのノードを適当に選びだして、同様なキャンセル処理をaddEventListenerでいれてしまいます。もしくは全然違う方法として(かつ根本的な解決),Eventの中にcaapturableという独自の仕様を作ってしまって、これがtrueの場合にのみキャプチャフェーズを開始するようにするなど。
まあ実装を自由にいじくれるので時間さえあればなんとかなるのですが、ディスパッチをそもそも無視してしまうのはキャプチャフェーズは確実に起こるというDOM Eventsの仕様に反するような気もします(この際全然気にしないのですが)。DOM Coreは割としっかりしている印象がありますが(NS系はすごい気持ち悪いですが)、DOM Eventsは全体においてしょぼいような気がするのです。いまだにKeyEventの仕様ないし。
結局のところ有効な対策としては、そもそもイベントをディスパッチしないようにする、キャプチャフェーズの面倒をあれこれ見てやる、getElementByIdとかgetAttributeとかchildNodesとかつまりDOMの全体的な機能を改良する(間接的なのですがリスナ側から使うのは確実なので。それにたぶん今回問題になったのはここ)、基本的にパフォーマンスの高い言語を選択する、DOMを捨てる(あるいはDOM Events)、なのかなあと思うのです。つまるところ、普通に考えればDOMNodeInsertedIntoDocumentとかDOMAttrModifiedの処理が頻繁に起こりすぎて普通の実装ではすぐにガタがきてしまうのは必至なのですが、大したことないだろうと楽観視していて痛い目にあったというそういう話です。そういう経験をしているのでDHTMLレンダリングエンジンはキャッシュとかアルゴリズムを駆使してできるだけ重くならないように工夫しているのだろうと容易に想像できるわけです。
最後にW3Cのリンクを貼っておきます。プログラミングに使う構造の一つとして勉強してみれば、結構ちゃんとした仕様になっていることがわかると思います。今回の仕事はかなり失敗気味ですが(もちろんなんとかしますが)、DOM CoreとかDOM Eventsに関してかなり詳しくなったので、これはこれで勝手によしとしています(今ぐらいDOMに詳しければ最初からAjax使ってたと思いますが、実際知らなかったので仕方ありません)。個人的にはhasFeatureとかcreate*とかimportNodeとか、かなりアイデアが得られました(Core 3はどんどん煩雑になっている感じをうけますが)。
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/dom-events/tbping
emacsでAntを使う
M-x compile RET ant RET でコンパイルできるといえばできるのですが、Ant特有のフォーマットが原因で正しくエラーメッセージをパースすることができないことがあります。antに-emacsオプションをつければ(標準の)emacsがパースできる形で表示してくれるらしいのですが、.antrcに自動判別コードを書けばもうすこしスマートにすることができるので紹介しときます。
.antrc:
# Detect (X)Emacs compile mode if [ "$EMACS" = "t" ] ; then ANT_ARGS="$ANT_ARGS -emacs" ANT_OPTS="$ANT_OPTS -Dbuild.compiler.emacs=true" fi
(詳しくはhttp://www.jajakarta.org/ant/ant-1.6.1/docs/ja/faq.html#emacs-mode)
ちなみに僕はマクロを使ってcompileの手間を省いています。 C-x ( compile RET C-a C-k cd /path/to/proj && ant RET C-o TAB C-) みたいな感じで登録しておいて、あとは C-x e でcompileを実行します。ある知人はC-c C-cをcompileに割りあてているらしいのですが、最近emacsのキーバインディングをいじるのが怖くなて、大体はマクロだとか関数だとかで解決しちゃっています。キーバインディングはできるだけデフォルトで一貫するのが一番かなと思っています。それなりに吟味されているわけですし。
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/emacs-ant/tbping
DocumentBuilder がリモート DTD を読みにいかないようにする
少し意外なのですが、 DocumentBuilder.parse はリモート DTD の場合でも強引に読みにいくらしいことが判明しました。ドキュメントをバリデートするためではなく( setValidation(false) )、パース中に遭遇したエンティティを解決するためにあらかじめ DTD を読みにいくのですが、インターネット環境が万全であることを前提としていないアプリケーションでは少しばかり困ったことになります(エンティティに遭遇するまで読みにいくのを遅延するぐらいはしてくれてもいいと思うのです)。 そういう時は以下のようなコードを加えてローカルから DTD を引っぱるようにしましょう。
HogeResolver.java:
package hoge; import java.io.IOException; import java.io.FileInputStream; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; public class HogeResolver implements EntityResolver { private static final String DTD_NAME = "hogehoge"; public InputSource resolveEntity(String publicId, String systemId) throws IOException { try { if (systemId != null && systemId.indexOf(DTD_NAME) > systemId.lastIndexOf("/")) { InputSource source = new InputSource(new FileInputStream("hogehoge.dtd")); source.setPublicId(publicId); source.setSystemId(systemId); return source; } } catch (IOException ex) { } return null; } }
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilder builder = factory.newDocumentBuilder(); builder.setEntityResolver(new HogeResolver()); Document doc = builder.parse(new File(fileName)); // ...
例えば以下のように DOCTYPE を宣言すればリモートからではなくローカルから DTD をひっぱってくるようになります。
<!DOCTYPE beans PUBLIC "-//HOGE//DTD HOGE//EN" "http://hoge/hogehoge.dtd">
ちなみに resolveEntity で null を返した場合はエラーではなくてデフォルトの振舞いになるので気をつけましょう。
- Category(s)
- java
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/let-documentbuilder-not-to-read-remote-DTD/tbping
Java での単語単位編集を効率的に
どうやら c-mode には subword-mode なるマイナーモードがあって、これを有効にしておくと camel notation なシンボルの編集がすこしばかり効率的になりそうです。例えば hogeFooBar というシンボルの末尾にカーソルがある状態で backward-word すると、 subword-mode が無効な状態ではそのままシンボルの先頭に移動してしまうのですが、 subword-mode を有効にしておくと上手にキャピタルを認識して B に移動してくれます。もちろん backward-kill-word や forward-word でも正しく動作します。
使い方は簡単で M-x c-subword-mode RET あるいは C-c C-w で subword-mode の有効/無効を切りかえることができます。 java-mode の場合は自動的に subword-mode になって欲しいので以下のような記述を .emacs に追加しておくといいかもしれません。
~/.emacs:
(add-hook 'java-mode-hook '(lambda () (c-subword-mode)))
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/effeciently-word-motion-in-java/tbping
Flymake を使って編集中にシンタックスエラーを検出する
ひげぽんの auto-compile.el の記事(*)を見たときに、 make のアウトプットを解析してエラーを解析すれば動的シンタックスチェックできるじゃんと思っていたのですが、たまたま今日 Emacs Wiki を見ていたらまさにその通りの機能を提供する Flymake に出会いました。動作原理は単純で、ファイル名からビルドするのに使うプログラムを調べて、それを適当なタイミング(保存時とか)にバックグラウンドで走らせます。その際に出力されたアウトプットを解析しエラーがあればそのファイル名や行番号からエラー箇所を特定してビジュアルに知らせてくれるのです。
(*) http://d.hatena.ne.jp/higepon/20061107/1162902929
例えば以下のように編集して保存します。
(バックグラウンドで ant が走って)エラーがあった場合、以下のようにビジュアルに知らせてくれます。
今回は ArrayList が見つからないというエラーなので、以下のように修正して保存すると、エラーが消えます。
このようにして java ではありがちな import 忘れを初期のうちに極力減らすことができます(もちろんその他のシンタックスエラーも)。ちなみにビルドが頻繁に行われるので編集が重たくなるかもしれませんが、(その代わりと言っていいのかわかりませんが)いざ手動でビルドするっていう時は前回のビルド結果が継続されるので通常よりビルドを速く終わらせることができるかもしれません。
さて、実際に Flymake を使うための設定を紹介しておきます。デフォルトの設定はなかなかと難ありで少しいじくる必要があります。
~/.emacs:
(load "flymake") ;; redefine to remove "check-syntax" target (defun flymake-get-make-cmdline (source base-dir) (list "make" (list "-s" "-C" base-dir (concat "CHK_SOURCES=" source) "SYNTAX_CHECK_MODE=1"))) ;; specify that flymake use ant instead of make (setcdr (assoc "\\.java\\'" flymake-allowed-file-name-masks) '(flymake-simple-ant-java-init flymake-simple-java-cleanup)) ;; redefine to remove "check-syntax" target (defun flymake-get-ant-cmdline (source base-dir) (list "ant" (list "-buildfile" (concat base-dir "/" "build.xml")))) (add-hook 'java-mode-hook '(lambda () (flymake-mode)))))
まず、 Flymake はビルドプログラムに check-syntax ターゲットを渡す傾向があるので、そいつを取りのぞいてやります。これはもちろん Makefile とか build.xml に check-syntax ターゲットを追加しても問題ないのですが、そんなこといちいちしていられないので、デフォルトターゲットを優先させるほうが良いでしょう。また java の場合、デフォルトのビルドプログラムが make になっているのですが、今時 make で java プログラムをコンパイルしている人なんて居なさそうなので ant に書きかえることにします(書きやすさ・速度の点では圧倒的に make のほうがいいのですが…)。あとは多少強引ですが java-mode 起動時に flymake-mode になるようにしておきます。
これで Emacs は編集機能の点でも Eclipse を圧倒的に凌駕しました。
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/detect-syntax-errors-by-flymake/tbping
DOM Node を文字列に変換する
Conv.java:
package hoge; import java.io.*; import javax.xml.transform.*; import javax.xml.transform.stream.*; import javax.xml.transform.dom.*; import org.w3c.dom.*; public class Conv { public static void main(String[] args) { Node srcNode = /* get from somewhere */; StringWriter writer = new StringWriter(); Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.transform(new DOMSource(srcNode), new StreamResult(writer)); System.out.println(writer.toString()); } }
これで srcNode がエレメントであろうが属性であろうが正しく文字列に変換されます。
この Transformer は DOM -> OutputStream や InputStream -> DOM や InputStream -> XSLT -> OutputStream などいろいろできる汎用インターフェースのようですが最近まで全く知りませんでした。汎用的すぎるいい例だと思います。
- Category(s)
- java
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/convert-dom-node-to-string/tbping
Re:Javaでdelegate
MyList list = ...;
final String name = "Bar";
final int age = 19;
Foo[] found = list.findIf(new Delegate
public Boolean invoke(Foo f) {
return f.name.equals(name) && (f.age == age);
}
});
delegate内部で外部のオブジェクトを複数利用したいときdataとなるクラス(パラメータークラス)を新しくつくるのも本末転倒です。
まず匿名クラスをつくる時点でかなりその処理コンテキストに依存してることは明白なので、外部データ用にテンプレートで対処しておくというのはあまり意味が無い気がします。
それよりfinalしてしまったほうが便利な気がするのですが、何か他の理由があるんでしょうか?
Re:Javaでdelegate
public Foo getFoo(String name) {
return findIf(new Closure<String, Boolean, Foo>(name) {
public Boolean invoke(Foo f) {
return f.name.equals(data);
}
})[0];
}
を定義したい場合にfinalで実装できるのか、という疑問があるのです。