ラベル Java の投稿を表示しています。 すべての投稿を表示
ラベル Java の投稿を表示しています。 すべての投稿を表示

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年10月14日月曜日

Apache POI XSSF Eventmodelお試し

Apache POI のイベントモデル(XSSF)でセルの値を読み込み、任意のBeanのフィールドへ設定するjavaのコードです。
メモリ使用量が半端じゃないPOIですが、イベントモデルを使用するとストリーミングで読み込めるので、メモリ使用量を抑えることができます。
その場合、xmlファイルをテキストとして読み込んで処理をすることになるので、ユーザーモデルに比べると分かりにくく日本語のドキュメントもあまり無かったので、勉強がてら実際に動くものを作ってみました。
取りあえず動くことを目標にしていたのでExceptionは全てthrowsで処理してしまっていたり、いろいろと手を抜いているところがあります。
イベントモデルではxmlファイルからの値は文字列型で取得することになるので、このサンプルでは取得した値の型を設定先のbeanのフィールドの型に変換します。ですのでbeanのフィールドがDateなのにセルの値が"abc"などの文字列だと値を取得できません。また1904年始まりのExcelファイルには対応押していません。
使用ライブラリはApachePOI3.9とApacheXerces2.11.0(+それらが使っているライブラリ群)です。
十分なデバッグはしていないので自己責任でご利用ください。

SheetToBeanUtils.java
package poi.example.eventmodel.mapping;

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

public class SheetToBeanUtils {

    private static Log log = LogFactory.getLog(SheetToBeanUtils.class);

    // ワークブックへのパスとbeanのインスタンスを渡すと、指定のシートの値をbeanへ設定して返します。
    // ワークブックをイベントモデルで読み込むのでメモリの消費量が少なく済みます。
    public static <B> B mapSheetToBean(String workbookPath, B bean)
            throws Exception {
        // excelから値を取得
        SheetToBeanMapper mapper = new SheetToBeanMapper(workbookPath, bean);
        mapper.execute();
        return bean;
    }
}



SheetToBeanMapper.java
package poi.example.eventmodel.mapping;

import java.io.InputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.xerces.parsers.SAXParser;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import poi.example.eventmodel.SheetHandler;
import poi.example.eventmodel.WorkbookHandler;
import poi.example.eventmodel.mapping.annotation.Sheet;

public class SheetToBeanMapper {

    private static Log log = LogFactory.getLog(SheetToBeanMapper.class);

    // 値を設定する対象のbean
    private Object bean = null;
    // ワークブックのパス
    private String filePath = null;
    // 値を取得するシート
    private String sheetName = null;

    public SheetToBeanMapper(String filePath, Object bean) {
        this.filePath = filePath;
        this.bean = bean;
        Sheet sheet = bean.getClass().getAnnotation(Sheet.class);
        if(sheet == null){
            throw new IllegalArgumentException("クラスに@Sheetアノテーションが設定されていません。");
        }
        sheetName = sheet.name();
    }

    // マッピング処理開始
    public void execute() throws Exception {

        OPCPackage pkg = OPCPackage.open(filePath);
        XSSFReader reader = new XSSFReader(pkg);

        // ワークブックの情報から対象シートのrIdを取得する
        XMLReader wbParser = new SAXParser();
        WorkbookHandler wbHandler = new WorkbookHandler(sheetName);
        wbParser.setContentHandler(wbHandler);
        // ワークブックの情報を取得
        InputSource wbSource = new InputSource(reader.getWorkbookData());
        wbParser.parse(wbSource);
        // 指定のシート名に対応するrIdを取得
        String rId= wbHandler.getrId();
        if(rId == null){
            throw new Exception("対象のシートがありません。シート名=" + sheetName);
        }

        // ワークシートから値を取得する
        SharedStringsTable sst = reader.getSharedStringsTable();
        // パーサを取得
        XMLReader parser = new SAXParser();
        SheetHandler handler = new SheetHandler(sst, new FieldValueArranger(bean));
        parser.setContentHandler(handler);
        // シートを指定して取得
        InputStream targetSheet = reader.getSheet(rId);
        InputSource sheetSource = new InputSource(targetSheet);
        // シートの内容をパースしながらbeanへマッピング
        // この中でSheetHandlerが処理を行ってます。
        parser.parse(sheetSource);
        targetSheet.close();
    }

}


