package com.github.mygreen.supercsv.cellprocessor; import static org.assertj.core.api.Assertions.*; import static com.github.mygreen.supercsv.tool.TestUtils.*; import static com.github.mygreen.supercsv.tool.HasCellProcessorAssert.*; import java.lang.annotation.Annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.Comparator; import java.util.Optional; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; import org.supercsv.cellprocessor.ift.CellProcessor; import org.supercsv.cellprocessor.ift.StringCellProcessor; import org.supercsv.exception.SuperCsvCellProcessorException; import org.supercsv.util.CsvContext; import com.github.mygreen.supercsv.annotation.CsvBean; import com.github.mygreen.supercsv.annotation.CsvColumn; import com.github.mygreen.supercsv.annotation.constraint.CsvConstraint; import com.github.mygreen.supercsv.annotation.constraint.CsvLengthBetween; import com.github.mygreen.supercsv.annotation.constraint.CsvLengthExact; import com.github.mygreen.supercsv.annotation.constraint.CsvLengthMax; import com.github.mygreen.supercsv.annotation.constraint.CsvLengthMin; import com.github.mygreen.supercsv.builder.ProcessorBuilderResolver; import com.github.mygreen.supercsv.builder.BuildCase; import com.github.mygreen.supercsv.builder.Configuration; import com.github.mygreen.supercsv.builder.FieldAccessor; import com.github.mygreen.supercsv.builder.standard.StringProcessorBuilder; import com.github.mygreen.supercsv.cellprocessor.constraint.LengthBetween; import com.github.mygreen.supercsv.cellprocessor.constraint.LengthBetweenFactory; import com.github.mygreen.supercsv.cellprocessor.constraint.LengthExact; import com.github.mygreen.supercsv.cellprocessor.constraint.LengthExactFactory; import com.github.mygreen.supercsv.cellprocessor.constraint.LengthMax; import com.github.mygreen.supercsv.cellprocessor.constraint.LengthMaxFactory; import com.github.mygreen.supercsv.cellprocessor.constraint.LengthMin; import com.github.mygreen.supercsv.cellprocessor.constraint.LengthMinFactory; import com.github.mygreen.supercsv.cellprocessor.format.TextFormatter; import com.github.mygreen.supercsv.exception.SuperCsvValidationException; /** * {@link ConstraintProcessorHandler}のテスタ * * @since 2.0 * @author T.TSUCHIE * */ public class ConstraintProcessorHandlerTest { @Rule public TestName name = new TestName(); private Configuration config; private ProcessorBuilderResolver builderResolver; private Comparator<Annotation> comparator; private ConstraintProcessorHandler handlerFactory; private final Class<?>[] groupEmpty = new Class[]{}; @Before public void setUp() throws Exception { this.config = new Configuration(); this.builderResolver = config.getBuilderResolver(); this.comparator = config.getAnnoationComparator(); this.handlerFactory = new ConstraintProcessorHandler(); } @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Repeatable(CsvCustomConstraint.List.class) @CsvConstraint(value=CustomConstraintFactory.class) public static @interface CsvCustomConstraint { String value(); Class<?>[] groups() default {}; int order() default 0; @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @interface List { CsvCustomConstraint[] value(); } } private static class CustomConstraintFactory implements ConstraintProcessorFactory<CsvCustomConstraint> { @Override public Optional<CellProcessor> create(CsvCustomConstraint anno, Optional<CellProcessor> next, FieldAccessor field, TextFormatter<?> formatter, Configuration config) { if(!String.class.isAssignableFrom(field.getType())) { // 検証対象のクラスタイプと一致しない場合は、弾きます。 return next; } // CellProcessorのインスタンスを作成します final CustomConstraint processor = next.map(n -> new CustomConstraint(anno.value(), n)) .orElseGet(() -> new CustomConstraint(anno.value())); return Optional.of(processor); } } private static class CustomConstraint extends ValidationCellProcessor implements StringCellProcessor { private String text; CustomConstraint(final String text) { super(); checkPreconditions(text); this.text = text; } CustomConstraint(final String text, final CellProcessor next) { super(next); checkPreconditions(text); this.text = text; } private static void checkPreconditions(final String text) { if(text == null) { throw new NullPointerException("text should not be null."); } else if(text.isEmpty()) { throw new NullPointerException("text should not be empty."); } } @Override public <T> T execute(final Object value, final CsvContext context) { if(value == null) { return next.execute(value, context); } final String result; if(value instanceof String) { result = (String)value; } else { // 検証対象のクラスタイプが不正な場合 throw new SuperCsvCellProcessorException(String.class, value, context, this); } // 最後が指定した値で終了するかどうか if(result.endsWith(text)) { // 正常な値の場合、次の処理に委譲します。 return next.execute(value, context); } // エラーがある場合、例外クラスを組み立てます。 throw createValidationException(context) .messageFormat("Not ends with %s.", text) .messageVariables("suffix", text) .build(); } } // テスト用のグループ1 private interface Group1 { } // テスト用のグループ2 private interface Group2 { } @CsvBean private static class TestCsv { @CsvColumn(number=1) String col_no_anno; @CsvColumn(number=2) @CsvLengthMax(5) String col_register; @CsvColumn(number=3) @CsvCustomConstraint(".csv") String col_custom; @CsvColumn(number=4) @CsvLengthMax(value=5, groups=Group1.class) @CsvCustomConstraint(".csv") String col_groups; @CsvColumn(number=5) @CsvLengthMax(value=5, order=1) @CsvCustomConstraint(value=".csv", order=2) String col_order1; @CsvColumn(number=6) @CsvLengthMax(value=5, order=2) @CsvCustomConstraint(value=".csv", order=1) String col_order2; @CsvColumn(number=7) @CsvLengthMax(value=5, order=1, cases={}) @CsvLengthMin(value=0, order=2, cases={BuildCase.Read}) @CsvLengthBetween(min=0, max=5, order=3, cases={BuildCase.Write}) @CsvLengthExact(value={3, 5}, order=5, cases={BuildCase.Read, BuildCase.Write}) String col_cases; } @Test public void testCreate_noAnno() { FieldAccessor field = getFieldAccessor(TestCsv.class, "col_no_anno", comparator); StringProcessorBuilder builder = (StringProcessorBuilder) builderResolver.resolve(String.class); TextFormatter<String> formatter = builder.getFormatter(field, config); Optional<CellProcessor> processor = handlerFactory.create(Optional.empty(), field, formatter, config, BuildCase.Read, groupEmpty); assertThat(processor).isEmpty(); } /** * 未登録のアノテーション */ @Test public void testCreate_unregister() { FieldAccessor field = getFieldAccessor(TestCsv.class, "col_register", comparator); StringProcessorBuilder builder = (StringProcessorBuilder) builderResolver.resolve(String.class); TextFormatter<String> formatter = builder.getFormatter(field, config); Optional<CellProcessor> processor = handlerFactory.create(Optional.empty(), field, formatter, config, BuildCase.Read, groupEmpty); assertThat(processor).isEmpty(); } /** * 登録済みのアノテーション */ @Test public void testCreate_register() { FieldAccessor field = getFieldAccessor(TestCsv.class, "col_register", comparator); StringProcessorBuilder builder = (StringProcessorBuilder) builderResolver.resolve(String.class); TextFormatter<String> formatter = builder.getFormatter(field, config); handlerFactory.register(CsvLengthMax.class, new LengthMaxFactory()); Optional<CellProcessor> processor = handlerFactory.create(Optional.empty(), field, formatter, config, BuildCase.Read, groupEmpty); printCellProcessorChain(processor, name.getMethodName()); CellProcessor actual = processor.get(); assertThat(actual).isInstanceOf(LengthMax.class); { String input = "sample.txt"; try { actual.execute(input, ANONYMOUS_CSVCONTEXT); } catch(Exception e) { assertThat(e).isInstanceOf(SuperCsvValidationException.class); SuperCsvValidationException validationException = (SuperCsvValidationException)e; assertThat(validationException.getProcessor()).isInstanceOf(LengthMax.class); } } } /** * 独自のアノテーション */ @Test public void testCreate_custom() { FieldAccessor field = getFieldAccessor(TestCsv.class, "col_custom", comparator); StringProcessorBuilder builder = (StringProcessorBuilder) builderResolver.resolve(String.class); TextFormatter<String> formatter = builder.getFormatter(field, config); Optional<CellProcessor> processor = handlerFactory.create(Optional.empty(), field, formatter, config, BuildCase.Read, groupEmpty); printCellProcessorChain(processor, name.getMethodName()); CellProcessor actual = processor.get(); assertThat(actual).isInstanceOf(CustomConstraint.class); { String input = "sample.txt"; try { actual.execute(input, ANONYMOUS_CSVCONTEXT); } catch(Exception e) { assertThat(e).isInstanceOf(SuperCsvValidationException.class); SuperCsvValidationException validationException = (SuperCsvValidationException)e; assertThat(validationException.getProcessor()).isInstanceOf(CustomConstraint.class); } } } /** * 属性groupsの指定 */ @Test public void testCreate_groups() { FieldAccessor field = getFieldAccessor(TestCsv.class, "col_groups", comparator); StringProcessorBuilder builder = (StringProcessorBuilder) builderResolver.resolve(String.class); TextFormatter<String> formatter = builder.getFormatter(field, config); handlerFactory.register(CsvLengthMax.class, new LengthMaxFactory()); { // グループの指定なし - デフォルトグループ Optional<CellProcessor> processor = handlerFactory.create(Optional.empty(), field, formatter, config, BuildCase.Read, groupEmpty); printCellProcessorChain(processor, name.getMethodName()); CellProcessor actual = processor.get(); assertThat(actual).isInstanceOf(CustomConstraint.class); String input = "sample.txt"; try { actual.execute(input, ANONYMOUS_CSVCONTEXT); } catch(Exception e) { assertThat(e).isInstanceOf(SuperCsvValidationException.class); SuperCsvValidationException validationException = (SuperCsvValidationException)e; assertThat(validationException.getProcessor()).isInstanceOf(CustomConstraint.class); } } { // グループの指定あり Optional<CellProcessor> processor = handlerFactory.create(Optional.empty(), field, formatter, config, BuildCase.Read, new Class[]{Group1.class}); printCellProcessorChain(processor, name.getMethodName()); CellProcessor actual = processor.get(); assertThat(actual).isInstanceOf(LengthMax.class); String input = "sample.txt"; try { actual.execute(input, ANONYMOUS_CSVCONTEXT); } catch(Exception e) { assertThat(e).isInstanceOf(SuperCsvValidationException.class); SuperCsvValidationException validationException = (SuperCsvValidationException)e; assertThat(validationException.getProcessor()).isInstanceOf(LengthMax.class); } } } /** * 属性orderの指定 1 */ @Test public void testCreate_order1() { FieldAccessor field = getFieldAccessor(TestCsv.class, "col_order1", comparator); StringProcessorBuilder builder = (StringProcessorBuilder) builderResolver.resolve(String.class); TextFormatter<String> formatter = builder.getFormatter(field, config); handlerFactory.register(CsvLengthMax.class, new LengthMaxFactory()); Optional<CellProcessor> processor = handlerFactory.create(Optional.empty(), field, formatter, config, BuildCase.Read, groupEmpty); printCellProcessorChain(processor, name.getMethodName()); CellProcessor actual = processor.get(); assertThat(actual).isInstanceOf(LengthMax.class); String input = "sample.txt"; try { actual.execute(input, ANONYMOUS_CSVCONTEXT); } catch(Exception e) { assertThat(e).isInstanceOf(SuperCsvValidationException.class); SuperCsvValidationException validationException = (SuperCsvValidationException)e; assertThat(validationException.getProcessor()).isInstanceOf(LengthMax.class); } } /** * 属性orderの指定 2 */ @Test public void testCreate_order2() { FieldAccessor field = getFieldAccessor(TestCsv.class, "col_order2", comparator); StringProcessorBuilder builder = (StringProcessorBuilder) builderResolver.resolve(String.class); TextFormatter<String> formatter = builder.getFormatter(field, config); handlerFactory.register(CsvLengthMax.class, new LengthMaxFactory()); Optional<CellProcessor> processor = handlerFactory.create(Optional.empty(), field, formatter, config, BuildCase.Read, groupEmpty); printCellProcessorChain(processor, name.getMethodName()); CellProcessor actual = processor.get(); assertThat(actual).isInstanceOf(CustomConstraint.class); String input = "sample.txt"; try { actual.execute(input, ANONYMOUS_CSVCONTEXT); } catch(Exception e) { assertThat(e).isInstanceOf(SuperCsvValidationException.class); SuperCsvValidationException validationException = (SuperCsvValidationException)e; assertThat(validationException.getProcessor()).isInstanceOf(CustomConstraint.class); } } /** * 属性casesの指定 - 読み込み時の場合 */ @Test public void testCreate_cases_read() { FieldAccessor field = getFieldAccessor(TestCsv.class, "col_cases", comparator); StringProcessorBuilder builder = (StringProcessorBuilder) builderResolver.resolve(String.class); TextFormatter<String> formatter = builder.getFormatter(field, config); handlerFactory.register(CsvLengthMax.class, new LengthMaxFactory()); handlerFactory.register(CsvLengthMin.class, new LengthMinFactory()); handlerFactory.register(CsvLengthBetween.class, new LengthBetweenFactory()); handlerFactory.register(CsvLengthExact.class, new LengthExactFactory()); Optional<CellProcessor> processor = handlerFactory.create(Optional.empty(), field, formatter, config, BuildCase.Read, groupEmpty); printCellProcessorChain(processor, name.getMethodName()); CellProcessor actual = processor.get(); // 指定したcasesを持つかどうか assertThat(actual).hasCellProcessor(LengthMax.class) .hasCellProcessor(LengthMin.class) .hasCellProcessor(LengthExact.class); } /** * 属性casesの指定 - 書き込み時の場合 */ @Test public void testCreate_cases_write() { FieldAccessor field = getFieldAccessor(TestCsv.class, "col_cases", comparator); StringProcessorBuilder builder = (StringProcessorBuilder) builderResolver.resolve(String.class); TextFormatter<String> formatter = builder.getFormatter(field, config); handlerFactory.register(CsvLengthMax.class, new LengthMaxFactory()); handlerFactory.register(CsvLengthMin.class, new LengthMinFactory()); handlerFactory.register(CsvLengthBetween.class, new LengthBetweenFactory()); handlerFactory.register(CsvLengthExact.class, new LengthExactFactory()); Optional<CellProcessor> processor = handlerFactory.create(Optional.empty(), field, formatter, config, BuildCase.Write, groupEmpty); printCellProcessorChain(processor, name.getMethodName()); CellProcessor actual = processor.get(); // 指定したcasesを持つかどうか assertThat(actual).hasCellProcessor(LengthMax.class) .hasCellProcessor(LengthBetween.class) .hasCellProcessor(LengthExact.class); } }