目次


TableModel FreeフレームワークでSwing開発を容易にする

TableModelの重荷からの解放

Comments

今年のJavaOneコンファレンスでは「Java ™ Desktopの再導入」が強調されていました。Swingはあまりにも遅く、使いにくく、また見た目が悪いと思っていた人達には、SwingやGUIの開発に改めて努力が向けられているというニュースはあまり歓迎されていません。ところが、最近Swingで作業したことのない人のために言うと、問題であったことの多くが解決されているのです。Swingはより効率良く動作するように、またJava 2D APIをよりうまく利用するように改善されています。Swing開発者の手によって1.4でのルック・アンド・フィールが改善され、最近リリースされた5.0では、それがさらに改善されました。Swingは復活し、今までにないほど良くなっているのです。

今Swingコミュニティーに必要なのは、GUI開発をより容易に、そして円滑にするための適切なツールです。そこでこの記事の登場です。

この記事では、GUI開発パッケージであるTableModel Free (TMF) フレームワークを紹介します。TMFフレームワークを使うと、JTableごとにTableModels(この記事では、これから紹介する新しい構造と区別するために古典的TableModelsと呼ぶことにします)を作る必要が無くなります。この記事を読めば、皆さんもJTableをより柔軟に構成し、維持管理できるようになるでしょう。

JTableを使ったことのある人であれば、いやでもTableModelを使わざるを得なかったでしょう。また、どのTableModelのコードも他のTableModelのコードとほとんど完全に同じであり、違っているコードは結局、コンパイルされたJavaクラスには属さない、ということにも恐らく気がついたでしょう。この記事では、現在TableModel/JTableの設計で使用している方法を解析してその欠点を説明し、なぜその方法では真の意味でのModel-View-Controller (MVC) パターンを実現できないのかを説明します。これを読むことで、TMFフレームワークを構成しているフレームワークやコードが分かると思いますが、このコードはオープン・ソースのプロジェクトで私が書いたり使ったりしたことのあるコードを組み合わせたものです。このフレームワークを使えば、TableModelのサイズを何百行ものコードから、たった一行にまで減らすことができ、また重要なテーブル情報を外部XMLファイルに置くことができるようになります。この記事を読み終われば、皆さんもJTableのデータを、下記に示すような、たった一行のコードで操作できるようになるのです。

  TableUtilities.setViewToModel("tableconfig.xml", "My Table", 
    myJTable, CollectionUtilities.observableList(myData));

JTableとTableModelにおけるMVCの問題

MVCはデータのビューとビジネス・ロジックを明確に分離していることから、非常に人気のあるUI設計パターンとなっています。StrutsはWebでMVC設計を使った非常に良い例です。Swingのうたい文句として最初に強調されたのは、MVC設計を使っていること、つまりビューとモデルを分離している、という点でした。つまりコードは非常にモジュラー化されているので、モデル中のコードを何ら変更することなくビューを入れ換えできる、と考えられたのです。JTablesやTableModelsで実際に作業したことのある人であれば、そんなことはできるわけがない、と大笑いするでしょう。理想的に言えばMVC設計パターンを使うことによって、データを表現するモデルの中のコードを変更することなく、JTableをJListまたはJComboBoxで代替することができるはずです。ところがSwingではそれができません。Swingでは、たとえJTable、JList、JComboBoxという3つのコンポーネントがどれも同じデータ・モデルのビューを提供するものであったとしても、アプリケーションに対してホット・スワップできないようになっているのです。これはSwingのMVC設計における大きな欠陥です。JListとJTableを交換したい時には、ビューの背後にあるモデル全体を書き直す必要があるのです。

MVCの面におけるJTable/TableModel特有の欠陥のもう一つは、モデルが変更された時にビューが更新されないことです。モデルがビューに対して「更新を行うように」と伝えるように、モデルの参照を保持し、ファンクションを呼ぶ必要があるのです。理想的には、何らコードを追加することなくこれが起こるべきなのです。