WorkbookHandler.java
package poi.example.eventmodel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

// ワークブックの情報を取得するハンドラ
// シート名から特定のシートを取得する際に必要
public class WorkbookHandler extends DefaultHandler {

    private static Log log = LogFactory.getLog(WorkbookHandler.class);

    // 行のタグ
    private static final String TAG_SHT = "sheet";
    // シート名の属性名
    private static final String NM_SNM = "name";
    // r:idの属性名
    private static final String NM_RID = "r:id";
    // 対象のシート
    private String sheetName = null;
    // 対象のシートのr:id
    private String rId = null;

    public String getrId() {
        return rId;
    }

    public WorkbookHandler(String sheetName) {
        this.sheetName = sheetName;
    }

    // 開始タグを取得した際に動作するメソッド
    public void startElement(String uri, String localName, String name,
            Attributes attributes) throws SAXException {
        // シート開始タグの場合
        if (name.equals(TAG_SHT)) {
            String sheetNameTemp = attributes.getValue(NM_SNM);
            String rIdTemp = attributes.getValue(NM_RID);
            if(sheetName.equals(sheetNameTemp)){
                rId = rIdTemp;
            }
        }
    }

}


SheetHandler.java
package poi.example.eventmodel;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import poi.example.eventmodel.mapping.FieldValueArranger;
import poi.example.util.ExcelUtils;

// シートを処理するhandler
// ここでシートの値を取得し、FieldValueArrangerに渡します。
public class SheetHandler extends DefaultHandler {

    private static Log log = LogFactory.getLog(SheetHandler.class);

    // 行のタグ
    private static final String TAG_ROW = "row";
    // 列のタグ
    private static final String TAG_COL = "c";
    // 値のタグ
    private static final String TAG_VAL = "v";
    // 行番号の属性名
    private static final String NM_RNO = "r";
    // レンジの属性名
    private static final String NM_RNG = "r";
    // タイプの属性名
    private static final String NM_TYP = "t";
    // 文字列タイプ
    private static final String TYP_STR = "s";

    // 共有している文字列テーブル
    private final SharedStringsTable sst;
    // 取得した値を扱うクラス
    private FieldValueArranger arranger = null;

    // 直前のコンテンツの値
    private String lastContents;

    // 次の値が文字列かどうか
    private boolean nextIsString;
    // 現在の行
    private int currentRow = 0;
    // 現在の列
    private int currentCol = 0;

    public SheetHandler(SharedStringsTable sst, FieldValueArranger arranger) {
        this.sst = sst;
        this.arranger = arranger;
    }

    // 開始タグを取得した際に動作するメソッド
    public void startElement(String uri, String localName, String name,
            Attributes attributes) throws SAXException {

        // 行開始タグの場合
        if (name.equals(TAG_ROW)) {
            // 行タグの場合は行番号を取得し、列番号をクリア
            currentRow = Integer.parseInt(attributes.getValue(NM_RNO));
            currentCol = 0;
        }

        // 列開始タグの場合
        if (name.equals(TAG_COL)) {
            // レンジを取得
            String range = attributes.getValue(NM_RNG);
            String rangeCol = ExcelUtils.getFirstColString(range);
            // 現在の列番号を取得
            currentCol = ExcelUtils.toColIndex(rangeCol);
            // セルタイプを取得する。
            String cellType = attributes.getValue(NM_TYP);
            // セルタイプが"s"の場合は文字列であることをフラグに設定
            if (cellType != null && cellType.equals(TYP_STR)) {
                nextIsString = true;
            } else {
                nextIsString = false;
            }
        }
        // コンテンツをクリア
        lastContents = "";
    }

    // 終了タグを取得した際に動作するメソッド
    public void endElement(String uri, String localName, String name)
            throws SAXException {
        // 文字列の場合は、今持っているlastContentsはSharedStringsTableのインデックスなので、文字列に置き換える。
        if (nextIsString) {
            // 直前の値を数値(参照先のインデックス)に変換
            int idx = Integer.parseInt(lastContents);
            // 文字列の場合はSharedStringsTableで別管理になっているので、そちらから取得
            lastContents = new XSSFRichTextString(sst.getEntryAt(idx))
                    .toString();
            // フラグをクリア
            nextIsString = false;
        }

        // 値終了タグの場合
        if (name.equals(TAG_VAL)) {
            // lastContentsにセルの値が入っているので、ここでbeanにマッピングする
            try {
                arranger.arrange(currentRow, currentCol, lastContents);
            } catch (Exception e) {
                log.warn("beanに値を設定できませんでした。row=" + currentRow + ",col="
                        + currentCol + ",value=" + lastContents, e);
            }
        }
    }

