watermint.org

Takayuki Okazaki’s blog

来月12月11日(金)にJapan JavaFX User Groupの第二回勉強会が開催されます。
_DSC3148.jpg
勉強会の後には忘年会もありますので、両方あるいはどちらか片方だけでも参加される方はぜひ参加登録サイトよりお申し込みください。僕はProject MaiTaiの紹介をする予定です。Ustreamによる中継も予定していますので、遠方あるいはご都合のあわない方はぜひそちらをご覧ください。URL等は後ほどご案内いたします。

Project MaiTaiのHudsonスナップショットからはダウンロードできませんでしたが、Kenaiのプロジェクトからソースをダウンロードしてビルドしてみました。なかなか面白いかもしれません。
Project MaiTai
まだ不安定なところもありますが、ムービーとして書き出しができるなど今後の展開に期待が持てます。

JavaFXで面白そうなプロジェクトがスタートしています。Josh MarinacciのProject MaiTai櫻庭さんが紹介されているのでご存知のかたも多いかも。ただこのMai Tai。知ってる方はお分かりの通り、それなんてQuartz Composer?っていうぐらい見事にパクリ似ています。maitaiMai-TaiScreenSnapz005.png

こちらはProject MaiTaiの画面。ダウンロードできなかったのでプロジェクトページに載っているスクリーンショットから。
QuartzComposer.png
こちらがQuartz Composer。

先週の土曜日は日本Javaユーザグループ(JJUG)のJavaOne 2009報告会でした。今回は、ライトニングトークでしゃべらせていただきましたが、事前になかなか時間がとれず資料は当日つくることに・・。隣にaqubiさんがいらっしゃったのですが、資料作成にいっぱいいっぱいでaqubiさんを含め他の方ともあまりお話できませんでした・・。
_DSC0106.jpg
丸山先生よりJavaOne全体の報告。SunはOracleに買収されてしまうこともあり、JavaOneは今年で最後ではないか(少なくともSunとしてのJavaOneは最後)という哀愁が語られました。
_DSC0107.jpg
下道さん。JavaOneというよりは、同時に開催されたCommunityOneの報告。
_DSC0108.jpg
クラウドのデモ。正直GUIはしょぼい気がしますが、本当に使えるようになったらおもしろそうです。それにしても、Sun神宮前オフィスの地下会議室はどうも電波の状況が良くないですね・・。Emobileもつながりません。
_DSC0109.jpg
櫻庭さんより、JavaFXおよびJava SE 7について。いつも通り時間をかけて作られたことがよく分かるプレゼン。プレゼンソフトもJavaFXでかかれています。
_DSC0111.jpg
木村さんよりJavaのエンタープライズ周りについて。

ライトニングトークです。去年に引き続きJavaOneに行かないままJavaOne報告をすることになったのですが、今年はAIR JavaOneと題してネット越しにJavaOneを楽しむ方法をご紹介しました。下記に参考URLをリストしておきます。

_DSC0112.jpg
マイコミジャーナルに執筆されている杉山さん。プレスの立場での参加についてと、メディアのあり方について話されました。
_DSC0113.jpg
技術評論社の馮(ふおん)さん。杉山さんと同様、プレスとしての参加について、メディアのあり方について。
_DSC0114.jpg
続いて櫻庭さんより。今回のLTは固い内容が多かった、との話でした。櫻庭さんからはサンフランシスコ(というか海外に行くときの)での食事情改善方法について。ミシュランガイド、ぜんぜん使ったことありません。櫻庭さんが今回べた褒めされていたOpenTable。その日の予約を、Web上のみで完結できるというのがいいですね。ちなみに、OpenTableは日本版(東京)もあります。東京カレンダーとのコラボもしてるようです。
_DSC0115.jpg
最後は太田さんよりCommunityOneスピーカーとして話された時のお話。

今年から日本Javaユーザグループ(JJUG)スタッフになりました。あと、関わっているJava系のコミュニティー Glassfishユーザ・グループ・ジャパン日本JavaFXユーザグループCommunity of Communitiesに入れていただきました。
_DSC9626.jpg

Japan JavaFX User Group
年末にドメインを取得してから放置したままだったjavafx.jpですが、ようやく始動しました。最初、Google Appsでコミュニケーションツールをそろえようかと思いましたが、結局メーリングリストの管理がさほど楽ではないので少なくともメーリングリストに関してはGoogle Groupsを使うことにしました。若干登録作業など一般利用者の敷居が高いような気がしますが、手動での作業が増えてしまうGoogle AppsはやめてGroupsでの運用にしました。その辺の準備もやや時間がかかりましたがそれよりも、今回はCMSを入れた上にデザインを一から作るというのに一番時間を費やしてしまいました。CMSにはWordPressを使っています。WordPressはブログエンジンですが、近代的なブログエンジンは簡単なCMSとしても使えますし、下手に高機能なCMSを使うより結果的にうまく運用できる印象があります。
_DSC8454.jpg
午前中である程度形にしようと思っていたのに、結局10時間ぐらいかかりました。Illustrator CS4のクラッシュ2回、Fireworks CS3のクラッシュ1回も泣き所でした。今週のjavafx.jpに関する作業はこのあたりにしておきますが、次は内容の整備ですね。

