いろいろ備忘録日記

主に .NET とか Go とか Flutter とか Python絡みのメモを公開しています。

特定のコンポーネント取得

以前、特定のコントロールの取得方法および削除方法にて、
C#での特定のコントロールの取得について書きましたが、今度はそれのjava版みたいなものです。


javaのswingの方には、C#みたいに欲しいコンポーネントをnameで検索して取得するような
機能がありません。(あるかもしれません。私が知らないだけかも・・・(^_^;; )


でも、アプリを作っていてイベントが起こった際にそのイベント送信元のコンポーネントの値のみを
更新する場合というのは稀です。普通は、関連する複数のコンポーネントを一気に更新します。
(たとえば、ステータスバーを更新したり、メニューを更新したり、ツールバーを更新したり・・)
そんなときに、特定のコンポーネントを自由に取得できないのはかなり不便です。
とか言って、更新がされそうなコンポーネントをフォームなどにgetterを作成して取得するように
するのはさらに面倒です。コンポーネントが増える度にgetterも増やさないといけないですので。
(フォームクラスに全てのイベント処理を書いて単一クラスでアプリケーション作成というのは論外)


てことで、C#と同じようなものがないなら大体似たようなものを作ればいいってことで。
nameプロパティの値を元にコンポーネントを検索するユーティリティを作成。


アプリを作成する際に気をつけないといけないのは、後で更新しそうなコンポーネントの場合は
一意なnameを与えることだけです。


んじゃ、早速ComponentFinderってクラスを作成しましょう・・・って感じですが
ここはちょっと考えましょう。いきなり単一クラスを直書きしてしまうと後で
困りそうです。検索処理はコンポーネントが大量になると遅くなるかもしれないし
その際は、キャッシュ付きの実装が必要になるかもしれません。GUIアプリのことを
考えるとシングルトンなものがいいかもしれません。


てことで、インターフェースを作っておきましょう。

/*
 * ComponentFinder.java
 *
 */

package gsf.lib.swing;

import java.awt.Component;

/**
 * コンポーネントの検索処理インターフェースを定義しています.<br/>
 *
 * @author  gsf_zero1
 * @version 1.0
 */
public interface ComponentFinder {

    /**
     * 検索を行います.<br/>
     *
     * @param name 検索対象のコンポーネント名称.
     * @param recurse 再帰的に検索を行うか否か.
     *
     * @return 見つかったコンポーネント, 見つからなかった場合はnull.
     */
    Component find(String name, boolean recurse);
    
}

んで、とりあえずデフォルトの実装をさくっと作成。
キャッシュとかシングルトンとか考えてないです。
まず、動くもの。

/*
 * DefaultComponentFinder.java
 *
 */

package gsf.lib.swing;

import java.awt.Component;
import java.awt.Container;
import javax.swing.RootPaneContainer;

/**
 * ComponentFinderのデフォルト実装クラスです.<br/>
 *
 * @author  gsf_zero1
 * @version 1.0
 */
public class DefaultComponentFinder implements ComponentFinder{

    /** 基点となるコンテナオブジェクト */
    private Container _container;
    
    /**
     * コンストラクタ.<br/>
     *
     * @param container 基点となるコンテナオブジェクト.
     */
    public DefaultComponentFinder(Container container) {
        _container = container;
    }

    /**
     * @see ComponentFinder#find(String, boolean)
     *
     * @throws IllegalArgumentException nameの値がnullまたは空文字とみなせる場合
     */
    public Component find(String name, boolean recurse) {
        
        if(name == null || name.trim().equals("")){
            throw new IllegalArgumentException("nameの値が不正です.");
        }
        
        Component result = null;
        if(_container instanceof RootPaneContainer){
            result = find(((RootPaneContainer) _container).getContentPane(), name, recurse);
        }else{
            result = find(_container, name, recurse);
        }
        
        return result;
    }

    /**
     * 検索処理を行います.<br/>
     * 引数currentに指定されたコンテナを基点として、検索を行います.<br/>
     * 引数recurseがtrueの場合、再帰的に検索を行います.<br/>
     * <p>
     * 該当するコンポーネントが見つからなかった場合、nullが返却されます.<br/>
     * </p>
     *
     * @param current 基点となるコンテナオブジェクト.
     * @param name    検索対象のコンポーネント名称
     * @param recurse 再帰的に検索を行うか否か.
     *
     * @return 見つかったコンポーネント, 見つからなかった場合はnull.
     */
    protected Component find(Container current, String name, boolean recurse) {
        
        Component result = null;
        
        for(Component comp : current.getComponents()){
            if(comp.getName().equalsIgnoreCase(name)){
                result = comp;
                break;
            }
            
            if((comp instanceof Container) && recurse){
                Component tmp = find((Container) comp, name, recurse);
                
                if(tmp != null){
                    result = tmp;
                    break;
                }
            }
        }
        
        return result;
    }
    
}

次に動くかどうかテスト.
通常は、実装の前に作っておくものですが今回はサンプルってことで(^.^;;

/*
 * DefaultComponentFinderTest.java
 * JUnit based test
 *
 */

package gsf.lib.swing;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import junit.framework.*;
import java.awt.Container;

/**
 * DefaultComponentFinderのテストクラスです.<br/>
 *
 * @author gsf_zero1
 */
public class DefaultComponentFinderTest extends TestCase {
    
    private ComponentFinder _finder;
    
    public DefaultComponentFinderTest(String testName) {
        super(testName);
    }
    
    protected void setUp() throws Exception {
        //
        // 以下の階層構造を作成.
        //
        // JFrame
        //    └JLabel(lbl1)
        //    └JPanel(p1)
        //         └JLabel(lbl2)
        //         └JPanel(p2)
        //              └JLabel(lbl3)
        //              └JPanel(p3)
        //                  └JLabel(lbl4)
        //
        JFrame f = new JFrame();
        
        JLabel lbl1 = new JLabel("lbl 1");
        lbl1.setName("lbl1");
        
        JPanel p1 = new JPanel();
        p1.setName("p1");
        JLabel lbl2 = new JLabel("lbl 2");
        lbl2.setName("lbl2");
        
        p1.add(lbl2);
        
        JPanel p2 = new JPanel();
        p2.setName("p2");
        JLabel lbl3 = new JLabel("lbl 3");
        lbl3.setName("lbl3");
        
        p2.add(lbl3);
        p1.add(p2);
        
        JPanel p3 = new JPanel();
        p3.setName("p3");
        JLabel lbl4 = new JLabel("lbl 4");
        lbl4.setName("lbl4");
        
        p3.add(lbl4);
        p2.add(p3);
        
        Container c = f.getContentPane();
        c.add(lbl1);
        c.add(p1);
        
        _finder = new DefaultComponentFinder(f);
        
    }
    
    public void testFind0Depth(){
        assertNotNull(_finder.find("lbl1", false));
        assertEquals("lbl1", _finder.find("lbl1", false).getName());
    }
    
    public void testFind0DepthWithRecurse(){
        assertNotNull(_finder.find("lbl1", true));
        assertEquals("lbl1", _finder.find("lbl1", true).getName());
    }
    
    public void testFind1Depth(){
        assertNotNull(_finder.find("lbl2", true));
        assertEquals("lbl2", _finder.find("lbl2", true).getName());
    }
    
    public void testFind2Depth(){
        assertNotNull(_finder.find("lbl3", true));
        assertEquals("lbl3", _finder.find("lbl3", true).getName());
    }
    
    public void testFind3Depth(){
        assertNotNull(_finder.find("lbl4", true));
        assertEquals("lbl4", _finder.find("lbl4", true).getName());
    }
    
    public void testFindFailure(){
        assertNull(_finder.find("lbl_not_found", false));
        assertNull(_finder.find("lbl_not_found", true));
        assertNull(_finder.find("lbl4", false));
    }
    
    public void testFindParameterNull(){
        
        try{
            
            _finder.find(null, true);
            fail("チェック処理が行われていない.");
            
        }catch(IllegalArgumentException ex){
            
        }
        
    }
    
    public void testFindParameterEmpty(){
        
        try{
            
            _finder.find("", true);
            fail("チェック処理が行われていない.");
            
        }catch(IllegalArgumentException ex){
            
        }
        
    }
}

これで、動くことも確認できたのでオッケイですが、
GUIアプリの場合は、シングルトンにしてどこからでも取れるように
した方が楽です。
てことで、プチシングルトンな実装も作成しましょう。

/*
 * SingletonComponentFinder.java
 *
 */

package gsf.lib.swing;

import java.awt.Container;

/**
 * ComponentFinderのシングルトンな実装を提供します.<br/>
 * 本クラスは、init()で最初に一度初期化を行い、以後getInstance()で<br/>
 * インスタンスを取得して利用します.<br/>
 *
 * @author  gsf_zero1
 * @version 1.0
 * @see DefaultComponentFinder
 */
public class SingletonComponentFinder extends DefaultComponentFinder{

    /** シングルトンインスタンス */
    private static SingletonComponentFinder _instance;

    /**
     * プライベートコンストラクタ.<br/>
     *
     * @param container 基点となるコンテナオブジェクト.
     */
    private SingletonComponentFinder(Container container) {
        super(container);
    }

    /**
     * クラスの初期化を行います.<br/>
     * <p>
     * 必ず最初にこのメソッドをコールして、初期化を行ってください.<br/>
     * </p>
     *
     * @param container 基点となるコンテナオブジェクト.
     */
    public static void init(Container container){
        if(_instance == null){
            _instance = new SingletonComponentFinder(container);
        }
    }
    
    /**
     * インスタンスを取得します.<br/>
     *
     * @return インスタンス.
     *
     * @throws IllegalStateException 初期化が行われていない場合.
     */
    public static ComponentFinder getInstance(){
        if(_instance == null){
            throw new IllegalStateException("初期化が行われていません. init()を使用して初期化を行ってください.");
        }
        
        return _instance;
    }
}

DefaultComponentFinderを作成してるので基本機能はそのままもらいます。

当然、テスト.

/*
 * SingletonComponentFinderTest.java
 * JUnit based test
 *
 */

package gsf.lib.swing;

import junit.framework.*;
import java.awt.Container;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

/**
 * SingletonComponentFinderのテストクラスです.<br/>
 *
 * @author gsf_zero1
 */
public class SingletonComponentFinderTest extends TestCase {

    private ComponentFinder _finder;
    private ComponentFinder _finder2;
    
    public SingletonComponentFinderTest(String testName) {
        super(testName);
    }

    protected void setUp() throws Exception {
        //
        // 以下の階層構造を作成.
        //
        // JFrame
        //    └JLabel(lbl1)
        //    └JPanel(p1)
        //         └JLabel(lbl2)
        //         └JPanel(p2)
        //              └JLabel(lbl3)
        //              └JPanel(p3)
        //                  └JLabel(lbl4)
        //
        JFrame f = new JFrame();
        
        JLabel lbl1 = new JLabel("lbl 1");
        lbl1.setName("lbl1");
        
        JPanel p1 = new JPanel();
        p1.setName("p1");
        JLabel lbl2 = new JLabel("lbl 2");
        lbl2.setName("lbl2");
        
        p1.add(lbl2);
        
        JPanel p2 = new JPanel();
        p2.setName("p2");
        JLabel lbl3 = new JLabel("lbl 3");
        lbl3.setName("lbl3");
        
        p2.add(lbl3);
        p1.add(p2);
        
        JPanel p3 = new JPanel();
        p3.setName("p3");
        JLabel lbl4 = new JLabel("lbl 4");
        lbl4.setName("lbl4");
        
        p3.add(lbl4);
        p2.add(p3);
        
        Container c = f.getContentPane();
        c.add(lbl1);
        c.add(p1);
        
        //
        // 同値性をテストするためにインスタンスを2つ取得.
        //
        SingletonComponentFinder.init(f);
        
        _finder =  SingletonComponentFinder.getInstance();
        _finder2 = SingletonComponentFinder.getInstance();
    }

    public void testSingleton(){
        assertEquals(_finder, _finder2);
    }
    
    public void testFind0Depth(){
        assertNotNull(_finder.find("lbl1", false));
        assertEquals("lbl1", _finder.find("lbl1", false).getName());
    }
    
    public void testFind0DepthWithRecurse(){
        assertNotNull(_finder.find("lbl1", true));
        assertEquals("lbl1", _finder.find("lbl1", true).getName());
    }
    
    public void testFind1Depth(){
        assertNotNull(_finder.find("lbl2", true));
        assertEquals("lbl2", _finder.find("lbl2", true).getName());
    }
    
    public void testFind2Depth(){
        assertNotNull(_finder.find("lbl3", true));
        assertEquals("lbl3", _finder.find("lbl3", true).getName());
    }
    
    public void testFind3Depth(){
        assertNotNull(_finder.find("lbl4", true));
        assertEquals("lbl4", _finder.find("lbl4", true).getName());
    }
    
    public void testFindFailure(){
        assertNull(_finder.find("lbl_not_found", false));
        assertNull(_finder.find("lbl_not_found", true));
        assertNull(_finder.find("lbl4", false));
    }
    
    public void testFindParameterNull(){
        
        try{
            
            _finder.find(null, true);
            fail("チェック処理が行われていない.");
            
        }catch(IllegalArgumentException ex){
            
        }
        
    }
    
    public void testFindParameterEmpty(){
        
        try{
            
            _finder.find("", true);
            fail("チェック処理が行われていない.");
            
        }catch(IllegalArgumentException ex){
            
        }
        
    }    
}

あとは、フォームが生成される際に、

SingletonComponentFinder.init(this);

ってして、

SingletonComponentFinder.getInstance()

とすればどこからでもコンポーネントが取れます。


SeasarなどのDIコンテナを使う場合は、インスタンス管理を
コンテナ側に任せられるのでDefaultComponentFinderをシングルトンで
管理しておいてもらえれば同じことですね。
DIコンテナ使っている方が実装をすぐに差し替えられるので楽です。


後で、キャッシュ機能つきの実装が必要になったらクラスを実装して
差し替えるだけです。