    // 要素の値を保持するためのメソッド
    public void characters(char[] ch, int start, int length)
            throws SAXException {
        // 現在の値を取得する
        lastContents += new String(ch, start, length);
    }

}


FieldValueArranger.java
package poi.example.eventmodel.mapping;

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

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

import poi.example.eventmodel.mapping.annotation.ImportField;
import poi.example.eventmodel.mapping.annotation.ImportListField;
import poi.example.util.ExcelUtils;
import poi.example.util.ReflectionUtils;

// row,col,valueを受け取り、beanへマッピングするクラス。
public class FieldValueArranger {

    private static Log log = LogFactory.getLog(FieldValueArranger.class);

    // 値を設定する対象のbean
    private Object bean = null;
    // 設定先のフィールドの情報
    private List<ImportFieldInfo> fieldInfoList = new ArrayList<ImportFieldInfo>();

    public FieldValueArranger(Object bean) {
        this.bean = bean;
        init(bean);
    }

    // マッピング対象のフィールド情報を収集します。
    private void init(Object bean) {
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (!initImportField(field)) {
                initImportBeanList(field);
            }
        }
    }

    // マッピング対象の単一フィールド情報を収集します。
    private boolean initImportField(Field field) {
        ImportField ifAn = field.getAnnotation(ImportField.class);
        if (ifAn != null) {
            fieldInfoList.add(new ImportFieldInfo(field, ifAn.row(), ExcelUtils
                    .toColIndex(ifAn.col())));
            return true;
        }
        return false;
    }

    // マッピング対象のリストフィールド情報を収集します。
    private void initImportBeanList(Field field) {
        ImportListField ilfAn = field.getAnnotation(ImportListField.class);
        if (ilfAn != null) {
            Class<?> childClazz = ReflectionUtils.getFirstChildClazz(field);
            Field[] childFields = childClazz.getDeclaredFields();
            for (Field childField : childFields) {
                ImportField ifAn = childField.getAnnotation(ImportField.class);
                if (ifAn != null) {
                    int childCol = ExcelUtils.toColIndex(ifAn.col());
                    // 子供のフィールドを追加
                    fieldInfoList.add(new ImportFieldInfo(field, childField,
                            ilfAn.startRow(), ilfAn.endRow(), childCol));
                }
            }
        }
    }

    // row,colを元にフィールドを特定し値を設定します
    public boolean arrange(int row, int col, String value) throws Exception {

        ImportFieldInfo info = getImportField(row, col);

        if (info != null) {
            if (info.isChildField()) {
                // リスト形式の項目へ設定
                info.parentField.setAccessible(true);
                @SuppressWarnings("unchecked")
                List<Object> children = (List<Object>) info.parentField
                        .get(bean);
                info.parentField.setAccessible(false);

                int rowIndex = info.getListIndex(row);

                Object childBean = null;
                if (children.size() - 1 < rowIndex) {
                    // 子クラスを取得する
                    Class<?> childClazz = info.childClazz;
                    // 子クラスのインスタンスを作る
                    childBean = childClazz.newInstance();
                    while (children.size() < rowIndex) {
                        children.add(null);
                    }
                    children.add(childBean);
                } else {
                    childBean = children.get(rowIndex);
                }
                // 子クラスのフィールドへ設定
                return setFieldValue(childBean, info.field, value);
            } else {
                // 単一項目へ設定
                return setFieldValue(bean, info.field, value);
            }
        }
        return false;
    }

    // row,colに対応するフィールド情報を取得する。
    private ImportFieldInfo getImportField(int row, int col) {
        return getImportField(null, row, col);
    }

    // row,colに対応するフィールド情報を取得する。
    private ImportFieldInfo getImportField(Field pareintField, int row, int col) {
        for (ImportFieldInfo range : fieldInfoList) {
            if (range.isIncleded(pareintField, row, col)) {
                return range;
            }
        }
        return null;
    }

    // fieldのclassに合わせた形式に変換し値を設定
    private boolean setFieldValue(Object bean, Field field, String value)
            throws Exception {
        field.setAccessible(true);
        if (field.getType().equals(String.class)) {
            // 文字列の場合
            field.set(bean, value);
        } else if (field.getType().equals(Integer.class)) {
            // 数値の場合
            field.set(bean, ExcelUtils.getIntegerValue(value));
        } else if (field.getType().equals(Long.class)) {
            // 数値の場合
            field.set(bean, ExcelUtils.getLongValue(value));
        } else if (field.getType().equals(Short.class)) {
            // 数値の場合
            field.set(bean, ExcelUtils.getShortValue(value));
        } else if (field.getType().equals(Double.class)) {
            // 数値の場合
            field.set(bean, ExcelUtils.getDoubleValue(value));
        } else if (field.getType().equals(Date.class)) {
            // 日付の場合
            field.set(bean, ExcelUtils.getDateValue(value));
        } else if (field.getType().equals(Boolean.class)) {
            // 真偽値の場合
            field.set(bean, ExcelUtils.getBooleanValue(value));
        } else {
            // その他
            field.setAccessible(false);
            return false;
        }
        field.setAccessible(false);
        return true;
    }

}