円高だということもあって思い切ってFlickrExport for Apertureを買ったのはいいものの、品質が低すぎて正直返品したい。良くても100枚一度にアップロードできない。ネットワークが途中で切断されたりすると必ずクラッシュする。ひどいときにはたった5枚すらアップロードする前にクラッシュ。しかも、単にpluginがクラッシュするだけではなくてApertureを巻き込んでクラッシュするのでたちが悪い。Nikon D90を買ってから急激に写真の枚数が増えてきたので、安定したアップローダがやっぱりほしい。jUploadrが一番安定しているから1000枚単位の大量アップロードをするときにはまだこれを使っているけど、いくつか不満な点が。
R0024191
その1。サムネイルを作る時間が長すぎるし、CPUを使いすぎる。jUploadrに写真をDrag & Dropすると写真のサムネイルを作って、その後、Photosetに入れたりするための操作ができるんですが、まずこのサムネイルを作るのにとても時間がかかる。1000枚単位の追加をするとCPUは20分ぐらい100%フル稼働でほかのことができない。そもそも、Apertureで整理した後なので、サムネイルを確認して一枚ずつ設定を変えることもないからサムネイル作成処理は僕にとって不要。ディレクトリ単位でアップロードの設定ができれば十分。個別にいじりたいときでもファイル名だけわかればとりあえずApertureで確認できるし、そのときに個別にサムネイルを作ってほしい。
R0024208
その2。Photosetsへの写真追加に失敗することがある。今まで試した、FlickrExport for Aperture、1001、jUploadr、Flickr Uploadr 2.x/3.xのいずれでも、あらかじめ設定しておいたとおりに写真がPhotosetsに追加されずに全部の処理が完了されましたと表示されてしまうことがあって、結局あとからもう一度手動でOrganizerを使って整理するのが必須でした。1000枚単位の写真をWeb画面で整理し出すと大変なので、このあたりの処理が確実に実行されるか、再試行について詳しく設定できるようにしてほしい。
R0024236
その3。未現像のRAW画像について設定ができない。これは仕組み上しょうがないんだけど、Apertureなどのような現像ソフトを使っている場合、RAW画像はAperture上で管理されていて、JPEGに書き出したファイルについてようやくjUploadrがファイルとして認識して、アップロードのタスクとして追加される。RAW現像にかかる時間もなかなか侮れなくて1000枚単位だと20分ぐらいはかかるし、いちいちRAW現像処理が終わったことを目視確認してからjUploadrにかけるなんてやってると、面倒くさい。なので、できればApertureなどであらかじめ指定したディレクトリにRAW現像するようにして、現像が終わったJPEGファイルからディレクトリに対して設定されたメタデータを元に随時アップロードするようにしてほしい。またアップロードができたJPEGファイルが完了後に自動削除されるオプションがあるとよりうれしい。
_DSC3935-D
そんなこんなで前々から作ろうと思っていたFlickrへのバッチアップローダを作り始めました。Flickr APIは認証周りの実装が面倒くさいので敬遠していました。どうせ作るなら安定して使えるJavaで実装したいのだけど、JavaのFlickr APIライブラリは古かったりアップロードAPIが実装されていなかったりするのも今まで着手していなかった理由の一つ。でも今回は、今実家にいてすることがないので、がっつりと時間を使って実装に集中することができ、とりあえず認証まわりとアップロードまでのライブラリが作りあがりました。次は、UIとアップロード順序の管理をするあたりを作ります。アプリケーションの名前はj20000という名前にしました。

id:Yoshioriさんの作ってるやつの別実装。id:taichitaichiさんのとも別の視点で作ってみました。ポイントは次のような感じです。

  • 一定時間しか保持しない、というよりは一定時間経過したエントリを隠す。
  • タイムアウトしたエントリの自動削除機構までは含まない(Mapの使われ方によって削除のタイミングはニーズが読めない)。ただし、明示的な削除方法は提供する。
  • 見えなくなる時間がわりと正確。ScheduledExecutorServiceを使って、正確なタイミングで削除されることを信頼しない。

この実装では、毎回Mapの操作に対してその時点でのスナップショットを作成します。snapshotの作成はわりと高価な作業ですが、それよりも時間に対して正確であることを目標としています。経験的に、Mapに数万エントリも入っていることはふつうの用途としてはまれなので、そこは目をつぶります。スレッドセーフとなるように気をつけましたが、テストはしていません。あ、全体的にテストはしていません。

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 指定時間以上経過したエントリを隠すマップ.
 *
 * @author takayuki
 */
public class TimeoutMap<K, V> implements Map<K, V>, Serializable {
    private final long timeout;
    private ConcurrentHashMap<K, ValueAndTime<V>> map
            = new ConcurrentHashMap<K, ValueAndTime<V>>();

    final class ValueAndTime<V> implements Serializable {
        private V value;
        private long time;

        public ValueAndTime(V value, long time) {
            this.value = value;
            this.time = time;
        }

        public long getTime() {
            return time;
        }

        public V getValue() {
            return value;
        }
    }

