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