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