都元ダイスケ IT-PRESS このページをアンテナに追加 RSSフィード

最近は会社ブログしか書いてません。

2011-12-25

[]細かすぎて伝わらないJava7の変更点

本日のエントリーはJava Advent Calendarの25日目です。昨日は @mike_neck さんのmike、mikeなるままに…: hamcrestを拡張してmoreThanとか作ってみたでした。本日はクリスマスですが、Advent Calendarはまだまだ続きます。明日はt.ogisawaさんのno titleです。

さて、今年はJava7がリリースされましたね。try-with-resources、diamond operator、invoke dynamic、Folk/Join framework…魅力的な新機能の数々が、多くの人によって紹介されています。が、Java7の変更はそれだけじゃないはず。小粒だが、キラリと光る変更がきっとあるはず。ということで、Java6とJava7のsrc.zipを比較してみました。小ネタなのでサラっと読んで頂ければと思います。

まず、比較に用いたのは以下の通り。


で、まず普通にdiffを掛けてみたのだが、意外とdocコメント上で頑張っていることが判明。docコメント内のcodeタグやttタグ、preタグの多くを{@code}や{@link}で書き直してある部分が目立つ。まぁ、未だにcodeタグのままの部分も多いのだが、頑張ったよね。個人的にはHTMLタグよりも読みやすいと思っているので、嬉しいです。

あと、Java6までのソースは、インデントがTABだったりSPACEだったりまちまちだった。そしてどうやらTAB幅8としているようで、TAB幅4の環境で見るとそれはそれは残念な感じになってしまう。もうこの辺りのコーディングスタイルに関しては、標準APIであるにも関わらずもう壊滅的でしたよね。自分のコーディング規約やJavadocの書き方ルール策定の参考にしようと思って愕然とした記憶がある。しかし、Java7では、インデントがSPACEに直っているのが目立つ。未確認だが、全部統一されているのかもしれない。喜ばしい。

といった所の差異までdiffで見えてしまうと、もはやノイズでしかないので、ここは思い切って両者のソースをいじってしまいます。

find . -type f | xargs sed -e 's/\<code\>\([^<]*\)\<\/code\>/{@code \1}/g' -i ""
find . -type f | xargs sed -e 's/\<tt\>\([^<]*\)\<\/tt\>/{@code \1}/g' -i ""

両者のソースに対して、上記の置換を掛けた後、俺俺コードフォーマッタを掛けてからdiffに挑みました。で、com.sunパッケージなんかの差を見始めてもアレなので、ひとまず java.* パッケージに絞って、そして都元が個人的に気になったポイントを中心に、以下にご紹介しまーす。

スペルミスや細かいバグフィックス

まずは軽く。Java6と7の比較、という視点ではありませんが、結構色々直していますね。

 * @throw new NullPointerException

なんていうdocコメントがあったり。勢いでnewって書いちゃったんだろうなーw

public Foo {
  // …
  private void Foo() {}
}

なんてやっちゃってるクラスもありました。


古いコーディングスタイルの刷新

Java6のソースにはstatic publicという語順(?)や、char foo[]; のような配列宣言、rawtype型など、古い書き方が内部に随分残ってます。こういった所がちょいちょい直してありました。あと、ダイアモンドオペレータもきっちり使われてましたよ。


各Exception実装クラスにserialVersionUIDが追加

ExceptionはSerializableのサブタイプです。従って、例外の実装クラスには全てserialVersionUIDを記述するのが望ましいんですね。まぁ、実装の詳細の話ではありますが、各ExceptionにserialVersionUIDが追加されてます。


Byte/Integer/Long/BigIntegerの文字列paese

int i1 = Integer.parseInt("3");
int i2 = Integer.parseInt("-6");
int i3 = Integer.parseInt("+2");

皆さん、このコード実行するとどうなると思います? 実は、Java6だと "+2" は NumberFormatException になってしまうのです。文字列の整数parseにおいて、プラス記号は今まで使えませんでした。これが、Java7からは普通に通るようになります。

ちなみに、DoubleやFloatのparseでは、Java6でもプラス記号が使えます。


primitive wrapper class

Integer.compare(10, 15);

というような、比較ロジックがstaticメソッドとして提供されるようになりました。プリミティブラッパー型にそれぞれ定義されています。比較ロジックを引き算で実装してバグを出してしまう位なら、このユーティリティメソッドに委譲してしまうのがよいですね。

比較ロジックの引き算実装については下記参考書籍の「パズル65」を参照。


ComparableとComparator

従来、Comparable#compareToやComparator#compareの引数にnullを渡した時の仕様は「未定義」でした。また、私は基底型のjavadocに明示してある例外しか投げないようにしているため、「nullとは比較できないComparable」を実装したい時、とても気持ち悪い思いをしていました。

そんな中、Java7ではこれらの比較メソッドjavadocに、NPEの記述が追加されました。

@throws NullPointerException if the specified object is null

引数がnullな時はNPEを投げてよくなったのですね。


Collections

以前から、Collectionsクラスには emptyList() 等のメソッドがありましたが、似たような感じで以下のメソッドが追加になりました。大したことではありませんが、使う機会があれば使った方が良いですね。

  • emptyEnumeration()
  • emptyIterator()
  • emptyListIterator()

Objets

新しいユーティリティクラスです。requireNonNullやnull-safeなequals/hashCode/toStringなど、小粒ながら使い勝手は良さそうです。


ReflectiveOperationException

従来、リフレクションAPIを利用しようとすると数多くのチェック例外を処理する必要がありました。NoSuchMethodException, InvocationTargetException, ClassNotFoundException, IllegalAccessException… 実際は発生しないと踏んでいる例外をこんなに大量にキャッチさせられるのはストレスでしたね。しかし、Java7からはReflectiveOperationExceptionという基底クラスが定義されました。これによって、リフレクション操作時の例外をまとめてキャッチできます。


AutoCloseable

try-catch-resources用に出て来た新しいインターフェイスです。Closeableはもちろん、Connection/ResultSet/Statementもこいつのサブタイプに。

従来はJDBC API用の closeQuietly 的なユーティリティを、I/O とは別に書かなければなりませんでしたが、今度からまとめられますね。


ThreadLocalRandom

っていうクラスが追加になっています。まぁ、内容は名前から想像できる通りです。


Scanner

ScannerがCloseableのサブタイプになりました。今までCloseableじゃなかったのは、単に忘れてただけなのでしょうかw


Integer, Short, LongのvalueOf

Sun(Oracle)のJavaにおいて、Integer型のインスタンスは-128〜127の値がキャッシュされる、というのは有名な(?)話でした。しかし、このキャッシュは「Sun(Oracle)の実装がたまたまそうなっている」だけであり、Javaの仕様ではないものでした。つまり、別のJava実装(例えばIBM Javaとか?)ではキャッシュをしていないかもしれません。キャッシュの範囲が違うかもしれません。

という状況だったのですが。Java7のjavadocには以下のような記述が追加されています。

【Integer#valueOf及びShort#valueOfより】
     * This method will always cache values in the range -128 to 127,
     * inclusive, and may cache other values outside of this range.
【Long#valueOfより】
     * Note that unlike the {@linkplain Integer#valueOf(int)
     * corresponding method} in the {@code Integer} class, this method
     * is <em>not</em> required to cache values within a particular
     * range.

これって…。Javadocに明示されたということは、キャッシュの挙動も含めて仕様化したということで良いんでしょうかね。


System

System#lineSeparator() っていうメソッドが追加。プラットフォーム依存の改行文字(列)をさくっと手に入れられて便利ですね。

また、Systemクラス内の in, out staticフィールドの初期化方法が変わったようです。Java6までは、staticフィールドが参照元クラスにインライン展開されないように、ちょっとしたハックがされていましたが、Java7では直接null初期化しています。これはインライン展開されなくなったんですかね。そこまでは追いきれませんでした。


暦システム関連

f:id:daisuke-m:20111225165712p:image:right

week yearのサポートが手厚くなってます。GregorianCalendarに以下の3メソッドが追加になりました。

  • getWeeksInWeekYear()
  • getWeekYear()
  • isWeekDateSupported()

って何の事だかわからんですね。えーと。「1週間は月曜〜日曜である」として、「2011年の第1週」って何日から何日だと思いますか? まぁこのネタはこちらが詳しいので参照してください。 → 今が年の何週目か - 気付いたとき、気が向いたとき。by ykhr

