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