Scala+AndroidでGoogle Mapsがコンパイルエラーになる(解決編?)

追記(2010/07/27): rev22630にて問題は修正されたようです。次期バージョン(2.8.1?)から下記現象は発生しないと思われます。
追記2(2010/10/03): Scala2.8.1.RC1でコンパイルし、問題が修正されていることを確認しました。

前回もScala+AndroidGoogle Mapsコンパイルできないという話を書いて、そのときはScalaMapを使って回避したんだけど、Scala2.8.0RC3からScalaMapを使ってもコンパイルでMissing Dependencyが出るようになってしまった。*1

  [scalac] error: error while loading MapView, Missing dependency 'class android.view.ViewGroup$LayoutParams', required by /opt/android/add-ons/google_apis-7_r01/libs/maps.jar(com/google/android/maps/MapView.class)

原因判明

ScalaMapの作者(brianhsu)さんが報告しているように#2464が原因なんだろうと思ったけど、そもそもこの不具合はすでに修正されている。実際手元のscala2.8.0RC6でもコンパイルは通る。(ただし#2464でbrianhsuさんの投稿したコードはあいかわらずMissing Dependencyになる)


最初はそれでもscalac側のみの問題だと考えて、不具合が再現するコードを書こうとしたが、どうにもうまくいかない。
そこで視点を変えてmaps.jar(Google Mapsライブラリ)のほうを疑うことにしてみたがこれがどうやら当たりだった。


もともとsdkで提供されているmaps.jarはほぼすべての実装がthrow new RuntimeException("stub");に置き換えられているスタブなのだけど、これを実現していると思われるのがmkstubsというツール。
このツールはAndroidのソースツリーの中にある。(development/tools/mkstubs)


このツールをつかって元のjarからスタブjarを生成する際にどうやら、javacでは解決できるけどscalacでは解決できない依存性をもつclassファイルが生成されてしまっているようなのだ。
特にstatic innerクラス(orインターフェイス)周りで依存性の問題が発生しやすい感じ。

カスタムmaps.jarの作成

ではどうやってこの問題を回避するか。結局のところコンパイルエラーになるclassファイルをmkstubsを通す前のものにすればよい。実際のソースは手に入らないけれど、結局はスタブなのだからインターフェイスさえちゃんとしていれば関数の中身とかはテキトウでよいはずだ。
というわけでmaps.jarをばらしてclassファイルを置き換えて再構成することにする。

http://gist.github.com/454723
http://gist.github.com/454727
以上2つのファイル(それぞれMapView.javaとMapController.javaのスタブ)をコンパイルして

$ javac -cp android.jar:maps.jar MapView.java
$ javac -cp android.jar:maps.jar MapController.java

MapView.classとMapController.classを置き換えたmaps.jarを作成する。

$ mkdir /tmp/workdir; cd /tmp/workdir
$ jar xf fuga/maps.jar
$ cp hoge/MapView.class com/google/android/maps
$ cp hoge/MapController.class com/google/android/maps
$ jar cf ../maps.jar *

できたmaps.jarを使えばScalaでもGoogle Mapsを使ったコードがコンパイル可能になる。
(ScalaMapはprotected static innerクラス回避のためにまだ必要)

最後に

結局この話はscalacの不具合なのだろうか、それともmkstubsがたまたまjavacは通るけど何かしら不正な形にclassファイルを変換してしまっているのだろうか。
自分はclassファイルのフォーマットにも明るくないし、mkstubsが完全には何をしているか把握していないので今の時点ではなんとも言えないのが残念。


追記(2010/06/28):
mkstubsのREADMEみたらこんなことが描いてあった。

- The generated constructors are not proper. They do not invoke the matching super()
  before the generated throw exception. Any attempt to load such a class should trigger
  an error from the byte code verifier or the class loader.

mkstubsで生成したスタブのコンストラクタではsuper呼んでないのでクラスローダではエラーになると。
scalac側には非はないと考えて良いのかな。

*1:正確にはRC1ではコンパイルできてたけど、RC3ではできなくなっていた。RC2では試していない