case object
状態を持たないcase classは、シングルトンにしたいなぁと思っていたら、kmizuさんにtwitterでcase objectの存在を教えてもらったので、試してみる。
case object Test extends java.io.Serializable {
private def readResolve: AnyRef = this
}
$ fsc Test.scala
/tmp/Test.scala:1: error: double definition:
method readResolve:()java.lang.Object and
method readResolve:=> AnyRef at line 2
have same type after erasure: ()java.lang.Object
case object Test extends java.io.Serializable {
^
one error found
バグかなぁ。変だよね。MLに投げてみた。
P.S. 速攻でバグとの返信。というわけでバグレポートを入れておいた。
UbuntuでSilkypix
これまでは、Silkypixは、VMWareでWindows2000を立ち上げて使っていたのだけど、最近、Wineがどんどん改善されてきていて、ほとんど支障が無くなってきた。前は、マルチモニタでは、そもそもダイアログがクリックできなかったりして、ほとんど使いものにならなかったのだけど、今は、これといって問題が見当たらない。
Commons collectionsのTreeListをScalaで使う。
なんか、思ったよりも簡単にラップできるんだな。
import scala.collection.jcl.BufferWrapper
import org.apache.commons.collections.list.{TreeList => OriginalTreeList}
class TreeList[A] private (wrapped: OriginalTreeList) extends BufferWrapper[A] with Cloneable {
private var typedCache = wrapped.asInstanceOf[java.util.List[A]]
def this() = this(new org.apache.commons.collections.list.TreeList)
override def underlying: java.util.List[A] = typedCache
override def clone = {
val c = super.clone.asInstanceOf[TreeList[A]]
c.typedCache = new OriginalTreeList(wrapped).asInstanceOf[java.util.List[A]]
c
}
}
ちょっとテストしてみる。
class TreeListTest {
@Test
def test01 {
val list = new TreeList[Int]
0 until 10 foreach(list.add(_))
assertEquals(10, list.length)
0 until 10 foreach(i => assertEquals(i, list(i)))
val from6 = list.from(6)
assertEquals(4, from6.length)
6 until 10 foreach(i => assertEquals(i, from6(i - 6)))
val range3to9 = list.range(3, 9)
assertEquals(6, range3to9.length)
3 until 9 foreach(i => assertEquals(i, range3to9(i - 3)))
val l = list.filter(_ < 3)
assertEquals(3, l.length)
0 until 3 foreach(i => assertEquals(i, l(i)))
val c = list.clone
c.remove(0)
assertEquals(10, list.length)
assertEquals(9, c.length)
}
}
大丈夫みたいだ。こんなに簡単だとは思わなかった。
scala.Enumerationの直列化
2.7.2 RC2で直列化できるようになったので、試してみた。
import java.io._
object MyEnum extends test.Enumeration {
val One = Value("One")
val Two = Value("Two")
}
object Test {
def main(args: Array[String]) {
val baos = new ByteArrayOutputStream()
val oos = new ObjectOutputStream(baos)
oos.writeObject(MyEnum.One)
oos.close()
val data = baos.toByteArray()
println("size = " + data.length)
val ois = new ObjectInputStream(new ByteArrayInputStream(data))
val o = ois.readObject()
println(MyEnum.One eq o)
}
}
だと、falseと表示されるし、サイズが、1041バイトもあった。そりゃエンクロージングクラスのインスタンスまで直列化しているからなぁ。Enumerationのソースを見ると、
protected class Val(i: Int, name: String) extends Value {
...
private def readResolve(): AnyRef =
if (values ne null) values(i)
else this
}
なるほど。valuesがnullだから、新しくインスタンス生成されてしまうわけだ。でもまぁ、Scalaの場合、==は、equals呼び出しになるから、直列化復元で別のオブジェクトになってしまっても、値が一緒ならいいのかもしれない。メモリ節約にこだわるなら、
object MyEnum extends Enumeration {
protected class Val(name: String) extends super.Val {
private def readResolve(): AnyRef = MyEnum.apply(id)
}
val One = new Val("One")
val Two = new Val("Two")
}
なら、うまくいった。う〜む、しかしEnum一個直列化して1k byteでは、ちょっとなぁ。エンクロージングクラスの名前を取る方法があれば、なんとかできそうな気がするけど、この手のsyntheticフィールド名は実装依存だし、APIにも取得用のメソッドは存在しない。無理だろうか。
初めてのテレビゲーム
今の子供はやっぱりゲームだよな。と思い、自分が子供の頃を思い返してみたら、そういえばテレビゲームを作るのに挑戦したなと。確か型番はAY-3-8500-1。あ、あった。最後の-1は余計か。なんか記憶の中では40pinだったんだけど、実際は28pinだった。初歩のラジオに製作記事があって、プリント基板からエッチングして作ったけど失敗。これはモノクロだったけど、1年後くらいには、カラーの模造品が巷にあふれ、そのうちの1つのキットを買って、ようやく完成させた。なんか数10種類のゲームが! なんて売り文句だったけど、どれも微妙なバリエーションですぐにあきちゃったっけ。あれってワイヤードロジックだったのかな。
ラジオ青年
ノートPC用のマウスが壊れたんで、先週買ったんだけど、いきなり初期不良。まぁ1000円を切るレーザマウスなんで文句も言えないか。で、日曜日、交換ついでにちょっとパーツ屋(パソコンじゃなくて、電子パーツね)を徘徊。青年がラジオの部品を買っていた。なんか店のオヤジが「へぇ黒を2つ?、めずらしいね」って、客より店主の方が幸せそうだよ。で、あの角型の缶に入ったコイルは、なんだっけ。
あぁ、IFTか(Superhet Radio)。そういえば、そんな名前だったかな。コアに色が付いてて、用途が違うんだっけ。
昔は、ハンダ付けが不要なゲルマニウムラジオのキットが売ってて、それを叔父に買ってもらったんだけど、今探すと無いんだよね。もっとも今の子供は、ラジオのキットなんてあげても喜ばないかなぁ。
ベルギービール
いつもはレフ ブラウンを買っていたんだけど、レフ ラデュースとレフ ヴィエイユ キュベが新しく入荷していたので、飲みくらべ。
レフ ヴィエイユ キュベ(左)のほうが、後味がスッキリしていて好みかな。
サーバのCPUをアップグレード
Sempronが余っていたので、Athlon ThunderBird 1.4GHzから、Sempron 3GHzに変更。といっても、マザーがK7S5Aなんで、FSBが266MHzどまり。Sempronは333MHzでフル回転なんで、かなりダウンクロック状態。マザー替えればいいんだけど、そうすると、OS入れ直さないと、ちょっと気持ち悪いんで、これでがまん。マザーまで替えるなら、Core2Duoが余っているから、そっちに行きたいし、OSもUbuntu 8.04あたりにしたいし。
まぁ、夏は部屋が40度近くなるんで、もともとマッタリとダウンクロック運用の予定だったので、これでよしかな。JBoss起動が40秒ほどになった。前は1分以上かかっていたから、それなりに効果はあった模様。数年分の埃と猫の毛もきれいに取ってスッキリ。
コマンドラインからexplorer
コーディングの掟
コーディングの掟artonさんと「開発の現場」に連載していた記事。個人的には、artonさんの「例外のひき逃げ」が感慨深い。例外ケースまで含めて考えると、単にファイルにアクセスするだけでも、なんと奥の深いことか。SCMのページは、CVSとSubversionを使った際の典型的なシナリオを書いたので、正直、ここは自分のノート替わりに重宝しそう。こういう記事書く時って、割と「後で自分にも役に立つもの」を意識しているかもしれない。
ケータイのページスクロール
Seamのpages.xml
JBoss Seamでは、pages.xmlを使うとアクションベースの処理が書けるとのことなんで、サンプルのmessagesに、WEB-INF/pages.xmlを加えてみた。
<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd">
<page view-id="/foo.seam" action="#{messageManager.bar}"/>
</pages>
で、動作を確かめるために、MessageManager.java/MessageManagerBean.javaにbarメソッドを追加。
@Local
public interface MessageManager
{
...
public void bar();
}
@Stateful
@Scope(SESSION)
@Name("messageManager")
public class MessageManagerBean implements Serializable, MessageManager
{
...
public void bar() {
System.err.println("*** bar is called *** foo = " + foo);
}
}
で、http://localhost:8080/seam-messages/foo.seamをブラウザで開く。よ、呼ばれませんが。
色々ためしてみたら、どうもFaces Servletのservlet-pammingのurl-patternが、
<url-pattern>*.seam</url-pattern>
だと、だめみたい。
<url-pattern>/seam/*</url-pattern>
にしてやったら、動いた。そういうもんなのかな。
好きで嫌いなJSP
基本的に素のJSPは割と好き。エディタで直してブラウザでリロードすればokだし。実際のところ自分のとこみたいに、ワークフローがほぼ皆無なところでは、いわゆるWebアプリケーションフレームワークなんて不要で、JSPでゴリゴリ書いて、完成したらロジック部分をJavaに移して、WEB- INF/classesに移動するなんてやり方で作ることが多い。このサイト、RDBを全く使ってないしね。
でもやっぱりJSPは嫌い。共通部分をくくり出すのに関数とか作れないし、どうしてもとなれば、あの作るのもデバッグするのも死ぬほど面倒なタグライブラリを作らないといけない。ELは、#だの$だのと混沌としているし、そもそも美しくない。
GroovyのMarkupBuilderには、結構感動した。リンクのページはGroovletで書いてある。
import groovy.xml.MarkupBuilder
response.setCharacterEncoding("UTF-8")
out.println(
"""<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
""")
html.html(xmlns:"http://www.w3.org/1999/xhtml") {
head {
link(rel:"stylesheet", type:"text/css", href:"/stylesheet.css")
title("リンク集")
}
body {
h1("リンク集")
createBlogEntry(mkp)
...
}
}
void createBlogEntry(builder) {
builder.h2("Blog")
new Link(builder)
.setUrl("http://d.hatena.ne.jp/uno/")
.setBody() {"はてなダイアリー - unoの日記"}
.render()
...
}
みたいな感じ。で、こういうのScalaで書けたらなぁとTwitterでつぶやいたら、あっという間にkeisukenさんが作ってくれましたよ。LiftとかRailsとか、確かにとりあえず作るとこまでは簡単なんだけど、どうもブラックボックス部分が多すぎて、自分の思い通りにしようと凝りだすと、逆にえらく時間がかかってしまう。やっぱり必要最低限のことしかしないツールが、自分の好みだなぁ。まだ開発中だけど完成が楽しみ。
15Fまで行くエレベータに乗って。
14Fのボタンを見て、「あぁ、14は、IDEの割り込みだよなぁ」とか頭に浮かんで、ハッと我に帰った。疲れているのか??
PebbleをApache経由で使う。
8080は、メンテ用にローカルからしかアクセスできないポートとして残し、Apacheから、8009経由でTomcatにアクセスする。しかし、Apache経由でアクセスした場合に、Pebbleが出力する、baseタグが、なぜかhttp://192.168.0.250:8080のようになってしまい、ページ中のリンクが、8080経由にされてしまう。
HttpServletRequest.getServerPort()の結果は80だった。どこで、8080なんて値を仕入れているんだろう。
あった、BlogLookupFilterでやっている模様。
String url = pebbleContext.getConfiguration().getUrl();
if (pebbleContext != null && (url == null || url.length() == 0)) {
String scheme = httpRequest.getScheme();
url = scheme + "://" + httpRequest.getServerName() + ":" + httpRequest.getServerPort() + httpRequest.getContextPath();
log.info("Setting Pebble URL to " + url);
PebbleContext.getInstance().getConfiguration().setUrl(url);
}
ん〜、そもそもスレッドセーフとか考えていないような... というか、これシングルトンだから、先に来たやつを覚えて、ずっと使い回しちゃうじゃん。
さて、どうするか... まずは以下の戦略を試してみる。
PebbleContextをスレッドローカルに持たせ、シングルトンはやめてリクエストごとにUrlを設定させる。
まずは、PebbleContextをちょっといじって、
// private static final PebbleContext instance = new PebbleContext();
static final ThreadLocal<PebbleContext> instance = new ThreadLocal<PebbleContext>() {
@Override protected PebbleContext initialValue() {
return new PebbleContext();
}
};
public static PebbleContext getInstance() {
// return instance;
return instance.get();
}
BlogLookupFilterを修正。
PebbleContext pebbleContext = PebbleContext.getInstance();
AbstractBlog blog;
String url = pebbleContext.getConfiguration().getUrl();
// if (pebbleContext != null && (url == null || url.length() == 0)) {
String scheme = httpRequest.getScheme();
url = scheme + "://" + httpRequest.getServerName() + ":" + httpRequest.getServerPort() + httpRequest.getContextPath();
log.info("Setting Pebble URL to " + url);
PebbleContext.getInstance().getConfiguration().setUrl(url);
// }
pebbleContextがnullになるわけないから、if文自体を削除。そして、ぬるぽ。
12:54:37,310 ERROR [[jsp]] サーブレット jsp のServlet.service()が例外を投げました java.lang.NullPointerException at net.sourceforge.pebble.web.filter.BlogLookupFilter.doFilter(BlogLookupFilter.java:91) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)以下の行で発生している模様。
String url = pebbleContext.getConfiguration().getUrl();
pebbleContextがnullになるわけないんだから、getConfiguration()がnullなんでしょう。これを、設定しているのは、、、PebbleContextListenerみたい。
Configuration config = (Configuration)applicationContext.getBean("pebbleConfiguration");
DAOFactory.setConfiguredFactory(config.getDaoFactory());
PebbleContext ctx = PebbleContext.getInstance();
ctx.setConfiguration(config);
これが呼ばれていないんかなぁ。あ、そうか、このConfigurationは全スレッドに見えないといけないのに、これじゃ、たまたま実行したスレッドにしか設定されないじゃん。とりあえず、ServletContextListenerとして、アプリケーション起動時に一度呼ばれるだけみたいなんで、PebbleContextを以下のように応急処置。
// private Configuration configuration;
static volatile Configuration configuration;
...
// private String webApplicationRoot;
static volatile String webApplicationRoot;
...
public void setConfiguration(Configuration conf) {
// this.configuration = configuration;
configuration = conf;
}
...
public void setWebApplicationRoot(String root) {
// this.webApplicationRoot = webApplicationRoot;
webApplicationRoot = root;
}
動いた!。ちゃんとポート80経由で行った場合も8080経由で行った場合もうまく行くようになった。そろそろ本番サーバも切り替えて大丈夫そうかな。
PebbleをJava6で使う。
PebbleをJava6で起動しようとすると、こんなエラーが出る。
17:27:34,510 INFO [STDOUT] 2008-09-02 17:27:34,508 [main] ERROR org.springframework.web.context.ContextLoader - Context initialization failed java.lang.LinkageError: loader constraint violation: when resolving method "javax.xml.parsers.DocumentBuilder.setErrorHandler(Lorg/xml/sax/ErrorHandler;)V" the class loader (instance of org/jboss/classloader/spi/base/BaseClassLoader) of the current class, org/springframework/beans/factory/xml/XmlBeanDefinitionReader, and the class loader (instance of) for resolved class, javax/xml/parsers/DocumentBuilder, have different Class objects for the type org/xml/sax/ErrorHandler used in the signature
org.xml.saxlErrorHandlerが、Java6で標準に入っているのに、アプリケーションのクラスパスにも入ってますよ、ってことみたいだ。Pebbleのwarの中のWEB-INF/libの中を調べてみたら、tidy.jarというファイルの中に発見。org/w3c/dom/*と、org/xml/sax/*を削除。というか、org/w3c/tidy/Parser.classとか、何なんだよ、このパッケージ名は。
気を取り直して、再起動。全く同じエラーが出る。おかしいな。と思ったら、xmlrpc-1.2-b1.jarにも入ってる! こちらも削除して、ようやく立ち上がるようになった。
家のサーバにJBoss 5 CR1を入れてみた。
とりあえず、今入ってるTomcatとポートをずらして入れた。Tracとかも動かしたいんで、今度はApache経由に(これまではTomcatをポート80で開いて、直で使っていた)。さすがにFedora Core 1用のApache 2.2パッケージは見つからなかったんで、自分でコンパイル。
ダウンクロックしたAthlon(1GHz)だと、JBoss起動に3分半かかった。普段使ってるCore 2(3.7GHz)だと20秒だから、7倍近くパフォーマンスが違うのか。さて、これから中身の入れ替え。Pebbleも最新にしよう。
localhost:8080だと見えるのに、自分のip address:8080だと見えない。別のマシンからhttp://ip address:8080/でも見えなくて悩む。ずっとiptablesを眺めていたけど、JBossの起動時にrun.sh -b 0.0.0.0と指定することで解決。
JBoss5 CR1で、Sunのチュートリアルを動かす。
SunのJavaEEチュートリアルにある、EJB3のサンプル、converterを、JBoss5 CR1で動かしてみた。で、結論から言うと動かないわけなんだけど、原因を見てみると、EJBのルックアップで失敗している。JSFなら、インジェクションできるんだけど、JSPなんで、JNDIルックアップをしている。チュートリアルでは、
InitialContext ic = new InitialContext();
converter = (Converter)ic.lookup(Converter.class.getName());
という感じで、クラスのパッケージ名を含めた完全修飾名を渡すことで取得している。JBossでは、これだと見つからないと言われる。ここの名前は”アプリケーション名/EJB名/remote"という書式で指定する必要があるようだ(追記:nekopさんからの情報で、やはりJNDI名は仕様で規定されていないらしい)。
InitialContext ic = new InitialContext();
converter = (Converter)ic.lookup("converter/ConverterBean/remote");
先頭の"converter"は、EARファイルの名前から".ear"を除いたもの。ConverterBeanの部分はEJBの実装クラス名。これはよろしくないので、@Stateless(name = "XXX")として、抽象的な名前を指定してやるのが良いかと思われる。
で、これだけだと、Converterにはキャストできないというエラーが出る。どうも別のクラスローダで、Converterインターフェースを読んでしまうようだ(これはJBossのバグなのかもしれない これもnekopさんからの情報によると、チュートリアルが、EJBのjarを、Web moduleのWEB-INF/libに入れてしまっているせい。このあたりも現在の仕様では明確な規定が無いそうだ)。jndi.propertiesをWebアプリケーションのクラスパスに入れてやればok (これは、Web ModuleのWEB-INF/classes, libからEJBのインターフェースを除いてやれば不要)。
java.naming.factory.url.pkgs=org.jnp.interfaces.NamingContextFactory java.naming.provider.url=localhost
う〜む、このあたりの互換性が無いのは、問題なんじゃないだろうか。