JTableとTableModelコンポーネントの設計における究極的な問題は、この2つがお互いに、あまりにも密接にからみあっていることです。JTableでコードを変更する場合には、その変更でTableModelを壊していないことを確認する必要があり、逆の場合も同じです。モジュラー性を旨として構築された設計パターンであるはずのものが、現在の実装ではむしろ、依存性を持った設計になってしまっています。

TMFフレームワークでは、JTableでのビューの役割とモデルの役割をより明確に分離することによって、MVCの目標に近づいたものになっています。コンポーネントをホット・スワップできるという、より高度な目標には達していませんが、正しい方向に向けての一歩ということができます。

TMFフレームワークを導入する

ではTMFフレームワークを調べながら、なぜTMFフレームワークに比べると古典的TableModelが時代遅れと考えられるのかを調べることにしましょう。フレームワークを設計する上での最初は、JTableの使い方を学ぶことです。つまり開発者がJTableをどのように使い、JTableが何を表示するのか、また、内部化、汎用化できるものは何か、そして開発者が構成できるようにしておくべきものは何かを理解することが必要です。同じことがTableModelsに対しても言えます。コードから除けるものは何か、残すべきものは何かを判断する必要があります。こうした問題が明確になれば、あとは誰もが使えるようにコードを充分汎用にし、なおかつ誰もが容易に構成できるような最適手法を決めるだけです。

TMFフレームワークは3つの基本部分に分かれています。どんなタイプのデータも操作できる汎用TableModelと、テーブル部分(テーブル毎に異なります)を構成できる外部XMLファイル、そしてモデルとビューの間のブリッジ、という3つです。

この記事で使用しているソースコードやサードパーティーJARファイル、Javadocなどをダウンロードするには、 Code アイコンをクリックしてください(または ダウンロード・セクション を見てください)。この記事で使用している全てのソースコードはsrcフォルダーの中に入っています。TMF特有のコードは com.ibm.j2x.swing.table パッケージにあります。

com.ibm.j2x.swing.table.BeanTableModel

BeanTableModelはTMFフレームワークの最初の部分です。これは汎用のTableModelとして動作し、どんなタイプのデータでも使うことができます。皆さんはきっと「なぜ、どんなデータでも動作する、と言えるのか?」と言いたいでしょう。いや確かに、確実にそう言えるわけではないことは明らかで、実際動作しない場合もあります。しかし私がJTableを使ってきた経験から(ちょっと私を肘でつつく人がいても)、JTableは99%の場合、データ・オブジェクトのリスト(つまりJavaBeansコンポーネントのArrayList)を表示するために使われている、と言って間違いありません。この想定の下で、私はどんなデータ・オブジェクト・リストの表示にも使用できる汎用のテーブル・モデル、BeanTableModelを作りました。

Bean中のフィールドを調べたり、正しいデータを表示したりするために、BeanTableModel ではJavaイントロスペクション(introspection)を頻繁に使います。またこの設計を補助するために、Jakarta Commons Collectionsフレームワーク( 囲み記事 に情報があります)にある2つのクラスも使います。

コードに入る前に、クラスの概念を幾つか説明しておきましょう。beansに対するイントロスペクションを使うので、bean自体に対する情報、その主なものとして、フィールド名は何なのか、を知る必要があります。これは通常のイントロスペクションで行うこともできます。つまりbeanを検査し、そのフィールドを割り出すのです。しかし、これはテーブルに対しては充分ではありません。大部分の場合では、テーブルのフィールドを特定な順序で表示する必要があるためです。さらに、イントロスペクションではbeanから得られない、テーブルの表示に必要な情報があります。それがカラム名です。ですからテーブルを正しく表示するためには各カラムに関して、カラム名とbean中にあるフィールド、という2つの情報が必要になります。私はこれを、カラム名がキー、フィールドが値という、キーと値の対という形で表現しています。

ここで、Jakarta CommonsのCollectionsフレームワークから使った2つのクラスが登場します。 BeanMap は、イントロスペクションに対応する際の面倒な仕事を全て引き受けるユーテリティー・クラスとして動作します。通常、イントロスペクションの開発には、テーブルには不必要な、多くの try / catch ブロックが必要になります。 BeanMap は入力としてbeanを取り、それをHashMapのように扱います。HashMapでは、キーはbean中のフィールド(例えば firstName )であり、値はgetファンクション(例えば getFirstName() )の結果です。BeanTableModelは BeanMap を頻繁に使うことでイントロスペクションに伴う面倒さを取り除くので、bean中の情報へのアクセスがずっと簡単になります。

