SwingコンポーネントをXPathで操作する

フレームやダイアログの中から特定の条件を満たすコンポーネントを見つけたり、トラバースできると便利だと思い、JDOMをベースにして以下のプログラムを作ってみました。

  • SwingDOMBuilder
  • SwingPath

Swingのコンポーネント階層をDOMに変換して、XPathを使えるようにするというものです。

使用例としては、メニュー項目をルックアップして実行したり

((JMenuItem) SwingPath.selectSingleComponent(
        frame, "//JMenuItem[@text='上書き保存']")).doClick();

ボタンを全てdisabledにしたり

for (Component c : SwingPath.selectComponents(dialog, "//JButton")) {
    c.setEnabled(false);
}

といったことができます。GUIテストの自動化とか、ロボット的な用途に使えるんじゃないかと思っています。

ソースは以下です。

SwingDOMBuilder.java

import java.awt.Component;
import java.awt.Container;

import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JMenu;

import org.jdom.Document;
import org.jdom.Element;

/**
 * @author Kaisei Hamamoto
 */

public class SwingDOMBuilder {
    public Document build(Component c) {
        Element root = doBuild(null, c);
        return new Document(root);
    }

    private Element doBuild(Element parent, Component c) {
        if (c == null) {
            return null;
        }

        Element elem = createElement(c);
        if (parent != null) {
            parent.addContent(elem);
        }

        if (c instanceof JMenu) {
            // JMenu は getComponents() でメニュー項目を取れないので特別扱い
            JMenu menu = (JMenu) c;
            for (int i = 0; i < menu.getItemCount(); i++) {
                doBuild(elem, menu.getItem(i));
            }
        } else if (c instanceof JComponent) {
            JComponent jc = (JComponent) c;
            for (Component child : jc.getComponents()) {
                doBuild(elem, child);
            }
        } else if (c instanceof Container) {
            Container container = (Container) c;
            for (Component child : container.getComponents()) {
                doBuild(elem, child);
            }
        }

        return elem;
    }

    private Element createElement(Component c) {
        Element elem = new ComponentElement(c);

        // 基礎的なプロパティを属性化する
        storeAttribute(elem, "name", c.getName());
        storeAttribute(elem, "enabled", c.isEnabled());
        storeAttribute(elem, "visible", c.isVisible());
        
        if (c instanceof AbstractButton) {
            AbstractButton button = (AbstractButton) c;
            storeAttribute(elem, "text", button.getText());
            storeAttribute(elem, "selected", button.isSelected());
        }

        return elem;
    }

    private void storeAttribute(Element elem, String name, Object value) {
        if (value != null) {
            elem.setAttribute(name, value.toString());
        }
    }
}

ComponentElement.java

import java.awt.Component;

import org.jdom.Element;
import org.jdom.Namespace;

/**
 * @author Kaisei Hamamoto
 */

public class ComponentElement extends Element {
    private static final long serialVersionUID = -3715167149549579682L;

    private Component component;

    public ComponentElement(Component component) {
        this.component = component;
        setName(component.getClass().getSimpleName());
        setNamespace(Namespace.NO_NAMESPACE);
    }

    public Component getComponent() {
        return component;
    }
}

SwingPath.java

import java.awt.Component;
import java.util.ArrayList;
import java.util.List;

import org.jdom.JDOMException;
import org.jdom.xpath.XPath;

/**
 * @author Kaisei Hamamoto
 */

public class SwingPath {
    private static Object wrapContext(Object context) {
        if (context instanceof Component) {
            return new SwingDOMBuilder().build((Component) context);
        } else {
            return context;
        }
    }

    public static Component selectSingleComponent(Object context, String path) {
        try {
            Object node = XPath.selectSingleNode(wrapContext(context), path);
            if (node instanceof ComponentElement) {
                return ((ComponentElement) node).getComponent();
            } else {
                return null;
            }
        } catch (JDOMException e) {
            throw new IllegalArgumentException(e);
        }
    }

    public static List<Component> selectComponents(Object context, String path) {
        try {
            List<Component> result = new ArrayList<Component>();
            for (Object node : XPath.selectNodes(wrapContext(context), path)) {
                if (node instanceof ComponentElement) {
                    result.add(((ComponentElement) node).getComponent());
                }
            }
            return result;
        } catch (JDOMException e) {
            throw new IllegalArgumentException(e);
        }
    }
}