で、結論としては、ISO8601的には1/3〜1/9が「2011年の第1週」ということになります。ということは、2010/12/27〜2011/01/02は「2010年の第52週」なんですね。デフォルトのGregorianCalendarの挙動は「1週間は日曜始まりで、1/1を含むのが第1週」ということにになっていますが、それをISO的に「1週間は月曜始まりで、1/4を含むのが第1週」という設定をするのが先ほどの id:ykhr-kokko 氏のエントリです。

で、ここで注目したいのは「2011/01/02は、2011年の日付であるにも関わらず、週レベルで見ると2010年に属する」ということです。めんどくさいですねw 今日は何年の第何週なのかが知りたいとします。第何週かは cal.get(Calendar.WEEK_OF_YEAR) で良いでしょう。では「ある日付を与えた時、それは週レベルでは何年に属するのか?」をどうやって取るのか。これが getWeekYear() です。

とは言え、大抵 Calendar.YEAR と一致してますから、年末年始だけ気をつけておけば簡単に計算できるんじゃね? …と思いきや、正確にやり出すと結構大変みたいです。1582年以前は、今のグレゴリオ暦ではなくユリウス暦が…詳しくは getWeekYear() の実装をご覧下さい。

んでまぁ、上記に関連してSimpleDateFormatに新しいパターンが追加になりました。Y, u, X の3つですが、このYってのがweek yearです。uとXは大した事ないので気になったら各自ググってくださいw

ちなみに「1週間は月曜始まりで、1/4を含むのが第1週」というルールを知っているのはCalendarですから、下記の通りSimpleDateFormatにcalを与えてやらないと、ISOのルール通りには動きませんので注意しましょう。

Calendar cal = new GregorianCalendar();
cal.setMinimalDaysInFirstWeek(4);
cal.setFirstDayOfWeek(Calendar.MONDAY);
SimpleDateFormat f = new SimpleDateFormat("yyyy/MM/ddはYYYY年の第ww週です。");
f.setCalendar(cal);
System.out.println(f.format(new Date()));

…と、まぁdiffをしてみて気づいたあれやこれやを並べてみました。明日以降も引き続き、Java Advent Calendarでお楽しみください :)

2011-11-26