LinkedMap はBeanTableModel全体で使われる、もう一方のクラスです。カラム名とフィールドとのマッピングに対するキーと値のデータ設定に戻ると、データ・オブジェクトの選択として明らかなのはHashMapです。ところがHashMapは挿入順序を保存しません。挿入順序はテーブルの重要部分なので、テーブルが表示される時には特定な順序でカラムが現れるようにしたいものです。ですから挿入順序は保存する必要があります。その解決方法が LinkedMap です。これは LinkedListHashMap の組み合わせであり、カラムとカラム順序情報の両方を保存します。リスト1を見ると、テーブルの情報を設定するために LinkedMapBeanMap をどのように使っているかが分かるでしょう。

リスト1. LinkedMapとBeanMapでテーブル情報を設定する
 protected List mapValues = new ArrayList();
   protected LinkedMap columnInfo = new LinkedMap();  
   
   protected void initializeValues(Collection values)
   {
      List listValues = new ArrayList(values);
      mapValues.clear();
      for (Iterator i=listValues.iterator(); i.hasNext();)
      {
         mapValues.add(new BeanMap(i.next()));
      }
   }

BeanTableModelのコードで調べてみると面白いのは、汎用TableModel部分そのもの、つまり AbstractTableModel を拡張するコード部分です。古典的TableModelを作るために皆さんが通常使用するコードと、リスト2のコードは似ていることが分かるでしょう。

リスト2. BeanTableModelでの汎用TableModelコード
   /**
    * Returns the number of BeanMaps, therefore the number of JavaBeans
    */    
   public int getRowCount() 
   {
      return mapValues.size();
   }

   /**
    * Returns the number of key-value pairings in the column LinkedMap
    */    
   public int getColumnCount() 
   {
      return columnInfo.size();
   }
   
   /**
    * Gets the key from the LinkedMap at the specified index (and a 
    * good example of why a LinkedMap is needed instead of a HashMap)
    */   
   public String getColumnName(int col) 
   {
      return columnInfo.get(col).toString();
   }

   /**
    * Gets the class of the column.  A lot of developers wonder what 
    * this is even used for.  It is used by the JTable to use custom 
    * cell renderers, some of which are built into JTables already 
    * (Boolean, Integer, String for example).  If you  write a custom cell 
    * renderer it would get loaded by the JTable for use in display  if that 
    * specified class were returned here.
    * The function uses the BeanMap to get the actual value out of the 
    * JavaBean and determine its class.  However, because the BeanMap 
    * autoboxes things -- it converts the primitives to Objects for you 
    * (e.g. ints to Integers) -- the code needs to unautobox it, since the 
    * function must return a Class Object.  Thus, it recognizes any primitives 
    * and converts them to their respective Object class.
    */   
   public Class getColumnClass(int col) 
   {
      BeanMap map = (BeanMap)mapValues.get(0);
      Class c = map.getType(columnInfo.getValue(col).toString());
      if (c == null)
         return Object.class;
      else if (c.isPrimitive())
         return ClassUtilities.convertPrimitiveToObject(c);
      else
         return c;
   }


   /**
    * The BeanTableModel automatically returns false, and if you 
    * need to make an editable table, you'll have to subclass 
    * BeanTableModel and override this function.
    */    
   public boolean isCellEditable(int row, int col) 
   {
      return false;
   }

   /**
    * The function that returns the value that you see in the JTable.  It gets 
    * the BeanMap wrapping the JavaBean based on the row, it uses the 
    * column number to get the field from the column information LinkedMap, 
    * and then uses the field to retrieve the value out of the BeanMap.  
    */
   public Object getValueAt(int row, int col) 
   {
      BeanMap map = (BeanMap)mapValues.get(row);
      return map.get(columnInfo.getValue(col));
   }

   /**
    * The opposite function of the getValueAt -- it duplicates the work of the 
    * getValueAt, but instead puts the Object value into the BeanMap instead 
    * of retrieving its value.
    */
   public void setValueAt(Object value, int row, int col) 
   {
      BeanMap map = (BeanMap)mapValues.get(row);
      map.put(columnInfo.getValue(col), value);
      super.fireTableRowsUpdated(row, row);
   }
   
   /**
    * The BeanTableModel implements the CollectionListener interface 
    * (1 of the 3 parts of the framework) and thus listens for changes in the 
    * data it is modeling and automatically updates the JTable and the 
    * model when a change occurs to the data.
    */  
   public void collectionChanged(CollectionEvent e)
   {
      initializeValues((Collection)e.getSource());
      super.fireTableDataChanged();
   }