    final class EntrySetImpl<K, V> implements Map.Entry<K, V>, Serializable {
        private K key;
        private AtomicReference<V> value;

        public EntrySetImpl(K key, V value) {
            this.key = key;
            this.value = new AtomicReference<V>(value);
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value.get();
        }

        public V setValue(V value) {
            return this.value.getAndSet(value);
        }
    }

    /**
     * コンストラクタ.
     * @param timeout タイムアウト(ミリ秒).
     */
    public TimeoutMap(long timeout) {
        this.timeout = timeout;
    }

    private ConcurrentHashMap<K, ValueAndTime<V>> snapshot() {
        long t = System.currentTimeMillis() - timeout;
        ConcurrentHashMap<K, ValueAndTime<V>> snapshot
                = new ConcurrentHashMap<K, ValueAndTime<V>>(map);

        for (K key : snapshot.keySet()) {
            if (snapshot.get(key).getTime() > t) {
                snapshot.remove(key);
            }
        }

        return snapshot;
    }

    private Collection<V> snapshotOfValues() {
        Collection<V> values = new ArrayList<V>();
        Map<K, ValueAndTime<V>> snapshot = snapshot();

        for (K key : snapshot.keySet()) {
            values.add(snapshot.get(key).getValue());
        }
        return values;
    }

    /**
     * タイムアウトしたキーのエントリを削除.
     */
    public void gc() {
        map = snapshot();
    }

    public int size() {
        return snapshot().size();
    }

    public boolean isEmpty() {
        return snapshot().isEmpty();
    }

    public boolean containsKey(Object key) {
        return snapshot().containsKey(key);
    }

    public boolean containsValue(Object value) {
        return snapshotOfValues().contains(value);
    }

    public V get(Object key) {
        ValueAndTime<V> v = snapshot().get(key);
        return v == null ? null : v.getValue();
    }

    public V put(K key, V value) {
        ValueAndTime<V> prev = map.put(key,
                new ValueAndTime(value, System.currentTimeMillis()));
        return prev == null ? null : prev.getValue();
    }

    public V remove(Object key) {
        return map.remove(key).getValue();
    }

    public void putAll(Map<? extends K, ? extends V> map) {
        for (K key : map.keySet()) {
            put(key, map.get(key));
        }
    }

    public void clear() {
        map.clear();
    }

    public Set<K> keySet() {
        return snapshot().keySet();
    }

    public Collection<V> values() {
        return snapshotOfValues();
    }

    public Set<Entry<K, V>> entrySet() {
        Set<Entry<K, V>> entryset = new HashSet<Entry<K, V>>();
        Map<K, ValueAndTime<V>> snapshot = snapshot();

        for (K key : snapshot.keySet()) {
            entryset.add(new EntrySetImpl(key, snapshot.get(key).getValue()));
        }

        return entryset;
    }
}

追記: スレッドセーフにしたいなあと思って書いているといろいろ、ぼろが見つかりますね。gc()が実行されている間、put(Object key, V value)と、remove(Object key)の実行をブロックしないとエントリの欠落や削除漏れが出そうです。

リリース当時は結構さわっていましたが、今年はあまりさわっていなかったのでとりあえずインストールしました。NetBeans 6.5とプラグイン、あとProduction Suite (イラレ、フォトショ用のJavaFX書き出しプラグイン)はまた今度さわる予定です。インストールしたのはJavaFX 1.0 SDKです。コマンドラインからJavaFXアプリケーションの実行をサポートするためのコマンドとライブラリがインストールされます。
今のところJavaFX 1.0 SDKはWindowsとMac用に提供されていますが、以下Mac版の話として書いていきます。
JavaFX 1.0 SDK disk image
Mac版のバイナリはdmg形式で配布されていて、開くとパッケージインストーラが利用可能になります。今回のJavaFX 1.0 SDKはSunのアプリケーションにしては珍しく、”Macらしい”アプリケーション構成になっています。インストールすると、/System/Library/Frameworks/JavaFX.framework/ というディレクトリが作成され、Mac用JDKとほぼ同様のディレクトリ構成でインストールされ、/System/Library/Frameworks/JavaFX.framework/Versions/Current/bin/javafxなどのコマンドが /usr/bin/javafxなどにシンボリックリンクされます。
JavaFX 1.0 requires JDK 6
インストーラの情報ではJava SE 6が実行環境として必要と出ますが、Mac版ではJDK 1.5.0_13以降 (Tigerでも実行可)が入っていればよいので、うちのMacのようにCore 2 Duo (64bit CPU)ではないのでJDK 6が使えないという悲しい環境でもJava FXは動作します。
ところで、インストーラで気になったのが使用許諾契約。
Empty EULA
からっぽです。
Agree with empty EULA
どうしようもないので、同意しましょう。

先週はSunが2007年に発表して、暖めてきたネタであるJavaFXの1.0リリースが出ましたね。そのリリース日、飲み会の話の流れで何となくドメインをとってしまいました。javafx.jpです。openjfx.dev.java.net/jaもあるし、使いどころが謎ですが、使い道は飲み会ドリブンで考えていこうと思います。何かアイデアがあれば是非。