2014年2月15日土曜日

DbUnit用のxmlデータを取得するLoader

DbUnitでエクスポートしたxmlデータをMapやbeanにロードするクラスです。
assertの比較元のデータを作る際に利用できそうなので作ってみました。
実際にテーブルが存在しなくてもDbUnitのxml形式でデータを作れば読み込むことができます。
dbunitの部分とreflectionの部分を別のクラスに分けたかったのですが、いずれやることにして取りあえずこのまま公開します。
例のごとくあまりテストはしていません。
使用しているライブラリは下記の通りです。
 ・commons-lang3-3.2.1.jar
 ・dbunit-2.4.9.jar
 ・guava-16.0.1.jar
+各ライブラリで必要なライブラリ

FlatXmlDataSetLoader.java
package my.junit.util;

import java.io.File;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.MalformedURLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.dbunit.dataset.Column;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ITable;
import org.dbunit.dataset.ITableMetaData;
import org.dbunit.dataset.xml.FlatXmlDataSetBuilder;

import com.google.common.base.CaseFormat;

public class FlatXmlDataSetLoader {

 /**
  * 日付用フォーマット
  */
 private static final String[] DEFAULT_DATE_FORMATS = {
   "yyyy-MM-dd hh:mm:ss.SSS", "yyyy-MM-dd hh:mm:ss", "yyyy-MM-dd" };

 /**
  * java.util.Date用フォーマット
  */
 private String[] dateFormats = null;

 /**
  * コンストラクタ
  */
 public FlatXmlDataSetLoader() {
  this.dateFormats = DEFAULT_DATE_FORMATS;
 }

 /**
  * コンストラクタ
  */
 public FlatXmlDataSetLoader(String... dateFormats) {
  this.dateFormats = dateFormats;
 }

 /**
  * DbUnit用のxmlファイルの1レコード分をMap化したリストを取得します。
  *
  * @param filePath
  *            xmlファイルのパス
  * @param tableName
  *            取得したいテーブル
  * @return 1レコード分をMapに設定したリスト
  */
 public List<Map<String, Object>> importToMapList(String filePath,
   String tableName) {
  try {
   IDataSet dataSet = new FlatXmlDataSetBuilder().build(new File(
     filePath));
   ITable table = dataSet.getTable(tableName);
   ITableMetaData meta = table.getTableMetaData();
   Column[] columns = meta.getColumns();

   List<Map<String, Object>> records = new ArrayList<Map<String, Object>>();
   for (int i = 0; i < table.getRowCount(); i++) {
    Map<String, Object> record = new HashMap<String, Object>();
    for (Column column : columns) {
     String columnName = column.getColumnName();
     record.put(columnName, table.getValue(i, columnName));
    }
    records.add(record);
   }
   return records;
  } catch (MalformedURLException e) {
   throw new IllegalArgumentException(e);
  } catch (DataSetException e) {
   throw new IllegalArgumentException(e);
  }
 }

 /**
  * DbUnit用のxmlファイルの1レコード分をbean化したリストを取得します。
  *
  * @param filePath
  *            xmlファイルのパス
  * @param tableName
  *            取得したいテーブル
  * @param beanClass
  *            設定対象のbean class
  * @return 1レコード分をbeanに設定したリスト
  */
 public <B> List<B> importToBeanList(String filePath, String tableName,
   Class<B> beanClass) {
  List<Map<String, Object>> snakeMapList = importToMapList(filePath,
    tableName);
  List<Map<String, Object>> camelMapList = new ArrayList<Map<String, Object>>();
  for (Map<String, Object> snakeMap : snakeMapList) {
   camelMapList.add(convertToLowerCamelKey(snakeMap));
  }
  return convertMapListToBeanList(camelMapList, beanClass);
 }

 /**
  * Mapのリストをbeanのリストに変換します。
  *
  * @param mapList
  *            Mapの
  * @param beanClass
  *            設定対象のbean class
  * @return beanのリスト
  */
 public <B> List<B> convertMapListToBeanList(
   List<Map<String, Object>> mapList, Class<B> beanClass) {
  Map<String, Field> fieldsInfo = getFieldInfo(beanClass);
  List<B> beans = new ArrayList<B>();
  for (Map<String, Object> map : mapList) {
   try {
    beans.add(convertMapToBean(map, beanClass.newInstance(),
      fieldsInfo));
   } catch (InstantiationException e) {
    throw new IllegalArgumentException(e);
   } catch (IllegalAccessException e) {
    throw new IllegalArgumentException(e);
   }
  }
  return beans;
 }

 /**
  * Mapのリストをbeanのリストに変換します。
  *
  * @param map
  *            Map
  * @param beanClass
  *            設定対象のbean class
  * @return bean
  */
 public <B> B convertMapToBean(Map<String, Object> map, Class<B> beanClass) {
  try {
   return convertMapToBean(map, beanClass.newInstance());
  } catch (InstantiationException e) {
   throw new IllegalArgumentException(e);
  } catch (IllegalAccessException e) {
   throw new IllegalArgumentException(e);
  }
 }

 /**
  * Mapのリストをbeanのリストに変換します。
  *
  * @param map
  *            Map
  * @param bean
  *            設定対象のbean
  * @return bean
  */
 public <B> B convertMapToBean(Map<String, Object> map, B bean) {
  Map<String, Field> fieldMap = getFieldInfo(bean.getClass());
  return convertMapToBean(map, bean, fieldMap);
 }

 /**
  * Mapのリストをbeanのリストに変換します。
  *
  * @param map
  *            Map
  * @param bean
  *            設定対象のbean
  * @param fieldsInfo
  *            beanのフィールド情報
  * @return bean
  */
 private <B> B convertMapToBean(Map<String, Object> map, B bean,
   Map<String, Field> fieldsInfo) {
  Set<Entry<String, Field>> fieldSet = fieldsInfo.entrySet();
  for (Entry<String, Field> fieldEntry : fieldSet) {
   Field field = fieldEntry.getValue();
   Object value = map.get(fieldEntry.getKey());
   setFieldValue(bean, field, value);
  }
  return bean;
 }

 /**
  * クラスのフィールド情報を取得します。
  *
  * @param beanClass
  *            対象のクラス
  * @return フィールド名をキーにしたフィールドのマップ
  */
 private static Map<String, Field> getFieldInfo(Class<?> beanClass) {
  Field[] fields = FieldUtils.getAllFields(beanClass);
  Map<String, Field> fieldMap = new HashMap<String, Field>();
  for (Field field : fields) {
   String key = field.getName();
   fieldMap.put(key, field);
  }
  return fieldMap;
 }

 /**
  * マップのキーをスネークケースからキャメルケースに変換します。
  *
  * @param snakeMap
  *            キーがスネークケースのマップ
  * @return キーがキャメルケースのマップ
  */
 private static <V> Map<String, V> convertToLowerCamelKey(
   Map<String, V> snakeMap) {
  Map<String, V> camelMap = new HashMap<String, V>();
  Set<String> snakeKeys = snakeMap.keySet();
  for (String snake : snakeKeys) {
   String camel = CaseFormat.UPPER_UNDERSCORE.to(
     CaseFormat.LOWER_CAMEL, snake);
   camelMap.put(camel, snakeMap.get(snake));
  }
  return camelMap;
 }