ご覧の通りBeanTableModelを使うとTableModel全体が、どんなテーブルに対しても使えるように充分汎用なものになります。イントロスペクションを最大限利用して、古典的TableModelsでは絶対に必要でありながら完全に冗長であったbean特有のコーディングを省略するのです。BeanTableModelはTMFフレームワークの外でも使うことができます(ただし、その強力さと柔軟性は一部失います)。

このコードを見ると、皆さんには幾つかの疑問がわいてくるでしょう。第一に、BeanTableModelはカラム名とフィールド・キーの対に関する情報をどこで得るのでしょう? 第二に、 ObservableCollection とはいったい何者なのでしょう? こうした疑問が正に、このフレームワークの、次の2つの部分につながって行きます。その疑問に対する答えは、この先の幾つかのセクションに出てきます。

Castor XMLパーサー

ここで必要なカラム名とフィールド情報の保存場所として、最も論理的なのはJavaクラスの外です。Javaクラス外であれば、Javaコードを再コンパイルせずに、この情報を変更することができます。TMFフレームワークにおけるテーブルの固有情報として唯一のものが、カラム名とフィールドに関するこの情報なので、テーブル全体が外部から構成可能であるということになります。

明らかなことですが、この解決方法ではコンフィギュレーション・ファイル用の言語としてXMLを選ぶことが必須、ということになります。コンフィギュレーション・ファイルは様々なテーブル・モデルの情報を保存する必要があります。またこのファイルを使って、各カラムのデータを規定できる必要もあります。さらにコンフィギュレーション・ファイルは、開発者でない人が変更を加えることもあり得るため、できるだけ読みやすい必要もあります。

そうした要求に応える最良の解がCastor XMLパーサーです(さらに詳しくは 囲み記事 を見てください)。Castorが使われているところを見るのに一番良い方法は、TMFフレームワークでどう使われているかを見ることです。

このコンフィギュレーション・ファイルの目標を考えてみましょう。テーブル・モデルとそのカラムの情報を保存することが目標です。XMLファイルはこの情報を、できるだけ簡単に表示する必要があります。TMFフレームワークのXMLファイルは、リスト3に示す形式を使ってテーブル・モデル情報を保存しています。

リスト3. TMFコンフィギュレーション・ファイルの例
   <model>
      <className>demo.hr.TableModelFreeExample</className>
      <name>Hire</name>
      <column>
         <name>First Name</name>
         <field>firstName</field>
      </column>
      <column>
         <name>Last Name</name>
         <field>lastName</field>
      </column>
   </model>

この目標を逆に言うと、開発者が取り扱うJavaオブジェクトは、XMLファイルと同じくらい理解しやすいものである必要がある、ということになります。これはカラム情報を保存するためにCastor XMLパーサーで使用している3つのJavaオブジェクト、つまり TableData (ファイル中の全テーブル・モデルを保存する)と TableModelData (テーブル・モデル固有情報を保存する)と TableModelColumnData (カラム情報を保存する)の中にある、と考えることができます。この3つのクラスは、TableModelに関して必要な全情報を取得するために必要となる、全てのラッパーを提供しています。

