谷本 心 in せろ部屋

はてなダイアリーから引っ越してきました

JGraphでシーケンス図を描いてみた。

JGraphを使って、簡単なシーケンス図を描いてみました。
まず実行結果から。

見ての通りシーケンス図です。
Returnが点線じゃないとかのツッコミは置いといて。


では、簡単にソースを見ていきましょう。
今回は、BTraceで取ったログからシーケンス図を作成するという想定なので
中間モデルを定義して、そこからJGraphのクラスを作っていきます。


まずは、ノード(要するにクラス)を示すモデル。

public class Node {
	/** ノード名 */
	private String name;

	// setter/getterは省略
}


続いて、メソッドのIN/OUTの端点(コネクタの両端)のモデル

public class EndPoint {
	/** 所属するノード */
	private Node node;

	// setter/getterは省略
}


モデルの最後です。メソッド呼び出しを示す矢印のモデル

public class Connector {
	/** 結線の名称 */
	private String name;

	/** 開始端点 */
	private EndPoint from;

	/** 終了端点 */
	private EndPoint to;

	// setter/getterは省略
}


親から子を参照するのではなく、子から親を参照する形式なのは若干RDBMS中毒のような気もしますね。
まぁJGraph的に親から子を参照する方が良かったら、そう修正すれば良いかなぐらいで、
あんまり深くは考えていません。


続いて、このモデルからシーケンス図を描くクラス。今回のメインソースです。

import java.awt.Color;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jgraph.JGraph;
import org.jgraph.graph.DefaultEdge;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.DefaultGraphModel;
import org.jgraph.graph.GraphConstants;
import org.jgraph.graph.GraphModel;

public class GraphCreator {
	/** ノードの幅 */
	private int nodeWidth = 80;

	/** ノードの高さ */
	private int nodeHeight = 20;

	/** ノードの間隔 */
	private int nodeInterval = 10;

	/** 結線の間隔 */
	private int edgeInterval = 25;

	/** 端点セルのマップ */
	private Map<EndPoint, DefaultGraphCell> endPointMap = new HashMap<EndPoint, DefaultGraphCell>();

	/** ノードセルのマップ */
	private Map<Node, DefaultGraphCell> nodeMap = new HashMap<Node, DefaultGraphCell>();

	/** 全オブジェクトのリスト */
	List<DefaultGraphCell> cellList = new ArrayList<DefaultGraphCell>();

	/** 結線の数 */
	private int edgeCount;

	/**
	 * コンストラクタ。モデルの変換を行います。
	 * @param connectorList 結線のリスト
	 */
	public GraphCreator(List<Connector> connectorList) {
		for (Connector connector : connectorList) {
			createConnectorCell(connector);
		}

		// 活性線の作成
		for (DefaultGraphCell nodeCell : this.nodeMap.values()) {
			DefaultGraphCell endCell = createEndPointCell(nodeCell);
			this.cellList.add(endCell);
			endCell.addPort();

			DefaultEdge edge = new DefaultEdge();
			edge.setSource(nodeCell.getChildAt(0));
			edge.setTarget(endCell.getChildAt(0));
			this.cellList.add(edge);
		}
	}

	/**
	 * グラフの描画を行います。
	 */
	public JGraph createGraph() {
		GraphModel model = new DefaultGraphModel();
		JGraph graph = new JGraph(model);
		graph.setJumpToDefaultPort(true);
		graph.getGraphLayoutCache().insert(this.cellList.toArray());

		return graph;
	}

	private void createConnectorCell(Connector connector) {
		EndPoint from = connector.getFrom();
		DefaultGraphCell fromCell = findEndPointCell(from);

		EndPoint to = connector.getTo();
		DefaultGraphCell toCell = findEndPointCell(to);

		DefaultEdge edge = new DefaultEdge(connector.getName());
		edge.setSource(fromCell.getChildAt(0));
		edge.setTarget(toCell.getChildAt(0));
		GraphConstants.setLineEnd(edge.getAttributes(),
				GraphConstants.ARROW_CLASSIC);
		GraphConstants.setLabelAlongEdge(edge.getAttributes(), true);

		this.cellList.add(edge);
		this.edgeCount++;
	}

	private DefaultGraphCell findEndPointCell(EndPoint endPoint) {
		DefaultGraphCell cell = this.endPointMap.get(endPoint);

		if (cell == null) {
			Node node = endPoint.getNode();
			DefaultGraphCell nodeCell = findNodeCell(node);
			cell = createEndPointCell(nodeCell);

			this.endPointMap.put(endPoint, cell);
			this.cellList.add(cell);
		}

		return cell;
	}

