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