前置きと挫折
Javaへのマイグレーション業務にてシーケンス図やクラス図などのUMLを作るかの検討を行なっており、ソースなどからUML起こすことを自動化できないか、を考えたのがきっかけ。
VSCodeの拡張機能などでないか探したが、なさそうだったので自分で作るか、といったん決意。
ただ、調べていくと、Javaのソースコードの構文解析を一から作成するのは無理そうだったので、せめてファイル設定値に従ってMermaid記法のシーケンス図を作るプログラムを作るか...ということで作業開始。
将来的にVSCodeの拡張機能化したいと考えていたため、Typescriptを選んだ。
Mermaidとは
上記にリンク添付、端的に説明すると、
Markdownテキストでクラス図やシーケンス図グラフを作成できるダイアグラムツール。
使えると便利。
Repository
概要
以下のようなYamlファイルから
classes :
- name: ServiceImpl.java
alias : service
- name : Repository.java
alias : repository
- name : Test.java
alias : test
flows :
- type : arrow
from : service
to : repository
action : findByTestModel
description : find records by TestModel <br>from repository
returnmsg : return records
args :
- type : TestModel
name : testModel
- type : TestModel
name : testModel2
- type : arrow
from : repository
to : test
action : testMethod
description : test説明です
returnmsg : 'return test'
args :
- type : TestModel
name : testModel
- type : arrow
from : test
to : test2
action : test2
returnmsg : 'return test2'
args :
- type : TestModel
name : testModel
- type : arrow
from : test
to : test
action : testRecursive
args :
- type : TestModel
name : testModel
Mermaid記法のシーケンス図を作成するプログラムである。
説明
シーケンス図の用語などは説明しません。
classes
classes:
- name: ServiceImpl.java
alias: service
- name: Repository.java
alias: repository
- name: Test.java
alias: test
ライフライン用の設定。
flow
typeを定義しているが、現状同期処理しか表現できない。
from,toが矢印の元、先。
actionがメソッド名、descriptionが矢印の下に表示される説明、
returnmsgが定義されていれば、戻り矢印に文言が表示される。
戻りの処理は、from,toのオブジェクト名をスタックに保存しているため、
このyamlファイルでは、同期処理に限って言えば戻り用のfrom,toを記載する必要はなく、
階層が上がれば自動的に戻り処理のMermaid文字列を組み立てるようになっている。
flows :
- type : arrow
from : service
to : repository
action : findByTestModel
description : find records by TestModel <br>from repository
returnmsg : return records
args :
- type : TestModel
name : testModel
- type : TestModel
name : testModel2
- type : arrow
from : repository
to : test
action : testMethod
description : test説明です
returnmsg : 'return test'
args :
- type : TestModel
name : testModel
- type : arrow
from : test
to : test
action : testRecursive
args :
- type : TestModel
name : testModel
実装
インターフェース
export const enum flowType {
Arrow = 'arrow'
}
export interface Arg {
type: string;
name: string;
}
export interface ArrowFlow {
type: string;
from: string;
to: string;
action: string;
description: string;
returnmsg: string;
args: Arg[];
}
実装中身
import {ArrowFlow} from "../model/YamlDataTypeModel";
/**
* type = arrow の時の文字列組み立て関数
* build string function when type is arrow
* @param flow yaml
* @param fromStack stack for from data
* @param toStack stack for to data
* @param returnMsgStack stack for return message
*/
export function arrowStrBuild(
flow: ArrowFlow,
fromStack: string[],
toStack: string[],
returnMsgStack: string[]
): string {
console.log(flow);
const fromName = flow.from;
const toName = flow.to;
let buildStr = "";
const action = flow.action;
const description = flow.description || "";
const rerunDescription = flow.returnmsg || "";
const args = flow.args
? "<br>(" +
flow.args.map((arg: any) => `${arg.name} : ${arg.type}`).join(", <br>") +
")"
: "";
let arrowOperator = "";
const existIndex = fromStack.lastIndexOf(fromName);
if (existIndex > -1) {
// 階層が上がっているか確認、上がっていれば上昇分だけ戻り矢印の処理を組み立てる。
let count = fromStack.length - existIndex;
buildStr += buildReturnArrowStr(count, fromStack, toStack, returnMsgStack);
}
if (fromName !== toName) {
// 内部処理でないか確認、内部処理でなければStackに追加する。
fromStack.push(fromName);
toStack.push(toName);
returnMsgStack.push(rerunDescription);
arrowOperator = "+";
}
buildStr += `${fromName} ->>${arrowOperator} ${toName}: ${action} ${args}\n`;
if (description) {
buildStr += `Note over ${fromName},${toName}: ${description}\n`;
}
return buildStr;
}
/**
* 戻り同期処理のシーケンス組み立て用関数
* @param count
* @param fromStack
* @param toStack
* @param returnMsgStack
*/
export function buildReturnArrowStr(
count: number,
fromStack: string[],
toStack: string[],
returnMsgStack: string[]
) {
let str = "";
for (let i = 0; i < count; i++) {
str += `${toStack.pop()} -->>- ${fromStack.pop()}: ${returnMsgStack.pop()}\n`;
}
return str;
}
課題、反省
- Type = Arrow、つまり同期処理のライフラインしか現状表現できないので、Loopや分岐、戻りを必要としない非同期処理などの実装が必要
- 入力形式としてYamlを選んだが、CSVなどの方が、汎用性があって良いかもしれない
- Mermaid記法のチートシート確認しながら自分でマークダウン書いた方が早いかもしれない
- PlantUMLなど、そもそもシーケンス図自動生成ツールは探せばありそう(、と作り始めてから思った)
最後に
冒頭にも書いた通り、本当はJavaコードを解析してシーケンス図を自動で作る、というプログラムを作りたい。
Typescriptのアウトプットの勉強になるか、ということでいったん作成して公開したが、
本来であれば、
Javaコードを解析する→解析結果をYAML形式などのインプットにする→インプットをもとにシーケンス図作成!を実現したかった。
ちょっと調べて難しそうだったので保留したが、構文解析のやり方が理解できるような書籍なりサイトがあれば教えてもらえるとありがたい。