 /**
  * fieldのclassに合わせた形式に変換し値を設定
  *
  * @param bean
  *            設定対象のbean
  * @param field
  *            設定対象のfield
  * @param value
  *            設定する値
  * @return true:設定完了、false:未設定
  */
 private boolean setFieldValue(Object bean, Field field, Object value) {
  if (value == null) {
   // nullの場合
   return false;
  }
  try {
   field.setAccessible(true);
   String val = value.toString();
   if (field.getType().equals(String.class)) {
    field.set(bean, val);
   } else if (field.getType().equals(char[].class)) {
    field.set(bean, val.toCharArray());
   } else if (field.getType().equals(Boolean.class)) {
    field.set(bean, new Boolean(val));
   } else if (field.getType().equals(boolean.class)) {
    field.set(bean, new Boolean(val).booleanValue());
   } else if (field.getType().equals(Byte.class)) {
    field.set(bean, Byte.valueOf(val));
   } else if (field.getType().equals(byte.class)) {
    field.set(bean, Byte.valueOf(val).byteValue());
   } else if (field.getType().equals(Integer.class)) {
    field.set(bean, Integer.valueOf(val));
   } else if (field.getType().equals(int.class)) {
    field.set(bean, Integer.valueOf(val));
   } else if (field.getType().equals(Long.class)) {
    field.set(bean, Long.valueOf(val));
   } else if (field.getType().equals(long.class)) {
    field.set(bean, Long.valueOf(val));
   } else if (field.getType().equals(Short.class)) {
    field.set(bean, Short.valueOf(val));
   } else if (field.getType().equals(short.class)) {
    field.set(bean, Short.valueOf(val));
   } else if (field.getType().equals(Double.class)) {
    field.set(bean, Double.valueOf(val));
   } else if (field.getType().equals(double.class)) {
    field.set(bean, Double.valueOf(val));
   } else if (field.getType().equals(BigDecimal.class)) {
    field.set(bean, new BigDecimal(val));
   } else if (field.getType().equals(Date.class)) {
    field.set(bean, DateUtils.parseDate(val, dateFormats));
   } else if (field.getType().equals(Timestamp.class)) {
    Date date = DateUtils.parseDate(val, dateFormats);
    field.set(bean, new Timestamp(date.getTime()));
   } else {
    return false;
   }
  } catch (Exception e) {
   throw new IllegalArgumentException(e);
  } finally {
   field.setAccessible(false);
  }
  return true;
 }

}



2014年2月11日火曜日

JUnit用のテキストファイルを比較するMatcher

JUnit4で使用可能なテキストファイル同士の内容をマッチングするMatcherです。
JUnit用と言っていますがhamcrestしか使っていません。
取りあえず作ってみた状態なのでテストはせず、ちょっと動かしてみた程度です。直さなければいけない部分もあるかと思いますが、作ったことを忘れてしまいそうなので上げておきます。
使用しているライブラリは下記の通りです。
 ・hamcrest-core-1.3.jar
+各ライブラリで必要なライブラリ

TextInputStreamMatcher.java
package my.junit.matcher;

import java.io.InputStream;

import org.hamcrest.Matcher;

/**
 * テキスト形式でInputStreamの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class TextInputStreamMatcher extends AbstractTextFileMatcher<InputStream> {

 /**
  * コンストラクタ
  */
 protected TextInputStreamMatcher(InputStream expected) {
  this(expected, CHAR_SET);
 }

 /**
  * コンストラクタ
  */
 protected TextInputStreamMatcher(InputStream expected, String charSet) {
  super();
  this.charSet = charSet;
  expectedName = "expected file";
  expectedIs = expected;
 }

 /**
  * <pre>
  * テキストファイル比較用のMatcherを取得します。
  * キャラクターセットはUTF-8です。
  * </pre>
  *
  * @param expected
  *            予想されるファイル
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<InputStream> equalsContentsOf(InputStream expected) {
  return new TextInputStreamMatcher(expected);
 }

 /**
  * テキストファイル比較用のMatcherを取得します。
  *
  * @param expected
  *            予想されるファイル
  * @param charSet
  *            キャラクターセット
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<InputStream> equalsContentsOf(InputStream expected,
   String charSet) {
  return new TextInputStreamMatcher(expected, charSet);
 }

 /**
  * {@inheritDoc}
  */
 @Override
 protected InputStream getActualIs(InputStream actual) {
  actualIs = actual;
  return actualIs;
 }

}


TextFileMatcher.java
package my.junit.matcher;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

import org.hamcrest.Matcher;

/**
 * テキストファイルの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class TextFileMatcher extends AbstractTextFileMatcher<File> {

 /**
  * コンストラクタ
  */
 protected TextFileMatcher(File expected) {
  this(expected, CHAR_SET);
 }

 /**
  * コンストラクタ
  */
 protected TextFileMatcher(File expected, String charSet) {
  super();
  this.charSet = charSet;
  File expectedFile = expected;
  expectedName = expectedFile.getPath();
  try {
   expectedIs = new FileInputStream(expectedFile);
  } catch (FileNotFoundException e) {
   throw new IllegalArgumentException(e);
  }
 }

 /**
  * <pre>
  * テキストファイル比較用のMatcherを取得します。
  * キャラクターセットはUTF-8です。
  * </pre>
  *
  * @param expected
  *            予想されるファイル
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<File> equalsContentsOf(File expected) {
  return new TextFileMatcher(expected);
 }

 /**
  * テキストファイル比較用のMatcherを取得します。
  *
  * @param expected
  *            予想されるファイル
  * @param charSet
  *            キャラクターセット
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<File> equalsContentsOf(File expected, String charSet) {
  return new TextFileMatcher(expected, charSet);
 }

 /**
  * {@inheritDoc}
  */
 @Override
 protected InputStream getActualIs(File actual) {
  actualName = actual.getPath();
  try {
   actualIs = new FileInputStream(actual);
  } catch (FileNotFoundException e) {
   throw new IllegalArgumentException(e);
  }
  return actualIs;
 }

}


TextFilePathMatcher.java
package my.junit.matcher;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;

import org.hamcrest.Matcher;

/**
 * テキストファイルの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class TextFilePathMatcher extends AbstractTextFileMatcher<String> {

 /**
  * コンストラクタ
  */
 protected TextFilePathMatcher(String expected) {
  this(expected, CHAR_SET);
 }

 /**
  * コンストラクタ
  */
 protected TextFilePathMatcher(String expected, String charSet) {
  super();
  this.charSet = charSet;
  String expectedPath = (String) expected;
  File expectedFile = new File(expectedPath);
  expectedName = expectedFile.getPath();
  try {
   expectedIs = new FileInputStream(expectedFile);
  } catch (FileNotFoundException e) {
   throw new IllegalArgumentException(e);
  }
 }

 /**
  * <pre>
  * テキストファイル比較用のMatcherを取得します。
  * キャラクターセットはUTF-8です。
  * </pre>
  *
  * @param expected
  *            予想されるファイルのパス
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<String> equalsContentsOf(String expected) {
  return new TextFilePathMatcher(expected);
 }

 /**
  * テキストファイル比較用のMatcherを取得します。
  *
  * @param expected
  *            予想されるファイルのパス
  * @param charSet
  *            キャラクターセット
  * @return テキストファイル比較用のMatcher
  */
 public static Matcher<String> equalsContentsOf(String expected,
   String charSet) {
  return new TextFilePathMatcher(expected, charSet);
 }

 /**
  * {@inheritDoc}
  */
 @Override
 protected InputStream getActualIs(String actual) {
  File actualFile = new File(actual);
  actualName = actualFile.getPath();
  try {
   actualIs = new FileInputStream(actualFile);
  } catch (FileNotFoundException e) {
   throw new IllegalArgumentException(e);
  }
  return actualIs;
 }

}


AbstractTextFileMatcher.java
package my.junit.matcher;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

