POIUtils.java

  1. package com.gh.mygreen.xlsmapper.util;

  2. import java.awt.Point;
  3. import java.lang.reflect.Field;
  4. import java.lang.reflect.InvocationTargetException;
  5. import java.lang.reflect.Method;
  6. import java.util.ArrayList;
  7. import java.util.Collection;
  8. import java.util.Collections;
  9. import java.util.Date;
  10. import java.util.List;
  11. import java.util.TimeZone;
  12. import java.util.concurrent.atomic.AtomicBoolean;

  13. import org.apache.poi.common.usermodel.HyperlinkType;
  14. import org.apache.poi.hssf.model.InternalSheet;
  15. import org.apache.poi.hssf.model.InternalWorkbook;
  16. import org.apache.poi.hssf.record.DVRecord;
  17. import org.apache.poi.hssf.record.Record;
  18. import org.apache.poi.hssf.record.aggregates.DataValidityTable;
  19. import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
  20. import org.apache.poi.hssf.usermodel.HSSFSheet;
  21. import org.apache.poi.hssf.usermodel.HSSFWorkbook;
  22. import org.apache.poi.ss.SpreadsheetVersion;
  23. import org.apache.poi.ss.usermodel.BorderStyle;
  24. import org.apache.poi.ss.usermodel.Cell;
  25. import org.apache.poi.ss.usermodel.CellStyle;
  26. import org.apache.poi.ss.usermodel.CellType;
  27. import org.apache.poi.ss.usermodel.DataValidation;
  28. import org.apache.poi.ss.usermodel.DataValidationConstraint;
  29. import org.apache.poi.ss.usermodel.DataValidationHelper;
  30. import org.apache.poi.ss.usermodel.Hyperlink;
  31. import org.apache.poi.ss.usermodel.Name;
  32. import org.apache.poi.ss.usermodel.Row;
  33. import org.apache.poi.ss.usermodel.Sheet;
  34. import org.apache.poi.ss.usermodel.Workbook;
  35. import org.apache.poi.ss.util.AreaReference;
  36. import org.apache.poi.ss.util.CellRangeAddress;
  37. import org.apache.poi.ss.util.CellRangeAddressBase;
  38. import org.apache.poi.ss.util.CellRangeAddressList;
  39. import org.apache.poi.ss.util.CellReference;
  40. import org.apache.poi.xssf.usermodel.XSSFSheet;
  41. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  42. import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDataValidation;
  43. import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTDataValidations;
  44. import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet;
  45. import org.slf4j.Logger;
  46. import org.slf4j.LoggerFactory;

  47. import com.gh.mygreen.xlsmapper.CellFormatter;
  48. import com.gh.mygreen.xlsmapper.DefaultCellFormatter;
  49. import com.github.mygreen.cellformatter.lang.ExcelDateUtils;

  50. /**
  51.  * Apache POIとJExcel APIの差を埋めるユーティリティクラス。
  52.  *
  53.  * @version 2.0
  54.  * @author T.TSUCHIE
  55.  *
  56.  */
  57. public class POIUtils {

  58.     private static final Logger logger = LoggerFactory.getLogger(POIUtils.class);

  59.     /** 標準のセルフォーマッター */
  60.     private static CellFormatter defaultCellFormatter = new DefaultCellFormatter();

  61.     /**
  62.      * シートの種類を判定する。
  63.      *
  64.      * @since 2.0
  65.      * @param sheet 判定対象のオブジェクト
  66.      * @return シートの種類。不明な場合はnullを返す。
  67.      * @throws IllegalArgumentException {@literal sheet == null}
  68.      */
  69.     public static SpreadsheetVersion getVersion(final Sheet sheet) {
  70.         ArgUtils.notNull(sheet, "sheet");

  71.         if(sheet instanceof HSSFSheet) {
  72.             return SpreadsheetVersion.EXCEL97;

  73.         } else if(sheet instanceof XSSFSheet) {
  74.             return SpreadsheetVersion.EXCEL2007;
  75.         }

  76.         return null;
  77.     }

  78.     /**
  79.      * シートの最大列数を取得する。
  80.      * <p>{@literal jxl.Sheet.getColumns()}</p>
  81.      * @param sheet シートオブジェクト
  82.      * @return 最大列数
  83.      * @throws IllegalArgumentException {@literal sheet == null.}
  84.      */
  85.     public static int getColumns(final Sheet sheet) {
  86.         ArgUtils.notNull(sheet, "sheet");

  87.         int minRowIndex = sheet.getFirstRowNum();
  88.         int maxRowIndex = sheet.getLastRowNum();
  89.         int maxColumnsIndex = 0;
  90.         for(int i = minRowIndex; i <= maxRowIndex; i++) {
  91.             final Row row = sheet.getRow(i);
  92.             if(row == null) {
  93.                 continue;
  94.             }

  95.             final int column = row.getLastCellNum();
  96.             if(column > maxColumnsIndex) {
  97.                 maxColumnsIndex = column;
  98.             }
  99.         }

  100.         return maxColumnsIndex;
  101.     }

  102.     /**
  103.      * シートの最大行数を取得する
  104.      *
  105.      * <p>{@literal jxl.Sheet.getRows()}</p>
  106.      * @param sheet シートオブジェクト
  107.      * @return 最大行数
  108.      * @throws IllegalArgumentException {@literal sheet == null.}
  109.      */
  110.     public static int getRows(final Sheet sheet) {
  111.         ArgUtils.notNull(sheet, "sheet");
  112.         return sheet.getLastRowNum() + 1;
  113.     }

  114.     /**
  115.      * シートから任意アドレスのセルを取得する。
  116.      * @since 0.5
  117.      * @param sheet シートオブジェクト
  118.      * @param address アドレス(Point.x=column, Point.y=row)
  119.      * @return セル
  120.      * @throws IllegalArgumentException {@literal sheet == null or address == null.}
  121.      */
  122.     public static Cell getCell(final Sheet sheet, final Point address) {
  123.         ArgUtils.notNull(sheet, "sheet");
  124.         ArgUtils.notNull(address, "address");
  125.         return getCell(sheet, address.x, address.y);
  126.     }

  127.     /**
  128.      * シートから任意アドレスのセルを取得する。
  129.      * @since 1.4
  130.      * @param sheet シートオブジェクト
  131.      * @param address セルのアドレス
  132.      * @return セル
  133.      * @throws IllegalArgumentException {@literal sheet == null or address == null.}
  134.      */
  135.     public static Cell getCell(final Sheet sheet, final CellPosition address) {
  136.         ArgUtils.notNull(sheet, "sheet");
  137.         ArgUtils.notNull(address, "address");
  138.         return getCell(sheet, address.getColumn(), address.getRow());
  139.     }

  140.     /**
  141.      * シートから任意アドレスのセルを取得する。
  142.      *
  143.      * <p>{@literal jxl.Sheet.getCell(int column, int row)}</p>
  144.      * @param sheet シートオブジェクト
  145.      * @param column 列番号(0から始まる)
  146.      * @param row 行番号(0から始まる)
  147.      * @return セル
  148.      * @throws IllegalArgumentException {@literal sheet == null}
  149.      */
  150.     public static Cell getCell(final Sheet sheet, final int column, final int row) {
  151.         ArgUtils.notNull(sheet, "sheet");

  152.         Row rows = sheet.getRow(row);
  153.         if(rows == null) {
  154.             rows = sheet.createRow(row);
  155.         }

  156.         Cell cell = rows.getCell(column);
  157.         if(cell == null) {
  158.             cell = rows.createCell(column, CellType.BLANK);
  159.         }

  160.         return cell;
  161.     }

  162.     /**
  163.      * 任意の行のセルを全て取得する。
  164.      * <p> {@literal jxl.Seet.getRow(int row)}</p>
  165.      * @param sheet シートオブジェクト
  166.      * @param row 行番号(0から始まる)
  167.      * @return 行レコード(カラムの集合)。
  168.      *         ただし、シートの最大列数以下の場合、空のセルを補完する。
  169.      * @throws IllegalArgumentException {@literal sheet == null}
  170.      */
  171.     public static Cell[] getRow(final Sheet sheet, final int row) {
  172.         ArgUtils.notNull(sheet, "sheet");

  173.         Row rows = sheet.getRow(row);
  174.         if(rows == null) {
  175.             rows = sheet.createRow(row);
  176.         }
  177.         int maxColumn = getColumns(sheet);
  178.         Cell[] cells = new Cell[maxColumn];
  179.         for(int i=0; i < maxColumn; i++) {
  180.             Cell cell = rows.getCell(i);
  181.             if(cell == null) {
  182.                 cell = rows.createCell(i, CellType.BLANK);
  183.             }
  184.             cells[i] = cell;
  185.         }

  186.         return cells;
  187.     }

  188.     /**
  189.      * 任意の列のセルを全て取得する。
  190.      * <p> {@literal jxl.Seet.getColumn(int col)}</p>
  191.      * @param sheet
  192.      * @param col 列番号(0から始まる)
  193.      * @return 列レコード(行の集合)。
  194.      *         ただし、シートの最大行数以下の場合、空のセルを補完する。
  195.      * @throws IllegalArgumentException {@literal sheet == null}
  196.      */
  197.     public static Cell[] getColumn(final Sheet sheet, final int col) {
  198.         ArgUtils.notNull(sheet, "sheet");

  199.         int maxRow = getRows(sheet);
  200.         Cell[] cells = new Cell[maxRow];
  201.         for(int i=0; i < maxRow; i++) {
  202.             Row rows = sheet.getRow(i);
  203.             if(rows == null) {
  204.                 rows = sheet.createRow(i);

  205.             }

  206.             Cell cell = rows.getCell(col);
  207.             if(cell == null) {
  208.                 cell = rows.createCell(col, CellType.BLANK);
  209.             }

  210.             cells[i] = cell;
  211.         }

  212.         return cells;
  213.     }

  214.     /**
  215.      * フォーマッターを指定してセルの値を取得する
  216.      *
  217.      * @param cell
  218.      * @param cellFormatter
  219.      * @return フォーマットした文字列
  220.      * @throws IllegalArgumentException {@literal cell or cellFormatter is null.}
  221.      */
  222.     public static String getCellContents(final Cell cell, final CellFormatter cellFormatter) {
  223.         ArgUtils.notNull(cell, "cell");
  224.         ArgUtils.notNull(cellFormatter, "cellFormatter");

  225.         return cellFormatter.format(cell);

  226.     }

  227.     /**
  228.      * 指定してセルの値が空かどうか判定する。
  229.      * <p>ブランクセルなどの判定は優先的に行う。</p>
  230.      * @param cell
  231.      * @return
  232.      */
  233.     public static boolean isEmptyCellContents(final Cell cell) {
  234.         return isEmptyCellContents(cell, defaultCellFormatter);
  235.     }

  236.     /**
  237.      * フォーマッターを指定してセルの値が空かどうか判定する。
  238.      * <p>ブランクセルなどの判定は優先的に行う。</p>
  239.      * @param cell セル
  240.      * @param cellFormatter セルのフォーマッタ
  241.      * @throws IllegalArgumentException {@literal  sheet == null or cellFormatter == null.}
  242.      * @return
  243.      */
  244.     public static boolean isEmptyCellContents(final Cell cell, final CellFormatter cellFormatter) {
  245.         ArgUtils.notNull(cell, "cell");
  246.         ArgUtils.notNull(cellFormatter, "cellFormatter");

  247.         return getCellContents(cell, cellFormatter).isEmpty();
  248.     }

  249.     /**
  250.      * 指定した書式のインデックス番号を取得する。シートに存在しない場合は、新しく作成する。
  251.      * @param sheet シート
  252.      * @param pattern 作成する書式のパターン
  253.      * @return 書式のインデックス番号。
  254.      * @throws IllegalArgumentException {@literal sheet == null.}
  255.      * @throws IllegalArgumentException {@literal pattern == null || pattern.isEmpty().}
  256.      */
  257.     public static short getDataFormatIndex(final Sheet sheet, final String pattern) {
  258.         ArgUtils.notNull(sheet, "sheet");
  259.         ArgUtils.notEmpty(pattern, "pattern");

  260.         return sheet.getWorkbook().getCreationHelper().createDataFormat().getFormat(pattern);

  261.     }

  262.     /**
  263.      * セルに設定されている書式を取得する。
  264.      * @since 1.1
  265.      * @param cell セルのインスタンス。
  266.      * @param cellFormatter セルのフォーマッタ
  267.      * @return 書式が設定されていない場合は、空文字を返す。
  268.      *         cellがnullの場合も空文字を返す。
  269.      *         標準の書式の場合も空文字を返す。
  270.      */
  271.     public static String getCellFormatPattern(final Cell cell, final CellFormatter cellFormatter) {
  272.         if(cell == null) {
  273.             return "";
  274.         }

  275.         String pattern = cellFormatter.getPattern(cell);
  276.         if(pattern.equalsIgnoreCase("general")) {
  277.             return "";
  278.         }

  279.         return pattern;

  280.     }

  281.     /**
  282.      * 指定した範囲のセルを結合する。
  283.      * @param sheet
  284.      * @param startCol
  285.      * @param startRow
  286.      * @param endCol
  287.      * @param endRow
  288.      * @return 結合した範囲のアドレス情報
  289.      * @throws IllegalArgumentException {@literal sheet == null}
  290.      */
  291.     public static CellRangeAddress mergeCells(final Sheet sheet, int startCol, int startRow, int endCol, int endRow) {
  292.         ArgUtils.notNull(sheet, "sheet");

  293.         // 結合先のセルの値を空に設定する
  294.         for(int r=startRow; r <= endRow; r++) {
  295.             for(int c=startCol; c <= endCol; c++) {

  296.                 if(r == startRow && c == startCol) {
  297.                     continue;
  298.                 }

  299.                 Cell cell = getCell(sheet, c, r);
  300.                 cell.setBlank();
  301.             }
  302.         }

  303.         final CellRangeAddress range = new CellRangeAddress(startRow, endRow, startCol, endCol);
  304.         sheet.addMergedRegion(range);
  305.         return range;
  306.     }

  307.     /**
  308.      * 指定したセルのアドレスの結合情報を取得する。
  309.      * @since 0.5
  310.      * @param sheet シート情報
  311.      * @param rowIdx 行番号
  312.      * @param colIdx 列番号
  313.      * @return 結合していない場合nullを返す。
  314.      */
  315.     public static CellRangeAddress getMergedRegion(final Sheet sheet, final int rowIdx, final int colIdx) {
  316.         ArgUtils.notNull(sheet, "sheet");

  317.         final int num = sheet.getNumMergedRegions();
  318.         for(int i=0; i < num; i ++) {
  319.             final CellRangeAddress range = sheet.getMergedRegion(i);
  320.             if(range.isInRange(rowIdx, colIdx)) {
  321.                 return range;
  322.             }
  323.         }

  324.         return null;
  325.     }

  326.     /**
  327.      * 指定した範囲の結合を解除する。
  328.      * @param sheet
  329.      * @param mergedRange
  330.      * @return 引数で指定した結合が見つからない場合。
  331.      */
  332.     public static boolean removeMergedRange(final Sheet sheet, final CellRangeAddress mergedRange) {
  333.         ArgUtils.notNull(sheet, "sheet");
  334.         ArgUtils.notNull(mergedRange, "mergedRange");

  335.         final String mergedAddress = mergedRange.formatAsString(sheet.getSheetName(), true);

  336.         final int num = sheet.getNumMergedRegions();
  337.         for(int i=0; i < num; i ++) {
  338.             final CellRangeAddress range = sheet.getMergedRegion(i);
  339.             final String rangeAddress = range.formatAsString(sheet.getSheetName(), true);
  340.             if(rangeAddress.equals(mergedAddress)) {
  341.                 sheet.removeMergedRegion(i);
  342.                 return true;
  343.             }
  344.         }

  345.         return false;
  346.     }

  347.     /**
  348.      * 領域の列サイズ(横セル数)を計算します。
  349.      *
  350.      * @since 2.0
  351.      * @param region 領域
  352.      * @return 列サイズ(横セル数)。引数がnullの時は、0を返します。
  353.      */
  354.     public static int getColumnSize(final CellRangeAddress region) {
  355.         if(region == null) {
  356.             return 0;
  357.         }
  358.         return region.getLastColumn() - region.getFirstColumn() + 1;
  359.     }

  360.     /**
  361.      * 領域の行サイズ(行セル数)を計算します。
  362.      *
  363.      * @since 2.0
  364.      * @param region 領域
  365.      * @return 行サイズ(行セル数)。引数がnullの時は、0を返します。
  366.      */
  367.     public static int getRowSize(final CellRangeAddress region) {
  368.         if(region == null) {
  369.             return 0;
  370.         }
  371.         return region.getLastRow() - region.getFirstRow() + 1;
  372.     }

  373.     /**
  374.      * 指定した行の下に行を1行追加する
  375.      * @param sheet
  376.      * @param rowIndex 追加する行数
  377.      * @return 追加した行を返す。
  378.      */
  379.     public static Row insertRow(final Sheet sheet, final int rowIndex) {

  380.         ArgUtils.notNull(sheet, "sheet");
  381.         ArgUtils.notMin(rowIndex, 0, "rowIndex");

  382.         // 最終行を取得する
  383.         int lastRow = sheet.getLastRowNum();
  384.         if(lastRow < rowIndex) {
  385.             // データが定義されている範囲害の場合は、行を新たに作成して返す。
  386.             return sheet.createRow(rowIndex);
  387.         }

  388.         sheet.shiftRows(rowIndex, lastRow+1, 1);
  389.         return sheet.createRow(rowIndex);
  390.     }

  391.     /**
  392.      * 指定した行を削除する。
  393.      * <p>削除した行は上に詰める。
  394.      * @since 0.5
  395.      * @param sheet
  396.      * @param rowIndex 削除する行数
  397.      * @return 削除した行
  398.      */
  399.     public static Row removeRow(final Sheet sheet, final int rowIndex) {

  400.         ArgUtils.notNull(sheet, "cell");
  401.         ArgUtils.notMin(rowIndex, 0, "rowIndex");

  402.         final Row row = sheet.getRow(rowIndex);
  403.         if(row == null) {
  404.             // 削除対象の行にデータが何もない場合
  405.             return row;
  406.         }

  407.         sheet.removeRow(row);

  408.         // 上に1つ行をずらす
  409.         int lastRow = sheet.getLastRowNum();
  410.         if(rowIndex +1 > lastRow) {
  411.             return row;
  412.         }

  413.         sheet.shiftRows(rowIndex+1, lastRow, -1);

  414.         return row;
  415.     }


  416.     /**
  417.      * 座標をExcelのアドレス形式'A1'などに変換する
  418.      * @param rowIndex 行インデックス
  419.      * @param colIndex 列インデックス
  420.      * @return
  421.      */
  422.     public static String formatCellAddress(final int rowIndex, final int colIndex) {
  423.         return CellReference.convertNumToColString(colIndex) + String.valueOf(rowIndex+1);
  424.     }

  425.     /**
  426.      * 座標をExcelのアドレス形式'A1'になどに変換する。
  427.      * @param cellAddress セルの位置情報
  428.      * @return
  429.      * @throws IllegalArgumentException address == null.
  430.      */
  431.     public static String formatCellAddress(final Point cellAddress) {
  432.         ArgUtils.notNull(cellAddress, "cellAddress");
  433.         return formatCellAddress(cellAddress.y, cellAddress.x);
  434.     }

  435.     /**
  436.      * セルのアドレス'A1'を取得する。
  437.      * @param cell セル情報
  438.      * @return IllegalArgumentException cell == null.
  439.      */
  440.     public static String formatCellAddress(final Cell cell) {
  441.         ArgUtils.notNull(cell, "cell");
  442.         return CellReference.convertNumToColString(cell.getColumnIndex()) + String.valueOf(cell.getRowIndex()+1);
  443.     }

  444.     /**
  445.      * リンクのアドレスを判定する。
  446.      * @param linkAddress リンクのアドレス(URL)
  447.      * @return 不明な場合は{@link HyperlinkType#NONE}を返す。
  448.      * @throws IllegalArgumentException linkAddress が空文字の場合。
  449.      */
  450.     public static HyperlinkType judgeLinkType(final String linkAddress) {

  451.         ArgUtils.notEmpty(linkAddress, "linkAddress");

  452.         if(linkAddress.matches(".*![\\p{Alnum}]+")) {
  453.             // !A1のアドレスを含むかどうか
  454.             return HyperlinkType.DOCUMENT;

  455.         } else if(linkAddress.matches("[\\p{Alpha}]+[0-9]+")) {
  456.             // A1の通常のアドレスの形式
  457.             return HyperlinkType.DOCUMENT;

  458.         } else if(linkAddress.matches(".+@.+")) {
  459.             // @を含むかどうか
  460.             return HyperlinkType.EMAIL;

  461.         } else if(linkAddress.matches("[\\p{Alpha}]+://.+")) {
  462.             // プロトコル付きかどうか
  463.             return HyperlinkType.URL;

  464.         } else if(linkAddress.matches(".+\\.[\\p{Alnum}]+")) {
  465.             // 拡張子付きかどうか
  466.             return HyperlinkType.FILE;

  467.         } else {
  468.             return HyperlinkType.NONE;
  469.         }

  470.     }

  471.     /**
  472.      * 入力規則の範囲を更新する。
  473.      * @since 0.5
  474.      * @param sheet シート
  475.      * @param oldRegion 更新対象の範囲。
  476.      * @param newRegion 新しい範囲。
  477.      * @return true:更新完了。false:指定した範囲を持つ入力規則が見つからなかった場合。
  478.      */
  479.     public static boolean updateDataValidationRegion(final Sheet sheet,
  480.             final CellRangeAddressList oldRegion, final CellRangeAddressList newRegion) {

  481.         ArgUtils.notNull(sheet, "sheet");
  482.         ArgUtils.notNull(oldRegion, "oldRegion");
  483.         ArgUtils.notNull(newRegion, "newRegion");

  484.         if(sheet instanceof XSSFSheet) {

  485.             final List<String> oldSqref = convertSqref(oldRegion);

  486.             try {
  487.                 final XSSFSheet xssfSheet = (XSSFSheet) sheet;
  488.                 Field fWorksheet = XSSFSheet.class.getDeclaredField("worksheet");
  489.                 fWorksheet.setAccessible(true);
  490.                 CTWorksheet worksheet = (CTWorksheet) fWorksheet.get(xssfSheet);

  491.                 CTDataValidations dataValidations = worksheet.getDataValidations();
  492.                 if(dataValidations == null) {
  493.                     return false;
  494.                 }

  495.                 for(int i=0; i < dataValidations.getCount(); i++) {
  496.                     CTDataValidation dv = dataValidations.getDataValidationArray(i);

  497.                     // 規則の範囲を比較し、同じならば範囲を書き換える。
  498.                     @SuppressWarnings("unchecked")
  499.                     List<String> sqref = new ArrayList<>(dv.getSqref());
  500.                     if(equalsSqref(sqref, oldSqref)) {
  501.                         List<String> newSqref = convertSqref(newRegion);
  502.                         dv.setSqref(newSqref);

  503.                         // 設定し直す
  504.                         dataValidations.setDataValidationArray(i, dv);
  505.                         return true;
  506.                     }

  507.                 }

  508.                 return false;

  509.             } catch(Exception e) {
  510.                 throw new RuntimeException("fail update DataValidation's Regsion.", e);
  511.             }

  512.         } else if(sheet instanceof HSSFSheet) {

  513.             final HSSFSheet hssfSheet = (HSSFSheet) sheet;
  514.             try {
  515.                 Field fWorksheet = HSSFSheet.class.getDeclaredField("_sheet");
  516.                 fWorksheet.setAccessible(true);
  517.                 InternalSheet worksheet = (InternalSheet) fWorksheet.get(hssfSheet);

  518.                 DataValidityTable dvt = worksheet.getOrCreateDataValidityTable();

  519.                 // シート内の入力規則のデータを検索して、一致するものがあれば書き換える。
  520.                 final AtomicBoolean updated = new AtomicBoolean(false);
  521.                 RecordVisitor visitor = new RecordVisitor() {

  522.                     @Override
  523.                     public void visitRecord(final Record r) {
  524.                         if (!(r instanceof DVRecord)) {
  525.                             return;
  526.                         }

  527.                         final DVRecord dvRecord = (DVRecord) r;
  528.                         final CellRangeAddressList region = dvRecord.getCellRangeAddress();
  529.                         if(equalsRegion(region, oldRegion)) {

  530.                             // 一旦既存の範囲を削除する。
  531.                             while(region.countRanges() != 0) {
  532.                                 region.remove(0);
  533.                             }

  534.                             // 新しい範囲を追加する。
  535.                             for(CellRangeAddress newRange : newRegion.getCellRangeAddresses()) {
  536.                                 region.addCellRangeAddress(newRange);
  537.                             }

  538.                             updated.set(true);
  539.                             return;
  540.                         }
  541.                     }
  542.                 };

  543.                 dvt.visitContainedRecords(visitor);

  544.                 return updated.get();

  545.             } catch(Exception e) {
  546.                 throw new RuntimeException("fail update DataValidation's Regsion.", e);
  547.             }
  548.         } else {
  549.             throw new UnsupportedOperationException("not supported update dava validation's region for type " + sheet.getClass().getName());
  550.         }

  551.     }

  552.     /**
  553.      * CellRangeAddressを文字列形式のリストに変換する。
  554.      * @since 0.5
  555.      * @param region
  556.      * @return
  557.      */
  558.     private static List<String> convertSqref(final CellRangeAddressList region) {

  559.         List<String> sqref = new ArrayList<>();
  560.         for(CellRangeAddress range : region.getCellRangeAddresses()) {
  561.             sqref.add(range.formatAsString());
  562.         }

  563.         return sqref;

  564.     }

  565.     /**
  566.      * 文字列形式のセルの範囲が同じかどうか比較する。
  567.      * @since 0.5
  568.      * @param sqref1
  569.      * @param sqref2
  570.      * @return
  571.      */
  572.     public static boolean equalsSqref(final List<String> sqref1, final List<String> sqref2) {

  573.         if(sqref1.size() != sqref2.size()) {
  574.             return false;
  575.         }

  576.         Collections.sort(sqref1);
  577.         Collections.sort(sqref2);

  578.         final int size = sqref1.size();
  579.         for(int i=0; i < size; i++) {
  580.             if(!sqref1.get(i).equals(sqref2.get(i))) {
  581.                 return false;
  582.             }
  583.         }

  584.         return true;

  585.     }

  586.     /**
  587.      * 文字列形式のセルの範囲が同じかどうか比較する。
  588.      * @since 0.5
  589.      * @param region1
  590.      * @param region2
  591.      * @return
  592.      */
  593.     public static boolean equalsRegion(final CellRangeAddressList region1, final CellRangeAddressList region2) {

  594.         return equalsSqref(convertSqref(region1), convertSqref(region2));

  595.     }

  596.     /**
  597.      * テンプレートの入力規則の制約「リスト」を追加する。
  598.      * <p>POI-3.7以上が必要。
  599.      * @param sheet シート
  600.      * @param constraints 制約とするコレクションの中身
  601.      * @param startPosition 開始位置
  602.      * @param endPosition 終了位置
  603.      */
  604.     public static void setupExplicitListConstaint(final Sheet sheet, final Collection<String> constraints,
  605.             final Point startPosition, final Point endPosition) {

  606.         ArgUtils.notNull(sheet, "sheet");
  607.         ArgUtils.notEmpty(constraints, "constraints");
  608.         ArgUtils.notNull(startPosition, "startPosition");
  609.         ArgUtils.notNull(endPosition, "endPosition");

  610.         setupExplicitListConstaint(sheet, constraints.toArray(new String[constraints.size()]),
  611.                 startPosition, endPosition);
  612.     }

  613.     /**
  614.      * テンプレートの入力規則の制約「リスト」を追加する。
  615.      * <p>POI-3.7以上が必要。
  616.      * @param sheet シート
  617.      * @param constraints 制約とするリストの中身
  618.      * @param startPosition 開始位置
  619.      * @param endPosition 終了位置
  620.      */
  621.     public static void setupExplicitListConstaint(final Sheet sheet, final String[] constraints,
  622.             final Point startPosition, final Point endPosition) {

  623.         ArgUtils.notNull(sheet, "sheet");
  624.         ArgUtils.notEmpty(constraints, "constraints");
  625.         ArgUtils.notNull(startPosition, "startPosition");
  626.         ArgUtils.notNull(endPosition, "endPosition");

  627.         final DataValidationHelper helper = sheet.getDataValidationHelper();
  628.         final DataValidationConstraint constraint = helper.createExplicitListConstraint(constraints);
  629.         setupConstaint(sheet, constraint, startPosition, endPosition);

  630.     }

  631.     /**
  632.      * テンプレートの入力規則の制約「リスト」を式形式で追加する。
  633.      * <p>POI-3.7以上が必要。
  634.      * @param sheet シート
  635.      * @param listFormula 入力規則の式('='は含まない)
  636.      * @param startPosition 設定するセルの開始位置
  637.      * @param endPosition 設定するセルの終了位置
  638.      */
  639.     public static void setupFormulaListConstaint(final Sheet sheet, final String listFormula,
  640.             final Point startPosition, final Point endPosition) {

  641.         ArgUtils.notNull(sheet, "sheet");
  642.         ArgUtils.notEmpty(listFormula, "listFormula");
  643.         ArgUtils.notNull(startPosition, "startPosition");
  644.         ArgUtils.notNull(endPosition, "endPosition");

  645.         final DataValidationHelper helper = sheet.getDataValidationHelper();
  646.         final DataValidationConstraint constraint = helper.createFormulaListConstraint("=" + listFormula);
  647.         setupConstaint(sheet, constraint, startPosition, endPosition);
  648.     }

  649.     /**
  650.      * 指定した範囲のセルに制約を追加する。
  651.      * <p>POI-3.7以上が必要。
  652.      * @param sheet シート
  653.      * @param constraint 制約
  654.      * @param startPosition 設定するセルの開始位置
  655.      * @param endPosition 設定するセルの終了位置
  656.      */
  657.     public static void setupConstaint(final Sheet sheet, final DataValidationConstraint constraint,
  658.             final Point startPosition, final Point endPosition) {

  659.         ArgUtils.notNull(sheet, "sheet");
  660.         ArgUtils.notNull(constraint, "constraint");
  661.         ArgUtils.notNull(startPosition, "startPosition");
  662.         ArgUtils.notNull(endPosition, "endPosition");

  663.         final DataValidationHelper helper = sheet.getDataValidationHelper();

  664.         final CellRangeAddressList region = new CellRangeAddressList(
  665.                 startPosition.y, endPosition.y,
  666.                 startPosition.x, endPosition.x
  667.                 );
  668.         final DataValidation dataValidation = helper.createValidation(constraint, region);
  669.         sheet.addValidationData(dataValidation);
  670.     }

  671.     /**
  672.      * 指定した範囲の名前を登録する。
  673.      * <p>POI-3.7以上が必要。
  674.      * <p>指定した名前が既に存在する場合は、新しい範囲に書き換える。
  675.      * @param sheet シート
  676.      * @param name 名前
  677.      * @param startPosition 設定するセルの開始位置
  678.      * @param endPosition 設定するセルの終了位置
  679.      * @return
  680.      */
  681.     public static Name defineName(final Sheet sheet, final String name,
  682.             final Point startPosition, final Point endPosition) {

  683.         ArgUtils.notNull(sheet, "sheet");
  684.         ArgUtils.notEmpty(name, "name");
  685.         ArgUtils.notNull(startPosition, "startPosition");
  686.         ArgUtils.notNull(endPosition, "endPosition");

  687.         final Workbook workbook = sheet.getWorkbook();
  688.         Name nameObj = workbook.getName(name);
  689.         if(nameObj == null) {
  690.             nameObj = workbook.createName();
  691.             nameObj.setNameName(name);
  692.         }

  693.         final AreaReference areaRef = buildNameArea(sheet.getSheetName(), startPosition, endPosition,
  694.                 sheet.getWorkbook().getSpreadsheetVersion());
  695.         nameObj.setRefersToFormula(areaRef.formatAsString());

  696.         return nameObj;

  697.     }

  698.     /**
  699.      * 名前の範囲の形式を組み立てる。
  700.      * <code>シート名!$A$1:$A:$5</code>
  701.      * @param sheetName シート名
  702.      * @param startPosition 設定するセルの開始位置
  703.      * @param endPosition 設定するセルの終了位置
  704.      * @param sheetVersion シートの形式
  705.      * @return
  706.      */
  707.     public static AreaReference buildNameArea(final String sheetName,
  708.             final Point startPosition, final Point endPosition, SpreadsheetVersion sheetVersion) {

  709.         ArgUtils.notEmpty(sheetName, "sheetName");
  710.         ArgUtils.notNull(startPosition, "startPosition");
  711.         ArgUtils.notNull(endPosition, "endPosition");

  712.         final CellReference firstRefs = new CellReference(sheetName, startPosition.y, startPosition.x, true, true);
  713.         final CellReference lastRefs = new CellReference(sheetName, endPosition.y, endPosition.x, true, true);

  714.         return new AreaReference(firstRefs, lastRefs, sheetVersion);
  715.     }

  716.     /**
  717.      * セルの範囲が重複(交錯)しているかどうか判定する。
  718.      * <p>このメソッドは、POI-3.14で追加されたメソッド{@literal Sheet#intersects(...)}と後方互換性を保つためのもの。</p>
  719.      *
  720.      * @param my
  721.      * @param other
  722.      * @return trueの場合、1つでもセルの範囲が重複している。
  723.      */
  724.     public static boolean intersectsRegion(final CellRangeAddressBase my, final CellRangeAddressBase other) {
  725.         return my.getFirstRow() <= other.getLastRow() &&
  726.                 my.getFirstColumn() <= other.getLastColumn() &&
  727.                 other.getFirstRow() <= my.getLastRow() &&
  728.                 other.getFirstColumn() <= my.getLastColumn();
  729.     }

  730.     /**
  731.      * 日時の開始日が1904年かどうか。
  732.      * 通常は、1900年始まり。
  733.      * @param workbook ワークブック
  734.      * @return trueの場合は、1904年始まり。falseの場合は、1900年始まり。
  735.      */
  736.     public static boolean isDateStart1904(final Workbook workbook) {

  737.         if(workbook instanceof HSSFWorkbook) {
  738.             try {
  739.                 Method method = HSSFWorkbook.class.getDeclaredMethod("getWorkbook");
  740.                 method.setAccessible(true);

  741.                 InternalWorkbook iw = (InternalWorkbook) method.invoke(workbook);
  742.                 return iw.isUsing1904DateWindowing();

  743.             } catch(NoSuchMethodException | SecurityException e) {
  744.                 logger.warn("fail access method HSSFWorkbook.getWorkbook.", e);
  745.                 return false;
  746.             } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
  747.                 logger.warn("fail invoke method HSSFWorkbook.getWorkbook.", e);
  748.                 return false;
  749.             }

  750.         } else if(workbook instanceof XSSFWorkbook) {
  751.             try {
  752.                 Method method = XSSFWorkbook.class.getDeclaredMethod("isDate1904");
  753.                 method.setAccessible(true);

  754.                 boolean value = (boolean) method.invoke(workbook);
  755.                 return value;

  756.             } catch(NoSuchMethodException | SecurityException e) {
  757.                 logger.warn("fail access method XSSFWorkbook.isDate1904.", e);
  758.                 return false;
  759.             } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
  760.                 logger.warn("fail invoke method XSSFWorkbook.isDate1904.", e);
  761.                 return false;
  762.             }

  763.         } else {
  764.             logger.warn("unknown workbook type.", workbook.getClass().getName());
  765.         }

  766.         return false;
  767.     }

  768.     /**
  769.      * 結合を考慮してセルの罫線(上部)を取得する。
  770.      *
  771.      * @param cell セル
  772.      * @return {@literal BorderStyle}
  773.      * @throws IllegalArgumentException {@literal cell is null.}
  774.      */
  775.     public static BorderStyle getBorderTop(final Cell cell) {

  776.         ArgUtils.notNull(cell, "cell");

  777.         final Sheet sheet = cell.getSheet();
  778.         CellRangeAddress mergedRegion = getMergedRegion(sheet, cell.getRowIndex(), cell.getColumnIndex());

  779.         final Cell target;
  780.         if(mergedRegion == null) {
  781.             // 結合されていない場合
  782.             target = cell;

  783.         } else {
  784.             if(mergedRegion.getFirstRow() == cell.getRowIndex()) {
  785.                 // 引数のCellが上部のセルの場合
  786.                 target = cell;
  787.             } else {
  788.                 target = getCell(sheet, cell.getColumnIndex(), mergedRegion.getFirstRow());
  789.             }

  790.         }

  791.         final CellStyle style = target.getCellStyle();
  792.         if(style == null) {
  793.             return BorderStyle.NONE;
  794.         } else {
  795.             return style.getBorderTop();
  796.         }

  797.     }

  798.     /**
  799.      * 結合を考慮してセルの罫線(下部)を取得する。
  800.      *
  801.      * @param cell セル
  802.      * @return {@literal BorderStyle}
  803.      * @throws IllegalArgumentException {@literal cell is null.}
  804.      */
  805.     public static BorderStyle getBorderBottom(final Cell cell) {

  806.         ArgUtils.notNull(cell, "cell");

  807.         final Sheet sheet = cell.getSheet();
  808.         CellRangeAddress mergedRegion = getMergedRegion(sheet, cell.getRowIndex(), cell.getColumnIndex());

  809.         final Cell target;
  810.         if(mergedRegion == null) {
  811.             // 結合されていない場合
  812.             target = cell;

  813.         } else {
  814.             if(mergedRegion.getLastRow() == cell.getRowIndex()) {
  815.                 // 引数のCellが下部のセルの場合
  816.                 target = cell;
  817.             } else {
  818.                 target = getCell(sheet, cell.getColumnIndex(), mergedRegion.getLastRow());
  819.             }

  820.         }

  821.         final CellStyle style = target.getCellStyle();
  822.         if(style == null) {
  823.             return BorderStyle.NONE;
  824.         } else {
  825.             return style.getBorderBottom();
  826.         }

  827.     }

  828.     /**
  829.      * 結合を考慮してセルの罫線(左部)を取得する。
  830.      *
  831.      * @param cell セル
  832.      * @return {@literal BorderStyle}
  833.      * @throws IllegalArgumentException {@literal cell is null.}
  834.      */
  835.     public static BorderStyle getBorderRight(final Cell cell) {

  836.         ArgUtils.notNull(cell, "cell");

  837.         final Sheet sheet = cell.getSheet();
  838.         CellRangeAddress mergedRegion = getMergedRegion(sheet, cell.getRowIndex(), cell.getColumnIndex());

  839.         final Cell target;
  840.         if(mergedRegion == null) {
  841.             // 結合されていない場合
  842.             target = cell;

  843.         } else {
  844.             if(mergedRegion.getLastColumn() == cell.getColumnIndex()) {
  845.                 // 引数のCellが右部のセルの場合
  846.                 target = cell;
  847.             } else {
  848.                 target = getCell(sheet, mergedRegion.getLastColumn(), cell.getRowIndex());
  849.             }

  850.         }

  851.         final CellStyle style = target.getCellStyle();
  852.         if(style == null) {
  853.             return BorderStyle.NONE;
  854.         } else {
  855.             return style.getBorderRight();
  856.         }

  857.     }

  858.     /**
  859.      * 結合を考慮してセルの罫線(右部)を取得する。
  860.      *
  861.      * @param cell セル
  862.      * @return {@literal BorderStyle}
  863.      * @throws IllegalArgumentException {@literal cell is null.}
  864.      */
  865.     public static BorderStyle getBorderLeft(final Cell cell) {

  866.         ArgUtils.notNull(cell, "cell");

  867.         final Sheet sheet = cell.getSheet();
  868.         CellRangeAddress mergedRegion = getMergedRegion(sheet, cell.getRowIndex(), cell.getColumnIndex());

  869.         final Cell target;
  870.         if(mergedRegion == null) {
  871.             // 結合されていない場合
  872.             target = cell;

  873.         } else {
  874.             if(mergedRegion.getFirstColumn() == cell.getColumnIndex()) {
  875.                 // 引数のCellが左部のセルの場合
  876.                 target = cell;
  877.             } else {
  878.                 target = getCell(sheet, mergedRegion.getFirstColumn(), cell.getRowIndex());
  879.             }

  880.         }

  881.         final CellStyle style = target.getCellStyle();
  882.         if(style == null) {
  883.             return BorderStyle.NONE;
  884.         } else {
  885.             return style.getBorderLeft();
  886.         }

  887.     }

  888.     /**
  889.      * ハイパーリンクを取得する。
  890.      * <p>結合されているセルの場合にも対応。
  891.      * @param cell
  892.      * @return 見つからない場合は、nullを返す。
  893.      * @throws IllegalArgumentException {@literal cell is null.}
  894.      */
  895.     public static Hyperlink getHyperlink(final Cell cell) {

  896.         ArgUtils.notNull(cell, "cell");

  897.         Hyperlink link = cell.getHyperlink();
  898.         if(link != null) {
  899.             return link;
  900.         }

  901.         final Sheet sheet = cell.getSheet();
  902.         CellRangeAddress mergedRange = getMergedRegion(sheet, cell.getRowIndex(), cell.getColumnIndex());
  903.         if(mergedRange == null) {
  904.             return null;
  905.         }

  906.         for(Hyperlink item : sheet.getHyperlinkList()) {
  907.             if(item.getFirstRow() == mergedRange.getFirstRow()
  908.                     && item.getFirstColumn() == mergedRange.getFirstColumn()) {
  909.                 return item;
  910.             }

  911.         }

  912.         return null;

  913.     }

  914.     /**
  915.      * {@literal 1900-01-01 00:00:00.000}の時間(単位はミリ秒)。
  916.      * <p>Excelは設定により、1900年始まりか1904年始まりか指定できるため、その基準値として利用する。
  917.      */
  918.     public static final long MILLISECONDS_19000101_END = ExcelDateUtils.parseDate("1900-01-01 23:59:54.999").getTime();

  919.     /**
  920.      * セルに日時を設定する。
  921.      * <p>1900年1月0日となる経過時間指定の場合は、POIのバグにより設定できあいため、数値として設定する。</p>
  922.      *
  923.      * @param cell 設定するセル
  924.      * @param date セルに設定する日時
  925.      * @param dateStart1904 1904年始まりの設定のシートかどうか
  926.      */
  927.     public static void setCellValueAsDate(Cell cell, Date date, boolean dateStart1904) {

  928.         ArgUtils.notNull(cell, "cell");
  929.         ArgUtils.notNull(date, "date");

  930.         if(dateStart1904) {
  931.             // 1904年始まりの場合は、そのまま設定する
  932.             cell.setCellValue(date);

  933.         } else {

  934.             long timemills = date.getTime();
  935.             if(timemills <= MILLISECONDS_19000101_END) {
  936.                 // 1900年1月0日の場合は、数値に変換してから設定する
  937.                 // タイムゾーンを除去する
  938.                 Date strip = new Date(date.getTime() + TimeZone.getDefault().getRawOffset());
  939.                 double num = ExcelDateUtils.convertExcelNumber(strip, dateStart1904);
  940.                 cell.setCellValue(num);

  941.             } else {
  942.                 cell.setCellValue(date);
  943.             }

  944.         }

  945.     }

  946. }