1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.dict.kuromoji;
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.codelibs.fess.util.KuromojiCSVUtil;
41 import org.dbflute.optional.OptionalEntity;
42
43 public class KuromojiFile extends DictionaryFile<KuromojiItem> {
44 private static final String KUROMOJI = "kuromoji";
45
46 List<KuromojiItem> kuromojiItemList;
47
48 public KuromojiFile(final String id, final String path, final Date timestamp) {
49 super(id, path, timestamp);
50 }
51
52 @Override
53 public String getType() {
54 return KUROMOJI;
55 }
56
57 @Override
58 public String getPath() {
59 return path;
60 }
61
62 @Override
63 public synchronized OptionalEntity<KuromojiItem> get(final long id) {
64 if (kuromojiItemList == null) {
65 reload(null);
66 }
67
68 for (final KuromojiItem kuromojiItem : kuromojiItemList) {
69 if (id == kuromojiItem.getId()) {
70 return OptionalEntity.of(kuromojiItem);
71 }
72 }
73 return OptionalEntity.empty();
74 }
75
76 @Override
77 public synchronized PagingList<KuromojiItem> selectList(final int offset, final int size) {
78 if (kuromojiItemList == null) {
79 reload(null);
80 }
81
82 if (offset >= kuromojiItemList.size() || offset < 0) {
83 return new PagingList<>(Collections.<KuromojiItem> emptyList(), offset, size, kuromojiItemList.size());
84 }
85
86 int toIndex = offset + size;
87 if (toIndex > kuromojiItemList.size()) {
88 toIndex = kuromojiItemList.size();
89 }
90
91 return new PagingList<>(kuromojiItemList.subList(offset, toIndex), offset, size, kuromojiItemList.size());
92 }
93
94 @Override
95 public synchronized void insert(final KuromojiItem item) {
96 try (KuromojiUpdater updater = new KuromojiUpdater(item)) {
97 reload(updater);
98 }
99 }
100
101 @Override
102 public synchronized void update(final KuromojiItem item) {
103 try (KuromojiUpdater updater = new KuromojiUpdater(item)) {
104 reload(updater);
105 }
106 }
107
108 @Override
109 public synchronized void delete(final KuromojiItem item) {
110 final KuromojiItem kuromojiItem = item;
111 kuromojiItem.setNewToken(StringUtil.EMPTY);
112 try (KuromojiUpdater updater = new KuromojiUpdater(item)) {
113 reload(updater);
114 }
115 }
116
117 protected void reload(final KuromojiUpdater 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 KuromojiUpdater updater, final InputStream in) {
126 final List<KuromojiItem> 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
132 final String replacedLine = line.replaceAll("#.*$", StringUtil.EMPTY).trim();
133
134
135 if (replacedLine.length() == 0) {
136 if (updater != null) {
137 updater.write(line);
138 }
139 continue;
140 }
141
142 final String[] values = KuromojiCSVUtil.parse(replacedLine);
143 String token = null;
144 String segmentation = null;
145 String reading = null;
146 String pos = null;
147 switch (values.length) {
148 case 4:
149 pos = values[3];
150 case 3:
151 reading = values[2];
152 case 2:
153 segmentation = values[1];
154 case 1:
155 token = values[0];
156 default:
157 break;
158 }
159
160 id++;
161 final KuromojiItem item = new KuromojiItem(id, token, segmentation, reading, pos);
162 if (updater != null) {
163 final KuromojiItem newItem = updater.write(item);
164 if (newItem != null) {
165 itemList.add(newItem);
166 } else {
167 id--;
168 }
169 } else {
170 itemList.add(item);
171 }
172 }
173 if (updater != null) {
174 final KuromojiItem item = updater.commit();
175 if (item != null) {
176 itemList.add(item);
177 }
178 }
179 kuromojiItemList = itemList;
180 } catch (final IOException e) {
181 throw new DictionaryException("Failed to parse " + path, e);
182 }
183 }
184
185 public String getSimpleName() {
186 return new File(path).getName();
187 }
188
189 public synchronized void update(final InputStream in) throws IOException {
190 try (KuromojiUpdater updater = new KuromojiUpdater(null)) {
191 reload(updater, in);
192 }
193 }
194
195 @Override
196 public String toString() {
197 return "KuromojiFile [path=" + path + ", kuromojiItemList=" + kuromojiItemList + ", id=" + id + "]";
198 }
199
200 protected class KuromojiUpdater implements Closeable {
201
202 protected boolean isCommit = false;
203
204 protected File newFile;
205
206 protected Writer writer;
207
208 protected KuromojiItem item;
209
210 protected KuromojiUpdater(final KuromojiItem newItem) {
211 try {
212 newFile = ComponentUtil.getSystemHelper().createTempFile(KUROMOJI, ".txt");
213 writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), Constants.UTF_8));
214 } catch (final Exception e) {
215 if (newFile != null) {
216 newFile.delete();
217 }
218 throw new DictionaryException("Failed to write a userDict file.", e);
219 }
220 item = newItem;
221 }
222
223 public KuromojiItem write(final KuromojiItem oldItem) {
224 try {
225 if ((item == null) || (item.getId() != oldItem.getId()) || !item.isUpdated()) {
226 writer.write(oldItem.toLineString());
227 writer.write(Constants.LINE_SEPARATOR);
228 return oldItem;
229 }
230 if (!item.equals(oldItem)) {
231 throw new DictionaryException("Kuromoji file was updated: old=" + oldItem + " : new=" + item);
232 }
233 try {
234 if (!item.isDeleted()) {
235
236 writer.write(item.toLineString());
237 writer.write(Constants.LINE_SEPARATOR);
238 return new KuromojiItem(item.getId(), item.getNewToken(), item.getNewSegmentation(), item.getNewReading(),
239 item.getNewPos());
240 } else {
241 return null;
242 }
243 } finally {
244 item.setNewToken(null);
245 }
246 } catch (final IOException e) {
247 throw new DictionaryException("Failed to write: " + oldItem + " -> " + item, e);
248 }
249 }
250
251 public void write(final String line) {
252 try {
253 writer.write(line);
254 writer.write(Constants.LINE_SEPARATOR);
255 } catch (final IOException e) {
256 throw new DictionaryException("Failed to write: " + line, e);
257 }
258 }
259
260 public KuromojiItem commit() {
261 isCommit = true;
262 if (item != null && item.isUpdated()) {
263 try {
264 writer.write(item.toLineString());
265 writer.write(Constants.LINE_SEPARATOR);
266 return item;
267 } catch (final IOException e) {
268 throw new DictionaryException("Failed to write: " + item, e);
269 }
270 }
271 return null;
272 }
273
274 @Override
275 public void close() {
276 try {
277 writer.flush();
278 } catch (final IOException e) {
279
280 }
281 CloseableUtil.closeQuietly(writer);
282
283 if (isCommit) {
284 try {
285 dictionaryManager.store(KuromojiFile.this, newFile);
286 } finally {
287 newFile.delete();
288 }
289 } else {
290 newFile.delete();
291 }
292 }
293 }
294
295 }