/**
 * テキストファイルの比較を行えるMatcherの抽象クラス
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
abstract class AbstractTextFileMatcher<T> extends TypeSafeMatcher<T> {

 /**
  * デフォルトキャラクターセット
  */
 protected static final String CHAR_SET = "UTF-8";

 /**
  * キャラクターセット
  */
 protected String charSet = null;

 /**
  * 予想されるファイルの表示名
  */
 protected String expectedName = null;

 /**
  * 予想されるファイルのインプットストリーム
  */
 protected InputStream expectedIs = null;

 /**
  * 実際のファイルの表示名
  */
 protected String actualName = null;

 /**
  * 実際のファイルのインプットストリーム
  */
 protected InputStream actualIs = null;

 /**
  * 不一致だった予想されるファイルの行
  */
 protected List<String> unmatchedExpectedLines = new ArrayList<String>();

 /**
  * 不一致だった実際のファイルの行
  */
 protected List<String> unmatchedActualLines = new ArrayList<String>();

 /**
  * 不一致だった行番号
  */
 protected List<Integer> unmatchedLineNums = new ArrayList<Integer>();

 /**
  * デフォルトコンストラクタ
  */
 protected AbstractTextFileMatcher() {
  super();
 }

 /**
  * コンストラクタ
  */
 protected AbstractTextFileMatcher(String expectedName,
   InputStream expectedIs) {
  this(expectedName, expectedIs, CHAR_SET);
 }

 /**
  * コンストラクタ
  */
 protected AbstractTextFileMatcher(String expectedName,
   InputStream expectedIs, String charSet) {
  this.expectedName = expectedName;
  this.expectedIs = expectedIs;
  this.charSet = charSet;
 }

 /**
  * 実際のファイルのインプットストリームを取得します。
  *
  * @param actual
  *            実際のファイルの情報
  * @return 実際のファイルのインプットストリーム
  */
 protected abstract InputStream getActualIs(T actual);

 /**
  * 実際のファイルの表示名を取得する。
  *
  * @return 実際のファイルの表示名
  */
 private String getActualName() {
  if (actualName == null) {
   return "actual file";
  } else {
   return actualName;
  }
 }

 /**
  * 比較を行います。
  *
  * @param actual
  *            実際の値
  * @return 比較結果
  */
 @Override
 public boolean matchesSafely(T actual) {
  // インプットストリームを取得
  actualIs = getActualIs(actual);

  BufferedReader expectedReader = null;
  try {
   // 予想されるファイル
   expectedReader = new BufferedReader(new InputStreamReader(
     expectedIs, charSet));
  } catch (UnsupportedEncodingException e) {
   throw new IllegalArgumentException(e);
  }

  BufferedReader actualReader = null;
  try {
   // 実際のファイル
   actualReader = new BufferedReader(new InputStreamReader(actualIs,
     charSet));
  } catch (UnsupportedEncodingException e) {
   throw new IllegalArgumentException(e);
  }

  int lineNum = 0;
  String expectedLine = null;
  String actualLine = null;
  try {
   while ((expectedLine = expectedReader.readLine()) != null
     & (actualLine = actualReader.readLine()) != null) {
    lineNum++;
    if (!expectedLine.equals(actualLine)) {
     // 1行分の内容が一致しない
     unmatchedLineNums.add(Integer.valueOf(lineNum));
     unmatchedExpectedLines.add(expectedLine);
     unmatchedActualLines.add(actualLine);
    }
   }
   if (expectedLine != null || actualLine != null) {
    // 行数が一致しない
    lineNum++;
    unmatchedLineNums.add(Integer.valueOf(lineNum));
    unmatchedExpectedLines.add(expectedLine);
    unmatchedActualLines.add(actualLine);
   }
  } catch (IOException e) {
   // どうにもならないので例外
   throw new IllegalStateException(e);
  } finally {
   try {
    expectedReader.close();
   } catch (IOException e) {
    // 比較は終わっているので握りつぶす
   }
   try {
    actualReader.close();
   } catch (IOException e) {
    // 比較は終わっているので握りつぶす
   }
  }
  return unmatchedLineNums.isEmpty();
 }

 /**
  * エラーの場合に表示する、実際の値を示す文字列を追加します。
  *
  * @param actual
  *            実際の値
  * @param description
  *            エラー時の文章
  */
 @Override
 public void describeMismatchSafely(T actual, Description mismatchDescription) {
  setUpDescription(mismatchDescription, getActualName(),
    unmatchedActualLines, unmatchedLineNums);
 }

 /**
  * エラーの場合に表示する、予測の値を示す文字列を追加します。
  *
  * @param description
  *            エラー時の文章
  */
 @Override
 public void describeTo(Description description) {
  setUpDescription(description, expectedName, unmatchedExpectedLines,
    unmatchedLineNums);
 }

 /**
  * エラーの詳細を設定します。
  *
  * @param description
  *            設定先
  * @param name
  *            表示名
  * @param lines
  *            エラーになった行の内容
  * @param lineNums
  *            エラーになった行番号
  */
 private void setUpDescription(Description description, String name,
   List<String> lines, List<Integer> lineNums) {
  description.appendText(name + " = ");
  if (lineNums.isEmpty()) {
   description.appendText("想定外のエラー");
  }
  for (int i = 0; i < lineNums.size(); i++) {
   if (i != 0) {
    description.appendText(", ");
   }
   description.appendText(lineNums.get(i) + ":");
   description.appendValue(lines.get(i));
  }
  return;
 }

}



2014年2月9日日曜日

JUnit用のマッチングするフィールドを指定できるMatcher(Iterable版)

JUnit4で使用可能なbeanの一部のフィールドだけでマッチングを行うMatcherの入力をIterableに対応させたものです。
ほとんどテストをしていないのは同様ですが、やっぱり作ったことを忘れてしまいそうなので上げておきます。
使用しているライブラリは下記の通りです。
 ・hamcrest-core-1.3.jar
 ・commons-lang3-3.2.1.jar
 ・JUnit用のマッチングするフィールドを指定できるMatcher
+各ライブラリで必要なライブラリ

IterableBeanPropertiesMatcher.java
package my.junit.matcher;

import org.hamcrest.Matcher;

/**
 * 一部のプロパティを選択してIterableの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class IterableBeanPropertiesMatcher<T> extends
  AbstractIterablePropertiesMatcher<T> {

 /**
  * コンストラクタ
  *
  * @param expected
  *            予想される値
  * @param properties
  *            比較対象のプロパティ
  */
 IterableBeanPropertiesMatcher(Iterable<T> expected, String... properties) {
  super();
  this.expected = expected;
  for (T bean : expected) {
   this.matchers.add(new BeanPropertiesMatcher<T>(bean, properties));
  }
 }

 /**
  * Matcherを取得します。
  *
  * @param expected
  *            予想される値
  * @param propeties
  *            比較対象のプロパティ
  * @return 一部のプロパティを選択してIterableの比較を行えるMatcher
  */
 public static <T> Matcher<Iterable<T>> containsValueOf(
   Iterable<T> expected, String... propeties) {
  return new IterableBeanPropertiesMatcher<T>(expected, propeties);
 }

}


IterableBeanIgnorePropertiesMatcher.java
package my.junit.matcher;

import org.hamcrest.Matcher;

/**
 * 一部のプロパティを無視してbean配列、コレクションの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class IterableBeanIgnorePropertiesMatcher<T> extends
  AbstractIterablePropertiesMatcher<T> {

 /**
  * コンストラクタ
  *
  * @param expected
  *            予想される値
  * @param properties
  *            比較対象外のプロパティ
  */
 IterableBeanIgnorePropertiesMatcher(Iterable<T> expected, String... ignoreProperties) {
  super();
  this.expected = expected;
  for (T bean : expected) {
   this.matchers.add(new BeanIgnorePropertiesMatcher<T>(bean, ignoreProperties));
  }
 }

 /**
  * Matcherを取得します。
  *
  * @param expected
  *            予想される値
  * @param ignoreProperties
  *            比較対象外のプロパティ
  * @return 一部のプロパティを無視してIterableの比較を行えるMatcher
  */
 public static <T> Matcher<Iterable<T>> containsExceptValueOf(
   Iterable<T> expected, String... ignoreProperties) {
  return new IterableBeanIgnorePropertiesMatcher<T>(expected, ignoreProperties);
 }

}


SelectIterablePropertiesMatcher.java
package my.junit.matcher;