これらをつなぎ合わせ、すべて包み込むのが、マッピング・ファイルとして知られているものです。これは、単純なXMLを単純なJavaオブジェクトにマップするためにCastorが使うXMLファイルです。理想的にはこのマッピング・ファイルも単純なのでしょうが、現実には少し複雑です。良くできたマッピング・ファイルであれば、マッピング・ファイル以外が非常に単純になります。ですから一般的には、マッピング・ファイルが複雑であればあるほど、コンフィギュレーション・ファイルやJavaオブジェクトは扱いが容易になります。マッピング・ファイルは、正にその名前の通り、XMLオブジェクトをJavaオブジェクトにマップします。リスト4はTMFフレームワークで使われるマッピング・ファイルを示しています。

リスト4. TMFフレームワーク用のCasterマッピング・ファイル
   <?xml version="1.0"?>
   <mapping>
      <description>A mapping file for externalized table models</description>
	
      <class name="com.ibm.j2x.swing.table.TableData">
         <map-to xml="data"/>
         <field name="tableModelData" collection="arraylist" type=
           "com.ibm.j2x.swing.table.TableModelData">
            <bind-xml name="tableModelData"/>
         </field>
      </class>
	
      <class name="com.ibm.j2x.swing.table.TableModelData">
         <map-to xml="model"/>
         <field name="className" type="string">
            <bind-xml name="className"/>
         </field>
         <field name="name" type="string">
            <bind-xml name="name"/>
         </field>
         <field name="columns" collection="arraylist" type=
           "com.ibm.j2x.swing.table.TableModelColumnData">
            <bind-xml name="columns"/>
         </field>
      </class>
	
      <class name="com.ibm.j2x.swing.table.TableModelColumnData">
         <map-to xml="column"/>
         <field name="name" type="string">
            <bind-xml name="name"/>
         </field>
         <field name="field" type="string">
            <bind-xml name="field"/>
         </field>		
      </class>
	
   </mapping>

マッピング・ファイルがテーブル・モデル情報の保存に使われる各クラスの概要を明確に示し、またクラス・タイプを定義し、XMLファイル中の名前をJavaオブジェクト中のフィールドにリンクしていることが、コードを見るだけで分かります。こうした名前を同じに保つことで様々なことが容易になり、操作しやすくなりますが、そうすることが必須なわけではありません。CastorのXMLマッピングに関するさらに詳しい情報は、 参考文献 を見てください。

これでカラム名とフィールドの情報が外部化でき、カラム情報を含むJavaオブジェクトとして読み込まれたので、容易にBeanTableModelに送り、カラムの設定に使うことができます。

ObservableCollection

TMFフレームワークで鍵となる最後の部分が、 ObservableCollection として知られているものです。皆さんの中には既に ObservableCollection の概念をよく知っている人がいるでしょう。 ObservableCollection では、Java Collectionsフレームワークのメンバーが、自分が変更された時にイベントを投げます。そしてそのイベントに基づいてリスナーがアクションを実行できるようになっています。これはJavaの正式リリースでは紹介されたことがありませんが、インターネットには既にいくつか、サードパーティーがこの概念を実装したものがあるのです。このフレームワークで必要なのは最も基本的な機能だけなので、この記事では私自身の ObservableCollection 実装を使いました。この実装では collectionChanged() というメソッドを使っています。このメソッドが変更される度に、このメソッドのリスナーが ObservableCollection を使ってこのメソッドを呼びます。この使い方はCollectionクラスのDecoratorと呼ばれ、数行のコードを追加するだけで、通常のCollectionクラスからCollectionクラスのObservableインスタンスを作ることができます(Collectionsフレームワークのサイトを見れば、他にもCollections用Decoratorsがあることが分かります)。リスト5はObservableCollectionが実際に使われているところの例です(これは単なる例で、j2x.zipのコードには含まれていません)

リスト5. ObservableCollectionの使用例
   // convert a normal list to an ObservableList
   ObservableList oList = CollectionUtilities.observableList(list);
   // A listener could then register for events from this list by calling
   oList.addCollectionListener(this);
   // trigger event oList.add(new Integer(3));
   // listener receives event
   public void collectionChanged(CollectionEvent e)
   {
      // event received here
   }