ImportFieldInfo.java
package poi.example.eventmodel.mapping;

import java.lang.reflect.Field;

import poi.example.util.ReflectionUtils;

// 値の設定先のフィールドの情報を管理するクラス
class ImportFieldInfo {

    // Listの子クラス
    Class<?> childClazz = null;
    // 親のフィールド
    Field parentField = null;
    // 対象のフィールド
    Field field = null;
    // 値を取得する対象となる範囲
    int startRow = 0;
    int endRow = 0;
    int col = 0;

    // beanのfield用のコンストラクタ
    ImportFieldInfo(Field field, int row, int col) {
        this(null, field, row, row, col);
    }

    // Listに保持されたbeanのfield用のコンストラクタ
    ImportFieldInfo(Field parentField, Field field, int startRow, int endRow,
            int col) {
        this.field = field;
        this.startRow = startRow;
        this.endRow = endRow;
        this.col = col;
        this.parentField = parentField;
        if (parentField != null) {
            this.childClazz = ReflectionUtils.getFirstChildClazz(parentField);
        }
    }

    // 指定のrow,colはfieldが値を取得する対象か
    boolean isIncleded(int row, int col) {
        return isIncleded(null, row, col);
    }

    // 指定のrow,colはfieldが値を取得する対象か(Listに保持されたbeanのfield用)
    boolean isIncleded(Field parentField, int row, int col) {
        if (parentField == null || parentField == this.parentField) {
            if (startRow <= row && row <= endRow && this.col == col) {
                return true;
            }
        }
        return false;
    }

    // rowに対応するListのindexを取得
    int getListIndex(int row) {
        return row - startRow;
    }

    // List配下のbeanのfieldか
    boolean isChildField() {
        return parentField != null;
    }

    @Override
    public String toString() {
        return "ImportFieldInfo [childClazz=" + childClazz + ", parentField="
                + parentField + ", field=" + field + ", startRow=" + startRow
                + ", endRow=" + endRow + ", col=" + col + "]";
    }

}


ExcelUtils.java
package poi.example.util;

import java.util.Date;

import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.util.CellReference;

// Excel関連の便利なものいろいろ
public class ExcelUtils {

    // TRUE扱いの値
    public static final String STRING_TRUE = "1";

    private ExcelUtils() {
    }

    // 日付形式で値を取得
    public static Date getDateValue(String value) {
        if (value == null) {
            return null;
        }
        Double dblVal = null;
        try {
            dblVal = getDoubleValue(value);
        } catch (Exception e) {
            throw new IllegalStateException("日付に変換できません。value=" + value, e);
        }
        // 1900年始まりのみ対応
        return DateUtil.getJavaDate(dblVal.doubleValue());
    }

    // 数値形式で値を取得
    public static Integer getIntegerValue(String value) {
        if (value == null) {
            return null;
        }
        Double dblVal = getDoubleValue(value);
        return Integer.valueOf(dblVal.intValue());
    }

    // 数値形式で値を取得
    public static Long getLongValue(String value) {
        if (value == null) {
            return null;
        }
        Double dblVal = getDoubleValue(value);
        return Long.valueOf(dblVal.longValue());
    }

