XMLのDOMをJTree用のTreeNodeに変換するアダプタ

この記事を参考にして、J2SE6でXMLのDOMをJTreeに表示するためのTreeNodeに変換するアダプタを作ってみた。

いまさらながら、TreeModelのアダプタにした方が楽だったのではと思ってたりするけど、気にしない。
あと、デザインパターンを必要以上に使っている気がしないでもない。

次のデザインパターンが使われている

AbstractTreeNodeAdapter

TreeNodeアダプタの主な機能を実装している抽象クラス。

package cnaos.treenodeadapter.xml;

import java.util.Enumeration;
import java.util.NoSuchElementException;

import javax.swing.tree.TreeNode;

import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * org.w3c.domのNodeをJTreeで表示するTreeNodeに表示するためのアダプタの抽象クラス。
 * 独自のアダプタをを作りたい場合は、このクラスを継承し、toStringメソッド、virtualConstructor()メソッドを実装する。
 *
 * @author cnaos
 */
public abstract class AbstractTreeNodeAdapter implements TreeNode {
    
    /**
     * org.w3c.domのNodeをTreeNodeに変換して返すEnumeration
     *
     * @version $Id$
     * @author cnaos
     */
    private final class EnumerationImplementation implements Enumeration<TreeNode> {
        
        /** org.w3c.ornのノードリスト */
        private final NodeList nodes;
        
        /** enumerationがどの位置までノードリストを走査したかのindex */
        private int index = 0;
        

        /**
         * インスタンスを生成する。
         *
         * @param nodes org.w3c.domのノードリスト
         */
        private EnumerationImplementation(NodeList nodes) {
            this.nodes = nodes;
        }
        
        /**
         * まだ返せるNodeが存在するかどうか。
         * @return まだ返せるNodeが存在する場合true
         */
        public boolean hasMoreElements() {
            return index < nodes.getLength();
        }
        
        /**
         * 次のorg.w3c.orgのNodeをTreeNodeに変換して取り出す。
         */
        public TreeNode nextElement() {
            if (!hasMoreElements()) {
                throw new NoSuchElementException();
            }
            
            Node tmpnode = nodes.item(index++);
            return virtualConstructor(tmpnode);
        }
    }
    

    /**
     * アダプタが保持しているorg.w3c.domのNode
     */
    private final Node node;
    
    /** ハッシュ計算用の値 */
    private static final int HASH_BASE = "AbstractTreeNodeAdapter".hashCode();
    

    /**
     *
     * インスタンスを生成する。
     *
     * @param aNode TreeNodeに変換したいorg.w3c.orgのNode
     */
    protected AbstractTreeNodeAdapter(Node aNode) {
        if (aNode == null) {
            throw new IllegalArgumentException("aNode is null");
        }
        node = aNode;
    }
    
    /**
     * 子供をTreeNodeで列挙するEnumerationを返す
     * @return TreeNodeのEnumeration
     */
    @Override
    public Enumeration<TreeNode> children() {
        final NodeList nodes = node.getChildNodes();
        return new EnumerationImplementation(nodes);
    }
    
    /**
     * 内部に保持しているorg.w3c.orgのNodeが同じだったらtrueを返す。
     *
     * @param obj 比較対象のオブジェクト
     * @return オブジェクトが同じorg.w3c.orgのNodeを保持している場合にtrue
     */
    @Override
    public boolean equals(Object obj) {
        if (obj instanceof AbstractTreeNodeAdapter) {
            Node anotherNode = ((AbstractTreeNodeAdapter) obj).getNode();
            return node.equals(anotherNode);
        } else {
            return false;
        }
    }
    
    /**
     * 常にtrueを返す。
     * @return 常にtrue
     */
    @Override
    public boolean getAllowsChildren() {
        return true;
    }
    