	private DefaultGraphCell createEndPointCell(DefaultGraphCell nodeCell) {
		DefaultGraphCell cell = new DefaultGraphCell();

		// X座標 = ノードの中央値
		Rectangle2D rectangle = (Rectangle2D) nodeCell.getAttributes().get(
				GraphConstants.BOUNDS);
		double x = rectangle.getCenterX();

		// Y座標 = 上端からの余白 + ノードの高さ + (結線の数 + 1) * 結線の間隔
		int y = this.nodeInterval + this.nodeHeight + (this.edgeCount + 1)
				* edgeInterval;

		GraphConstants.setBounds(cell.getAttributes(), new Rectangle2D.Double(
				x, y, 0, 0));
		cell.addPort();

		return cell;
	}

	private DefaultGraphCell findNodeCell(Node node) {
		DefaultGraphCell cell = this.nodeMap.get(node);

		if (cell == null) {
			cell = new DefaultGraphCell(node.getName());

			// X座標 = 左端からの余白 + ノード数 * (ノード幅 + ノード間隔)
			int x = this.nodeInterval + this.nodeMap.size()
					* (this.nodeWidth + this.nodeInterval);

			// Y座標 = 上端からの余白
			int y = this.nodeInterval;

			GraphConstants.setBounds(cell.getAttributes(),
					new Rectangle2D.Double(x, y, nodeWidth, nodeHeight));
			GraphConstants.setBorderColor(cell.getAttributes(), Color.black);

			this.nodeMap.put(node, cell);
			this.cellList.add(cell);
			cell.addPort();
		}

		return cell;
	}
}

JGraphの使い方をあまりよく理解しようとしてないので、親子関係とかは適当です。
とにかく、シーケンス図さえ描ければ良いと思ってゴリゴリ書きました。


さて、最後に、上のGraphCreatorクラスを利用するサンプルクラスです。

import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JScrollPane;

import org.jgraph.JGraph;

public class Sample {
	public static void main(String[] args) {
		Node node1 = new Node();
		node1.setName("Class1");

		Node node2 = new Node();
		node2.setName("Class2");

		Node node3 = new Node();
		node3.setName("Class3");

		Connector connector1 = new Connector();
		connector1.setName("method1");

		EndPoint endPoint1From = new EndPoint();
		endPoint1From.setParentNode(node1);

		EndPoint endPoint1To = new EndPoint();
		endPoint1To.setParentNode(node2);

		connector1.setFrom(endPoint1From);
		connector1.setTo(endPoint1To);

		Connector connector2 = new Connector();
		connector2.setName("method2");

		EndPoint endPoint2From = new EndPoint();
		endPoint2From.setParentNode(node2);

		EndPoint endPoint2To = new EndPoint();
		endPoint2To.setParentNode(node3);

		connector2.setFrom(endPoint2From);
		connector2.setTo(endPoint2To);

		Connector connector3 = new Connector();
		connector3.setName("return2");

		EndPoint endPoint3From = new EndPoint();
		endPoint3From.setParentNode(node3);

		EndPoint endPoint3To = new EndPoint();
		endPoint3To.setParentNode(node2);

		connector3.setFrom(endPoint3From);
		connector3.setTo(endPoint3To);

		Connector connector4 = new Connector();
		connector4.setName("return1");

		EndPoint endPoint4From = new EndPoint();
		endPoint4From.setParentNode(node2);

		EndPoint endPoint4To = new EndPoint();
		endPoint4To.setParentNode(node1);

		connector4.setFrom(endPoint4From);
		connector4.setTo(endPoint4To);

		List<Connector> connectorList = new ArrayList<Connector>();
		connectorList.add(connector1);
		connectorList.add(connector2);
		connectorList.add(connector3);
		connectorList.add(connector4);

		GraphCreator graphCreator = new GraphCreator(connectorList);
		JGraph graph = graphCreator.createGraph();

		JFrame frame = new JFrame();
		frame.getContentPane().add(new JScrollPane(graph));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.pack();
		frame.setVisible(true);
	}
}

特にボリュームも大きくなく、難しいところもありません。
これぐらい簡単なソースでシーケンス図が描けたのは、まさにJGraphのおかげですね。