import org.hamcrest.Matcher;


interface SelectIterablePropertiesMatcher<T> extends Matcher<Iterable<T>>{
 // 特になし
}


AbstractIterablePropertiesMatcher.java
package my.junit.matcher;

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

import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

/**
 * 一部のプロパティを指定してbean配列、コレクションの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
abstract class AbstractIterablePropertiesMatcher<T> extends
  TypeSafeMatcher<Iterable<T>> implements
  SelectIterablePropertiesMatcher<T> {

 /**
  * 予想される値毎のMatcher
  */
 protected List<SelectPropertiesMatcher<T>> matchers = new ArrayList<SelectPropertiesMatcher<T>>();

 /**
  * 予想される値
  */
 protected Iterable<T> expected = null;

 /**
  * 実際の値
  */
 protected Iterable<T> actual = null;

 /**
  * コンストラクタ
  */
 protected AbstractIterablePropertiesMatcher() {
 }

 /**
  * 比較を行います。
  *
  * @param actual
  *            実際の値
  * @return 比較結果
  */
 @Override
 public boolean matchesSafely(Iterable<T> actual) {
  this.actual = actual;
  if (expected == actual) {
   // 完全に同じもの
   return true;
  }
  if (expected == null || actual == null) {
   // 片方だけnullだから違う
   return false;
  }
  // ここからは配列やコレクション内の要素毎に比較
  Iterator<T> actualIte = actual.iterator();
  Iterator<SelectPropertiesMatcher<T>> matcherIte = matchers.iterator();
  boolean result = true;
  while (matcherIte.hasNext() & actualIte.hasNext()) {
   T actualEntry = actualIte.next();
   SelectPropertiesMatcher<T> matcher = matcherIte.next();
   if (!matcher.matches(actualEntry)) {
    result = false;
   }
  }
  if ((!matcherIte.hasNext() && !actualIte.hasNext()) == false) {
   // 要素数が合っていない場合はfalse
   return false;
  }
  // 一致しなかったMatcherがない場合はtrue
  return result;
 }

 /**
  * エラーの場合に表示する、実際の値を示す文字列を追加します。
  *
  * @param actual
  *            実際の値
  * @param description
  *            エラー時の文章
  */
 @Override
 public void describeMismatchSafely(Iterable<T> actual,
   Description mismatchDescription) {
  if (actual == null) {
   mismatchDescription.appendValue(null);
   return;
  }
  int index = -1;
  boolean first = true;
  for (SelectPropertiesMatcher<T> matcher : matchers) {
   index++;
   if (matcher.getUnmatchProperties().isEmpty()) {
    continue;
   }
   if (first) {
    first = false;
   } else {
    mismatchDescription.appendText(", ");
   }
   mismatchDescription.appendText("[" + Integer.valueOf(index) + "] ");
   matcher.describeMismatch(mismatchDescription);
  }
 }

 /**
  * エラーの場合に表示する、予測の値を示す文字列を追加します。
  *
  * @param description
  *            エラー時の文章
  */
 @Override
 public void describeTo(Description description) {
  if (actual == null) {
   description.appendValue(expected.getClass().getName());
   return;
  }
  int index = -1;
  boolean first = true;
  for (SelectPropertiesMatcher<T> matcher : matchers) {
   index++;
   if (matcher.getUnmatchProperties().isEmpty()) {
    continue;
   }
   if (first) {
    first = false;
   } else {
    description.appendText(", ");
   }
   description.appendText("[" + Integer.valueOf(index) + "] ");
   matcher.describeTo(description);
  }
 }

}

JUnit用のマッチングするフィールドを指定できるMatcher

JUnit4で使用可能なbeanの一部のフィールドだけでマッチングを行うMatcherです。
JUnit用と言っていますがhamcrestしか使っていません。
ほとんどテストをしていないのですが、作ったことを忘れてしまいそうなので上げておきます。
使用しているライブラリは下記の通りです。
 ・hamcrest-core-1.3.jar
 ・commons-lang3-3.2.1.jar
+各ライブラリで必要なライブラリ

BeanPropertiesMatcher.java
package my.junit.matcher;

import org.hamcrest.Matcher;

/**
 * 一部のプロパティを選択してbeanの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class BeanPropertiesMatcher<T> extends AbstractBeanPropertiesMatcher<T> {

 /**
  * コンストラクタ
  *
  * @param expected
  *            予想される値
  * @param properties
  *            比較対象のプロパティ
  */
 BeanPropertiesMatcher(T expected, String... properties) {
  super();
  this.expected = expected;
  this.properties = properties;
 }

 /**
  * Matcherを取得します。
  *
  * @param expected
  *            予想される値
  * @param propeties
  *            比較対象のプロパティ
  * @return 一部のプロパティを選択して比較を行えるMatcher
  */
 public static <T> Matcher<T> equalsValueOf(T expected, String... propeties) {
  return new BeanPropertiesMatcher<T>(expected, propeties);
 }

}

BeanIgnorePropertiesMatcher.java
package my.junit.matcher;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.ArrayUtils;
import org.hamcrest.Matcher;

/**
 * 一部のプロパティを無視してbeanの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
class BeanIgnorePropertiesMatcher<T> extends AbstractBeanPropertiesMatcher<T> {

 /**
  * コンストラクタ
  *
  * @param expected
  *            予想される値
  * @param ignoreProperties
  *            比較対象外のプロパティ
  */
 BeanIgnorePropertiesMatcher(T expected, String[] ignoreProperties) {
  super();
  this.expected = expected;
  if(expected == null){
   this.properties = new String[0];
  }else{
   // 全てのFieldから比較対象外のプロパティを除いたプロパティ配列を作る
   List<String> properties = new ArrayList<String>();
   Field[] fields = expected.getClass().getDeclaredFields();
   for (Field field : fields) {
    if (!ArrayUtils.contains(ignoreProperties, field.getName())) {
     properties.add(field.getName());
    }
   }
   this.properties = properties.toArray(new String[properties.size()]);
  }
 }

 /**
  * Matcherを取得します。
  *
  * @param expected
  *            予想される値
  * @param ignoreProperties
  *            比較対象外のプロパティ
  * @return 一部のプロパティを無視して比較を行えるMatcher
  */
 public static <T> Matcher<T> equalsExceptValueOf(T expected, String... ignoreProperties) {
  return new BeanIgnorePropertiesMatcher<T>(expected, ignoreProperties);
 }

}
SelectPropertiesMatcher.java
package my.junit.matcher;

import java.util.List;

import org.hamcrest.Description;
import org.hamcrest.Matcher;

interface SelectPropertiesMatcher<T> extends Matcher<T> {

 /**
  * 予想される値を取得します。
  *
  * @return 予想される値
  */
 T getExpected();

 /**
  * 実際の値を取得します。
  *
  * @return 実際の値
  */
 Object getActual();

 /**
  * 一致しなかったプロパティを取得します。
  *
  * @return 一致しなかったプロパティ
  */
 List<String> getUnmatchProperties();

 /**
  * エラーの場合に表示する、実際の値を示す文字列を追加します。
  *
  * @param description
  *            エラー時の文章
  */
 public void describeMismatch(Description mismatchDescription);

}
AbstractBeanPropertiesMatcher.java
package my.junit.matcher;

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

import org.apache.commons.lang3.reflect.FieldUtils;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;

/**
 * 一部のプロパティを指定してbeanの比較を行えるMatcher
 *
 * @author blog owner
 *
 * @param <T>
 *            任意のクラス
 */