    /**
     * インデックス childIndex にある子 TreeNode を返します。
     * @param childIndex 子供のindex
     * @return 指定されたindexのTreeNodeを返す。
     */
    @Override
    public TreeNode getChildAt(int childIndex) {
        NodeList childNodes = node.getChildNodes();
        int elementCount = childNodes.getLength();
        if (elementCount == 0) {
            throw new ArrayIndexOutOfBoundsException("node has no children");
        }
        if (childIndex >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(childIndex + " >= " + elementCount);
        }
        Node childNode = childNodes.item(childIndex);
        return virtualConstructor(childNode);
    }
    
    /**
     * 子供の個数を返す。
     * @retrun 子供の個数
     */
    @Override
    public int getChildCount() {
        NodeList childNodes = node.getChildNodes();
        return childNodes.getLength();
    }
    
    /**
     * 引数で指定された子供ノードが何番目かを返す。
     * 子供ノードがこのノードの子供でない場合、-1が返される。
     * @param child 子供のノード
     * @return 子供のノードの位置
     */
    @Override
    public int getIndex(TreeNode child) {
        if (child == null) {
            throw new IllegalArgumentException("argument is null");
        }
        if (!isNodeChild(child)) {
            return -1;
        }
        final Node childNode = ((AbstractTreeNodeAdapter) child).getNode();
        final Node parent = childNode.getParentNode();
        
        NodeList childList = parent.getChildNodes();
        int childCount = childList.getLength();
        for (int index = 0; index < childCount; index++) {
            Node currentNode = childList.item(index);
            if (currentNode.isSameNode(childNode)) {
                return index;
            }
        }
        return -1;
    }
    
    /**
     * 内部に保持しているノードを返す
     * @return アダプタが保持しているorg.w3c.domのNode
     */
    protected Node getNode() {
        return node;
    }
    
    /**
     * 現在のTreeNodeの親のTreeNodeを返す。
     */
    @Override
    public TreeNode getParent() {
        Node parent = node.getParentNode();
        if (parent == null) {
            return null;
        }
        return virtualConstructor(parent);
    }
    
    /**
     * ハッシュ値を返す。
     * @return ハッシュ値
     */
    @Override
    public int hashCode() {
        // ノードが同じならば同じハッシュ値を返し、
        // 内部に格納しているNodeオブジェクトとアダプタオブジェクトが
        // 同じハッシュコードを返さないようにするため、
        // ハッシュコードはノードのハッシュコードに独自のキーを加えて返す。
        
        return HASH_BASE ^ node.hashCode();
    }
    
    /**
     * このノードが葉かどうかを返す。
     *
     * @return 子供を持っていなければtrueを返す。
     */
    @Override
    public boolean isLeaf() {
        return !(node.hasChildNodes());
    }
    
    /**
     * 引数で指定されたノードがこのノードの子供の場合trueを返す。
     *
     * @param aNode 判定対象のノード。
     * @return 引数のノードが、このノードの子供ならばtrue
     */
    public boolean isNodeChild(TreeNode aNode) {
        boolean retval;
        
        if (aNode == null) {
            retval = false;
        } else {
            if (getChildCount() == 0) {
                retval = false;
            } else {
                // サブクラスが同じインスタンスを返さない場合も考慮し、equalsで判定する。
                retval = equals(aNode.getParent());
            }
        }
        
        return retval;
    }
    
    /**
     *
     * サブクラスのインスタンスを生成するためのファクトリメソッド。
     *
     * @param aNode TreeNodeに変換したいorg.w3c.domのNode
     * @return TreeNodeアダプタのインスタンス。
     */
    protected abstract TreeNode virtualConstructor(Node aNode);
}

DisplayTreeNodeAdapter

package cnaos.treenodeadapter.xml;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

/**
 * org.w3c.domのNodeをTreeNodeにマッピングするアダプタ。
 *
 * @author cnaos
 */
public class DisplayTreeNodeAdapter extends AbstractTreeNodeAdapter {
    
