View Javadoc
1   /*
2    * Copyright 2012-2017 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.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                 // Remove comments
124                 final String replacedLine = line.replaceAll("#.*$", StringUtil.EMPTY).trim();
125 
126                 // Skip empty lines or comment lines
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                                 // update
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                 // ignore
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 }