ObservableCollection はTMFフレームワークのスコープ外でも多くのアプリケーションを持っています。皆さんがTMFフレームワークを使わないと決めたとしても、 ObservableCollection フレームワークにはコード開発に使えるような実際的な使い方が数多くあることに、皆さんも気がつくでしょう。

ただしTMFフレームワークでのObservableCollectionの使い方は、データが変更された時には自動的にビューを更新することによってビューとモデルの関係をより良く定義できる、という意味から重要なものです。先に書いたことを思い出して欲しいのですが、古典的TableModelを大きく制限していたのは、データが変更される度に、そのビューを更新するためにテーブル・モデルの参照を使う必要があるという点でした。TMFフレームワークでのObservableCollectionの使い方では、データが変更されるとビューは自動的に更新され、モデルへの参照を維持する必要はありません。これに関しては既に、 collectionChanged() メソッドのBeanTableModel実装で実際に使われているところを見ました。

TableUtilities

最後のステップは、TMFフレームワークを簡単に使えるようにする幾つかのユーティリティー・ファンクションとして、全てをまとめ上げることです。こうしたユーティリティー・メソッドは com.ibm.j2x.swing.table.TableUtilities クラスにあります。このクラスでは、必要となる下記のようなヘルパー機能の全てを提供しています。

  • getColumnInfo() : このユーティリティー・メソッドは、指定されたファイルをCastor XMLファイルを使って構文解析し、指定されたテーブル・モデルに関するカラム情報の全てをBeanTableModelが必要とする LinkedMap 形式で戻します。BeanTableModelをサブクラス化するようにした時には、このメソッドが重要になります。
  • getTableModel() : このユーティリティー・メソッドは、上記の getColumnInfo() メソッドから構築されます。これはカラム情報を取得してそれをBeanTableModelに渡し、既に設定された全カラム情報をBeanTableModelに戻すことで行われます。
  • setViewToModel() : このユーティリティー・メソッドはTMFフレームワークで最も重要な機能であり、最も大きな魅力でもあります。このメソッドは getTableModel() メソッドから構築され、テーブルに表示されるデータへの参照だけではなく、このテーブル・モデルを持つJTableへの参照も行います。それからJTableにTableModelを設定し、TableModelにデータを渡します。実質的に、たった一行のコードで、JTableに対するTableModelを完全に設定するのです。TMFフレームワークの素晴らしさはこの機能に最も良く現れています。TableModelが下記の単純な機能によって、永遠に置き換えられるのです。
    TableUtilities.setViewToModel("table_config.xml", "Table", myJTable, myList);

TMFフレームワークの実際

GUIプログラミングに関する記事には必ずサンプルが必要ですが、この記事もその例外ではありません。このサンプルの目標は、古典的TableModel設計の代わりにTMFフレームワークで作業する主な利点を明らかにすることです。このサンプルで示すアプリケーションは画面上に一つ以上のテーブルを持ち、このテーブルに追加削除を行います。また様々なタイプの情報( StringintBooleanBigDecimal )を含むテーブルを持っています。そして最も重要なこととして、定常的に変更する必要のある、可変構成のカラム情報を持っています。

このサンプル・アプリケーションのコードは J2X パッケージとは別で、ソースコードはHRフォルダーのsrcディレクトリーにあります。このアプリケーションはまた、build/libフォルダーにあるコンパイルしたJARファイルをダブル・クリックすることで、JREを通して実行することもできます。

このサンプル・アプリケーションでは、 TableModelFreeExampleTableModelExample という、お互いに交換可能な2つのクラスがあります。どちらのクラスも、このアプリケーションでは同じことを行い、アプリケーションに同じ振る舞いをさせます。ただし両者の設計は異なっており、一方はTMFフレームワークを使い、もう一方は古典的TableModelを使っています。両者を見て最初に気がつくのは、TMFクラスである TableModelFreeExample は63行のコードから構成されているのに対して、古典的TableModel版の TableModelExample は285行もの長さがあることでしょう。

Evil HR Directorアプリケーション

