1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.dict.mapping;
17
18 import java.io.BufferedReader;
19 import java.io.BufferedWriter;
20 import java.io.Closeable;
21 import java.io.File;
22 import java.io.FileOutputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.io.OutputStreamWriter;
27 import java.io.Writer;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Date;
31 import java.util.List;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34
35 import org.apache.logging.log4j.LogManager;
36 import org.apache.logging.log4j.Logger;
37 import org.codelibs.core.io.CloseableUtil;
38 import org.codelibs.core.lang.StringUtil;
39 import org.codelibs.curl.CurlResponse;
40 import org.codelibs.fess.Constants;
41 import org.codelibs.fess.dict.DictionaryException;
42 import org.codelibs.fess.dict.DictionaryFile;
43 import org.codelibs.fess.util.ComponentUtil;
44 import org.dbflute.optional.OptionalEntity;
45
46
47
48
49
50
51
52
53
54
55 public class CharMappingFile extends DictionaryFile<CharMappingItem> {
56
57 private static final Logger logger = LogManager.getLogger(CharMappingFile.class);
58
59
60 private static final String MAPPING = "mapping";
61
62
63 List<CharMappingItem> mappingItemList;
64
65
66
67
68
69
70
71
72 public CharMappingFile(final String id, final String path, final Date timestamp) {
73 super(id, path, timestamp);
74 }
75
76
77
78
79
80
81 @Override
82 public String getType() {
83 return MAPPING;
84 }
85
86
87
88
89
90
91 @Override
92 public String getPath() {
93 return path;
94 }
95
96
97
98
99
100
101
102 @Override
103 public OptionalEntity<CharMappingItem> get(final long id) {
104 if (mappingItemList == null) {
105 reload(null);
106 }
107
108 for (final CharMappingItem mappingItem : mappingItemList) {
109 if (id == mappingItem.getId()) {
110 return OptionalEntity.of(mappingItem);
111 }
112 }
113 return OptionalEntity.empty();
114 }
115
116
117
118
119
120
121
122
123 @Override
124 public synchronized PagingList<CharMappingItem> selectList(final int offset, final int size) {
125 if (mappingItemList == null) {
126 reload(null);
127 }
128
129 if (offset >= mappingItemList.size() || offset < 0) {
130 return new PagingList<>(Collections.<CharMappingItem> emptyList(), offset, size, mappingItemList.size());
131 }
132
133 int toIndex = offset + size;
134 if (toIndex > mappingItemList.size()) {
135 toIndex = mappingItemList.size();
136 }
137
138 return new PagingList<>(mappingItemList.subList(offset, toIndex), offset, size, mappingItemList.size());
139 }
140
141
142
143
144
145
146 @Override
147 public synchronized void insert(final CharMappingItem item) {
148 try (MappingUpdater updater = new MappingUpdater(item)) {
149 reload(updater);
150 }
151 }
152
153
154
155
156
157
158 @Override
159 public synchronized void update(final CharMappingItem item) {
160 try (MappingUpdater updater = new MappingUpdater(item)) {
161 reload(updater);
162 }
163 }
164
165
166
167
168
169
170 @Override
171 public synchronized void delete(final CharMappingItem item) {
172 final CharMappingItem mappingItem = item;
173 mappingItem.setNewInputs(StringUtil.EMPTY_STRINGS);
174 mappingItem.setNewOutput(StringUtil.EMPTY);
175 try (MappingUpdater updater = new MappingUpdater(item)) {
176 reload(updater);
177 }
178 }
179
180
181
182
183
184
185 protected void reload(final MappingUpdater updater) {
186 try (CurlResponse curlResponse = dictionaryManager.getContentResponse(this)) {
187 reload(updater, curlResponse.getContentAsStream());
188 } catch (final IOException e) {
189 throw new DictionaryException("Failed to parse " + path, e);
190 }
191 }
192
193
194
195
196
197
198
199
200 protected void reload(final MappingUpdater updater, final InputStream in) {
201 final Pattern parsePattern = Pattern.compile("(.*?)\\s*+=>\\s*+(.*?)\\s*+$");
202 final List<CharMappingItem> itemList = new ArrayList<>();
203 try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, Constants.UTF_8))) {
204 long id = 0;
205 String line = null;
206 while ((line = reader.readLine()) != null) {
207
208 final String replacedLine = line.replaceAll("#.*$", StringUtil.EMPTY).trim();
209
210
211 if (replacedLine.length() == 0) {
212 if (updater != null) {
213 updater.write(line);
214 }
215 continue;
216 }
217
218 String[] inputs;
219 String output;
220
221 final Matcher m = parsePattern.matcher(replacedLine);
222
223 if (!m.find()) {
224 logger.warn("Failed to parse mapping: line={}, path={}", line, path);
225 if (updater != null) {
226 updater.write("# " + line);
227 }
228 continue;
229 }
230
231 inputs = m.group(1).trim().split(",");
232 output = m.group(2).trim();
233
234 if (inputs == null || output == null || inputs.length == 0) {
235 logger.warn("Failed to parse mapping: line={}, path={}", line, path);
236 if (updater != null) {
237 updater.write("# " + line);
238 }
239 continue;
240 }
241
242 id++;
243 final CharMappingItem item = new CharMappingItem(id, inputs, output);
244
245 if (updater != null) {
246 final CharMappingItem newItem = updater.write(item);
247 if (newItem != null) {
248 itemList.add(newItem);
249 } else {
250 id--;
251 }
252 } else {
253 itemList.add(item);
254 }
255 }
256 if (updater != null) {
257 final CharMappingItem item = updater.commit();
258 if (item != null) {
259 itemList.add(item);
260 }
261 }
262 mappingItemList = itemList;
263 } catch (final IOException e) {
264 throw new DictionaryException("Failed to parse " + path, e);
265 }
266 }
267
268
269
270
271
272
273 public String getSimpleName() {
274 return new File(path).getName();
275 }
276
277
278
279
280
281
282
283 public synchronized void update(final InputStream in) throws IOException {
284 try (MappingUpdater updater = new MappingUpdater(null)) {
285 reload(updater, in);
286 }
287 }
288
289
290
291
292
293
294 @Override
295 public String toString() {
296 return "MappingFile [path=" + path + ", mappingItemList=" + mappingItemList + ", id=" + id + "]";
297 }
298
299
300
301
302
303
304 protected class MappingUpdater implements Closeable {
305
306
307 protected boolean isCommit = false;
308
309
310 protected File newFile;
311
312
313 protected Writer writer;
314
315
316 protected CharMappingItem item;
317
318
319
320
321
322
323 protected MappingUpdater(final CharMappingItem newItem) {
324 FileOutputStream fos = null;
325 try {
326 newFile = ComponentUtil.getSystemHelper().createTempFile(MAPPING, ".txt");
327 fos = new FileOutputStream(newFile);
328 writer = new BufferedWriter(new OutputStreamWriter(fos, Constants.UTF_8));
329 fos = null;
330 } catch (final Exception e) {
331 if (fos != null) {
332 try {
333 fos.close();
334 } catch (final IOException ioe) {
335
336 }
337 }
338 if (newFile != null) {
339 newFile.delete();
340 }
341 throw new DictionaryException("Failed to write a userDict file.", e);
342 }
343 item = newItem;
344 }
345
346
347
348
349
350
351
352 public CharMappingItem write(final CharMappingItem oldItem) {
353 try {
354 if (item == null || item.getId() != oldItem.getId() || !item.isUpdated()) {
355 writer.write(oldItem.toLineString());
356 writer.write(Constants.LINE_SEPARATOR);
357 return oldItem;
358 }
359 if (!item.equals(oldItem)) {
360 throw new DictionaryException("Mapping file was updated: old=" + oldItem + " : new=" + item);
361 }
362 try {
363 if (!item.isDeleted()) {
364
365 writer.write(item.toLineString());
366 writer.write(Constants.LINE_SEPARATOR);
367 return new CharMappingItem(item.getId(), item.getNewInputs(), item.getNewOutput());
368 }
369 return null;
370 } finally {
371 item.setNewInputs(null);
372 item.setNewOutput(null);
373 }
374 } catch (final IOException e) {
375 throw new DictionaryException("Failed to write: " + oldItem + " -> " + item, e);
376 }
377 }
378
379
380
381
382
383
384 public void write(final String line) {
385 try {
386 writer.write(line);
387 writer.write(Constants.LINE_SEPARATOR);
388 } catch (final IOException e) {
389 throw new DictionaryException("Failed to write: " + line, e);
390 }
391 }
392
393
394
395
396
397
398 public CharMappingItem commit() {
399 isCommit = true;
400 if (item != null && item.isUpdated()) {
401 try {
402 writer.write(item.toLineString());
403 writer.write(Constants.LINE_SEPARATOR);
404 return item;
405 } catch (final IOException e) {
406 throw new DictionaryException("Failed to write: " + item, e);
407 }
408 }
409 return null;
410 }
411
412
413
414
415
416
417 @Override
418 public void close() {
419 try {
420 writer.flush();
421 } catch (final IOException e) {
422
423 }
424 CloseableUtil.closeQuietly(writer);
425
426 if (isCommit) {
427 try {
428 dictionaryManager.store(CharMappingFile.this, newFile);
429 } finally {
430 newFile.delete();
431 }
432 } else {
433 newFile.delete();
434 }
435 }
436 }
437
438 }