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 java.io.Closeable;
20  import java.io.IOException;
21  import java.util.Collections;
22  import java.util.LinkedHashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import com.orangesignal.csv.CsvReader;
27  import com.orangesignal.csv.filters.CsvNamedValueFilter;
28  
29  /**
30   * 項目名と項目値のマップで区切り文字形式データアクセスを行う区切り文字形式入力ストリームを提供します。
31   * 
32   * @author Koji Sugisawa
33   * @since 1.4.0
34   */
35  public class CsvColumnNameMapReader implements Closeable {
36  
37  	/**
38  	 * 区切り文字形式入力ストリームを保持します。
39  	 */
40  	private CsvReader reader;
41  
42  	/**
43  	 * 項目名のリストを保持します。
44  	 */
45  	private List<String> columnNames;
46  
47  	/**
48  	 * 項目名の数を一時的に保存します。
49  	 */
50  	private int columnCount = -1;
51  
52  	/**
53  	 * 項目名のマップを一時的に保存します。
54  	 */
55  	private Map<String, String> base;
56  
57  	/**
58  	 * 区切り文字形式データフィルタを保持します。
59  	 */
60  	private CsvNamedValueFilter filter;
61  
62  	// ------------------------------------------------------------------------
63  	// コンストラクタ
64  
65  	/**
66  	 * 指定された区切り文字形式入力ストリームを使用して、このクラスを構築するコンストラクタです。
67  	 * 
68  	 * @param reader 区切り文字形式入力ストリーム
69  	 * @throws IllegalArgumentException {@code reader} が {@code null} の場合。
70  	 */
71  	public CsvColumnNameMapReader(final CsvReader reader) {
72  		this(reader, null);
73  	}
74  
75  	/**
76  	 * 指定された区切り文字形式入力ストリームと項目名のリストを使用して、このクラスを構築するコンストラクタです。
77  	 * 
78  	 * @param reader 区切り文字形式入力ストリーム
79  	 * @param columnNames 項目名のリスト
80  	 * @throws IllegalArgumentException {@code reader} が {@code null} の場合。
81  	 */
82  	public CsvColumnNameMapReader(final CsvReader reader, final List<String> columnNames) {
83  		if (reader == null) {
84  			throw new IllegalArgumentException("CsvReader must not be null");
85  		}
86  		this.reader = reader;
87  
88  		if (columnNames != null) {
89  			this.columnNames = Collections.unmodifiableList(columnNames);
90  		}
91  	}
92  
93  	// ------------------------------------------------------------------------
94  	// プライベート メソッド
95  
96  	/**
97  	 * Checks to make sure that the stream has not been closed
98  	 */
99  	private void ensureOpen() throws IOException {
100 		if (reader == null) {
101 			throw new IOException("CsvReader closed");
102 		}
103 	}
104 
105 	private void ensureHeader() throws IOException {
106 		synchronized (this) {
107 			if (columnNames == null) {
108 				columnNames = Collections.unmodifiableList(reader.readValues());
109 				if (columnNames == null) {
110 					// ヘッダがない場合は例外をスローします。
111 					throw new IOException("No header is available");
112 				}
113 			}
114 			if (columnCount == -1) {
115 				// ヘッダ部を処理します。
116 				columnCount = columnNames.size();
117 				base = new LinkedHashMap<String, String>(columnCount);
118 				for (final String columnName : columnNames) {
119 					base.put(columnName, null);
120 				}
121 			}
122 		}
123 	}
124 
125 	// ------------------------------------------------------------------------
126 	// オーバーライド メソッド
127 
128 	@Override
129 	public void close() throws IOException {
130 		synchronized (this) {
131 			ensureOpen();
132 			reader.close();
133 			reader = null;
134 			columnNames = null;
135 			columnCount = -1;
136 			base = null;
137 		}
138 	}
139 
140 	// ------------------------------------------------------------------------
141 	// パブリック メソッド
142 
143 	/**
144 	 * 項目名のリストを返します。
145 	 * 
146 	 * @return 項目名のリスト
147 	 * @throws IOException 入出力エラーが発生した場合
148 	 */
149 	public List<String> getHeader() throws IOException {
150 		synchronized (this) {
151 			ensureOpen();
152 			ensureHeader();
153 			return columnNames;
154 		}
155 	}
156 
157 	/**
158 	 * 論理行を読込み項目名と項目値のマップとして返します。
159 	 *
160 	 * @return 項目名と項目値のマップ。ストリームの終わりに達した場合は {@code null}
161 	 * @throws IOException 入出力エラーが発生した場合
162 	 */
163 	public Map<String, String> read() throws IOException {
164 		synchronized (this) {
165 			ensureOpen();
166 			ensureHeader();
167 			final List<String> values = nextValues();
168 			if (values == null) {
169 				return null;
170 			}
171 			return convert(values);
172 		}
173 	}
174 
175 	/**
176 	 * 論理行を読込み CSV トークンの値をリストとして返します。
177 	 * 
178 	 * @return CSV トークンの値をリスト。ストリームの終わりに達している場合は {@code null}
179 	 * @throws IOException 入出力エラーが発生した場合
180 	 */
181 	public List<String> readValues() throws IOException {
182 		synchronized (this) {
183 			ensureOpen();
184 			ensureHeader();
185 			return nextValues();
186 		}
187 	}
188 
189 	/**
190 	 * 指定された CSV トークンの値をリストを項目名と項目値のマップへ変換して返します。
191 	 * 
192 	 * @param values CSV トークンの値をリスト
193 	 * @return 変換された項目名と項目値のマップ
194 	 * @throws IOException 入出力エラーが発生した場合
195 	 */
196 	public Map<String, String> toMap(final List<String> values) throws IOException {
197 		synchronized (this) {
198 			ensureOpen();
199 			ensureHeader();
200 			return convert(values);
201 		}
202 	}
203 
204 	private List<String> nextValues() throws IOException {
205 		List<String> values;
206 		while ((values = reader.readValues()) != null) {
207 			if (filter != null && !filter.accept(columnNames, values)) {
208 				continue;
209 			}
210 			return values;
211 		}
212 		return null;
213 	}
214 
215 	private Map<String, String> convert(final List<String> values) {
216 		final Map<String, String> map = new LinkedHashMap<String, String>(base);
217 		final int len = Math.min(columnCount, values.size());
218 		for (int pos = 0; pos < len; pos++) {
219 			map.put(columnNames.get(pos), values.get(pos));
220 		}
221 		return map;
222 	}
223 
224 	// ------------------------------------------------------------------------
225 	// セッター / ゲッター
226 
227 	/**
228 	 * 区切り文字形式データフィルタを返します。
229 	 * 
230 	 * @return 区切り文字形式データフィルタ。または {@code null}
231 	 */
232 	public CsvNamedValueFilter getFilter() {
233 		return filter;
234 	}
235 
236 	/**
237 	 * 区切り文字形式データフィルタを設定します。
238 	 * 
239 	 * @param filter 区切り文字形式データフィルタ
240 	 */
241 	public void setFilter(final CsvNamedValueFilter filter) {
242 		synchronized (this) {
243 			this.filter = filter;
244 		}
245 	}
246 
247 }