これから使おうとしているサンプル・アプリケーションは、Evil HR Directorアプリケーションです(HRはHuman Resourcesで人事、Evil HR Directorは意地悪な人事部長の意)。このアプリケーションでは、HRディレクター(恐らく毛むくじゃらで、メガネをかけているでしょう)がJTableで一群の入社希望者を見て、このテーブルから人を選んで雇うことができます。新入社員は雇われると、現従業員用の2つのJTableに移されます。一つのJTableには個人情報があり、もう一つの方には賃金情報があります。こうした情報から、このディレクターは好きなように従業員を解雇することができます。このアプリケーションのUIを図1に示します。

図1. Evil HR Directorアプリケーション
Evil HR Director
Evil HR Director

TMFフレームワークの単純さをさらに確認するために、リスト6を見てください。このリストには、Evil HR Directorアプリケーションで使う3つのテーブル用モデルの生成に必要となる、3行のコードがあります。これらのコードは TableModelFreeExample の中にあります。

リスト6. Evil HR Directorアプリケーションでのモデルを生成するために必要なコード
   TableUtilities.setViewToModel("demo/hr/resources/evil_hr_table.xml", 
     "Hire", hireTable, candidates);	
   TableUtilities.setViewToModel("demo/hr/resources/evil_hr_table.xml", 
     "Personal", personalTable, employees);
   TableUtilities.setViewToModel("demo/hr/resources/evil_hr_table.xml", 
     "Financial", financialTable, employees);

比較のために TableModelExample では、3つのテーブル用のモデルを古典的TableModel手法で生成するために必要なコードを含んでいます。具体的にはサンプル・パッケージにあるコードを見てください。ここに全リストは載せませんが、205行もあります!

TMFフレームワークの柔軟性を実証する

TMFフレームワークの大きな利点の一つは、JTableベースのアプリケーションを実稼働状態で使い始めてからでも変更がずっと容易だ、という点です。これを示すために、Evil HR Directorアプリケーションを日々使う上で起こり得る2つのシナリオを考えてみましょう。TMFフレームワークを使ことで、ユーザーの求める変更に対してアプリケーションがいかに容易に対応できるかが、それぞれのシナリオを見れば分かるでしょう。

シナリオ1: 会社の方針が変更され、社内アプリケーションで誰かの既婚・未婚情報を見ることは禁止となった。

  • TableModelFree : エンド・ユーザーはXMLコンフィギュレーション・ファイルから <name>Married?</name><field>married</field> を削除する必要があります。
  • Classic TableModel : Javaコードをいじってカラム名「Married?」を返さないように getColumnName() を変更し、元々よりも一つ少ないカラム数を返すように getColumnCount() を変更し、また getValueAt()isMarried() を返さないように変更する必要があります。そうしてからJavaコードを再コンパイルし、アプリケーションを再展開する必要があります。

シナリオ2: 会社の方針が変更され、入社希望者の住所のある、州の名前を含めることが必要となった。

  • TableModelFree : エンド・ユーザーはXMLコンフィギュレーション・ファイルに <name>State</name><field>state</field> を追加する必要があります。
  • Classic TableModel : Javaコードをいじって「State」という新しいカラムを追加するように getColumnName() を変更し、一つ増加したカラム数を返すように getColumnCount() を変更し、 getState() を返すように getValueAt() を変更する必要があります。そうしてからJavaコードを再コンパイルし、アプリケーションを再展開する必要があります。

ご覧になると分かる通り、アプリケーション中のテーブルを変更する時には(とんがり頭のボスの一声で、変更は頻繁に行われるものです)、XMLファイルを編集した方が、全アプリケーションを展開し直すよりもずっと容易なのです。

コードを使う

皆さんが大急ぎでTableModelコードを全て削除してしまう前に、j2x.zipファイルの中身と、それを皆さんのプロジェクトでどのように使うかについて、少し説明しておきましょう。(TMF特有のコードは com.ibm.j2x.swing.table パッケージにあることを忘れないでください。J2Xパッケージには、以前、「Go state-of-the-art with IFrame」という記事で紹介した他のコードも入っています。この記事へのリンクは 参考文献 を見てください。)

