View Javadoc
1   /*
2    * Copyright 2013 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.orangesignal.csv.io;
18  
19  import static com.orangesignal.csv.bean.CsvEntityTemplate.getPosition;
20  import static com.orangesignal.csv.bean.FieldUtils.setFieldValue;
21  
22  import java.io.Closeable;
23  import java.io.IOException;
24  import java.lang.reflect.Array;
25  import java.lang.reflect.Field;
26  import java.util.Collections;
27  import java.util.List;
28  
29  import com.orangesignal.csv.CsvReader;
30  import com.orangesignal.csv.annotation.CsvColumn;
31  import com.orangesignal.csv.annotation.CsvColumnException;
32  import com.orangesignal.csv.annotation.CsvColumns;
33  import com.orangesignal.csv.annotation.CsvEntity;
34  import com.orangesignal.csv.bean.CsvEntityTemplate;
35  
36  /**
37   * 区切り文字形式データ注釈要素 {@link CsvEntity} で注釈付けされた Java プログラム要素で区切り文字形式データアクセスを行う区切り文字形式入力ストリームを提供します。
38   * 
39   * @author Koji Sugisawa
40   * @since 1.4.0
41   */
42  public class CsvEntityReader<T> implements Closeable {
43  
44  	/**
45  	 * 区切り文字形式入力ストリームを保持します。
46  	 */
47  	private CsvReader reader;
48  
49  	/**
50  	 * Java プログラム要素操作の簡素化ヘルパーを保持します。
51  	 */
52  	private final CsvEntityTemplate<T> template;
53  
54  	/**
55  	 * 項目名のリストを保持します。
56  	 */
57  	private List<String> columnNames;
58  
59  	private Field[] fields;
60  
61  	// ------------------------------------------------------------------------
62  	// 利便性のための静的メソッド
63  
64  	/**
65  	 * 新しい {@link CsvEntityReader} のインスタンスを返します。
66  	 * このメソッドは利便性のために提供しています。
67  	 * 
68  	 * @param reader 区切り文字形式入力ストリーム
69  	 * @param entityClass 区切り文字形式データ注釈要素 {@link CsvEntity} で注釈付けされた Java プログラム要素の型
70  	 * @return 新しい {@link CsvEntityReader} のインスタンス
71  	 * @throws IllegalArgumentException {@code reader} または {@code entityClass} が {@code null} の場合。
72  	 */
73  	public static <T> CsvEntityReader<T> newInstance(final CsvReader reader, final Class<T> entityClass) {
74  		return new CsvEntityReader<T>(reader, entityClass);
75  	}
76  
77  	/**
78  	 * 新しい {@link CsvEntityReader} のインスタンスを返します。
79  	 * このメソッドは利便性のために提供しています。
80  	 * 
81  	 * @param reader 区切り文字形式入力ストリーム
82  	 * @param template Java プログラム要素操作の簡素化ヘルパー
83  	 * @return 新しい {@link CsvEntityReader} のインスタンス
84  	 * @throws IllegalArgumentException {@code reader} または {@code template} が {@code null} の場合。
85  	 */
86  	public static <T> CsvEntityReader<T> newInstance(final CsvReader reader, final CsvEntityTemplate<T> template) {
87  		return new CsvEntityReader<T>(reader, template);
88  	}
89  
90  	// ------------------------------------------------------------------------
91  	// コンストラクタ
92  
93  	/**
94  	 * 指定された区切り文字形式入力ストリームと Java プログラム要素の型を使用して、このクラスを構築するコンストラクタです。
95  	 * 
96  	 * @param reader 区切り文字形式入力ストリーム
97  	 * @param entityClass 区切り文字形式データ注釈要素 {@link CsvEntity} で注釈付けされた Java プログラム要素の型
98  	 * @throws IllegalArgumentException {@code reader} または {@code entityClass} が {@code null} の場合。
99  	 */
100 	public CsvEntityReader(final CsvReader reader, final Class<T> entityClass) {
101 		this(reader, new CsvEntityTemplate<T>(entityClass));
102 	}
103 
104 	/**
105 	 * 指定された区切り文字形式入力ストリームと Java プログラム要素操作の簡素化ヘルパーを使用して、このクラスを構築するコンストラクタです。
106 	 * 
107 	 * @param reader 区切り文字形式入力ストリーム
108 	 * @param template Java プログラム要素操作の簡素化ヘルパー
109 	 * @throws IllegalArgumentException {@code reader} または {@code template} が {@code null} の場合。
110 	 */
111 	public CsvEntityReader(final CsvReader reader, final CsvEntityTemplate<T> template) {
112 		if (reader == null) {
113 			throw new IllegalArgumentException("CsvReader must not be null");
114 		}
115 		if (template == null) {
116 			throw new IllegalArgumentException("CsvEntityTemplate must not be null");
117 		}
118 		this.reader = reader;
119 		this.template = template;
120 	}
121 
122 	// ------------------------------------------------------------------------
123 	// プライベート メソッド
124 
125 	/**
126 	 * Checks to make sure that the stream has not been closed
127 	 */
128 	private void ensureOpen() throws IOException {
129 		if (reader == null) {
130 			throw new IOException("CsvReader closed");
131 		}
132 	}
133 
134 	private void ensureHeader() throws IOException {
135 		synchronized (this) {
136 			if (columnNames == null) {
137 				// ヘッダ行が有効な場合は項目名の一覧を取得します。
138 				final List<String> names;
139 				if (template.getType().getAnnotation(CsvEntity.class).header()) {
140 					names = reader.readValues();
141 				} else {
142 					names = template.createColumnNames();
143 				}
144 
145 				fields = template.getType().getDeclaredFields();
146 				template.prepare(names, fields);
147 				columnNames = Collections.unmodifiableList(names);
148 			}
149 		}
150 	}
151 
152 	// ------------------------------------------------------------------------
153 	// オーバーライド メソッド
154 
155 	@Override
156 	public void close() throws IOException {
157 		synchronized (this) {
158 			ensureOpen();
159 			reader.close();
160 			reader = null;
161 			columnNames = null;
162 			fields = null;
163 		}
164 	}
165 
166 	// ------------------------------------------------------------------------
167 	// パブリック メソッド
168 
169 	/**
170 	 * 項目名のリストを返します。
171 	 * 
172 	 * @return 項目名のリスト
173 	 * @throws IOException 入出力エラーが発生した場合
174 	 */
175 	public List<String> getHeader() throws IOException {
176 		synchronized (this) {
177 			ensureOpen();
178 			ensureHeader();
179 			return columnNames;
180 		}
181 	}
182 
183 	/**
184 	 * 論理行を読込み Java プログラム要素として返します。
185 	 *
186 	 * @return Java プログラム要素。ストリームの終わりに達した場合は {@code null}
187 	 * @throws CsvColumnException 区切り文字形式のデータ項目の検証操作実行中にエラーが発生した場合
188 	 * @throws IOException 入出力エラーが発生した場合
189 	 */
190 	public T read() throws IOException {
191 		synchronized (this) {
192 			ensureOpen();
193 			ensureHeader();
194 			final List<String> values = nextValues();
195 			if (values == null) {
196 				return null;
197 			}
198 			return convert(values);
199 		}
200 	}
201 
202 	/**
203 	 * 論理行を読込み CSV トークンの値をリストとして返します。
204 	 * 
205 	 * @return CSV トークンの値をリスト。ストリームの終わりに達している場合は {@code null}
206 	 * @throws IOException 入出力エラーが発生した場合
207 	 */
208 	public List<String> readValues() throws IOException {
209 		synchronized (this) {
210 			ensureOpen();
211 			ensureHeader();
212 			return nextValues();
213 		}
214 	}
215 
216 	/**
217 	 * 指定された CSV トークンの値をリストを Java プログラム要素へ変換して返します。
218 	 * 
219 	 * @param values CSV トークンの値をリスト
220 	 * @return 変換された Java プログラム要素
221 	 * @throws CsvColumnException 区切り文字形式のデータ項目の検証操作実行中にエラーが発生した場合
222 	 * @throws IOException 入出力エラーが発生した場合
223 	 */
224 	public T toEntity(final List<String> values) throws IOException {
225 		synchronized (this) {
226 			ensureOpen();
227 			ensureHeader();
228 			return convert(values);
229 		}
230 	}
231 
232 	private List<String> nextValues() throws IOException {
233 		List<String> values;
234 		while ((values = reader.readValues()) != null) {
235 			if (template.isAccept(columnNames, values)) {
236 				continue;
237 			}
238 			return values;
239 		}
240 		return null;
241 	}
242 
243 	private T convert(final List<String> values) throws IOException {
244 		final T entity = template.createBean();
245 		for (final Field field : fields) {
246 			Object object = null;
247 			final CsvColumns columns = field.getAnnotation(CsvColumns.class);
248 			if (columns != null) {
249 				if (field.getType().isArray()) {
250 					object = Array.newInstance(field.getType().getComponentType(), columns.value().length);
251 					int arrayIndex = 0;
252 					for (final CsvColumn column : columns.value()) {
253 						if (!column.access().isReadable()) {
254 							continue;
255 						}
256 						String value;
257 						final int pos = getPosition(column, field, columnNames);
258 						if (pos != -1) {
259 							value = values.get(pos);
260 						} else {
261 							value = null;
262 						}
263 						if (value == null && !column.defaultValue().isEmpty()) {
264 							// デフォルト値が指定されていて、値がない場合はデフォルト値を代入します。
265 							value = column.defaultValue();
266 						}
267 						if (value == null && column.required()) {
268 							// 必須項目の場合に、値がない場合は例外をスローします。
269 							throw new CsvColumnException(String.format("[line: %d] %s must not be null", reader.getStartLineNumber(), columnNames.get(pos)), values);
270 						}
271 						Array.set(object, arrayIndex++, template.stringToObject(field, value));
272 					}
273 				} else {
274 					final StringBuilder sb = new StringBuilder();
275 					for (final CsvColumn column : columns.value()) {
276 						if (!column.access().isReadable()) {
277 							continue;
278 						}
279 						final int pos = getPosition(column, field, columnNames);
280 						if (pos != -1) {
281 							final String s = values.get(pos);
282 							if (s != null) {
283 								sb.append(s);
284 							} else if (!column.defaultValue().isEmpty()) {
285 								// デフォルト値が指定されていて、値がない場合はデフォルト値を代入します。
286 								sb.append(column.defaultValue());
287 							} else if (column.required()) {
288 								// 必須項目の場合に、値がない場合は例外をスローします。
289 								throw new CsvColumnException(String.format("[line: %d] %s must not be null", reader.getStartLineNumber(), columnNames.get(pos)), values);
290 							}
291 						}
292 					}
293 					object = template.stringToObject(field, sb.toString());
294 				}
295 			}
296 			final CsvColumn column = field.getAnnotation(CsvColumn.class);
297 			if (column != null && column.access().isReadable()) {
298 				final int pos = getPosition(column, field, columnNames);
299 				if (pos != -1) {
300 					String value = values.get(pos);
301 					if (value == null && !column.defaultValue().isEmpty()) {
302 						// デフォルト値が指定されていて、値がない場合はデフォルト値を代入します。
303 						value = column.defaultValue();
304 					}
305 					if (value == null && column.required()) {
306 						// 必須項目の場合に、値がない場合は例外をスローします。
307 						throw new CsvColumnException(String.format("[line: %d] %s must not be null", reader.getStartLineNumber(), columnNames.get(pos)), values);
308 					}
309 					object = template.stringToObject(field, value);
310 				}
311 			}
312 			if (object != null) {
313 				setFieldValue(entity, field, object);
314 			}
315 		}
316 		return entity;
317 	}
318 
319 	// ------------------------------------------------------------------------
320 	// getter / setter
321 
322 	/**
323 	 * Java プログラム要素操作の簡素化ヘルパーを返します。
324 	 * 
325 	 * @return Java プログラム要素操作の簡素化ヘルパー
326 	 * @since 2.1
327 	 */
328 	public CsvEntityTemplate<T> getTemplate() {
329 		return template;
330 	}
331 
332 }