java7のtry-with-resourceを書いてみる。

気が付けば、もう6月。
1年ぶりといってもいいほどノンアクティブ。
インドの話も書こうと思っていたのだが、忙しいのを言い訳にだんだんとログインすることすら
面倒になってしまい、そのまま放置。。
twitterも同様に1年以上放置。理由も同じでアプリのボタンを押すことすら面倒。
飽きっぽいのかな?
閑話休題
最近javaの1.7が出たそうで色々とパワーアップしている模様。
また、oracleによる買収によりjavaの試験もDBと同様に金銀銅メダルになるそうで。
そういうわけで、java7の新機能の一つtry-with-resourceを書いてみた。
本家サイトによると今までIOを閉じるのにfinallyで書いていたおまじないをjvmがやってくれるみたい。おかげでコードがすっきりし読みやすくなる模様。
以下、サンプルコード

package hogehoge;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ParamcheckMain {
	
	/** プロパティからデータを取得するための正規表現  */
	public static final Pattern PROPERTIES_PATTERN = Pattern.compile("^(\\w{6})\\p{Space}??=\\p{Space}??(.*)$");
	
	/** メッセージから代替引数を取得するための正規表現  */
	public static final Pattern MESSAGE_ARGS_PATTERN = Pattern.compile("\\{\\d{1}\\}");
	
	/** クラス名取得用正規表現  */
	public static final Pattern CLASS_NAME_PATTERN = Pattern.compile("class\\p{Space}(\\p{Upper}\\w{1,})\\p{Space}[extends|implements]??");
	
	/** 使用しているメッセージパターン */
	public static final Pattern USED_MESSAGE_PATTERN = Pattern.compile("XXMessageId\\.(\\w{6})\\.setParams\\(([^\\)]*)(\\)??;??)$");
	
	/** 行を跨いでいるパターン */
	public static final Pattern USED_MESSAGE_PATTERN2 = Pattern.compile("([^\\)]*)\\)??;");
	

	/**
	 * xx000111 = これは{1}テストです。
	 * 形式のプロパティからデータを取得する。
	 * 
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
		
		String path = "C:\\workspace\\";
		
		String path2 = "C:\\workspace\\hoge\\properties\\test.properties";
		//プロパティファイルの取得、編集
		HashMap<String, PropertyCls> plist = getPropertiesList(path2);
		filterMsgArgsCount(plist);
		
		//javaクラスからデータ取得
		ArrayList<HashMap<String, ArrayList<MsgCls>>> allsrclist = getMsgFromSrc(path);
		//全データをチェック
		compareMsg(plist, allsrclist);
		
	}
	
	
	public static void compareMsg(HashMap<String, PropertyCls> pmap, ArrayList<HashMap<String, ArrayList<MsgCls>>> allsrclist) {
		
		//クラスに含まれるメッセージ群
		for(HashMap<String, ArrayList<MsgCls>> smap : allsrclist) {
			//
			Set<Entry<String, ArrayList<MsgCls>>> enset = smap.entrySet();
			for(Entry<String, ArrayList<MsgCls>> ent : enset) {
				//1クラスで使用していたメッセージ
				ArrayList<MsgCls> msglist = ent.getValue();
				for(MsgCls msgCls : msglist) {
					PropertyCls pclz = pmap.get(msgCls.messageId);
					System.out.println("メッセージID : " + msgCls.messageId);
					System.out.println("行数 : " + msgCls.lineNum);
					System.out.println("引数の数整合性 : " + compareArgs(msgCls.args, pclz.count));
					System.out.println("メッセージID整合性 : " + msgCls.messageId.equals(pclz.messageId));
				}
			}
		}
	}
	
	public static boolean compareArgs(String args, int argsNum) {
		String cargs = args.trim();
		String[] arargs = cargs.split(",");
		return arargs.length == argsNum;
	}
	
		
	/**
	 * プロパティファイルからメッセージを取得する。
	 * @param path
	 * @return
	 * @throws IOException
	 */
	public static HashMap<String, PropertyCls> getPropertiesList(String path) throws IOException {
		try(BufferedReader in = new BufferedReader(new FileReader(path))) {
			HashMap<String, PropertyCls> pmap = new HashMap<String, PropertyCls>();
			String tmp;
			while((tmp = in.readLine()) != null) {
				Matcher m = PROPERTIES_PATTERN.matcher(tmp);
				while(m.find()) {
					
					PropertyCls clzz = new PropertyCls();
					clzz.messageId = m.group(1);
					clzz.message = m.group(2);
					clzz.messageflg = true;
					
					pmap.put(m.group(1), clzz);
				}
			}
			return pmap;
		}
	}
	
	/**
	 * 
	 * @param plist
	 */
	public static void filterMsgArgsCount(HashMap<String, PropertyCls> pmap) {
		Set<Entry<String, PropertyCls>> enset = pmap.entrySet();
		for(Entry<String, PropertyCls> ent : enset) {
			PropertyCls pcls = ent.getValue();
			Matcher m = MESSAGE_ARGS_PATTERN.matcher(pcls.message);
			int count = 0;
			while(m.find()) {
				count++;
			}
			pcls.count = count;
		}
	}
	
	/**
	 * 
	 * @param path
	 * @throws Exception
	 */
	public static ArrayList<HashMap<String, ArrayList<MsgCls>>> getMsgFromSrc(String path) throws Exception {
		File root = new File(path);
		File[] flist = root.listFiles();
		ArrayList<String> srclist = new ArrayList<String>();
		
		//パッケージごとにソースをチェック。
		for(File child : flist) {
			File file2 = findDir(child, "src");
			findSrc(file2, ".java", srclist);
		}
		return filterJavaSrc(srclist);
	}
	
	/**
	 * 
	 * @param srclist
	 * @return
	 * @throws IOException
	 */
	public static ArrayList<HashMap<String, ArrayList<MsgCls>>> filterJavaSrc(ArrayList<String> srclist) throws IOException {
		ArrayList<HashMap<String, ArrayList<MsgCls>>> alllist = new ArrayList<HashMap<String, ArrayList<MsgCls>>>();

		for(String path : srclist) {
			HashMap<String, ArrayList<MsgCls>> list = getJarsrc(path);
			if(list == null) {
				continue;
			}
			alllist.add(list);
		}
		return alllist;
	}
	
	/**
	 * javaソースからクラス名とメッセージID、メッセージの引数、それらが出現した行数をまとめたものをリスト化し、
	 * クラス名をキーとするハッシュマップに格納する。
	 * @param path
	 * @return
	 * @throws IOException
	 */
	public static HashMap<String, ArrayList<MsgCls>> getJarsrc(String path) throws IOException {
		try (BufferedReader in = new BufferedReader(new FileReader(path))) {
			HashMap<String, ArrayList<MsgCls>> list = new HashMap<String, ArrayList<MsgCls>>();
			
			ArrayList<MsgCls> msglist = new ArrayList<MsgCls>();
			String clsName = "dummy";
			int count = 0;
			boolean clsflg = true;
			String tmp;
			while((tmp = in.readLine()) != null) {
				count++;
				if(clsflg) {
					Matcher m = CLASS_NAME_PATTERN.matcher(tmp);
					while(m.find()) {
						clsName = m.group(1);
						clsflg = false;
					}
				}
				if(!clsflg) {
					Matcher m = USED_MESSAGE_PATTERN.matcher(tmp);
					while(m.find()) {
						MsgCls msgCls = new MsgCls();
						msgCls.messageId = m.group(1);
						
						String args = m.group(2);
						String sufx = m.group(3);

						if(args == null || "".equals(args.trim())) {
							if(sufx.contains(";")) {//引数なし
								msgCls.args = "";
								msgCls.lineNum = count;
							} else {//行跨ぎ
								String tmp2 = in.readLine();
								Matcher m2 = USED_MESSAGE_PATTERN2.matcher(tmp2);
								m2.find();
								msgCls.args = m2.group(1);
								msgCls.lineNum = ++count;
							}
						} else if(!sufx.contains(";")) {//行跨ぎ
							String tmp2 = in.readLine();
							Matcher m2 = USED_MESSAGE_PATTERN2.matcher(tmp2);
							m2.find();
							msgCls.args = new StringBuilder(args.trim()).append(m2.group(1).trim()).toString();
							msgCls.lineNum = count++;
						} else {//通常
							msgCls.args = m.group(2);
							msgCls.lineNum = count;
						}
						msglist.add(msgCls);
					}
				}
			}
			if(clsName.equals("dummy") || msglist.size() == 0) {
				return null;
			}
			list.put(clsName, msglist);
			return list;
		}
	}
	
	
	/**
	 * 1階層内で指定したディレクトリのファイル返す。
	 * @param path
	 * @param dirName
	 * @return
	 */
	public static File findDir(File path, String dirName) {
		if(path == null || path.isFile() || path.getName().startsWith(".") ) {
			return null;
		}
		File[] filelist = path.listFiles();
		for(File fle : filelist) {
			if(fle.isFile() || fle.isHidden() || fle.getName().startsWith(".")) {
				continue;
			}
			if(fle.isDirectory() && fle.getName().startsWith(dirName)) {
				return fle;
			}
		}
		return null;
	}
	
	/**
	 * 階層を進んで該当サフィックスを持つファイルの絶対パスをリストへ格納する。
	 * @param path 
	 * @param suffix 
	 * @param srclist 
	 */
	public static void findSrc(File path, String suffix, ArrayList<String> srclist) {
		if(path == null) {
			return;
		}
		File[] filelist = path.listFiles();
		for(File fle : filelist) {
			if(fle.isDirectory() && !fle.isHidden()) {
				findSrc(fle, suffix, srclist);
			}
			if(fle.isFile() && fle.getName().endsWith(suffix)) {
				srclist.add(fle.getAbsolutePath());
				continue;
			}
		}
	}
	
	/**
	 * クラスから取得したメッセージ
	 */
	static class MsgCls {
		public String messageId;//メッセージID
		public String args;//メッセージ引数
		public int lineNum;//行数
		public int count;//メッセージに含まれる代替引数の個数
	}	
	
	/**
	 * プロパティクラス
	 */
	static class PropertyCls {
		public String messageId;//メッセージID
		public String message;//メッセージ
		public int count;//メッセージに含まれる代替引数の個数
		public boolean messageflg;//コメントになっているか否か
	}
	
}

try with使うついでに何かを書いてみた。
これは、プロパティファイルに書かれている、メッセージID = メッセージがきちんとソースで使用されているかを確認する。
メッセージに代替引数が入っている場合({1}とか)、ソースでメッセージにパラメータを設定しているか確認するもの。
引数を代入するときのメソッドが改行を跨いでいる場合も考慮したつもり。
これ多分、スレッド毎にパッケージで振り分け、タスク1:内部のソースを取得・編集後、タスク2:メッセージチェックと分割させてやれば早く終わるのだろう。
スレッドプール作ってmainでタスク1をプールに投げ、タスク1が終わったスレッドがタスク2をプールに投げればいいかな。
ここまできちんとタスク分けしたら、ちまたで話題のmap shuffle reduceとか使えるのかなあ。
どちらもそのうちやってみようと思う。
気が向いたらJquery体験も書いてみよう。
あと、最近気づいたのだけれど、eclipseのコンソールってinも兼ねていたのね。。
いじょ....org