View Javadoc
1   /*
2    * Copyright 2012-2021 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
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                 // Remove comments
132                 final String replacedLine = line.replaceAll("#.*$", StringUtil.EMPTY).trim();
133 
134                 // Skip empty lines or comment lines
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                         // update
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                 // ignore
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 }