abstract class AbstractBeanPropertiesMatcher<T> extends TypeSafeMatcher<T>
  implements SelectPropertiesMatcher<T> {

 /**
  * 予想される値
  */
 protected T expected = null;

 /**
  * 実際の値
  */
 protected T actual = null;

 /**
  * 比較対象のプロパティ
  */
 protected String[] properties = null;

 /**
  * 一致しなかったプロパティ
  */
 protected List<String> unmatchProperties = new ArrayList<String>();

 /**
  * コンストラクタ
  */
 protected AbstractBeanPropertiesMatcher() {
 }

 /**
  * 比較を行います。
  *
  * @param actual
  *            実際の値
  * @return 比較結果
  */
 @Override
 public boolean matchesSafely(T actual) {
  this.actual = actual;
  if (expected == actual) {
   // 完全に同じもの
   return true;
  }
  if (expected == null || actual == null) {
   // 片方だけnullだから違う
   return false;
  }
  // 指定のプロパティを取得しながら比較する
  for (String property : properties) {
   Object expectedValue = null;
   Object actualValue = null;
   try {
    // 予測した値
    expectedValue = FieldUtils.readDeclaredField(expected,
      property, true);
    // 実際の値
    actualValue = FieldUtils.readDeclaredField(actual, property,
      true);
   } catch (Exception e) {
    throw new IllegalArgumentException(e);
   }
   if (expectedValue == actualValue) {
    // 完全に同じもの
    continue;
   }
   if (expectedValue == null || actualValue == null) {
    // 片方だけnullだから違う
    unmatchProperties.add(property);
    continue;
   }
   if (!expectedValue.equals(actualValue)) {
    // 違う値
    unmatchProperties.add(property);
    continue;
   }
  }
  // 一致しなかったプロパティがない場合はtrue
  return unmatchProperties.isEmpty();
 }

 /**
  * エラーの場合に表示する、実際の値を示す文字列を追加します。
  *
  * @param description
  *            エラー時の文章
  */
 public void describeMismatch(Description mismatchDescription) {
  describeMismatchSafely(actual, mismatchDescription);
 }

 /**
  * エラーの場合に表示する、実際の値を示す文字列を追加します。
  *
  * @param actual
  *            実際の値
  * @param description
  *            エラー時の文章
  */
 @Override
 public void describeMismatchSafely(T actual, Description mismatchDescription) {
  setUpDescription(mismatchDescription, actual, unmatchProperties);
 }

 /**
  * エラーの場合に表示する、予測の値を示す文字列を追加します。
  *
  * @param description
  *            エラー時の文章
  */
 @Override
 public void describeTo(Description description) {
  setUpDescription(description, expected, unmatchProperties);
 }

 /**
  * エラーの詳細を設定します。
  *
  * @param description
  *            設定先
  * @param bean
  *            対象のbean
  * @param properties
  *            対象のプロパティ名リスト
  */
 private void setUpDescription(Description description, Object bean,
   List<String> properties) {
  if (bean == null) {
   description.appendValue(null);
   return;
  }
  if (properties.isEmpty()) {
   description.appendValue(bean.getClass().getName());
   return;
  }
  int i = -1;
  for (String property : properties) {
   i++;
   if (i != 0) {
    description.appendText(", ");
   }
   description.appendText(property + "=");
   try {
    description.appendValue(FieldUtils.readDeclaredField(bean,
      property, true));
   } catch (IllegalAccessException e) {
    throw new IllegalArgumentException(e);
   }
  }
  return;
 }

 /**
  * 予想される値を取得します。
  *
  * @return 予想される値
  */
 public T getExpected() {
  return expected;
 }

 /**
  * 実際の値を取得します。
  *
  * @return 実際の値
  */
 public Object getActual() {
  return actual;
 }

 /**
  * 一致しなかったプロパティを取得します。
  *
  * @return 一致しなかったプロパティ
  */
 public List<String> getUnmatchProperties() {
  return unmatchProperties;
 }

}


2013年12月15日日曜日

Unitテスト用DAO

Spring+ibatis+JUnitを想定したDAOのMockです。
かなり雑な作りです。こんなのも作ったんだという覚書として上げます。
使う場合は実際のDAOの仕様に合わせて改修する必要があります。

MockDao.java
package my.junit.dao;

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

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * DAOに割り込んで任意の結果を返すクラス
 * 実際には本来のDAOと同じインターフェイスをimplementsする。
 * DAOがDIされているシステムを想定。
 * @author blog owner
 */
public class MockDao {

 private static final Log LOG = LogFactory.getLog(MockDao.class);

 /**
  * 任意の結果を保持するマップ
  */
 protected static Map<Object, Object> resultMap = null;

 /**
  * 本来のDAOを想定したクラス
  *
  * @author blog owner
  *
  */
 private static class Dao {
  public Object execute(String sqlId, Object params) {
   return null;
  }
 }

 /**
  * 本来のDAO
  */
 private Dao dao = null;

 /**
  * 任意の結果を追加
  * @param sqlId
  * @param result
  */
 public static void putResult(String sqlId, Object result) {
  resultMap.put(sqlId, result);
 }

 /**
  * 任意の結果を追加
  * @param sqlId
  * @param params
  * @param result
  */
 public static void putResult(String sqlId, Object params, Object result) {
  put(sqlId, params, result);
 }

 /**
  * 任意の結果を追加
  * @param sqlId
  * @param params
  * @param result
  */
 protected static void put(String sqlId, Object params, Object result) {
  List<Object> key = new ArrayList<Object>();
  key.add(sqlId);
  key.add(params);
  resultMap.put(key, result);
 }

 /**
  * 任意の結果を初期化
  * テストメソッド実行時には必ずこれをやる
  */
 public static void initialize() {
  resultMap = new HashMap<Object, Object>();
 }

 /**
  * 任意の結果をクリア
  */
 public static void clearResult() {
  resultMap = null;
 }

 /**
  * 任意の結果の件数を取得
  * @return
  */
 public static int size() {
  if (resultMap == null) {
   return 0;
  } else {
   return resultMap.size();
  }
 }

 /**
  * sqlを実行
  * sqlId+paramまたはsqlIdをキーとして一致する任意の結果がある場合は、その結果を返す
  * 無い場合は本来のDAOでsqlを実行した結果を返す。
  * @param sqlId
  * @param params
  * @return
  * @throws Exception
  */
 public Object execute(String sqlId, Object params) throws Exception {
  try {
   Object result = getResult(sqlId, params);
   if (result instanceof Exception) {
    throw (Exception) result;
   } else {
    return result;
   }
  } catch (NoSuchKeyException e) {
   return dao.execute(sqlId, params);
  }
 }

 /**
  * sqlId+paramまたはsqlIdをキーとして一致する任意の結果がある場合は、その結果を返す
  * 結果がない場合は例外を投げる
  * @param sqlId
  * @param params
  * @return
  * @throws NoSuchKeyException
  */
 protected static Object getResult(String sqlId, Object params)
   throws NoSuchKeyException {
  if(resultMap == null || resultMap.isEmpty()){
   LOG.debug("sqlId=" + sqlId + "でテーブルへアクセスします。");
   throw new NoSuchKeyException();
  }
  List<Object> key = new ArrayList<Object>();
  key.add(sqlId);
  key.add(params);
  if (resultMap.containsKey(key)) {
   LOG.debug("key=" + key + "のダミーの結果を返します。");
   return resultMap.get(key);
  } else if (resultMap.containsKey(sqlId)) {
   LOG.debug("sqlId=" + sqlId + "のダミーの結果を返します。");
   return resultMap.get(sqlId);
  } else {
   LOG.debug("sqlId=" + sqlId + "でテーブルへアクセスします。");
   throw new NoSuchKeyException();
  }
 }
}


2013年10月14日月曜日

サクラエディタ用Grep&行削除マクロ(full)

サクラエディタのGrep結果を基に行の削除を行うVBSのマクロです。
以前公開していたものはそれ単品では動作しない形式だったので、
必要な関数なども併せてフルバージョンとして置いておきます。
個人的に作成・利用をしているマクロで十分なデバッグが行われているとは言えません。
また処理対象を直接編集・保存している為、処理後に元に戻すことができません。
こういった点を理解した上、自己責任での利用をお願いします。

