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