    // 数値形式で値を取得
    public static Short getShortValue(String value) {
        if (value == null) {
            return null;
        }
        Double dblVal = getDoubleValue(value);
        return Short.valueOf(dblVal.shortValue());
    }

    // 数値形式で値を取得
    public static Double getDoubleValue(String value) {
        if (value == null) {
            return null;
        }
        try {
            return Double.parseDouble(value);
        } catch (NumberFormatException e) {
            throw new IllegalStateException("数値に変換できません。value=" + value, e);
        }
    }

    // 真偽値形式で値を取得
    public static Boolean getBooleanValue(String value) {
        return Boolean.valueOf(STRING_TRUE.equals(value));
    }

    // A1:B1などの表記から先頭のカラム文字列を取り出す
    public static String getFirstColString(String range) {
        char[] cs = range.toCharArray();
        String col = "";
        for (char c : cs) {
            if ('0' <= c && c <= '9') {
                return col;
            } else {
                col += c;
            }
        }
        return col;
    }

    // A1表記からR1C1表記のカラムへ変換
    public static int toColIndex(String colString) {
        return CellReference.convertColStringToIndex(colString) + 1;
    }
}


ReflectionUtils.java
package poi.example.util;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

// リフレクション用のユーティリティクラス
public class ReflectionUtils {

    private ReflectionUtils() {
    }

    // Listの子要素のクラスを取得するメソッド
    public static Class<?> getFirstChildClazz(Field field) {
        // Genericも含めたTypeを取得
        Type type = field.getGenericType();
        // GenericのTypeを取得
        ParameterizedType pType = (ParameterizedType) type;
        return (Class<?>) pType.getActualTypeArguments()[0];
    }

}


Sheet.java
package poi.example.eventmodel.mapping.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 値の取得元になるシートを指定する為のアノテーション
@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Sheet {
    // シート名
    String name();

}


ImportField.java
package poi.example.eventmodel.mapping.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 単独の値のマッピングを行うためのアノテーション
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ImportField {
    // 行のデフォルト値
    int DFAULT_ROW = 0;
    // 行
    int row() default DFAULT_ROW;
    // 列
    String col();
}


ImportListField.java
package poi.example.eventmodel.mapping.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// 繰り返し項目に設定するアノテーション
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ImportListField {
    // 終了行のデフォルト値
    int DFAULT_END_ROW = Integer.MAX_VALUE;
    // 開始行
    int startRow();
    // 終了行
    int endRow() default DFAULT_END_ROW;
}


ParentBean.java
package poi.example.eventmodel.mapping.bean;

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

import poi.example.eventmodel.mapping.annotation.ImportField;
import poi.example.eventmodel.mapping.annotation.ImportListField;
import poi.example.eventmodel.mapping.annotation.Sheet;

@Sheet(name = "Sheet1")
public class ParentBean {
    // セルB1の値をStringで取得
    @ImportField(row = 1, col = "B")
    private String field1 = null;
    // セルB2の値をDateで取得
    @ImportField(row = 2, col = "B")
    private Date field2 = null;
    // セルH1の値をBooleanで取得
    @ImportField(row = 1, col = "H")
    private Boolean field3 = null;
    // 6行目以降をChildBeanのリストとして取得
    @ImportListField(startRow = 6)
    private List<ChildBean> listField = new ArrayList<ChildBean>();

    @Override
    public String toString() {
        return "ParentBean [field1=" + field1 + ", field2=" + field2
                + ", field3=" + field3 + ", listField=" + listField + "]";
    }

}


ChildBean.java
package poi.example.eventmodel.mapping.bean;

import java.util.Date;

import poi.example.eventmodel.mapping.annotation.ImportField;

public class ChildBean {
    // ParentBeanのlistFieldで指定された行以降の、列Aの値をLongで取得
    @ImportField(col = "A")
    private Long field1 = null;
    // ParentBeanのlistFieldで指定された行以降の、列Bの値をStringで取得
    @ImportField(col = "B")
    private String field2 = null;
    // ParentBeanのlistFieldで指定された行以降の、列Cの値をDateで取得
    @ImportField(col = "C")
    private Date field3 = null;

    @Override
    public String toString() {
        return "ChildBean [field1=" + field1 + ", field2=" + field2
                + ", field3=" + field3 + "]";
    }

}