GrepAndDelete.vbs
'Grep結果を基に行削除を行うマクロ
'
'【詳細】
'サクラエディタのGrep結果を基に行の削除を行います。
'削除の対象はGrep結果に載っているファイル&行だけです。
'---------------------------------------------------------------------
'行削除処理を行うStrategy
'Publicで宣言されている部分の処理を差し替えることで、
'Grep結果に対する様々な処理を実装することができます。
Class Strategy

    Private lngSize
    Private strPreviousPath
    Private strErrMessage

    'コンストラクタ
    Private Sub Class_Initialize()
    End Sub

    'デストラクタ
    Private Sub Class_Terminate()
    End Sub

    '初期化処理
    Public Function initialize()
        initialize = False
      
        lngSize = 0
        strPreviousPath = ""
        strErrMessage = ""

        initialize = True
    End Function

    '前処理
    Public Function execPreProc()
        execPreProc = True
    End Function

    '処理
    Public Function exec(ByRef objGrepResult)
        With objGrepResult
            If strPreviousPath <> .strFilePath Then
                If strPreviousPath <> "" Then
                    Call Editor.FileSave()
                    Call Editor.FileClose()
                End If
                Call Editor.FileOpen(.strFilePath)
            End If
            If deleteLine(.lngRow) Then
                lngSize = lngSize + 1
            End If
            strPreviousPath = .strFilePath
        End With
        exec = True
    End Function

    '後処理
    Public Sub execPostProc()
        Call Editor.FileSave()
        Call Editor.FileClose()
    End Sub

    'マクロ名
    Public Function getTitle()
        getTitle = "GrepAndDelete"
    End Function

    '処理開始時メッセージ
    Public Function getNoticeMessage()
        getNoticeMessage = _
            "Grep結果に挙がっている全てのファイル&行に対し削除を行います。" & vbCrLf & _
            "Grep結果が自動的に閉じられますので、Grep結果を残したい場合は事前に保存をしてください。" & vbCrLf & _
            "また実行時にはGrep結果以外のファイルは全て閉じてください。"
    End Function

    '処理終了時メッセージ
    Public Function getEndMessage()
        getEndMessage = lngSize & "行削除しました。"
    End Function

    'エラー時メッセージ
    Public Function getErrMessage()
        getErrMessage = strErrMessage
    End Function

    Public Sub setErrMessage(ByRef strArg)
        strErrMessage = strArg
    End Sub

    '指定行を削除
    Private Function deleteLine(ByRef lngRow)
        deleteLine = False
        With Editor
            Call .Jump(lngRow, 1)
            Call .GoLineTop(1)
            Call .LineDeleteToEnd()
            Call .DeleteLine()
        End With
        deleteLine = True
    End Function

End Class

'ここから下は流用可能なロジック
'---------------------------------------------------------------------
Call manageMain()

'メイン処理の実行
Sub manageMain()
    'Strategyのインスタンス生成
    Dim objStrategy: Set objStrategy = New Strategy
    '初期化処理
    If objStrategy.initialize() = False Then
        '初期化処理に失敗した場合は処理終了
        MsgBox objStrategy.getErrMessage()
        Set objStrategy = Nothing
        Exit Sub
    End If

    '処理開始可否確認
    Dim objDialog: Set objDialog = CreateObject("WScript.Shell")
    Dim lngRtn: lngRtn = objDialog.Popup(objStrategy.getNoticeMessage(), 0, objStrategy.getTitle(), 1)
    If lngRtn = 1 Then
        If execMain(objStrategy) Then
            '正常に処理した場合
            MsgBox objStrategy.getEndMessage()
        Else
            '処理中断した場合
            MsgBox objStrategy.getErrMessage()
        End If
    Else
        MsgBox "処理を中止しました。"
    End If
    Set objDialog = Nothing
    Set objStrategy = Nothing
End Sub

'メイン処理
Function execMain(Byref objStrategy)
    execMain = False
    'Grepされたフォルダのパスを取得
    Dim strFolderPath: strFolderPath = getFolderPath()
    If strFolderPath = "" Then
        Call objStrategy.setErrMessage("Grep結果ファイルが想定外のフォーマットです。")
        Exit Function
    End If
    'Grep結果の取得
    Dim objGrepResults: objGrepResults = getGrepResults(strFolderPath)
    'Grep結果のファイルを閉じる
    Editor.FileClose()
    '前処理
    If objStrategy.execPreProc() Then
        '前処理が正常な場合のみ実行
        Dim blnErr: blnErr = False
        Dim lngIu: lngIu = UBound(objGrepResults)
        Dim lngI
        For lngI = 0 To lngIu
            '本処理
            If objStrategy.exec(objGrepResults(lngI)) = False Then
                '処理が正常終了しなかった場合は終わり
                blnErr = True
                Exit For
            End If
        Next
        If blnErr = False Then
            execMain = True
        End If
    End If
    '後処理
    Call objStrategy.execPostProc()
End Function

'対象ファイルのパスと行を全て取得する。
Function getGrepResults(Byref strFolderPath)
    Dim lngRu: lngRu = Editor.GetLineCount(0)
    Dim lngR
    Dim lngI: lngI = -1
    Dim objGrepResults(): Redim objGrepResults(lngRu)
    Dim strLine
    For lngR = lngRu To 1 Step -1
        strLine = getLine(lngR)
        If beginWith(strLine, strFolderPath) Then
            lngI = lngI + 1
            Set objGrepResults(lngI) = getGrepResult(strLine)
        End If
    Next
    Redim Preserve objGrepResults(lngI)
    getGrepResults = objGrepResults
End Function

'対象ファイルのパスと行を取得する。
Function getGrepResult(ByRef strArgLine)
    Dim objGrepResult: Set objGrepResult = New GrepResult
    With objGrepResult
        .strLine = strArgLine
        .strFilePath = getFilePath(strArgLine)
        .lngRow = getRow(strArgLine)
        .lngCol = getCol(strArgLine)
    End With
    Set getGrepResult = objGrepResult
    Set objGrepResult = Nothing
End Function

'Grep結果から行を取得する。
Function getLine(ByRef lngR)
    getLine = getString(Editor.GetLineStr(lngR))
End Function

'末尾の改行を削除した文字列を取得する。
Function getString(ByRef strLine)
    If endWith(strLine, vbCrLf) Then
        getString = Left(strLine, Len(strLine) - Len(vbCrLf))
    ElseIf endWith(strLine, vbCr) Then
        getString = Left(strLine, Len(strLine) - Len(vbCr))
    ElseIf endWith(strLine, vbLf) Then
        getString = Left(strLine, Len(strLine) - Len(vbLf))
    Else
        getString = strLine
    End If
End Function

'Grep結果から対象フォルダを取得する。
Function getFolderPath()
    Const STR_FOLDER_PREFIX = "フォルダ   "
    getFolderPath = ""
    Dim lngRu: lngRu = Editor.GetLineCount(0)
    Dim lngR
    Dim strLine
    For lngR = 1 To lngRu
        strLine = getLine(lngR)
        If beginWith(strLine, STR_FOLDER_PREFIX) Then
            getFolderPath = Mid(strLine, Len(STR_FOLDER_PREFIX) + 1)
            Exit Function
        End If
    Next
End Function

'strArg0がstrArg1で始まる文字列か。
Function beginWith(ByRef strArg0, ByRef strArg1)
    beginWith = False
    If Len(strArg0) < Len(strArg1) Then
        Exit Function
    End If
    If InStr(1, strArg0, strArg1) = 1 Then
        beginWith = True
    End If
End Function

'strArg0がstrArg1で終わる文字列か。
Function endWith(ByRef strArg0, ByRef strArg1)
    endWith = False
    If Len(strArg0) < Len(strArg1) Then
        Exit Function
    End If
  
    If Right(strArg0, Len(strArg1)) = strArg1 Then
        endWith = True
    End If
