package com.gh.mygreen.xlsmapper.fieldprocessor; import static org.junit.Assert.*; import static org.hamcrest.Matchers.*; import static com.gh.mygreen.xlsmapper.TestUtils.*; import static com.gh.mygreen.xlsmapper.xml.XmlBuilder.*; import java.awt.Point; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellStyle; import org.apache.poi.ss.usermodel.IndexedColors; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.WorkbookFactory; import org.apache.poi.ss.util.CellReference; import org.junit.BeforeClass; import org.junit.Test; import com.gh.mygreen.xlsmapper.AnnotationInvalidException; import com.gh.mygreen.xlsmapper.CellFormatter; import com.gh.mygreen.xlsmapper.POIUtils; import com.gh.mygreen.xlsmapper.Utils; import com.gh.mygreen.xlsmapper.XlsMapper; import com.gh.mygreen.xlsmapper.XlsMapperConfig; import com.gh.mygreen.xlsmapper.annotation.OverRecordOperate; import com.gh.mygreen.xlsmapper.annotation.RecordTerminal; import com.gh.mygreen.xlsmapper.annotation.XlsCell; import com.gh.mygreen.xlsmapper.annotation.XlsColumn; import com.gh.mygreen.xlsmapper.annotation.XlsFormula; import com.gh.mygreen.xlsmapper.annotation.XlsHorizontalRecords; import com.gh.mygreen.xlsmapper.annotation.XlsPostSave; import com.gh.mygreen.xlsmapper.annotation.XlsSheet; import com.gh.mygreen.xlsmapper.cellconvert.ConversionException; import com.gh.mygreen.xlsmapper.validation.SheetBindingErrors; import com.gh.mygreen.xlsmapper.xml.bind.XmlInfo; /** * アノテーション{@link XlsFormula}のテスタ。 * <p>このテスタは、式の解析の確認などのテストを行う。 * <p>プロセッサとの組み合わせは、それぞれのテスタで行う。 * * @since 1.5 * @author T.TSUCHIE * */ public class AnnoFormulaTest { /** * テスト結果ファイルの出力ディレクトリ */ private static File OUT_DIR; @BeforeClass public static void setUpBeforeClass() throws Exception { OUT_DIR = createOutDir(); } /** * 正常 - 式を直接指定 */ @Test public void test_normal_formula() throws Exception { // テストデータの作成 final FormulaSheet outSheet = new FormulaSheet(); // アノテーションの組み立て XmlInfo xmlInfo = createXml() .classInfo(createClass(FormulaSheet.class) .field(createField("c1") .override(true) .annotation(createAnnotation(XlsFormula.class) .attribute("value", "SUM(C2:${x:colToAlpha(columnNumber+2)}3)") .buildAnnotation()) .buildField()) .buildClass()) .buildXml(); // ファイルへの書き込み XlsMapper mapper = new XlsMapper(); mapper.getConig().setContinueTypeBindFailure(true); File outFile = new File(OUT_DIR, "anno_Formula_out.xlsx"); try(InputStream template = new FileInputStream("src/test/data/anno_Formula_template.xlsx"); OutputStream out = new FileOutputStream(outFile)) { mapper.save(template, out, outSheet, xmlInfo.toInputStream()); } // 書き込んだファイルを読み込み値の検証を行う。 try(InputStream in = new FileInputStream(outFile)) { Workbook book = WorkbookFactory.create(in); Sheet sheet = book.getSheet("Formula(通常)"); Cell cell = POIUtils.getCell(sheet, new CellAddress("A2")); String formula = cell.getCellFormula(); CellFormatter formatter = mapper.getConig().getCellFormatter(); String value = formatter.format(cell); assertThat(formula, is("SUM(C2:C3)")); assertThat(value, is("17.468")); } } /** * 正常 - メソッドで式を組み立て */ @Test public void test_normal_method() throws Exception { // テストデータの作成 final FormulaSheet outSheet = new FormulaSheet(); // アノテーションの組み立て XmlInfo xmlInfo = createXml() .classInfo(createClass(FormulaSheet.class) .field(createField("c1") .override(true) .annotation(createAnnotation(XlsFormula.class) .attribute("methodName", "getC1Formula") .buildAnnotation()) .buildField()) .buildClass()) .buildXml(); // ファイルへの書き込み XlsMapper mapper = new XlsMapper(); mapper.getConig().setContinueTypeBindFailure(true); File outFile = new File(OUT_DIR, "anno_Formula_out.xlsx"); try(InputStream template = new FileInputStream("src/test/data/anno_Formula_template.xlsx"); OutputStream out = new FileOutputStream(outFile)) { mapper.save(template, out, outSheet, xmlInfo.toInputStream()); } // 書き込んだファイルを読み込み値の検証を行う。 try(InputStream in = new FileInputStream(outFile)) { Workbook book = WorkbookFactory.create(in); Sheet sheet = book.getSheet("Formula(通常)"); Cell cell = POIUtils.getCell(sheet, new CellAddress("A2")); String formula = cell.getCellFormula(); CellFormatter formatter = mapper.getConig().getCellFormatter(); String value = formatter.format(cell); assertThat(formula, is("SUM(D2:D3)")); assertThat(value, is("579")); } } /** * 正常 - 数式を優先 */ @Test public void test_normal_primay() throws Exception { // テストデータの作成 final FormulaSheet outSheet = new FormulaSheet(); outSheet.c1(12.345d); // アノテーションの組み立て XmlInfo xmlInfo = createXml() .classInfo(createClass(FormulaSheet.class) .field(createField("c1") .override(true) .annotation(createAnnotation(XlsFormula.class) .attribute("value", "SUM(C2:C3)") .attribute("primary", true) .buildAnnotation()) .buildField()) .buildClass()) .buildXml(); // ファイルへの書き込み XlsMapper mapper = new XlsMapper(); mapper.getConig().setContinueTypeBindFailure(true); File outFile = new File(OUT_DIR, "anno_Formula_out.xlsx"); try(InputStream template = new FileInputStream("src/test/data/anno_Formula_template.xlsx"); OutputStream out = new FileOutputStream(outFile)) { mapper.save(template, out, outSheet, xmlInfo.toInputStream()); } // 書き込んだファイルを読み込み値の検証を行う。 try(InputStream in = new FileInputStream(outFile)) { Workbook book = WorkbookFactory.create(in); Sheet sheet = book.getSheet("Formula(通常)"); Cell cell = POIUtils.getCell(sheet, new CellAddress("A2")); assertThat(cell.getCellType(), is(Cell.CELL_TYPE_FORMULA)); String formula = cell.getCellFormula(); CellFormatter formatter = mapper.getConig().getCellFormatter(); String value = formatter.format(cell); assertThat(formula, is("SUM(C2:C3)")); assertThat(value, is("17.468")); } } /** * 正常 - 値を優先 */ @Test public void test_normal_not_primay() throws Exception { // テストデータの作成 final FormulaSheet outSheet = new FormulaSheet(); outSheet.c1(12.345d); // アノテーションの組み立て XmlInfo xmlInfo = createXml() .classInfo(createClass(FormulaSheet.class) .field(createField("c1") .override(true) .annotation(createAnnotation(XlsFormula.class) .attribute("value", "SUM(C2:C3)") .attribute("primary", false) .buildAnnotation()) .buildField()) .buildClass()) .buildXml(); // ファイルへの書き込み XlsMapper mapper = new XlsMapper(); mapper.getConig().setContinueTypeBindFailure(true); File outFile = new File(OUT_DIR, "anno_Formula_out.xlsx"); try(InputStream template = new FileInputStream("src/test/data/anno_Formula_template.xlsx"); OutputStream out = new FileOutputStream(outFile)) { mapper.save(template, out, outSheet, xmlInfo.toInputStream()); } // 書き込んだファイルを読み込み値の検証を行う。 try(InputStream in = new FileInputStream(outFile)) { Workbook book = WorkbookFactory.create(in); Sheet sheet = book.getSheet("Formula(通常)"); Cell cell = POIUtils.getCell(sheet, new CellAddress("A2")); assertThat(cell.getCellType(), is(Cell.CELL_TYPE_NUMERIC)); CellFormatter formatter = mapper.getConig().getCellFormatter(); String value = formatter.format(cell); assertThat(value, is("12.345")); } } /** * 正常 - 空の数式を返す場合 */ @Test public void test_normal_empty_formula() throws Exception { // テストデータの作成 final FormulaSheet outSheet = new FormulaSheet(); // アノテーションの組み立て XmlInfo xmlInfo = createXml() .classInfo(createClass(FormulaSheet.class) .field(createField("c1") .override(true) .annotation(createAnnotation(XlsFormula.class) .attribute("methodName", "getEmptyFormula") .buildAnnotation()) .buildField()) .buildClass()) .buildXml(); // ファイルへの書き込み XlsMapper mapper = new XlsMapper(); mapper.getConig().setContinueTypeBindFailure(true); File outFile = new File(OUT_DIR, "anno_Formula_out.xlsx"); try(InputStream template = new FileInputStream("src/test/data/anno_Formula_template.xlsx"); OutputStream out = new FileOutputStream(outFile)) { mapper.save(template, out, outSheet, xmlInfo.toInputStream()); } // 書き込んだファイルを読み込み値の検証を行う。 try(InputStream in = new FileInputStream(outFile)) { Workbook book = WorkbookFactory.create(in); Sheet sheet = book.getSheet("Formula(通常)"); Cell cell = POIUtils.getCell(sheet, new CellAddress("A2")); assertThat(cell.getCellType(), is(Cell.CELL_TYPE_BLANK)); CellFormatter formatter = mapper.getConig().getCellFormatter(); String value = formatter.format(cell); assertThat(value, is("")); } } /** * 式やメソッドが設定されていない場合 */ @Test(expected=AnnotationInvalidException.class) public void test_error_empty() throws Exception { // テストデータの作成 final FormulaSheet outSheet = new FormulaSheet(); // アノテーションの組み立て XmlInfo xmlInfo = createXml() .classInfo(createClass(FormulaSheet.class) .field(createField("c1") .override(true) .annotation(createAnnotation(XlsFormula.class) .buildAnnotation()) .buildField()) .buildClass()) .buildXml(); // ファイルへの書き込み XlsMapper mapper = new XlsMapper(); mapper.getConig().setContinueTypeBindFailure(true); File outFile = new File(OUT_DIR, "anno_Formula_out.xlsx"); try(InputStream template = new FileInputStream("src/test/data/anno_Formula_template.xlsx"); OutputStream out = new FileOutputStream(outFile)) { mapper.save(template, out, outSheet, xmlInfo.toInputStream()); } fail(); } /** * 式が不正 - 式言語として不正 */ @Test(expected=AnnotationInvalidException.class) public void test_error_wrongFormula_forExpression() throws Exception { // テストデータの作成 final FormulaSheet outSheet = new FormulaSheet(); // アノテーションの組み立て XmlInfo xmlInfo = createXml() .classInfo(createClass(FormulaSheet.class) .field(createField("c1") .override(true) .annotation(createAnnotation(XlsFormula.class) .attribute("value", "{test} ${hoge}") .buildAnnotation()) .buildField()) .buildClass()) .buildXml(); // ファイルへの書き込み XlsMapper mapper = new XlsMapper(); mapper.getConig().setContinueTypeBindFailure(true); File outFile = new File(OUT_DIR, "anno_Formula_out.xlsx"); try(InputStream template = new FileInputStream("src/test/data/anno_Formula_template.xlsx"); OutputStream out = new FileOutputStream(outFile)) { mapper.save(template, out, outSheet, xmlInfo.toInputStream()); } fail(); } /** * 式が不正 - Excelの数式として不正 */ @Test(expected=ConversionException.class) public void test_error_wrongFormula_forExcel() throws Exception { // テストデータの作成 final FormulaSheet outSheet = new FormulaSheet(); // アノテーションの組み立て XmlInfo xmlInfo = createXml() .classInfo(createClass(FormulaSheet.class) .field(createField("c1") .override(true) .annotation(createAnnotation(XlsFormula.class) .attribute("value", ")1+TESTA1)") .buildAnnotation()) .buildField()) .buildClass()) .buildXml(); // ファイルへの書き込み XlsMapper mapper = new XlsMapper(); mapper.getConig().setContinueTypeBindFailure(true); File outFile = new File(OUT_DIR, "anno_Formula_out.xlsx"); try(InputStream template = new FileInputStream("src/test/data/anno_Formula_template.xlsx"); OutputStream out = new FileOutputStream(outFile)) { mapper.save(template, out, outSheet, xmlInfo.toInputStream()); } fail(); } /** * 指定したメソッドが見つからない */ @Test(expected=AnnotationInvalidException.class) public void test_error_notFoundMethod() throws Exception { // テストデータの作成 final FormulaSheet outSheet = new FormulaSheet(); // アノテーションの組み立て XmlInfo xmlInfo = createXml() .classInfo(createClass(FormulaSheet.class) .field(createField("c1") .override(true) .annotation(createAnnotation(XlsFormula.class) .attribute("methodName", "hoge") .buildAnnotation()) .buildField()) .buildClass()) .buildXml(); // ファイルへの書き込み XlsMapper mapper = new XlsMapper(); mapper.getConig().setContinueTypeBindFailure(true); File outFile = new File(OUT_DIR, "anno_Formula_out.xlsx"); try(InputStream template = new FileInputStream("src/test/data/anno_Formula_template.xlsx"); OutputStream out = new FileOutputStream(outFile)) { mapper.save(template, out, outSheet, xmlInfo.toInputStream()); } fail(); } /** * サンプル */ @Test public void test_normal_sample() throws Exception { // テストデータの作成 final SampleSheet outSheet = new SampleSheet(); // 各人のレコード(合計値の設定は行わない。) outSheet.add(new SampleRecord().name("山田太郎").kokugo(90).sansu(85)); outSheet.add(new SampleRecord().name("鈴木一郎").kokugo(85).sansu(80)); outSheet.add(new SampleRecord().name("林三郎").kokugo(80).sansu(60)); // 平均値用のレコード(点数などのデータ部分はなし) outSheet.add(new SampleRecord().name("平均")); // ファイルへの書き込み XlsMapper mapper = new XlsMapper(); mapper.getConig().setContinueTypeBindFailure(true); File outFile = new File(OUT_DIR, "anno_Formula_out.xlsx"); try(InputStream template = new FileInputStream("src/test/data/anno_Formula_template.xlsx"); OutputStream out = new FileOutputStream(outFile)) { mapper.save(template, out, outSheet); } } @XlsSheet(name="Formula(通常)") private static class FormulaSheet { private Map<String, Point> positions; private Map<String, String> labels; @XlsCell(address="A2") @XlsFormula(value="1+2") private Double c1; private String getC1Formula(final Sheet sheet, final Cell cell, final Point point, final XlsMapperConfig config, final Object object) { assertThat(sheet, is(notNullValue())); assertThat(Utils.formatCellAddress(cell), is("A2")); assertThat(Utils.formatCellAddress(point), is("A2")); assertThat(config, is(notNullValue())); assertThat(object, is(nullValue())); return "SUM(D2:D3)"; } public FormulaSheet c1(Double c1) { this.c1 = c1; return this; } /** * 空の数式を返す。 * @param point * @return */ private String getEmptyFormula(final Point point) { return ""; } } @XlsSheet(name="Formula(サンプル)") private static class SampleSheet { // マッピングした位置情報 private Map<String, Point> positions; @XlsHorizontalRecords(tableLabel="成績一覧", bottom=2, terminal=RecordTerminal.Border, overRecord=OverRecordOperate.Insert) private List<SampleRecord> records; // レコードを追加する public void add(SampleRecord record) { if(records == null) { this.records = new ArrayList<>(); } // 自身のインスタンスを渡す record.setParent(this); // No.を自動的に振る record.setNo(records.size()+1); this.records.add(record); } public List<SampleRecord> getRecords() { return records; } } private static class SampleRecord { // マッピングした位置情報 private Map<String, Point> positions; // 親のBean情報 private SampleSheet parent; @XlsColumn(columnName="No.") private int no; @XlsColumn(columnName="名前") private String name; @XlsColumn(columnName="国語") @XlsFormula(methodName="getKyokaAvgFormula", primary=false) private Integer kokugo; @XlsColumn(columnName="算数") @XlsFormula(methodName="getKyokaAvgFormula", primary=false) private Integer sansu; @XlsColumn(columnName="合計") @XlsFormula(value="SUM(C{rowNumber}:D{rowNumber})", primary=true) private Integer sum; // 各教科の平均の数式を組み立てる public String getKyokaAvgFormula(Point point) { // レコード名が平均のときのみ数式を出力する if(!name.equals("平均")) { return null; } // レコードのサイズ(平均用のレコード行を覗いた値) final int dataSize = parent.getRecords().size() -1; // 列名 final String colAlpha = CellReference.convertNumToColString(point.x); // 平均値の開始/終了の行番号 final int startRowNumber = point.y - dataSize+1; final int endRowNumber = point.y; return String.format("AVERAGE(%s%d:%s%d)", colAlpha, startRowNumber, colAlpha, endRowNumber); } // 最後のレコードのときにセルの色を変更 @XlsPostSave public void handlePostSave(final Sheet sheet) { if(!name.equals("平均")) { return; } final Workbook book = sheet.getWorkbook(); for(Point address : positions.values()) { Cell cell = POIUtils.getCell(sheet, address); CellStyle style = book.createCellStyle(); style.cloneStyleFrom(cell.getCellStyle()); // 塗りつぶし style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); style.setFillPattern(CellStyle.SOLID_FOREGROUND); // 罫線の上部を変更 style.setBorderTop(CellStyle.BORDER_DOUBLE); cell.setCellStyle(style); } } public void setParent(SampleSheet parent) { this.parent = parent; } public void setNo(int no) { this.no = no; } public SampleRecord name(final String name) { this.name = name; return this; } public SampleRecord kokugo(final Integer kokugo) { this.kokugo = kokugo; return this; } public SampleRecord sansu(final Integer sansu) { this.sansu = sansu; return this; } } }