CellTypeを問わず、Cellの値を透過的に取得したい(Apache POI)


Excel大好き日本企業にシステムを納めるJava大好きSIerとして働いている以上、避けては通れないのが[Apache POI](https://poi.apache.org/)。POIはExcelのセルを抽象化した`Cell`というクラス(インタフェース)を持つのですが、この`Cell`から値を取り出すのが意外と面倒。とりわけ__セルの表示形式(`CellType`)に合わせてメソッドを呼び分ける必要があり、かつ`CellType`に合致しないメソッドを呼び出した場合は即座に例外を投げる__という仕様には何度苦しめられたことか(´・ω・｀)

そこで__`CellType`を問わず、透過的にセルの値を取得できるユーティリティメソッド__を準備しておくことにしました。なお簡単な仕様としては以下の通りです。

- POIがサポートする`CellType`のうち`BLANK`, `BOOLEAN`, `NUMERIC`, `STRING`のみを対象とする。ただし日付についてもサポートする。
- 戻り値は`Object`型とする。キャストするなり`String#valueOf`なり好きにすること。
- サポートしない`ERROR`と`FORMULA`の場合は例外を送出する
    - 以下はサンプルのため`RuntimeException`を投げているが、実際に利用する場合はちゃんとした例外(?)をスローすること。
- `null`安全ではない。つまり引数が`null`の場合は`NullPointerException`を投げる。また万が一に備えてPOIがサポートしていない`CellType`のセルが引数と渡された場合も例外とする。


```java
package my.excel;

import java.util.Objects;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DateUtil;

final public class CellUtils {

    public static Object getCellValue(Cell cell) {
        Objects.requireNonNull(cell, "cell is null");
        
        CellType cellType = cell.getCellTypeEnum();
        if (cellType == CellType.BLANK) {
            return null;
        } else if (cellType == CellType.BOOLEAN) {
            return cell.getBooleanCellValue();
        } else if (cellType == CellType.ERROR) {
            throw new RuntimeException("Error cell is unsupported");
        } else if (cellType == CellType.FORMULA) {
            throw new RuntimeException("Formula cell is unsupported");
        } else if (cellType == CellType.NUMERIC) {
            if (DateUtil.isCellDateFormatted(cell)) {
                return cell.getDateCellValue();
            } else {
                return cell.getNumericCellValue();
            }
        } else if (cellType == CellType.STRING) {
            return cell.getStringCellValue();
        } else {
            throw new RuntimeException("Unknow type cell");
        }
    }
    
}
```
では実際に利用してみましょう(´・ω・｀) まずは次のような`sample.xlsx`を用意します。

![sample.PNG](https://qiita-image-store.s3.amazonaws.com/0/105859/54f65928-8335-e0d3-469b-0c833a184525.png)

あとは次のようなコードを書いて、正しく読み込めるかどうかを確認してみます。

```java
package my.excel;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

public class Main {
    public static void main(String ... args) {
        Path path = Paths.get("C:\\hogehoge\\sample.xlsx");
        try (Workbook workbook = WorkbookFactory.create(Files.newInputStream(path))) {
            Sheet sheet = workbook.getSheetAt(0);
            for (int i = 0; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                Cell cell = row.getCell(1); // B列のセルを取得する。
                Object value = CellUtils.getCellValue(cell);
                System.out.println(value);
            }
        } catch (IOException 
                    | InvalidFormatException 
                    | EncryptedDocumentException e) {
            e.printStackTrace();
        } 
    }
 }
```

コンパイルして実行した結果を確認してみると――確かに想定通り機能していることがわかります。やったね(´・ω・｀)

```
ABC
true
100.0
Mon Jan 02 00:00:00 JST 2017
```