End Function


'Grep結果からファイルのパスを取得する。
Function getFilePath(ByRef strArgLine)
    getFilePath = ""
    Dim lngPlPos: lngPlPos = getPlPos(strArgLine)
    Dim strPath: strPath = Left(strArgLine, lngPlPos - 1)
    getFilePath = strPath
End Function

'Grep結果から行を取得する。
Function getRow(ByRef strArgLine)
    getRow = 0
    Dim lngPlPos: lngPlPos = getPlPos(strArgLine)
    Dim lngCmmPos: lngCmmPos = getCmmPos(strArgLine)
    Dim strRow: strRow = Mid(strArgLine, lngPlPos + 1, lngCmmPos - lngPlPos - 1)
    getRow = CLng(Trim(strRow))
End Function

'Grep結果から列を取得する。
Function getCol(ByRef strArgLine)
    getCol = 0
    Dim lngCmmPos: lngCmmPos = getCmmPos(strArgLine)
    Dim lngPrPos: lngPrPos = getPrPos(strArgLine)
    Dim strCol: strCol = Mid(strArgLine, lngCmmPos + 1, lngPrPos - lngCmmPos - 1)
    getCol = CLng(Trim(strCol))
End Function

'"("の位置を取得
Function getPlPos(ByRef strArgLine)
    getPlPos = -1
    Dim lngCrnPos: lngCrnPos = InStr(3, strArgLine, ":")
    Dim lngP
    Dim lngPlPos
    For lngP = lngCrnPos To 3 Step -1
        If Mid(strArgLine, lngP, 1) = "(" Then
            lngPlPos = lngP
            Exit For
        End If
    Next
    getPlPos = lngPlPos
End Function

'","の位置を取得
Function getCmmPos(ByRef strArgLine)
    getCmmPos = -1
    Dim lngPlPos: lngPlPos = getPlPos(strArgLine)
    Dim lngCmmPos: lngCmmPos = InStr(lngPlPos, strArgLine, ",")
    getCmmPos = lngCmmPos
End Function

'")"の位置を取得
Function getPrPos(ByRef strArgLine)
    getPrPos = -1
    Dim lngCmmPos: lngCmmPos = getCmmPos(strArgLine)
    Dim lngPrPos: lngPrPos = InStr(lngCmmPos, strArgLine, ")")
    getPrPos = lngPrPos
End Function

Class GrepResult
    Public strLine
    Public strFilePath
    Public lngRow
    Public lngCol
End Class

サクラエディタ用Grep&置換マクロ(full)

サクラエディタのGrep結果を基に置換を行うVBSのマクロです。
以前公開していたものはそれ単品では動作しない形式だったので、
必要な関数なども併せてフルバージョンとして置いておきます。
個人的に作成・利用をしているマクロで十分なデバッグが行われているとは言えません。
また処理対象を直接編集・保存している為、処理後に元に戻すことができません。
こういった点を理解した上、自己責任での利用をお願いします。

GrepAndReplace.vbs
'Grep結果を基に置換を行うマクロ
'
'【詳細】
'サクラエディタのGrep結果を基に文字列の置換を行います。
'置換の対象はGrep結果に載っているファイル&行だけです。
'置換文字列の指定はGrep結果の1行目に下記のフォーマットで記入してください。
'置換前後の文字列に「タブ」や「改行」は指定できません。
'【フォーマット】
'置換前文字列[tab]置換後文字列
'【例】"あいうえお"を"かきくけこ"に置換する場合
'あいうえお    かきくけこ
'---------------------------------------------------------------------
'サクラエディタの検索パラメータ
Const OPT_WORD = 1    '単語単位で探す(W)
Const OPT_DIFF = 2    '英大文字と小文字を区別する(C)
Const OPT_RE = 4    '正規表現(E)
Const OPT_MSG = 8    '見つからないときにメッセージを表示(M)
Const OPT_DCLS = 16    '検索ダイアログを自動的に閉じる(L)
Const OPT_LOOP = 32    '先頭(末尾)から再検索する(Z)
Const OPT_CB = 64    'クリップボードから貼り付ける(T)
Const OPT_TGT = 128    '0:ファイル全体(O),1:選択範囲(S)