[][]Spring 3.1 の Cache Abstraction(キャッシュ抽象化

しばらくコード付きのエントリ書いてないなぁ、と思ったので。Springの新機能についてひとつ。

Spring3.1は、まだリリース版は出ていないのだけど、RC1が出ている。(参考 Spring 3.1 RC1リリース

その新機能にCache Abstraction(キャッシュ抽象化)ってのがあって、色々調べてみた。例えばWebAPIなんかを叩いて情報を取ってくるようなメソッドは、情報があまり変化しないものであればキャッシュしちゃえばいいよね。例えば Amazon API で、ASINから商品名やら何やかんやを取ってくるメソッドとか。

下準備

package jp.xet.sample;

public interface EntityRepository {
    
  String get(int id);
    
  void put(int id, String value);
    
}

例えばこんな(↑)インターフェイスがあって、このgetのコストが高いとしましょう。で、今回使うサンプルの実装がコレ(↓)。コストの高さをThread.sleepで表現してみました。実際は単なるMapストレージなんだけども。

package jp.xet.sample;

import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Repository;

@Repository
public class EntityRepositoryImpl implements EntityRepository {
    
  private Map<Integer, String> storage = new HashMap<Integer, String>();
    
    
  @Override
  public String get(int id) {
      
    // simulate slow operation
    try {
      Thread.sleep(1000L);
    } catch (InterruptedException e) {
      throw new AssertionError(e);
    }
      
    return storage.get(id);
  }
    
  @Override
  public void put(int id, String value) {
    storage.put(id, value);
  }
}

で、こいつ(↑)には@Repositoryアノテーションがついている。まぁ@Componentと全く一緒*1らしい。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">

 <context:component-scan base-package="jp.xet.sample"/>

</beans>

で、jp.xet.sampleパッケージ以下のアノテーション付きのコンポーネントをcontext.xmlで読んでもらう、と。まぁ、EntityRepositoryImplしかないんですが。そんなわけで、Mainクラスいきましょう。

package jp.xet.sample;

import org.apache.commons.lang.time.StopWatch;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Main {
    
    
  public static void main(String[] args) {
    ApplicationContext ctx =
      new ClassPathXmlApplicationContext("/context.xml");
    EntityRepository repos = ctx.getBean(EntityRepository.class);

こうして出来たコンテナからは、EntityRepositoryが取り出せる。あとは、適当にデータを置いて、時間をはかってみる。

    repos.put(1, "one");
    repos.put(2, "two");
    repos.put(3, "three");
    
    StopWatch sw = new StopWatch();
    sw.start();
    
    System.out.format("time=%2$s, value=%1$s%n",
        repos.get(1), sw.toString());
    System.out.format("time=%2$s, value=%1$s%n",
        repos.get(2), sw.toString());
    System.out.format("time=%2$s, value=%1$s%n",
        repos.get(1), sw.toString());
    System.out.format("time=%2$s, value=%1$s%n",
        repos.get(2), sw.toString());
    System.out.format("time=%2$s, value=%1$s%n",
        repos.get(3), sw.toString());
    
    repos.put(1, "壱");
    repos.put(3, "参");
    
    System.out.format("time=%2$s, value=%1$s%n",
        repos.get(1), sw.toString());
    System.out.format("time=%2$s, value=%1$s%n",
        repos.get(2), sw.toString());
    System.out.format("time=%2$s, value=%1$s%n",
        repos.get(1), sw.toString());
    System.out.format("time=%2$s, value=%1$s%n",
        repos.get(2), sw.toString());
    System.out.format("time=%2$s, value=%1$s%n",
        repos.get(3), sw.toString());
  }
}
time=0:00:01.002, value=one
time=0:00:02.012, value=two
time=0:00:03.014, value=one
time=0:00:04.014, value=two
time=0:00:05.015, value=three
time=0:00:06.016, value=壱
time=0:00:07.018, value=two
time=0:00:08.018, value=壱
time=0:00:09.019, value=two
time=0:00:10.020, value=参

だいたい、1getにつき1秒掛かってますね。

このget処理をキャッシングして、高速化しよう

まずcontext.xmlに手をいれましょう。キャッシュの設定。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:context="http://www.springframework.org/schema/context"
  xmlns:cache="http://www.springframework.org/schema/cache"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">

 <cache:annotation-driven />
 <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
  <property name="caches">
   <set>
    <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
     <property name="name" value="default"/>
    </bean>
   </set>
  </property>
 </bean>

 <context:component-scan base-package="jp.xet.sample"/>

</beans>

この設定は、裏ではシンプルに普通のConcurrentHashMapに値をキャッシュする設定です。この他に、springではehcacheにも対応したりしてるらしい。で、ここでは「default」という名前のキャッシュを1つ作りました、ということになります。次にEntityRepositoryImplに @Cacheable アノテーションをつける。

  @Cacheable(value = "default")
  public String get(int id) {
    // ...
  }

こんな感じ。このメソッドの結果はキャッシュして、キャッシュヒットした場合は中身は実際には呼ばずに済ますよ、という意味です。この状態でもっかいMainを実行してみよう。

time=0:00:01.004, value=one
time=0:00:02.016, value=two
time=0:00:02.017, value=one
time=0:00:02.017, value=two
time=0:00:03.018, value=three
time=0:00:03.018, value=one
time=0:00:03.019, value=two
time=0:00:03.019, value=one
time=0:00:03.019, value=two
time=0:00:03.019, value=three

最初のone,twoには1秒ずつ掛かっているけど、その次のone,twoは一瞬で終わっている。threeは初アクセスなので再び1秒掛かってる。で、残りはもう全部キャッシュに載っているのでずばーーーっと。計約3秒。すばらしい。

漢数字はどうした

    repos.put(1, "壱");
    repos.put(3, "参");

途中でoneとthreeの値を書き換えたのだが、無視してキャッシュを返しちゃってますね。これを何とかしたい。ならば@CacheEvictアノテーションだ。

  @CacheEvict(value = "default", key = "#id")
  public void put(int id, String value) {
    // …
  }

このアノテーションがついたメソッドが呼ばれた時は、特定のキャッシュエントリを無効にします、っていうアノテーションだ。で、どのキャッシュエントリを無効にするの? ってのが key パラメータ。ここでは「idって名前の引数を利用します」ってことです。この表記法はSpEL(Spring Expression Langage)参照。

ちなみに、@CacheEvict(value = "default", allEntries = true) とすると、全キャッシュエントリをクリアしてくれる。

という対策を施し、再びMainを回す。

time=0:00:01.001, value=one
time=0:00:02.011, value=two
time=0:00:02.012, value=one
time=0:00:02.012, value=two
time=0:00:03.013, value=three
time=0:00:04.014, value=壱
time=0:00:04.014, value=two
time=0:00:04.015, value=壱
time=0:00:04.015, value=two
time=0:00:05.015, value=参

キャッシュのevictが上手く動いていることが分かります。

*1:ざっくりとしたコンポーネントには@Componentを付ければいいんだけど「こいつはコンポーネントだけど、特にリポジトリなんだよ」っていう気分で@Repositoryってのが用意されている。あとは@Serviceなんてのもある。気分で使い分ければよろし。

2010-12-23

[][]UnsupportedOperationExceptionと相続拒否

昨日ご紹介したbaseunitsですが、そのコードを社内コードレビューに掛けた際、id:cobonasからこんな指摘がありました。

package jp.tricreo.baseunits.util;

import java.util.Iterator;

/**
 * 明示的に、対象のコレクションに対する操作ができないことを表す反復子。
 * 
 * @param <T> 要素の型
 */
public abstract class ImmutableIterator<T> implements Iterator<T> {

    @Override
    public void remove() {
        throw new UnsupportedOperationException("sorry, no can do :-(");
    }

}

https://github.com/tricreo/baseunits/blob/master/src/main/java/jp/tricreo/baseunits/util/ImmutableIterator.java

これって「相続拒否」的な話ですかね?あんまりよろしくないとどこかで見た気がする。

ニュアンスはわかるんですが、「相続拒否」という言葉を技術分野で初めて聞いたので調べてみると、こんなのがありました。コードの不吉な匂い、つまりリファクタリングを検討するケースですね。正直、とても良い質問だと思ったので、詳しく解説してみます。

サブクラスは親の属性と操作を継承するのが普通ですが、ほんの一部しか利用していない場合 があります。

http://www.objectclub.jp/technicaldoc/refactoring/jpg/smell21.jpg

- 不吉な匂い

リファクタリング?

こういった場合、以下のようなリファクタリング案が提示されています。

ImmutableIteratorはIteratorを実装(implements)せず、内包→委譲するようにする。
public abstract class ImmutableIterator<T> {
    private final Iterator<T> itr;
    public ImmutableIterator(Iterator<T> itr) { this.itr = itr };
    public boolean hasNext() { return itr.hasNext(); }
    public T next() { return itr.next(); }
}

しかし、ImmutableIteratorは、普通にIteratorとしても使いたかったりするので、Iteratorのサブタイプで居てもらいたいと考えています。従って、compositionとするアイデアは採用できません。まぁ、そもそもIteratorの実装を継承したい訳じゃなくて、型を継承したいのです。(Iteratorはinterfaceなので、継承する実装もありませんし…)

メソッドを引き下げる。
public interface Iterator<T> {
    boolean hasNext();
    T next();
    // void remove();
}

public interface RemovableIterator<T> extends Iterator<T> {
    void remove();
}

Iteratorは削除の手段を提供しない。削除したい場合はRemovableIteratorを使うべき。という体制にもっていくリファクタリング。しかし、IteratorはJavaAPI仕様の型であるため、我々の制御下にないわけです。というわけで、引き下げもできない。

だから、短絡的に考えると「相続拒否しちゃいけない」、つまり「必ずremove機能を提供すべき」ということになると思います。

ImmutableIterator implements Iteratorであるということは、ImmutableIterator is-a Iterator であって、「hasNextとnextとremoveの機能を提供するのがIteratorである」という定義なのですから、「removeを提供しないImmutableIteratorはIteratorではない」、つまりis-aが成り立たないのでダメ、という論調ですね。私が大好きな、リスコフの置換原則に則るための、大事な考え方です。

しかし、「Iteratorは必ずremoveを提供しなければならない」という強力な縛りを課すと、色々なところに歪みが発生します。

強力すぎる制約による歪み

どのような歪みが発生するか、見ていきましょう。例えば「不変(Immutable)なコレクション(A)」や「要素の追加はできるけど削除はできない、という制限付きのコレクション(B)」等を作った時、これらのコレクションはイテレータを提供できなくなります。

Collectionはiterator()メソッドを宣言しているので、これも「必ず提供しなければならない」、つまり、AとBはCollectionとは認められない。implements Collectionしてはいけないのです。そんなわけで、AとBはCollectionをcomposition(内包)としなければいけなくなりました…。

となると、せっかくAやBのようなクラスをつくっても、Collectionを引数に取るメソッドにImmutableCollectionは渡すことができない。サブタイプじゃないですから。そんなクラスは不便で存在価値が薄いですね。

あくまでも「匂い」

「コードの不吉な匂い」は、あくまでも「予感」であって、「絶対的な悪」ではありません。リファクタリングが必須なわけではなく、検討する必要があるだけです。

検討の結果、リファクタリングが不要だという結論に至ることもあるのです。しかし、ここで甘えてもいけません。何でもかんでもリファクタリング不要、としてしまうのも大問題なので、このような匂いを感じたにも関わらずリファクタリングをしない場合は、正当な理由があるべきです。

例えば、上に説明してきたように「サブタイプである必要があり、かつ、自分の制御下にない型の修正が必要になる」というのは正当な理由です。ただ、個人的にはこの理由も少し「弱い」と感じているので、もう少し根拠を補強したい。

実はリスコフの置換原則に違反していない

「リスコフの置換原則」とはなんでしょう。詳しいことはググってもらうとして。A extends B の時、A型のインスタンスはB型としても何の問題もなく振舞わなくてはならない。つまり、ImmutableIterator型のインスタンスは、Iterator型としても何の問題もなく振舞わなくてはならない、という原則です。

では、ImmutableIterator型のインスタンスは、Iterator型として問題なく振舞うでしょうか? 通常、Iterator#remove()メソッドを呼び出したら、基になるコレクションから、反復子によって最後に返された要素を削除します。しかしImmutableIteratorはUnsupportedOperationExceptionを投げてremoveを拒否します。これは「Iteratorとして問題ない挙動」としていいのでしょうか?

Iteratorの仕様を確認しないまま「remove拒否」の挙動を判断しようとすると「removeだと言っているのにremoveしないのは、Iteratorの責務を果たしていないので、問題である」と考えると思います。しかしここは良く出来ているのです。Iterator#removeの仕様を確認してみましょう。

f:id:daisuke-m:20101223152457p:image:w550

まず「任意のオペレーション」に注目です。和訳されて、かえって分かりにくくなっていますが、英語では「optional operation」です。つまり、「機能を提供しても提供しなくても、どちらでもよい操作」なのです。また、例外の節でも「Iterator が remove オペレーションをサポートしない場合」はUnsupportedOperationExceptionを投げることが明示してあります。

つーまり。「remove拒否するのもIterator」なのです。

削除を拒否ったって いいじ... - みつを - はてなセリフ

相田みつをが「人間はつまづくこともある」とするのと同じように、Javaは「Iteratorはremoveを拒否することもある」としているのです。

だから「ImmutableIteratorは、removeを拒否してもよい」のです。そもそもIterator#removeしたら必ず削除されるなんて思う方が間違いなんです。仕様に「拒否ることもある」と書いてあるわけだから、Iterator#removeを呼ぶ際は、拒否される可能性を念頭に置いてコーディングしなきゃいけないのです。

っていう論調なのが「契約プログラミング」って考え方。removeが失敗してバグった時、それは「removeを提供しないIteratorが悪い」のか「remove拒否される可能性を考慮しなかった呼び出し側が悪い」のか。そんな責任の所在を明確にする流派です。この考え方も、自分は結構好き。

まぁ、世の中にはいろんな考えがあって。がっちがちでリスコフ&契約原理主義な人もいる。俺のように「基本的にリスコフ&契約信者だけど、まぁアレな時もたまには…」なスタンスもあり。「そもそも契約の為には契約書(Javadoc)をきっちり漏れ無く明示しなきゃいけないけど、いちいち書いてらんないよね、無理だろ」という人もいるはずだ。

どんなスタンスで、どこまで根拠を求めるか。みなさんも、自分のスタンスを検証してみるといいかもしれないです。

余談

ちなみに、例えば Collection#iterator() は optional operation ではないので、必ず真っ当なイテレータを返さなければダメです。

また、 Collection#size() も必ず要素の個数を返さなければいけません*1。こいつもoptionalではないのです。従って「要素の数が不明なコレクション」、たとえば無限に要素を返すコレクションや、ネットの向こうに要素があって、個数はこちら側で把握できないコレクションなどは、コレクションと呼べないのです。sizeをサポートできないから。

しかししかし。Collection#add() や remove() というのは実はoptional operation。add/removeを許さないコレクションであっても、Collectionを名乗って良いです。じゃないとImmutableなCollectionが存在できなくなります。

さらに余談

JavaAPIの設計者は、なぜ「メソッドの引き下げ」をしなかったんでしょうね。removeが無いIteratorと、removeがあるRemovableIteratorの階層にしておけば、このような問題は発生しなかったのに。また、Collectionも、addやremoveを提供しない層とする層に階層化すればよかったのに。

これをし始めると、おそらく過モデリングになります。型の階層構造が深くなって複雑化する。オブジェクト指向原理主義だとUnsupportedOperationExceptionは悪ですが、現実を見ると、良いバランスが取れた結論なんじゃないでしょうか?

Java: The Good Parts: Unearthing the Excellence in Java

Java: The Good Parts: Unearthing the Excellence in Java

という話は、この本に書いてあったよ。洋書なんですが、こちらの本は現在翻訳が進んでいます*2Javaの良いところが再確認できる良書だと思います。お楽しみに。

追記:和訳本、出たよー。

Java: The Good Parts

Java: The Good Parts

結論

というわけで、Iteratorのサブタイプがremoveを拒否るのは正当である、という判断です。根拠は上記の通り。

*1:ただし、個数がInteger.MAX_VALUEを超えたらInteger.MAX_VALUEを返してもよい

*2:監訳者の方から、この本の訳が進められていることは、公に言っても書いてもかまいません、と許可を頂いてます。

2008-07-02

[][]Apache Commonsを総ざらってみる。

ということで、現状のコンポーネントをリストアップして、概要を日本語(一部英語のままw)でまとめてみました。今後、個人的に興味のあるものを順にピックアップして、もう少し深く知っておく、という作業をしてみようかな、と思ってます。

まずは、commons-langを掘ってみている。すげええええええの連続です。近々ご紹介しますとも。

Properシリーズ(正式プロジェクト)

コンポーネントmvn説明参考
attributesメタデータ属性(docletタグ等)ランタイムAPIjavadocアノテーションを処理する。Java4以前で有用かも。参考
beanutilsReflection/Introspection APIのラッパー123
betwixtJavaBeans・XML間の相互マッピングAPI参考
chain"Chain of Responsibility"パターンの実装。
cliコマンドラインの引数・オプション・オプショングループ・強制オプションなどの処理API
codec一般的な「エンコード/デコード」のアルゴリズムをカプセル化。
collectionsJava Collections Frameworkの拡張・追加クラス群
configurationconfやproperties等、さまざまなフォーマットの設定ファイルを読み込むAPI#
daemonUnixデーモンに似た、Invokeの代わりとなるJavaコードによるメカニズムを提供。
dbcpデータベース・コネクションプーリングに関するサービスを提供。
dbutilsJDBCの関数及びコンポーネント群。
digesterXMLで記述された設定ファイルを解析する、XMとJavaオブジェクトのマッピングユーティリティ。
discovery様々なスキーマを使ってサービス/リファレンスの名称をリソース(クラスファイル群を含む)にマッピングすることにより、リソースの場所を示す為のツール。
elJSP 2.0表現言語のインタプリタ。
emailJavaからメールを送るシンプルなライブラリ。#
exec×Javaにおける外部プロセス実行や環境管理を行う。
fileupload容易且つ堅牢でハイパフォーマンスなファイルアップロード機能をサーブレット及びWebアプリケーションに提供
ioI/Oユーティリティ群#
jciJavaで実装されたJavaコンパイラインタフェース。参考
jellyXMLベースのスクリプト処理エンジン。
jexlJSTLの表現言語を拡張した表現言語。
jxpathJavaBeansを、XPath文法に基づいて操作するためのユーティリティ。mapやDOMやその他のオブジェクトモデルもサポート。
langjava.langクラスの追加機能を提供する汎用的なユーティリティクラス群。#
launcherクロスプラットホームな、Javaアプリケーションのランチャー。
logging様々なログ用APIのラッパークラス群。
math軽量の数学・統計用コンポーネント
modelerJMX仕様と互換のあるModel MBeansを作成するためのAPI
netネットワークユーティリティ群。
pool汎用オブジェクトプール用インターフェース、モジュール式のオブジェクトプールを生成するツールキット、一般目的の為のオブジェクトプール実装。
primitivesprimitive型のコレクションとユーティリティクラスを提供。
proxyダイナミックプロキシを生成する為のライブラリ。
scxmlSCXML(State Chart XML)エンジンのJava実装。
transactionファイルアクセスにトランザクション機能 (ACID特性) を提供。参考
validatorXMLファイルの中の妥当性(妥当性メソッド)や妥当性規則を定義する。
vfsファイルやFTP,SMB,ZIPといったものを、単一の論理ファイルシステム(仮想ファイルシステム)として扱うライブラリ。

Sandboxシリーズ

コンポーネントmvn説明参考
compress圧縮ファイルを扱うためのAPI
csv×CSVの読み書きAPI
finder×UNIXのfindコマンドのような機能を提供。
functor×関数をオブジェクトとして扱うライブラリ。
i18nスローする例外や表示するエラー/成功メッセージを特定の地域に合わせてローカライズするライブラリ。
id×識別子(ID)を生成するライブラリ。
javaflow×Continuation implementation to capture the state of the application.
jnet×JNet allows to use dynamically register url stream handlers through the java.net API.
monitoring×Monitoring provides a simple, lightweight toolkit to instrument java application monitoring capabilities, including performances and thread concurrency.
nabla×Nabla provides automatic differentiation classes that can generate derivative of any function implemented in the Java language.
OpenPGP×Interface to signing and verifying data using OpenPGP.
performance×A small framework for microbenchmark clients, with implementations for Commons DBCP and Pool.
pipeline×Provides a set of pipeline utilities designed around work queues that run in parallel to sequentially process data objects data objects.

Dormantシリーズ

コンポーネントmvn説明参考
cache×オブジェクトキャッシュ機能を提供する。
clazz×Clazz focuses on introspection and class manipulation.
contract×This component makes all the nice features available to the java programming language that come along with contract based programming.
convert×provide a single library dedicated to the task of converting an object of one type to another.
events×additional classes for firing and handling events. It focusses on the Java Collections Framework, providing decorators to other collections that fire events.
feedparser×Javaによる各種バージョンの RSS/Atom を同じように扱えるパーサ。
jjar×Jakarta JAR Archive Repository
latkaQAテスト/承認テスト/リグレッション・テストの自動化の為のHTTP機能テストスイート。
mapper×thin abstraction layer around a project's chosen data mapping technology (a.k.a. DAO pattern).
messengerWeb層でJMSが機能する為のフレームワーク。
resourcesjava.util.Localeで作成される国際化メッセージ文字列やメッセージキーを規定したり調べたりする軽量のフレームワーク。
scaffold×Webアプリケーション構築ツールキット。
threadpoolオブジェクトプール内のスレッドに処理を非同期的にディスパッチする為のコンポーネント
workflow×ワークフロー管理システムを構築するためのフレームワーク。
xmlio×Simple and fast importer for XML configuration or import files.

Commonsプロジェクト以外にホスティングされているもの

コンポーネントmvn説明参考
AltRMI×AltRMI is a from-scratch replacement for RMI. It has a number of different features that make it easier to use. It tries as far as possible to be transparent in use.
Cactus×Cactus is a simple test framework for unit testing server-side java code (Servlets, EJBs, Tag Libs, Filters, ...). The intent of Cactus is to lower the cost of writing tests for server-side code.
HiveMind×HiveMind is a services and configuration microkernel. HiveMind allows you to create your application using a service oriented architecture.
httpclientクライアント側でHTTPプロトコルを操作するフレームワーク
namingThe Naming subproject will contain common JNDI code along with various JNDI providers.

Maven Reposで見つけた「なんぞこれ」

2007-11-17

[][]Java-ja第4.5回 肉の会に行ってきた。

絶対イジられると覚悟してはいたが「薬剤師ハカー」から「Seasarコミッタ薬剤師ハカー」になっていた(汗) まだ1件しかcommitしたことないっつの。

以下、ハカーと言われた時の心理描写。こんな感じで凹みます。超orz

ハカ|の肩書きが許されるの... - キモーイ キャハハハハハハ - はてなセリフ

まぁ、それはそれとして。小学生の感想文じゃないけど「無茶苦茶楽しかった」。

いや、今まで私の周りの環境って、「普通」だったんですよ。コンピュータに関してディープな話が出来る仲間がほとんどいない。たまにそんな相手がいたとしても、同席するこの手の話に明るくない人達に遠慮して、軽くしか触れないのが基本スタンス。「そんな相手」も結局はサラリーマンプログラマっぽかったりして、サシで飲んでいても熱く語る機会は多くない。ちょっとちょっと深入りしたテーマを語ろうとすると、相手がついてこられなかったりする。その逆(自分が相手の話題に追従できない)の経験はほとんどない。

それがJava-jaはどうだ。参加者十数名が100%全員ハカー。しかも熱い*1。普段話したくても話す環境が無い/相手が居ない話題を、臆することなく口にできる環境だ。しかも、自分が追従できない程ディープな話題がぼんぼん出てくる。そんな超特殊な環境に若干戸惑い、逆の意味で臆してしまうくらいだ。でも、むちゃくちゃ楽しいのだ。今までを振り返っても、このような環境は初体験だ。

ちきしょー、参ったぜオイ。

*1:例えば医療業界繋がりで飲むと、みんな医療のプロではあるが、熱くない事が多い…。医療・看護・調剤行為の法規制が厳しく、思った様に動く事が難しいという理由もあるだろう。また、守秘義務が絡むので、話題も制限されるってのも。

2007-10-20

[]暴かれた正体(´д`;)

Java界隈を中心に幅広くコミュニティ活動に参加されているid:bose999さんと飲んだ。>bose999の試験管の中の話

SJC-Pってどんななんだろー なんて書いてた時にメールを頂いて、そこから盛り上がって飲みに発展。飲みながら人と話をするのは大好きなので、今回もホイホイと出かけて行った訳です。

Eclipseプラグイン開発勉強会で一度お会いしていたのですが、今回は前回にも増してディープなお話で盛り上がってしまいました(笑) 色々相談にも乗っていただいて、至れり尽くせり。本気で感謝でございます。

直接お会いした人にはお話しているんですが、実は私、薬屋さんで働いてます。自分からオンラインに書くのは、誰かに暴かれてからにしよう、とか、特に何の理由もなくそんな事を考えていたら、遂に暴かれた、って感じです(笑)

なーんか、はセリ画像まで作って頂いて…。ちょー恥ずかしい(汗) もっと辱めてぇぇ〜*1

ほめ殺しエントリーを頂いたので、レスポンスエントリー(笑)

何のプラグインを作ったって... - みつを - はてなセリフ

*1:注:基本的に、そーゆーキャラじゃありません。

2007-03-23

[]DBFlute二度目のチャレンジ(1)

先日のエントリーのコメント欄に書いた通り、以前DBFluteの導入に挫折しています。

が、とにかく便利そうであるのは確かで、とっても魅力的です。また、当時と違って、ブログ等を通してサポートを頂ける環境も実感できて来ましたので、ここで再チャレンジといこうと思います。

まずはsetup!

オフィシャルサイトのドキュメントHow to Setupを参考に、まずはsetupを。

前提条件は以下の通り。

  • プロジェクト名は "cepoc"
  • ルートパッケージは "jp.xet.cepoc"
  • 使用するデータベースは PostgreSQL

SunのPageよりJRE-5.0(以上)をDownloadし、Installして下さい。{必須}

Ant-1.5(以上)を以下のSiteよりDownloadし、Installして下さい。{必須}

環境変数として{ANT_HOME}を追加します。

環境変数{Path}に%ANT_HOME%\binを追加します。

ここまでは済んでおりましたので、スルー。

環境変数として{DBFLUTE_HOME}を追加します。{必須}

DBFlute本体は、d:\develop\dbflute-0.4.3 に展開したので、それをセット。

%DBFLUTE_HOME%/etc/client_directory-templateのDirectoryに、実行Directory(Client-Directory)のTemplateがあります。任意のTemplateをCopyし、LocalPCの任意の場所に配置して下さい。

eclipseのプロジェクト(Doltengで作成したプロジェクト)のルートにdbfluteフォルダを作成し、%DBFLUTE_HOME%/etc/client_directory-template/minimumPropertiesの内容をごっそりコピー。

_project.batの「MY_PROJECT_NAME」変数の値を任意のProject名に変更して下さい。

build-ldb.propertiesのFile名をbuild-[xxx].properties(xxxは任意のProject名)に変更して下さい。

Project名は "cepoc" で。build-cepoc.properties ですね。

各Propertyの詳細は%DBFLUTE_HOME%/etc/client_directory-template/fullPropertiesのbuild-ldb.properties内に記述されている各PropertyのCommentを参考にして下さい。

続いてbuild-cepoc.propertiesの内容を設定していきます。変更したもののみ、以下に示します。(よくわからないものはスルーしましたw)

torque.database = postgresql
torque.packageBase = jp.xet.cepoc
torque.database.driver = org.postgresql.Driver
torque.database.url = jdbc:postgresql://localhost/cepoc
torque.database.schema = public
torque.database.user = cepoc
torque.database.password = ****

j2ee.diconの設定にて、Component定義を以下のように差し替えてください。

component class="org.seasar.extension.jdbc.impl.BasicStatementFactory"

component class="org.seasar.extension.jdbc.impl.BasicResultSetFactory"

↓↓↓

component class="xxx.allcommon.s2dao.S2DaoStatementFactory"

component class="xxx.allcommon.s2dao.FetchNarrowingResultSetFactory"

んと…、jdbc.diconでいいんですよね。jp.xet.cepoc.allcommon.s2dao.〜 に書き換えました。抜粋で以下の通り。

<component class="jp.xet.cepoc.allcommon.s2dao.FetchNarrowingResultSetFactory"/>
<component class="org.seasar.extension.jdbc.impl.ConfigurableStatementFactory">
	<arg>
		<component class="jp.xet.cepoc.allcommon.s2dao.S2DaoStatementFactory"/>
	</arg>
	<property name="fetchSize">100</property>
	
</component>

リバースエンジニアリング!

jdbc.batを実行します。

ドキドキの瞬間!

        1 個のファイルをコピーしました。
Buildfile: D:\develop\dbflute-0.4.3\build-torque.xml

jdbc:
     [echo] +-----------------------------------------------+
     [echo] |                                               |
     [echo] | Generating XML from JDBC connection !         |
     [echo] |                                               |
     [echo] +-----------------------------------------------+
     [echo]
     [echo] +-------------------------------------------------------------------------------+
     [echo] contextProperties = build.properties
     [echo] dbDriver          = org.postgresql.Driver
     [echo] dbPassword        = ****
     [echo] dbSchema          = public
     [echo] dbUrl             = jdbc:postgresql://localhost/cepoc
     [echo] dbUser            = cepoc
     [echo] outputFile        = ./schema/project-schema-cepoc.xml
     [echo] sameJavaName      = ${torque.sameJavaName}
     [echo] +-----------------+
[torque-jdbc-transform] 2007-03-24 01:43:50,664 [main] DEBUG (DfAntTaskUtil#getBuildProperties():62) - Using contextProperties file: D:\develop\dbflute-0.4.3\build.properties
[torque-jdbc-transform] 2007-03-24 01:43:50,680 [main] DEBUG (DfAntTaskUtil#getBuildProperties():87) - [Properties]: size=19
[torque-jdbc-transform] 2007-03-24 01:43:50,680 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.statementResultSetConcurrency = ResultSet.CONCUR_READ_ONLY
[torque-jdbc-transform] 2007-03-24 01:43:50,680 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database.driver = org.postgresql.Driver
[torque-jdbc-transform] 2007-03-24 01:43:50,680 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.project = cepoc
[torque-jdbc-transform] 2007-03-24 01:43:50,680 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.isMakeConditionQueryNumericArgumentLong = false
[torque-jdbc-transform] 2007-03-24 01:43:50,680 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database.schema = public
[torque-jdbc-transform] 2007-03-24 01:43:50,680 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.isMakeDeprecated = false
[torque-jdbc-transform] 2007-03-24 01:43:50,680 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.packageBase = jp.xet.cepoc
[torque-jdbc-transform] 2007-03-24 01:43:50,680 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.statementResultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE
[torque-jdbc-transform] 2007-03-24 01:43:50,680 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database.user = cepoc
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.invokeSqlDirectoryDefinitionMap = map:{ isAutoCommit = false ; isRollbackOnly = true ; isErrorContinue = false ; sqlDirectory = ./playsql }
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database.url = jdbc:postgresql://localhost/cepoc
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database.password = ****
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.targetLanguage = java
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.isMakeConditionQueryEqualEmptyString = false
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.isMakeBehaviorForUpdate = false
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.isAvailableBehaviorGeneration = true
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.invokeReplaceSchemaDefinitionMap = map:{ isAutoCommit = false ; isRollbackOnly = false ; isErrorContinue = true ; sqlFile = ./playsql/replace-schema.sql }
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.java.dir = ../src/main/java
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database = postgresql
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] INFO  (TorqueJDBCTransformTask#doExecute():205) - ------------------------------------------------------- [Torque - JDBCToXMLSchema] Start!
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] INFO  (TorqueJDBCTransformTask#doExecute():206) - Your DB settings are:
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] INFO  (TorqueJDBCTransformTask#doExecute():207) -   driver : org.postgresql.Driver
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] INFO  (TorqueJDBCTransformTask#doExecute():208) -   URL    : jdbc:postgresql://localhost/cepoc
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] INFO  (TorqueJDBCTransformTask#doExecute():209) -   user   : cepoc
[torque-jdbc-transform] 2007-03-24 01:43:50,695 [main] INFO  (TorqueJDBCTransformTask#doExecute():210) -   schema : public
[torque-jdbc-transform] 2007-03-24 01:43:50,726 [main] INFO  (TorqueJDBCTransformTask#generateXML():248) - ...Instantiate DB-driver
[torque-jdbc-transform] 2007-03-24 01:43:50,726 [main] INFO  (TorqueJDBCTransformTask#generateXML():251) - ...Getting DB-connection
[torque-jdbc-transform] 2007-03-24 01:43:50,851 [main] INFO  (TorqueJDBCTransformTask#generateXML():254) - ...Getting DB-meta-data
[torque-jdbc-transform] 2007-03-24 01:43:50,867 [main] INFO  (TorqueJDBCTransformTask#generateXML():257) - $ /**************************************************************************
[torque-jdbc-transform] 2007-03-24 01:43:50,867 [main] INFO  (TorqueJDBCTransformTask#generateXML():258) - $
[torque-jdbc-transform] 2007-03-24 01:43:50,867 [main] INFO  (TorqueJDBCTransformTask#generateXML():259) - $ dbMetaData.toString(): org.postgresql.jdbc3.Jdbc3DatabaseMetaData@12a0f6c
[torque-jdbc-transform] 2007-03-24 01:43:50,867 [main] INFO  (TorqueJDBCTransformTask#generateXML():260) - $ dbMetaData.getMaxRowSize(): 1073741824
[torque-jdbc-transform] 2007-03-24 01:43:50,867 [main] INFO  (TorqueJDBCTransformTask#generateXML():261) - $
[torque-jdbc-transform] 2007-03-24 01:43:50,867 [main] INFO  (TorqueJDBCTransformTask#generateXML():262) - $ /------------------------------------ ...Getting table list
[torque-jdbc-transform] 2007-03-24 01:43:50,883 [main] INFO  (DfTableNameHandler#logDatabaseTypes():113) - $ DatabaseTypes are 'TABLE - VIEW'
[torque-jdbc-transform] 2007-03-24 01:43:50,930 [main] INFO  (TorqueJDBCTransformTask#generateXML():266) - $
[torque-jdbc-transform] 2007-03-24 01:43:50,930 [main] INFO  (TorqueJDBCTransformTask#generateXML():267) - $ TableCount: 28
[torque-jdbc-transform] 2007-03-24 01:43:50,930 [main] INFO  (TorqueJDBCTransformTask#generateXML():268) - $ ---------------------- /
[torque-jdbc-transform] 2007-03-24 01:43:50,930 [main] INFO  (TorqueJDBCTransformTask#generateXML():269) - $
[torque-jdbc-transform] 2007-03-24 01:43:50,930 [main] INFO  (TorqueJDBCTransformTask#generateXML():270) - $ *************************************/
[torque-jdbc-transform] 2007-03-24 01:43:51,086 [main] INFO  (TorqueJDBCTransformTask#generateXML():281) - ...Processing table: テーブル名1
[torque-jdbc-transform] 2007-03-24 01:43:51,164 [main] INFO  (TorqueJDBCTransformTask#generateXML():281) - ...Processing table: テーブル名2
	(中略)
[torque-jdbc-transform] 2007-03-24 01:43:52,461 [main] INFO  (TorqueJDBCTransformTask#generateXML():281) - ...Processing table: テーブル名27
[torque-jdbc-transform] 2007-03-24 01:43:52,508 [main] INFO  (TorqueJDBCTransformTask#generateXML():281) - ...Processing table: テーブル名28
[torque-jdbc-transform] 2007-03-24 01:43:52,555 [main] INFO  (TorqueJDBCTransformTask#doExecute():219) - $
[torque-jdbc-transform] 2007-03-24 01:43:52,555 [main] INFO  (TorqueJDBCTransformTask#doExecute():220) - $
[torque-jdbc-transform] 2007-03-24 01:43:52,555 [main] INFO  (TorqueJDBCTransformTask#doExecute():221) - $ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
[torque-jdbc-transform] 2007-03-24 01:43:52,570 [main] INFO  (TorqueJDBCTransformTask#doExecute():222) - $ ...Serializing XML: ./schema/project-schema-cepoc.xml
[torque-jdbc-transform] 2007-03-24 01:43:52,617 [main] INFO  (TorqueJDBCTransformTask#doExecute():232) - $ * * * * * * * * */
[torque-jdbc-transform] 2007-03-24 01:43:52,617 [main] INFO  (TorqueJDBCTransformTask#doExecute():233) - $
[torque-jdbc-transform] 2007-03-24 01:43:52,617 [main] INFO  (TorqueJDBCTransformTask#doExecute():239) - -------------------------------------------------------
 [Torque - JDBCToXMLSchema] Finish!

BUILD SUCCESSFUL
Total time: 4 seconds
        1 個のファイルをコピーしました。
続行するには何かキーを押してください . . .

./schema以下にproject-schema-xxx.xmlが作成されていたら成功です。

まぁ、ファイルがproject-schema-cepoc.xmlが出来ているので成功ですかね。中をのぞいてみて、それっぽいのが書かれています。ああ、のっけから28テーブルのデータベースをネタにしてしまった…。もっと簡単なのから試せよな〜、自分。しかも、データベースは設計し直す気満々なのに(笑)

ソース生成!

generate.batを実行します。

押忍。

        1 個のファイルをコピーしました。
Buildfile: D:\develop\dbflute-0.4.3\build-torque.xml

check-use-classpath:

check-run-only-on-schema-change:

om-check:

om:
     [echo] +------------------------------------------+
     [echo] |                                          |
     [echo] | Generating Peer-based Object Model for   |
     [echo] | YOUR Torque project!                     |
     [echo] |                                          |
     [echo] +------------------------------------------+

om-classpath:

om-template:
[torque-data-model] Using contextProperties file: D:\develop\dbflute-0.4.3\build.properties
[torque-data-model] 2007-03-24 01:51:36,273 [main] DEBUG (DfAntTaskUtil#getBuildProperties():62) - Using contextProperties file: D:\develop\dbflute-0.4.3\build.properties
[torque-data-model] 2007-03-24 01:51:36,289 [main] DEBUG (DfAntTaskUtil#getBuildProperties():87) - [Properties]: size=19
[torque-data-model] 2007-03-24 01:51:36,289 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.statementResultSetConcurrency = ResultSet.CONCUR_READ_ONLY
[torque-data-model] 2007-03-24 01:51:36,305 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database.driver = org.postgresql.Driver
[torque-data-model] 2007-03-24 01:51:36,305 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.project = cepoc
[torque-data-model] 2007-03-24 01:51:36,305 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.isMakeConditionQueryNumericArgumentLong = false
[torque-data-model] 2007-03-24 01:51:36,305 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database.schema = public
[torque-data-model] 2007-03-24 01:51:36,305 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.isMakeDeprecated = false
[torque-data-model] 2007-03-24 01:51:36,320 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.packageBase = jp.xet.cepoc
[torque-data-model] 2007-03-24 01:51:36,320 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.statementResultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE
[torque-data-model] 2007-03-24 01:51:36,320 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database.user = cepoc
[torque-data-model] 2007-03-24 01:51:36,320 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.invokeSqlDirectoryDefinitionMap = map:{ isAutoCommit = false ; isRollbackOnly = true ; isErrorContinue = false ; sqlDirectory = ./playsql }
[torque-data-model] 2007-03-24 01:51:36,351 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database.url = jdbc:postgresql://localhost/cepoc
[torque-data-model] 2007-03-24 01:51:36,351 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database.password = ****
[torque-data-model] 2007-03-24 01:51:36,351 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.targetLanguage = java
[torque-data-model] 2007-03-24 01:51:36,351 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.isMakeConditionQueryEqualEmptyString = false
[torque-data-model] 2007-03-24 01:51:36,351 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.isMakeBehaviorForUpdate = false
[torque-data-model] 2007-03-24 01:51:36,351 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.isAvailableBehaviorGeneration = true
[torque-data-model] 2007-03-24 01:51:36,351 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.invokeReplaceSchemaDefinitionMap = map:{ isAutoCommit = false ; isRollbackOnly = false ; isErrorContinue = true ; sqlFile = ./playsql/replace-schema.sql }
[torque-data-model] 2007-03-24 01:51:36,351 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.java.dir = ../src/main/java
[torque-data-model] 2007-03-24 01:51:36,351 [main] DEBUG (DfAntTaskUtil#getBuildProperties():91) -     torque.database = postgresql
[torque-data-model] Generating to file D:\develop\src\main\java\report.cepoc.om.generation
[torque-data-model] 2007-03-24 01:51:36,726 [main] INFO  (DTDResolver#resolveEntity():117) - Resolver: used database.dtd from 'org.apache.torque.engine.database.transform' package
[torque-data-model] 2007-03-24 01:51:37,273 [main] DEBUG (Database#initializeCustomizeDao():544) - /=============================
[torque-data-model] 2007-03-24 01:51:37,273 [main] DEBUG (Database#initializeCustomizeDao():545) - ...Initializing customize dao.
[torque-data-model] 2007-03-24 01:51:37,273 [main] DEBUG (Database#initializeCustomizeDao():575) - ========/
[torque-data-model] 2007-03-24 01:51:37,289 [main] DEBUG (DfAdditionalForeignKeyInitializer#initializeAdditionalForeignKey():56) - /======================================
[torque-data-model] 2007-03-24 01:51:37,289 [main] DEBUG (DfAdditionalForeignKeyInitializer#initializeAdditionalForeignKey():57) - ...Initializing additional foreign key.
[torque-data-model] 2007-03-24 01:51:37,523 [main] DEBUG (DfAdditionalForeignKeyInitializer#initializeAdditionalForeignKey():128) - ========/
[torque-data-model] 2007-03-24 01:51:37,523 [main] DEBUG (Database#initializeIncludeQuery():599) - /=============================
[torque-data-model] 2007-03-24 01:51:37,539 [main] DEBUG (Database#initializeIncludeQuery():600) - ...Initializing customize dao.
[torque-data-model] 2007-03-24 01:51:37,539 [main] DEBUG (Database#initializeIncludeQuery():632) - ========/
[torque-data-model] 2007-03-24 01:51:37,555 [main] DEBUG (Database#debug():1450) - public void parseAnnotation) {
[torque-data-model] 2007-03-24 01:51:37,555 [main] DEBUG (Database#debug():1450) -     parseOutsideSql();
[torque-data-model] 2007-03-24 01:51:37,555 [main] DEBUG (Database#debug():1450) - }
[torque-data-model] 2007-03-24 01:51:37,555 [main] DEBUG (Database#debug():1450) - [behavior-interface & abstract]
[torque-data-model] 2007-03-24 01:51:37,555 [main] DEBUG (Database#debug():1450) -     ...Parsing BehaviorReadable
[torque-data-model] 2007-03-24 01:51:37,570 [main] DEBUG (Database#debug():1450) -     ...Parsing BehaviorWritable
[torque-data-model] 2007-03-24 01:51:37,586 [main] DEBUG (Database#debug():1450) -     ...Parsing AbstractBehaviorReadable
[torque-data-model] 2007-03-24 01:51:37,601 [main] DEBUG (Database#debug():1450) -     ...Parsing AbstractBehaviorWritable
[torque-data-model] 2007-03-24 01:51:37,601 [main] DEBUG (Database#debug():1450) - ...Parsing dao-interface
[torque-data-model] 2007-03-24 01:51:37,617 [main] DEBUG (Database#debug():1450) - public void parseEntityInterface() {
[torque-data-model] 2007-03-24 01:51:37,617 [main] DEBUG (Database#debug():1450) -     parseEntity();
[torque-data-model] 2007-03-24 01:51:37,617 [main] DEBUG (Database#debug():1450) -     parseEntityDefinedCommonColumn();
[torque-data-model] 2007-03-24 01:51:37,633 [main] DEBUG (Database#debug():1450) - }
[torque-data-model] 2007-03-24 01:51:37,633 [main] DEBUG (Database#debug():1450) - public void parseConditionBeanFramework() {
[torque-data-model] 2007-03-24 01:51:37,633 [main] DEBUG (Database#debug():1450) -     ...Parsing FetchNarrowingBeanContext
[torque-data-model] 2007-03-24 01:51:37,664 [main] DEBUG (Database#debug():1450) -     ...Parsing FetchNarrowingBean
[torque-data-model] 2007-03-24 01:51:37,664 [main] DEBUG (Database#debug():1450) -     ...Parsing SimpleOrderByBean
[torque-data-model] 2007-03-24 01:51:37,664 [main] DEBUG (Database#debug():1450) -     ...Parsing SimplePagingBean
[torque-data-model] 2007-03-24 01:51:38,101 [main] DEBUG (Database#debug():1450) -     ...Parsing PagingResultBean
[torque-data-model] 2007-03-24 01:51:38,101 [main] DEBUG (Database#debug():1450) -     ...Parsing ListResultBean
[torque-data-model] 2007-03-24 01:51:38,117 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionBeanContext
[torque-data-model] 2007-03-24 01:51:38,117 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionBean
[torque-data-model] 2007-03-24 01:51:38,164 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionQuery
[torque-data-model] 2007-03-24 01:51:38,164 [main] DEBUG (Database#debug():1450) -     ...Parsing PagingBean
[torque-data-model] 2007-03-24 01:51:38,164 [main] DEBUG (Database#debug():1450) -     ...Parsing OrderByBean
[torque-data-model] 2007-03-24 01:51:38,164 [main] DEBUG (Database#debug():1450) -     ...Parsing SelectResource
[torque-data-model] 2007-03-24 01:51:38,164 [main] DEBUG (Database#debug():1450) -     ...Parsing AbstractConditionBean
[torque-data-model] 2007-03-24 01:51:38,180 [main] DEBUG (Database#debug():1450) -     ...Parsing AbstractConditionQuery
[torque-data-model] 2007-03-24 01:51:38,180 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey
[torque-data-model] 2007-03-24 01:51:38,195 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_equal
[torque-data-model] 2007-03-24 01:51:38,195 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_notEqual
[torque-data-model] 2007-03-24 01:51:38,195 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_greaterThan
[torque-data-model] 2007-03-24 01:51:38,211 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_lessThan
[torque-data-model] 2007-03-24 01:51:38,211 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_greaterEqual
[torque-data-model] 2007-03-24 01:51:38,211 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_lessEqual
[torque-data-model] 2007-03-24 01:51:38,211 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_prefixSearch
[torque-data-model] 2007-03-24 01:51:38,226 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_likeSearch
[torque-data-model] 2007-03-24 01:51:38,226 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_inScope
[torque-data-model] 2007-03-24 01:51:38,226 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_notInScope
[torque-data-model] 2007-03-24 01:51:38,242 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_isNull
[torque-data-model] 2007-03-24 01:51:38,242 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionKey_isNotNull
[torque-data-model] 2007-03-24 01:51:38,242 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionOption
[torque-data-model] 2007-03-24 01:51:38,258 [main] DEBUG (Database#debug():1450) -     ...Parsing SimpleStringOption
[torque-data-model] 2007-03-24 01:51:38,258 [main] DEBUG (Database#debug():1450) -     ...Parsing LikeSearchOption
[torque-data-model] 2007-03-24 01:51:38,258 [main] DEBUG (Database#debug():1450) -     ...Parsing InScopeOption
[torque-data-model] 2007-03-24 01:51:38,273 [main] DEBUG (Database#debug():1450) -     ...Parsing SplitOptionParts
[torque-data-model] 2007-03-24 01:51:38,273 [main] DEBUG (Database#debug():1450) -     ...Parsing ToUpperLowerCaseOptionParts
[torque-data-model] 2007-03-24 01:51:38,273 [main] DEBUG (Database#debug():1450) -     ...Parsing ToSingleByteOptionParts
[torque-data-model] 2007-03-24 01:51:38,273 [main] DEBUG (Database#debug():1450) -     ...Parsing JapaneseOptionPartsAgent
[torque-data-model] 2007-03-24 01:51:38,273 [main] DEBUG (Database#debug():1450) -     ...Parsing ConditionValue
[torque-data-model] 2007-03-24 01:51:38,320 [main] DEBUG (Database#debug():1450) -     ...Parsing SqlClause
[torque-data-model] 2007-03-24 01:51:38,320 [main] DEBUG (Database#debug():1450) -     ...Parsing AbstractSqlClause
[torque-data-model] 2007-03-24 01:51:38,336 [main] DEBUG (Database#debug():1450) -     ...Parsing SqlClause_derby
[torque-data-model] 2007-03-24 01:51:38,336 [main] DEBUG (Database#debug():1450) -     ...Parsing SqlClause_oracle
[torque-data-model] 2007-03-24 01:51:38,336 [main] DEBUG (Database#debug():1450) -     ...Parsing SqlClause_firebird
[torque-data-model] 2007-03-24 01:51:38,351 [main] DEBUG (Database#debug():1450) -     ...Parsing SqlClause_mysql
[torque-data-model] 2007-03-24 01:51:38,351 [main] DEBUG (Database#debug():1450) -     ...Parsing SqlClause_postgresql
[torque-data-model] 2007-03-24 01:51:38,351 [main] DEBUG (Database#debug():1450) -     ...Parsing SqlClause_mssql
[torque-data-model] 2007-03-24 01:51:38,351 [main] DEBUG (Database#debug():1450) -     ...Parsing SqlClause_db2
[torque-data-model] 2007-03-24 01:51:38,367 [main] DEBUG (Database#debug():1450) -     ...Parsing SqlClause_interbase
[torque-data-model] 2007-03-24 01:51:38,367 [main] DEBUG (Database#debug():1450) -     ...Parsing SqlClause_default
[torque-data-model] 2007-03-24 01:51:38,367 [main] DEBUG (Database#debug():1450) -     ...Parsing OrderByClause
[torque-data-model] 2007-03-24 01:51:38,367 [main] DEBUG (Database#debug():1450) -     ...Parsing OrderByElement
[torque-data-model] 2007-03-24 01:51:38,367 [main] DEBUG (Database#debug():1450) - }
[torque-data-model] 2007-03-24 01:51:38,383 [main] DEBUG (Database#debug():1450) - public void parseDBMetaClass() {
[torque-data-model] 2007-03-24 01:51:38,383 [main] DEBUG (Database#debug():1450) -     parseDBMeta();
[torque-data-model] 2007-03-24 01:51:38,383 [main] DEBUG (Database#debug():1450) -     parseAbstractDBMeta();
[torque-data-model] 2007-03-24 01:51:38,383 [main] DEBUG (Database#debug():1450) -     parseDBMetaInstanceHandler();
[torque-data-model] 2007-03-24 01:51:38,398 [main] DEBUG (Database#debug():1450) - public void parseExceptionClass() {
[torque-data-model] 2007-03-24 01:51:38,398 [main] DEBUG (Database#debug():1450) -     parseRecordHasAlreadyBeenDeletedException();
[torque-data-model] 2007-03-24 01:51:38,398 [main] DEBUG (Database#debug():1450) -     parseRecordHasOverlappedException();
[torque-data-model] 2007-03-24 01:51:38,398 [main] DEBUG (Database#debug():1450) -     parseSelectedCountExceedMaxCountException();
[torque-data-model] 2007-03-24 01:51:38,414 [main] DEBUG (Database#debug():1450) - }
[torque-data-model] 2007-03-24 01:51:38,414 [main] DEBUG (Database#debug():1450) - public void parseHelperClass() {
[torque-data-model] 2007-03-24 01:51:38,414 [main] DEBUG (Database#debug():1450) -     parseMapListString();
[torque-data-model] 2007-03-24 01:51:38,414 [main] DEBUG (Database#debug():1450) -     parseMapListStringImpl();
[torque-data-model] 2007-03-24 01:51:38,414 [main] DEBUG (Database#debug():1450) -     parseMapStringBuilder();
[torque-data-model] 2007-03-24 01:51:38,430 [main] DEBUG (Database#debug():1450) -     parseMapStringBuilderImpl();
[torque-data-model] 2007-03-24 01:51:38,445 [main] DEBUG (Database#debug():1450) -     parseGeneralCharacter();
[torque-data-model] 2007-03-24 01:51:38,445 [main] DEBUG (Database#debug():1450) -     parseGeneralCharacterImpl();
[torque-data-model] 2007-03-24 01:51:38,476 [main] DEBUG (Database#debug():1450) -     parseJapaneseCharacter();
[torque-data-model] 2007-03-24 01:51:38,476 [main] DEBUG (Database#debug():1450) -     parseJapaneseCharacterImpl();
[torque-data-model] 2007-03-24 01:51:38,476 [main] DEBUG (Database#debug():1450) - }
[torque-data-model] 2007-03-24 01:51:38,476 [main] DEBUG (Database#debug():1450) - parseDaoDicon() {
[torque-data-model] 2007-03-24 01:51:38,476 [main] DEBUG (Database#debug():1450) -     /dbflute.dicon
[torque-data-model] 2007-03-24 01:51:38,492 [main] DEBUG (Database#debug():1450) - }
[torque-data-model] 2007-03-24 01:51:38,492 [main] DEBUG (Database#debug():1450) - public void parseDaoSelector() {
[torque-data-model] 2007-03-24 01:51:38,492 [main] DEBUG (Database#debug():1450) -     parseDaoSelector();
[torque-data-model] 2007-03-24 01:51:38,492 [main] DEBUG (Database#debug():1450) -     parseCacheDaoSelector();
[torque-data-model] 2007-03-24 01:51:38,508 [main] DEBUG (Database#debug():1450) - }
[torque-data-model] 2007-03-24 01:51:38,508 [main] DEBUG (Database#debug():1450) - parseGenMetaData();
[torque-data-model] 2007-03-24 01:51:38,508 [main] DEBUG (Database#debug():1450) - public void parseInterceptorClass() {
[torque-data-model] 2007-03-24 01:51:38,523 [main] DEBUG (Database#debug():1450) -     parseCommonColumnSetupAbstractInterceptor();
[torque-data-model] 2007-03-24 01:51:38,523 [main] DEBUG (Database#debug():1450) -     parseCommonColumnSetupBeforeInsertInterceptor();
[torque-data-model] 2007-03-24 01:51:38,523 [main] DEBUG (Database#debug():1450) -     parseCommonColumnSetupBeforeUpdateInterceptor();
[torque-data-model] 2007-03-24 01:51:38,523 [main] DEBUG (Database#debug():1450) -     parseCommonColumnSetupBeforeDeleteInterceptor();
[torque-data-model] 2007-03-24 01:51:38,539 [main] DEBUG (Database#debug():1450) - }
[torque-data-model] 2007-03-24 01:51:38,570 [main] DEBUG (Database#debug():1450) - public void parseTableList() {
[torque-data-model] 2007-03-24 01:51:38,570 [main] DEBUG (Database#debug():1450) -     final List<Table> tableList = getTableList();
[torque-data-model] 2007-03-24 01:51:38,570 [main] DEBUG (Database#debug():1450) -     for (Table tbl : tableList) {
[torque-data-model] 2007-03-24 01:51:38,570 [main] DEBUG (Database#debug():1450) -         parseTable(tbl); // テーブル名1 : テーブル名1
	(中略)
[torque-data-model] 2007-03-24 01:51:43,023 [main] DEBUG (Database#debug():1450) -         parseTable(tbl); // テーブル名28 : テーブル名28
[torque-data-model] 2007-03-24 01:51:43,148 [main] DEBUG (Database#debug():1450) -     }
[torque-data-model] 2007-03-24 01:51:43,148 [main] DEBUG (Database#debug():1450) - }

BUILD SUCCESSFUL
Total time: 14 seconds
続行するには何かキーを押してください . . . 

Propertyにて指定した出力DirectoryにSourceが作成されていたら成功です。

ソース生成されていました。

確認のために生成されたSourceをCompileしてみて下さい。

eclipseなので、勝手にコンパイルされてるハズ。

確認のためにCompileされたDaoでSelectしてみて下さい。

んーーーと。これらのテーブル、データが全然入ってません。というわけで、データ用意しなきゃですね。ちょっと手間かかるので、明日にでも。

以上で、初期セットアップはOKですかね。

本日のまとめ、というか気づいたこと。

  • %DBFLUTE_HOME%からtorque.java.dir(../src/main/java)を辿ったところにreport.cepoc.om.generationっていうファイルが…。こんな所にファイル作られても…。
  • ルートパッケージを、Doltengで指定したものと同一にしてしまったが、Teedaのパッケージと混在してしまうので、jp.xet.cepoc.dbflute等にしておいた方が良いのかな?と思いました。
  • Teeda上で使う場合、src/main/java に生成された dbflute.dicon は src/main/resources に移動した方がいいですかね。あ、でも繰り返し生成する時に混乱するかな?

というわけで、本日はここまで!