    /** 生成したDisplayTreeNodeAdapterのプール */
    private static final Map<Node, DisplayTreeNodeAdapter> POOL = createPool();
    

    /**
     * 生成したDisplayTreeNodeAdapteのインスタンスを保持するためのMapを作る
     *
     * @return org.w3c.domのNodeをキーにして、DisplayTreeNodeAdapterを格納するMap
     */
    private static Map<Node, DisplayTreeNodeAdapter> createPool() {
        return Collections.synchronizedMap(new HashMap<Node, DisplayTreeNodeAdapter>());
    }
    
    /**
     *
     * コンストラクタの代わりのstaticファクトリメソッド。
     * 同じNodeが渡された場合に、Poolに格納されている生成済みの同じインスタンスを返すために用意している。
     *
     * @param aNode TreeNodeにマッピングするorg.w3c.domのNode
     * @return DisplayTreeNodeAdapternoのインスタンス
     */
    public static DisplayTreeNodeAdapter newInstance(Node aNode) {
        DisplayTreeNodeAdapter adapter = POOL.get(aNode);
        if (adapter == null) {
            adapter = new DisplayTreeNodeAdapter(aNode);
            POOL.put(aNode, adapter);
        }
        return adapter;
    }
    
    /**
     * 外部から自由にインスタンスを生成できないようにするために、隠蔽。
     *
     * @param node アダプタ変換したいorg.w3c.domのNode
     */
    private DisplayTreeNodeAdapter(Node node) {
        super(node);
    }
    
    /**
     * org.w3c.domのElementをJTree表示用に変換する。
     * <ul>
     * <li>ElementがAttributeを持つ場合
     *   <ul>
     *   <li>&lt;タグ 属性1='値1', 属性2='値2'&gt;
     *   </ul>
     * <li>ElementがAttributeを持たない場合
     *   <ul>
     *   <li>&lt;タグ&gt;
     *   </ul>
     * </ul>
     *
     * @param element 変換対象のorg.w3c.domのElement
     * @return org.w3c.domのElementを表示用に変換した文字列。
     */
    String dumpNodeStr(Element element) {
        NamedNodeMap map = element.getAttributes();
        final int count = map.getLength();
        if (count == 0) {
            return String.format("<%s>", element.getNodeName());
        }
        List<String> attrList = new ArrayList<String>(count);
        for (int index = 0; index < count; index++) {
            Node attr = map.item(index);
            attrList.add(String.format("%s=\'%s\'", attr.getNodeName(), attr.getNodeValue()));
        }
        return String.format("<%s %s>", element.getNodeName(), StringUtils.join(attrList, ", "));
    }
    
    /**
     * org.w3c.domのNodeをJTreeに表示する際の形式を変更するためにオーバーライド。
     *
     * Nodeの種別によって以下のように変換する。
     * <dl>
     * <dt>ELEMENTノード</dt>
     * <dd>dumpNodeStr()メソッドで変換した値を表示</dd>
     * <dt>TEXTノード</dt>
     * <dd>NodeのgetNodeValueを表示。(タグにかこまれた中身をそのまま表示)</dd>
     * <dt>それ以外のTノード</dt>
     * <dd>NodeのtoStringを表示</dd>
     * </dl>
     *
     * @return org.w3c.domのNodeのJTree表示用文字列
     */
    @Override
    public String toString() {
        Node node = getNode();
        if (node.getNodeType() == Node.ELEMENT_NODE) {
            return dumpNodeStr((Element) node);
        } else if (node.getNodeType() == Node.TEXT_NODE) {
            return node.getNodeValue();
        } else {
            return node.toString();
        }
    }
    
    /**
     * AbstractTreeNodeAdapterがサブクラスのインスタンスを生成できるようにするためのファクトリメソッド。
     */
    @Override
    protected DisplayTreeNodeAdapter virtualConstructor(Node aNode) {
        return newInstance(aNode);
    }
    
}