'置換処理を行うStrategy
'Publicで宣言されている部分の処理を差し替えることで、
'Grep結果に対する様々な処理を実装することができます。
Class Strategy

    Private lngSize
    Private strBefore
    Private strAfter
    Private lngOption
    Private strPreviousPath
    Private strErrMessage

    'コンストラクタ
    Private Sub Class_Initialize()
    End Sub

    'デストラクタ
    Private Sub Class_Terminate()
    End Sub

    '初期化処理
    Public Function initialize()
        initialize = False
       
        '1:選択範囲(S) + '検索ダイアログを自動的に閉じる(L) + '英大文字と小文字を区別する(C)
        lngOption = OPT_TGT + OPT_DCLS + OPT_DIFF
        lngSize = 0
        strPreviousPath = ""
        strErrMessage = ""

        Dim strReplaceStringArray
        strReplaceStringArray = Split(getString(Editor.GetLineStr(1)), vbTab)
        If UBound(strReplaceStringArray) <> 1 Then
            '置換前後の文字列が正しく設定されていない場合は処理しない
            strErrMessage = "置換前後の文字列が正しく設定されていません。" & vbCrLf & "処理を終了します。"
        Else
            strBefore = strReplaceStringArray(0)
            strAfter = strReplaceStringArray(1)
            initialize = True
        End If
    End Function

    '前処理
    Public Function execPreProc()
        execPreProc = True
    End Function

    '処理
    Public Function exec(ByRef objGrepResult)
        With objGrepResult
            If strPreviousPath <> .strFilePath Then
                If strPreviousPath <> "" Then
                    Call Editor.FileSave()
                    Call Editor.FileClose()
                End If
                Call Editor.FileOpen(.strFilePath)
            End If
            If replaceLine(.lngRow, strBefore, strAfter) Then
                lngSize = lngSize + 1
            End If
            strPreviousPath = .strFilePath
        End With
        exec = True
    End Function

    '後処理
    Public Sub execPostProc()
        Call Editor.FileSave()
        Call Editor.FileClose()
    End Sub

    'マクロ名
    Public Function getTitle()
        getTitle = "GrepAndReplace"
    End Function

    '処理開始時メッセージ
    Public Function getNoticeMessage()
        getNoticeMessage = _
            "Grep結果に挙がっている全てのファイル&行に対し置換を行います。" & vbCrLf & _ 
            "Grep結果が自動的に閉じられますので、Grep結果を残したい場合は事前に保存をしてください。" & vbCrLf & _
            "また実行時にはGrep結果以外のファイルは全て閉じてください。" & vbCrLf & _
            vbCrLf & _
            "置換前文字列:""" & strBefore & """" & vbCrLf & _
            "置換後文字列:""" & strAfter & """" 
    End Function

    '処理終了時メッセージ
    Public Function getEndMessage()
        getEndMessage = lngSize & "件置換しました。"
    End Function

    'エラー時メッセージ
    Public Function getErrMessage()
        getErrMessage = strErrMessage
    End Function

    Public Sub setErrMessage(ByRef strArg)
        strErrMessage = strArg
    End Sub

    '指定行のみを置換
    Private Function replaceLine(ByRef lngRow, ByRef strBfr, ByRef strAft)
        replaceLine = False
        Dim lngR
        Dim strOrgLine
        With Editor
            Call .Jump(lngRow, 1)
            strOrgLine = .GetLineStr(lngRow)
            Call .GoLineTop(1)
            Call .BeginSelect()
            Call .Jump(lngRow + 1, 1)
            If .GetLineCount(0) <> lngRow Then
                Call .GoLineTop(1)
                Call .Left_Sel()
            End If
            Call .ReplaceAll(strBfr, strAft, lngOption)
            If strOrgLine = .GetLineStr(lngRow) Then
                replaceLine = False
            Else
                replaceLine = True
            End If
        End With
    End Function

End Class

'ここから下は流用可能なロジック
'---------------------------------------------------------------------
Call manageMain()

'メイン処理の実行
Sub manageMain()
    'Strategyのインスタンス生成
    Dim objStrategy: Set objStrategy = New Strategy
    '初期化処理
    If objStrategy.initialize() = False Then
        '初期化処理に失敗した場合は処理終了
        MsgBox objStrategy.getErrMessage()
        Set objStrategy = Nothing
        Exit Sub
    End If

    '処理開始可否確認
    Dim objDialog: Set objDialog = CreateObject("WScript.Shell")
    Dim lngRtn: lngRtn = objDialog.Popup(objStrategy.getNoticeMessage(), 0, objStrategy.getTitle(), 1)
    If lngRtn = 1 Then
        If execMain(objStrategy) Then
            '正常に処理した場合
            MsgBox objStrategy.getEndMessage()
        Else
            '処理中断した場合
            MsgBox objStrategy.getErrMessage()
        End If
    Else
        MsgBox "処理を中止しました。"
    End If
    Set objDialog = Nothing
    Set objStrategy = Nothing
End Sub

'メイン処理
Function execMain(Byref objStrategy)
    execMain = False
    'Grepされたフォルダのパスを取得
    Dim strFolderPath: strFolderPath = getFolderPath()
    If strFolderPath = "" Then
        Call objStrategy.setErrMessage("Grep結果ファイルが想定外のフォーマットです。")
        Exit Function
    End If
    'Grep結果の取得
    Dim objGrepResults: objGrepResults = getGrepResults(strFolderPath)
    'Grep結果のファイルを閉じる
    Editor.FileClose()
    '前処理
    If objStrategy.execPreProc() Then
        '前処理が正常な場合のみ実行
        Dim blnErr: blnErr = False
        Dim lngIu: lngIu = UBound(objGrepResults)
        Dim lngI
        For lngI = 0 To lngIu
            '本処理
            If objStrategy.exec(objGrepResults(lngI)) = False Then
                '処理が正常終了しなかった場合は終わり
                blnErr = True
                Exit For
            End If
        Next
        If blnErr = False Then
            execMain = True
        End If
    End If
    '後処理
    Call objStrategy.execPostProc()
End Function

'対象ファイルのパスと行を全て取得する。
Function getGrepResults(Byref strFolderPath)
    Dim lngRu: lngRu = Editor.GetLineCount(0)
    Dim lngR
    Dim lngI: lngI = -1
    Dim objGrepResults(): Redim objGrepResults(lngRu)
    Dim strLine
    For lngR = lngRu To 1 Step -1
        strLine = getLine(lngR)
        If beginWith(strLine, strFolderPath) Then
            lngI = lngI + 1
            Set objGrepResults(lngI) = getGrepResult(strLine)
        End If
    Next
    Redim Preserve objGrepResults(lngI)
    getGrepResults = objGrepResults
End Function

'対象ファイルのパスと行を取得する。
Function getGrepResult(ByRef strArgLine)
    Dim objGrepResult: Set objGrepResult = New GrepResult
    With objGrepResult
        .strLine = strArgLine
        .strFilePath = getFilePath(strArgLine)
        .lngRow = getRow(strArgLine)
        .lngCol = getCol(strArgLine)
    End With
    Set getGrepResult = objGrepResult
    Set objGrepResult = Nothing
End Function

'Grep結果から行を取得する。
Function getLine(ByRef lngR)
    getLine = getString(Editor.GetLineStr(lngR))
End Function

'末尾の改行を削除した文字列を取得する。
Function getString(ByRef strLine)
    If endWith(strLine, vbCrLf) Then
        getString = Left(strLine, Len(strLine) - Len(vbCrLf))
    ElseIf endWith(strLine, vbCr) Then
        getString = Left(strLine, Len(strLine) - Len(vbCr))
    ElseIf endWith(strLine, vbLf) Then
        getString = Left(strLine, Len(strLine) - Len(vbLf))
    Else
        getString = strLine
    End If
End Function

'Grep結果から対象フォルダを取得する。
Function getFolderPath()
    Const STR_FOLDER_PREFIX = "フォルダ   "
    getFolderPath = ""
    Dim lngRu: lngRu = Editor.GetLineCount(0)
    Dim lngR
    Dim strLine
    For lngR = 1 To lngRu
        strLine = getLine(lngR)
        If beginWith(strLine, STR_FOLDER_PREFIX) Then
            getFolderPath = Mid(strLine, Len(STR_FOLDER_PREFIX) + 1)
            Exit Function
        End If
    Next
End Function

'strArg0がstrArg1で始まる文字列か。
Function beginWith(ByRef strArg0, ByRef strArg1)
    beginWith = False
    If Len(strArg0) < Len(strArg1) Then
        Exit Function
    End If
    If InStr(1, strArg0, strArg1) = 1 Then
        beginWith = True
    End If
End Function

'strArg0がstrArg1で終わる文字列か。
Function endWith(ByRef strArg0, ByRef strArg1)
    endWith = False
    If Len(strArg0) < Len(strArg1) Then
        Exit Function
    End If
   
    If Right(strArg0, Len(strArg1)) = strArg1 Then
        endWith = True
    End If
End Function


'Grep結果からファイルのパスを取得する。
Function getFilePath(ByRef strArgLine)
    getFilePath = ""
    Dim lngPlPos: lngPlPos = getPlPos(strArgLine)
    Dim strPath: strPath = Left(strArgLine, lngPlPos - 1)
    getFilePath = strPath
End Function

'Grep結果から行を取得する。
Function getRow(ByRef strArgLine)
    getRow = 0
    Dim lngPlPos: lngPlPos = getPlPos(strArgLine)
    Dim lngCmmPos: lngCmmPos = getCmmPos(strArgLine)
    Dim strRow: strRow = Mid(strArgLine, lngPlPos + 1, lngCmmPos - lngPlPos - 1)
    getRow = CLng(Trim(strRow))
End Function

'Grep結果から列を取得する。
Function getCol(ByRef strArgLine)
    getCol = 0
    Dim lngCmmPos: lngCmmPos = getCmmPos(strArgLine)
    Dim lngPrPos: lngPrPos = getPrPos(strArgLine)
    Dim strCol: strCol = Mid(strArgLine, lngCmmPos + 1, lngPrPos - lngCmmPos - 1)
    getCol = CLng(Trim(strCol))
End Function

'"("の位置を取得
Function getPlPos(ByRef strArgLine)
    getPlPos = -1
    Dim lngCrnPos: lngCrnPos = InStr(3, strArgLine, ":")
    Dim lngP
    Dim lngPlPos
    For lngP = lngCrnPos To 3 Step -1
        If Mid(strArgLine, lngP, 1) = "(" Then
            lngPlPos = lngP
            Exit For
        End If
    Next
    getPlPos = lngPlPos
End Function

'","の位置を取得
Function getCmmPos(ByRef strArgLine)
    getCmmPos = -1
    Dim lngPlPos: lngPlPos = getPlPos(strArgLine)
    Dim lngCmmPos: lngCmmPos = InStr(lngPlPos, strArgLine, ",")
    getCmmPos = lngCmmPos
End Function

'")"の位置を取得
Function getPrPos(ByRef strArgLine)
    getPrPos = -1
    Dim lngCmmPos: lngCmmPos = getCmmPos(strArgLine)
    Dim lngPrPos: lngPrPos = InStr(lngCmmPos, strArgLine, ")")
    getPrPos = lngPrPos
End Function

Class GrepResult
    Public strLine
    Public strFilePath
    Public lngRow
    Public lngCol
End Class