No Bugs, No Life

読んだ本や、プログラミング、システム開発等のねたを中心に。文章を書く練習なので少し硬派に書くつもりだけど、どうなることやら。

MWの近況と小ネタ

MementoWeaver開発記
ここ暫くは読書に関するエントリばかりだったので、MWの近況的なメモを少々。

MementoWeaverの近況

とはいえ、技術的にはあまり新しいこと(というか、新たに駄文をBigDataの波に追加するほどのこと)は無い。
が、それではエントリが成り立たないので幾つかの進捗を。

  1. JavaFX2.2ではDialog系が無くなってしまっているのでJavaFxDialogを採用
  2. ディレクトリツリーの走査にJavaSE7で導入されたFiles#walkFileTree()を使用
  3. Java正規表現

以下に簡単にメモする。

JavaFX2.2ではDialog系が無くなってしまっているのでJavaFxDialogを採用

JavaFX2.2では標準でダイアログを表示することが出来ずに自前でSceneとStageをこねくり回す必要がある様子。
どうやらJavaFX1.3まではAlertクラスが存在していて使えていたようだが、現在の2系では使えなくなっているらしい。(因みに、次世代のJavaFX8系では復活するようだし、sandboxも公開されている)

ダイアログの自作は各所に書かれていたので自作することも考えたが、何だか途中でめんどくさくなったので大人しく先人の知恵を拝借する。
使用したのはJavaFX Dialogs, Anyone? (The Java Jungle)のJavaFXDialog(githubはこちら)

ソースツリーの形で公開されているので、GitHubからcloneしてEclipseにインポートすることで使用が可能になる。
MWに関しては3rdsrcフォルダを新たに作成し、3rdsrcフォルダ配下にインポートした。
f:id:kazyury:20130425002310p:plain

あとはこんな感じでふぁんしーなダイアログが表示される*1。作者様に感謝します。

// pathInputが空若しくは存在しないパスならばエラーメッセージを表示してreturn
if (!sourceDirectory.isDirectory()) {
	Dialog.showWarning("不正なパスです", sourceDirectory.toString() + " はディレクトリではありません.");
	return false;
}

ディレクトリツリーの走査にJavaSE7で導入されたFiles#walkFileTree()を使用

Java7でNIO2が導入されたのに伴って、ファイル処理周りはだいぶん利用が簡単になったと思う。
ディレクトリツリーを再帰的に走査してくれるFiles#walkFileTree()は非常にありがたい。
Oracleがチュートリアル(Walking the File Tree (The Java™ Tutorials > Essential Classes > Basic I/O))を提供してくれているので、参考にして実装してみる。

// ディレクトリツリー走査の呼び出し側
public List<ScannedResult> scanMementoFiles() throws MWException {
	// MWROOT以下の*.htmlを全走査
	MementoFinder finder = new MementoFinder("*.html", scanmode);
	File start = new File(PathUtil.getDirectoryProperty(Constants.DIRPROP_KEY_MW_ROOT));
	try {
		Files.walkFileTree(start.toPath(), finder);
	} catch (IOException e) {
		logger.severe("走査中にIOExceptionが発生しました.");
		throw new MWException(e);
	}
	return finder.getResult();
}

//走査過程で呼ばれるVisitor(関連箇所のみを抜粋)
/**
 * 指定されたパターンにマッチするファイルを走査する.
 * @see <a href="http://docs.oracle.com/javase/tutorial/essential/io/walk.html">Walking the File Tree</a>
 * @author kazyury
 *
 */
public static class MementoFinder extends SimpleFileVisitor<Path> {
	private final PathMatcher matcher;
	private List<ScannedResult> resultList = new ArrayList<ScannedResult>();
	
	public MementoFinder(String pattern, String scanmode) {
		matcher=FileSystems.getDefault().getPathMatcher("glob:"+pattern);
	}
		
	// Invoke the pattern matching method on each file.
	// 今回はFileに対する走査だけなのでpreVisitDirectory等はoverride不要
	@Override
	public FileVisitResult visitFile(Path path, BasicFileAttributes bfa) throws IOException 
		Path name = path.getFileName();
		if(name != null && matcher.matches(name)){
			ScannedResult sr = findOrCreateScannedResult(path);
			
			// scanmodeによる挙動変更
			if(scanmode.equals(Constants.SCANTYPE_ALL)) {
				resultList.add(sr);
			}
		}
		return FileVisitResult.CONTINUE;
	}
}

いちいちVisitorを作らなくてはいけない等、やはりJavaだなって感じではあるけど、Javaなりに随分スッキリしたような気がする。
因みにrubyだったらこんな感じで済む*2のに...。設計思想の違い(とIDEの存在)は大きいな。

require 'find'
result=[]
Find.find('/tmp') do |path|
  next if File.directory?(path)
  result.push path if File.extname(path) == ".html"
end

Java正規表現

