View Javadoc
1   package com.github.mygreen.supercsv.builder;
2   
3   import java.util.List;
4   import java.util.Optional;
5   import java.util.TreeSet;
6   import java.util.stream.Collectors;
7   
8   import org.supercsv.exception.SuperCsvException;
9   
10  import com.github.mygreen.supercsv.annotation.CsvColumn;
11  import com.github.mygreen.supercsv.annotation.CsvPartial;
12  import com.github.mygreen.supercsv.annotation.conversion.CsvFixedSize;
13  import com.github.mygreen.supercsv.cellprocessor.conversion.PaddingProcessor;
14  import com.github.mygreen.supercsv.exception.SuperCsvInvalidAnnotationException;
15  import com.github.mygreen.supercsv.io.LazyCsvAnnotationBeanReader;
16  import com.github.mygreen.supercsv.io.LazyCsvAnnotationBeanWriter;
17  import com.github.mygreen.supercsv.localization.MessageBuilder;
18  
19  /**
20   * {@link BeanMapping}を組み立てる時のヘルパクラス。
21   *
22   * @version 2.5
23   * @since 2.1
24   * @author T.TSUCHIE
25   *
26   */
27  public class BeanMappingFactoryHelper {
28  
29      /**
30       * カラム番号が重複しているかチェックする。また、番号が1以上かもチェックする。
31       * @param beanType Beanタイプ
32       * @param list カラム情報の一覧
33       * @return チェック済みの番号
34       * @throws SuperCsvInvalidAnnotationException {@link CsvColumn}の定義が間違っている場合
35       */
36      public static TreeSet<Integer> validateDuplicatedColumnNumber(final Class<?> beanType, final List<ColumnMapping> list) {
37  
38          final TreeSet<Integer> checkedNumber = new TreeSet<>();
39          final TreeSet<Integer> duplicatedNumbers = new TreeSet<>();
40          for(ColumnMapping columnMapping : list) {
41  
42              if(checkedNumber.contains(columnMapping.getNumber())) {
43                  duplicatedNumbers.add(columnMapping.getNumber());
44              }
45              checkedNumber.add(columnMapping.getNumber());
46  
47          }
48  
49          if(!duplicatedNumbers.isEmpty()) {
50              // 重複している 属性 numberが存在する場合
51              throw new SuperCsvInvalidAnnotationException(MessageBuilder.create("anno.attr.duplicated")
52                      .var("property", beanType.getName())
53                      .varWithAnno("anno", CsvColumn.class)
54                      .var("attrName", "number")
55                      .var("attrValues", duplicatedNumbers)
56                      .format());
57          }
58  
59          // カラム番号が1以上かチェックする
60          final int minColumnNumber = checkedNumber.first();
61          if(minColumnNumber <= 0) {
62              throw new SuperCsvInvalidAnnotationException(MessageBuilder.create("anno.attr.min")
63                      .var("property", beanType.getName())
64                      .varWithAnno("anno", CsvColumn.class)
65                      .var("attrName", "number")
66                      .var("attrValue", minColumnNumber)
67                      .var("min", 1)
68                      .format());
69  
70          }
71  
72          return checkedNumber;
73      }
74  
75      /**
76       * 欠けているカラム番号がある場合、その番号を持つダミーのカラムを追加する。
77       * @param beanType Beanタイプ
78       * @param list カラム情報の一覧
79       * @param partialAnno Beanに設定されているアノテーション{@link CsvPartial}の情報。
80       * @param suppliedHeaders 提供されたヘッダー。提供されてない場合は、長さ0の配列。
81       * @param configuration 設定情報
82       * @return
83       */
84      public static TreeSet<Integer> supplyLackedNumberMappingColumn(final Class<?> beanType, final List<ColumnMapping> list,
85              final Optional<CsvPartial> partialAnno, final String[] suppliedHeaders, final Configuration configuration) {
86  
87          final TreeSet<Integer> checkedNumber = list.stream()
88                  .filter(col -> col.isDeterminedNumber())
89                  .map(col -> col.getNumber())
90                  .collect(Collectors.toCollection(TreeSet::new));
91  
92          // 定義されている列番号の最大値
93          final int maxColumnNumber = checkedNumber.last();
94  
95          // Beanに定義されていない欠けているカラム番号の取得
96          final TreeSet<Integer> lackedNumbers = new TreeSet<Integer>();
97          for(int i=1; i <= maxColumnNumber; i++) {
98              if(!checkedNumber.contains(i)) {
99                  lackedNumbers.add(i);
100             }
101         }
102 
103         // 定義されているカラム番号より、大きなカラム番号を持つカラム情報の補足
104         if(partialAnno.isPresent()) {
105 
106             final int partialColumnSize = partialAnno.get().columnSize();
107             if(maxColumnNumber > partialColumnSize) {
108                 throw new SuperCsvInvalidAnnotationException(partialAnno.get(), MessageBuilder.create("anno.CsvPartial.columSizeMin")
109                         .var("property", beanType.getName())
110                         .var("columnSize", partialColumnSize)
111                         .var("maxColumnNumber", maxColumnNumber)
112                         .format());
113 
114             }
115 
116             if(maxColumnNumber < partialColumnSize) {
117                 for(int i= maxColumnNumber+1; i <= partialColumnSize; i++) {
118                     lackedNumbers.add(i);
119                 }
120             }
121 
122         }
123 
124         // 不足分のカラムがある場合は、部分的な読み書き用カラムとして追加する
125         if(lackedNumbers.size() > 0) {
126 
127             for(int number : lackedNumbers) {
128                 list.add(createPartialColumnMapping(number, partialAnno, getSuppliedHeaders(suppliedHeaders, number), configuration));
129             }
130 
131             list.sort(null);
132         }
133 
134         return lackedNumbers;
135 
136     }
137 
138     /**
139      * 提供されたヘッダーから該当するカラム番号のヘッダーを取得する。
140      * @param suppliedHeaders 提供されたヘッダー。提供されてない場合は、長さ0の配列。
141      * @param columnNumber カラム番号。1から始まる。
142      * @return 該当するカラムのヘッダー。見つからない場合は空を返す。
143      */
144     private static Optional<String> getSuppliedHeaders(final String[] suppliedHeaders, final int columnNumber) {
145 
146         final int length = suppliedHeaders.length;
147         if(length == 0) {
148             return Optional.empty();
149         }
150 
151         if(columnNumber < length) {
152             return Optional.ofNullable(suppliedHeaders[columnNumber-1]);
153         }
154 
155         return Optional.empty();
156 
157     }
158 
159     /**
160      * 部分的なカラムの場合の作成
161      * @param columnNumber 列番号
162      * @param partialAnno アノテーション {@literal @CsvPartial}のインスタンス
163      * @param suppliedHeader 補完対象のヘッダーの値
164      * @param configuration 設定情報
165      * @return 部分的なカラム情報。
166      */
167     private static ColumnMapping createPartialColumnMapping(int columnNumber, final Optional<CsvPartial> partialAnno,
168             final Optional<String> suppliedHeader, final Configuration configuration) {
169 
170         final ColumnMappingumnMapping.html#ColumnMapping">ColumnMapping columnMapping = new ColumnMapping();
171         columnMapping.setNumber(columnNumber);
172         columnMapping.setPartialized(true);
173 
174         String label = String.format("column%d", columnNumber);
175         if(suppliedHeader.isPresent()) {
176             label = suppliedHeader.get();
177         }
178         
179         Optional<CsvFixedSize> fixedSizeAnno = Optional.empty();
180 
181         if(partialAnno.isPresent()) {
182             for(CsvPartial.Header header : partialAnno.get().headers()) {
183                 if(header.number() == columnNumber) {
184                     label = header.label();
185                     
186                     if(header.fixedSize().length > 0) {
187                         // 固定長の設定がある場合
188                         fixedSizeAnno = Optional.ofNullable(header.fixedSize()[0]);
189                     }
190                     break;
191                 }
192             }
193         }
194         
195         columnMapping.setLabel(label);
196         
197         if(fixedSizeAnno.isPresent()) {
198             FixedSizeColumnProperty fixedSizeProperty = createFixedSizeColumnProperty(fixedSizeAnno.get(), configuration);
199             columnMapping.setFixedSizeProperty(fixedSizeProperty);
200         }
201 
202         return columnMapping;
203 
204     }
205 
206     /**
207      * カラム番号が決定していないカラムをチェックする。
208      * <p>{@link LazyCsvAnnotationBeanReader}/{@link LazyCsvAnnotationBeanWriter}において、
209      *    CSVファイルや初期化時のヘッダーが不正により、該当するラベルがヘッダーに見つからないときをチェックする。
210      * </p>
211      *
212      * @since 2.2
213      * @param beanType Beanタイプ
214      * @param list カラム情報の一覧
215      * @param headers ヘッダー
216      * @throws SuperCsvException カラム番号が決定していないとき
217      */
218     public static void validateNonDeterminedColumnNumber(final Class<?> beanType, final List<ColumnMapping> list,
219             String[] headers) {
220 
221         final List<String> nonDeterminedLabels = list.stream()
222                 .filter(col -> !col.isDeterminedNumber())
223                 .map(col -> col.getLabel())
224                 .collect(Collectors.toList());
225 
226         if(!nonDeterminedLabels.isEmpty()) {
227 
228             throw new SuperCsvException(MessageBuilder.create("lazy.noDeteminedColumns")
229                     .var("property", beanType.getName())
230                     .var("labels", nonDeterminedLabels)
231                     .var("headers", headers)
232                     .format());
233 
234         }
235 
236     }
237     
238     /**
239      * 固定長のアノテーション{@link CsvFixedSize}から、固定長のプロパティを作成する。
240      * 
241      * @since 2.5
242      * @param fixedAnno 固定長のアノテーション
243      * @param configuration 設定情報
244      * @return 固定長カラムのプロパティ。
245      */
246     public static FixedSizeColumnProperty createFixedSizeColumnProperty(final CsvFixedSize fixedAnno, final Configuration configuration) {
247         
248         FixedSizeColumnPropertyoperty.html#FixedSizeColumnProperty">FixedSizeColumnProperty fixedSizeProperty = new FixedSizeColumnProperty(fixedAnno.size());
249         fixedSizeProperty.setPadChar(fixedAnno.padChar());
250         fixedSizeProperty.setChopped(fixedAnno.chopped());
251         fixedSizeProperty.setRightAlign(fixedAnno.rightAlign());
252         
253         fixedSizeProperty.setPaddingProcessor((PaddingProcessor)configuration.getBeanFactory().create(fixedAnno.paddingProcessor()));
254         
255         return fixedSizeProperty;
256     }
257 
258 }