[POI] 対処編:テンプレートファイルが書き変わるようになってしまった

Apache POI

Apache POI 3.13 を使用してエクセルファイルを作成する際、テンプレートファイルを開いて編集しようとすると、編集内容が随時、テンプレートファイルに書き込まれてしまう事象に出会いました。
その事象について、前回の投稿「[POI] テンプレートが書き変わるようになってしまった」で、Apache POI 3.11 から変更になっていたことを突き止めました。

今回の投稿では、具体的な対応方法をご紹介します。
(もし他に良い方法をご存知でしたらご教示願います。)

この一見迷惑な変更は、既存システムにおける Apache POI のバージョンアップを難しくしました。
しかしその一方で恐らくは、以前よりも大きなファイルを取り扱えるようになったことでしょう。
編集前と編集中の情報とを分けてメモリに確保しておく必要がなくなるはずだからです。

… ドキュメントもソースも見ないで語ってます(笑)

で、前回の投稿から試行錯誤しましたが、Apache POI にファイル又はその参照を渡してる場合、ファイルが書き変わってしまうのは、どうも避けられないようです。
たとえ write メソッドを呼ばなくても書き込まれてしまうのです。

ならテンプレートファイルを予め複製しておくしかなさそうですね。

ただし、わざわざ複製をファイルに書き出すのはしたくない…、それも状況によっては正解ですが。
なぜなら、同時にアクセスされてもファイルが衝突しないよう気を付け、編集が終わったらファイルを消す…と頑張ることが増えてしまいますし、ハードディスクを使うということはボトルネックになりえます。
ローカルエリアネットワーク内のシステムで、資源を持て余している環境ならばメモリ内で処理してしまうと良いと思います。

ということで、今回ご紹介する方法は、メモリ内で何とかしてしまおうというものです。

Java でそこそこ経験を積んでいる方ならすぐに思いつくでしょう。
ByteArrayInputStream を使って、ファイルとデータを切り離します。
要は Apache POI 側からファイルをたどれなくしてしまえば良いということですね。
長々と説明するのも難なので、下のサンプルをご覧ください。

package test;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;

/**
 * テンプレートファイルを使ってエクセルファイルを生成するプログラムの例.
 * <p>
 * Apache POI 3.11 からテンプレートファイルが随時書き変わるようになっていたので、
 * 対応するプログラムのサンプルを作ってみました。<br />
 * このプログラムは  Apache POI 3.13 (Java8) での対応方法の例です。<br />
 * 3.11 や 3.12 だと書き方が多少異なりますが、対応方法は同じです。
 * </p>
 */
public class Example {
	/** テンプレートファイルのパス. */
	private static final String TEMPLATE = "input.xlsx";

	/**
	 * メインメソッド.
	 * @param args 不使用
	 * @throws IOException 入出力時の例外
	 */
	public static void main(final String[] args) throws IOException {
		try (final ByteArrayOutputStream os = new ByteArrayOutputStream()) {
			create(os, "Apache POI 3.11 からテンプレートファイルが随時書き変わるようになっていたので、対応するプログラムのサンプルを作ってみました。");

			// 編集した内容をファイルに出力
			final File output = new File("output.xlsx");
			output.delete();
			output.createNewFile();
			try (FileOutputStream fio = new FileOutputStream(output)) {
				fio.write(os.toByteArray());
			}
		}
	}

	/**
	 * エクセルファイルの生成.
	 * @param os エクセルファイルの出力先
	 * @throws IOException 入出力時の例外
	 */
	private static void create(final OutputStream os, final String data) throws IOException {
		try (final Workbook workbook = loadTemplate()) {
			edit(workbook.getSheetAt(0), data);
			workbook.write(os);
		}
	}

	/**
	 * データ編集.
	 * @param sheet 対象シート
	 * @param data 書き込むデータ
	 */
	private static void edit(final Sheet sheet, final String data) {
		sheet.createRow(sheet.getLastRowNum() + 1).createCell(0).setCellValue(data);
	}

	/**
	 * テンプレートファイルを開く.
	 * @return テンプレートファイル
	 * @throws IOException 入出力時の例外
	 */
	private static Workbook loadTemplate() throws IOException {
		// Apache POI 3.11 からファイルが直接編集されるようになっていますので、
		// テンプレートファイルは一度バイト配列に変換してから使用します。
		try (final InputStream is = copy(new File(TEMPLATE))) {
			return WorkbookFactory.create(is);
		} catch (final InvalidFormatException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * ファイルのコピーを生成.
	 * <p>
	 * ファイルデータをバイト配列で扱います。<br />
	 * 大きなファイルを取り扱う際には、注意が必要です。
	 * </P>
	 * @param file 対象ファイル
	 * @return ファイルのコピー
	 * @throws IOException 入出力時の例外
	 */
	private static InputStream copy(final File file) throws IOException {
		try (final InputStream fio = new FileInputStream(file)) {
			final byte[] data = new byte[(int) file.length()];
			fio.read(data);
			return new ByteArrayInputStream(data);
		}
	}
}

コメント