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