j2x.zipファイルには2つのフォルダーがあります。

  • src -- この記事で使用したソースコードを含んでいます。srcフォルダーの下には2つのフォルダーがあります。HRにはEvil HR Directorアプリケーションを作るためのソースコードが入っており、J2XにはJ2Xプロジェクトで使われた全ソースコードが入っています。
  • build -- Evil HR DirectorアプリケーションとJ2Xプロジェクトの両方の、コンパイルされたクラス・ファイルが入っています。このフォルダー内のlibフォルダーには、HRアプリケーションとJ2Xプロジェクトの両方のJARファイルが入っています。

lib.zipファイルには次のフォルダーがあります。

  • lib -- J2Xプロジェクトを利用する任意のプロジェクトと、アプリケーションのどちらを実行するにも必要な、サードパーティーJARファイルの全てが入っています。これらサードパーティーのプロジェクトに関するライセンスも、このフォルダーの中に入っています。

docs.zipファイルには次のフォルダーがあります。

  • docs -- J2Xプロジェクトに関するJavaDoc情報の全てが入っています。

皆さんのアプリケーションでJ2Xパッケージを使うには、build/libフォルダーにあるj2x.jarと、libフォルダーにある3つのサードパーティーJARファイルの全てを CLASSPATH が指すようにする必要があります。これらサードパーティー・パッケージのライセンス条件によると、この記事にあるパッケージは全て再配布することができますが、皆さんが少しでも修正してみようと思っているのであれば、ライセンス条件を良く読んでください。

まとめ

TableModel Freeフレームワークがあれば、二度と古典的TableModelを書く必要はなくなるでしょう。TMFフレームワークを使うことによって、それぞれの部分がより明確に分離され、JTableとTableModelとの間のMVC関係が改善されます。将来のリリースでは、モデル・コードを何ら変更することなく、コンポーネントをホット・スワップすることさえできるようになります。TMFフレームワークではまた、モデルが変更された時には自動的にビューを更新するので、古典的TableModel設計でのビューとモデル間では必要だった通信が、必要なくなります。

TMFフレームワークではまた、GUI開発、特にJTableに関する作業に必要な時間が劇的に減少します。その一例として、何年か前に私は、それぞれがオリジナルのテーブル・モデルを持つ150以上ものJTableがあるアプリケーションを扱ったことがあります。TMFフレームワークを使えば、150行のコードで始末ができたのですが、残念ながらTMFはまだできていませんでした。ですから必要なテーブル・モデルを生成するために、結局私達は15,000行も余分に書く羽目になったのです。これでは開発の時間ばかりではなく、テストやデバッグにも大きな時間がかかってしまうことになります。

TMFフレームワークを使うことにより、古典的TableModelを使う場合よりもずっと楽にJTableを構成できるようになります。例えば5件、別々の顧客に販売したPOSアプリケーションを考えてみてください。それぞれの顧客にとって関心のある情報は異なるので、顧客としてはその顧客固有のカラム・セットがアプリケーションのGUIに現れることを期待します。TMFフレームワークが無かったら、それぞれの顧客に対して固有のTableModelを作る必要があります。結局、その顧客固有のアプリケーションを作る必要があるわけです。構成可能なXMLファイルを使えば、各顧客が同じアプリケーションを使うことができ、顧客のオフィスにいるビジネス・アナリストが、必要に応じてXMLファイルを変更することができるのです。開発やサポートのコストがどれほど節約できるか考えてみてください!

TableModel FreeフレームワークはSwing開発者コミュニティー特有の要求、つまり開発時間や、JTableを相手に作業するための維持管理オーバーヘッドを減少させ、よりエンド・ユーザーに使いやすくする、という要求に応えるものです。Swingデスクトップが復活し、TMFフレームワークのようなツールによってSwingでの作業やGUIアプリケーションの開発が容易になります。そのために取るべき最初のステップは、全てのTableModelsを一行のTMFフレームワークのコードで置き換え、そうしたTableModelsを電脳空間のブラックホールに放り込んでしまうことだと言えるでしょう。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=226620
ArticleTitle=TableModel FreeフレームワークでSwing開発を容易にする
publish-date=10122004