Java正規表現をまともに使ったことが無かったので一応記録しておくけど、正規表現自体はJava1.4でも導入されていたので、もう常識レベルなんだろうな。
実は私、Java正規表現をなめてました。自分が使う程度の正規表現ならJavaで十分に実装されていたとは知らなかったのです。
PatternとかMatcherとかを使うことがほぼ必須となる点については、PerlRubyとかと比べると冗長に感じてしまうけど、この程度ならそんなに違和感なし。メタ文字のエスケープは若干うるさいが。
コード例

public void scanMaterialsIn(ScannedResult scannedResult) throws MWException {
	
	Set<Path> materialSet = new TreeSet<Path>();
	
	// 正規表現でスキャン ../materials/20100102_012345.jpg とか。
	String regexp = "\\.\\.\\/materials\\/\\d{8}_\\d{6}\\.(jpg|mov)";
	Pattern p = Pattern.compile(regexp);
	File in = new File(scannedResult.getProductionPath());
	try {
		for(String line:Files.readAllLines(in.toPath(), Charset.forName("MS932"))){
			Matcher matcher = p.matcher(line);
			while(matcher.find()){
				File materialPath = new File(matcher.group());
				materialSet.add(materialPath.toPath());
			}
		}
	} catch (IOException e) {
		throw new MWException(e);
	}
}		

「未システム化メメントの使用素材を管理する」の要件範囲見直し

ユースケース「未システム化メメントの使用素材を管理する」の位置づけがぶれていたが、なんとなく整理が付いた。ぶれていたのは、要件をきちんと整理できていなかったからだな。反省して以下にその根拠付けをなんとなく記録しておく。

当初想定の要件の関係はこの程度だった。
f:id:kazyury:20130425210206p:plain

このゴールグラフ内で末端の「システムによる生成ではないメメントが使用している素材をシステムで管理できること」に対応するシステムユースケースとして「未システム化メメントの使用素材を管理する」を想定していたが、移行に際してはメメント-素材の関係と同時に、そのメモも移行する必要があった。
逆に、「システムによる生成ではないメメント」に関しては、(どうせ自動生成しないのだから)メモを管理する必要は無い。
つまり、ゴールグラフは以下のようになる。
f:id:kazyury:20130425210901p:plain

これらの要件を、1つの「未システム化メメントの使用素材を管理する」で実現するためには、このユースケースを実装する処理は「今後作成される任意のメメントをスキャンして、素材の使用有無、素材に関連付けられたメモを抽出できる」必要があるが、これがほぼ無理。

因みに、現在のメメントでは以下のように素材とメモが記述されている。

album/prizes 等の定型的なメメントの例
<div class="photo">
  <a class="highslide" onclick="return hs.expand(this)" href="../materials/20110821_085144.jpg">
    <img src="../materials/thumbnail/20110821_085144.jpg" title="クリックすると大きくなります。" border="0"><br>
  </a>
<div class='highslide-caption'>ピーマンをつかった料理です。</div>
</div>
手作成メメント#1
<area shape="rect" coords="8,62,154,107"    href="../materials/20090411_143726.jpg" class="highslide" onclick="return hs.expand(this)"/> 
<div class='highslide-heading'>20090411_143726</div>
<div class='highslide-caption'>2009年4月11日 烏山図書館制覇</div>
手作成メメント#2
<td>
  <a href="../materials/20090730_150744.jpg" class="highslide" onclick="return hs.expand(this)">京都</a>
  <div class="highslide-caption">2009年7月(日本縦断) アパホテル京都駅前</div>
</td>
手作成メメント#3
<tr>
  <td>10倍がゆとブロッコリーペースト<br>
    <img src="../materials/20020827_120000.jpg" width="200" height="200" border="0">
  </td>
  <td>きのうまでスプーンを見るだけで泣いてたのに、今日は全部食べた。</td>
</tr>
手作成メメント#4
<div class="album-container-small">
  <div class="photo">
    <img src="../materials/thumbnail/20090721_093952.jpg">
  </div>
  <div class="comment">羽田~那覇:ANA995便</div>
</div>

これらのパターンだけでも素材とメモの関係を自動で紐付けるのはほぼ無理だろと冷静に考え直し、素材とメモの関連付けは、別途移行プログラムを使って移行作業時のみ行うことにした*3

ということで、結局ユースケース「未システム化メメントの使用素材を管理する」の処理では、ディレクトリツリーを走査して、正規表現で../materials/yyyymmdd_hhmmss.(jpg|mov) をスキャンする機能を実装することになり、冒頭の話題に回帰するというおち。


結局ごみエントリを書き散らしただけになってしまった。

*1:因みにこの例ではProcessingレイヤーでDialogを使うなどというかなり駄目なつくりになっている。例外系を真面目に設計して見直す予定。

*2:全く一緒のロジックを組んでいる訳ではないので比較するのも適切ではないが

*3:逃避したともいう