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.stopwords;
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.dbflute.optional.OptionalEntity;
41  
42  public class StopwordsFile extends DictionaryFile<StopwordsItem> {
43      private static final String STOPWORDS = "stopwords";
44  
45      List<StopwordsItem> stopwordsItemList;
46  
47      public StopwordsFile(final String id, final String path, final Date timestamp) {
48          super(id, path, timestamp);
49      }
50  
51      @Override
52      public String getType() {
53          return STOPWORDS;
54      }
55  
56      @Override
57      public String getPath() {
58          return path;
59      }
60  
61      @Override
62      public synchronized OptionalEntity<StopwordsItem> get(final long id) {
63          if (stopwordsItemList == null) {
64              reload(null);
65          }
66  
67          for (final StopwordsItem StopwordsItem : stopwordsItemList) {
68              if (id == StopwordsItem.getId()) {
69                  return OptionalEntity.of(StopwordsItem);
70              }
71          }
72          return OptionalEntity.empty();
73      }
74  
75      @Override
76      public synchronized PagingList<StopwordsItem> selectList(final int offset, final int size) {
77          if (stopwordsItemList == null) {
78              reload(null);
79          }
80  
81          if (offset >= stopwordsItemList.size() || offset < 0) {
82              return new PagingList<>(Collections.<StopwordsItem> emptyList(), offset, size, stopwordsItemList.size());
83          }
84  
85          int toIndex = offset + size;
86          if (toIndex > stopwordsItemList.size()) {
87              toIndex = stopwordsItemList.size();
88          }
89  
90          return new PagingList<>(stopwordsItemList.subList(offset, toIndex), offset, size, stopwordsItemList.size());
91      }
92  
93      @Override
94      public synchronized void insert(final StopwordsItem item) {
95          try (SynonymUpdater updater = new SynonymUpdater(item)) {
96              reload(updater);
97          }
98      }
99  
100     @Override
101     public synchronized void update(final StopwordsItem item) {
102         try (SynonymUpdater updater = new SynonymUpdater(item)) {
103             reload(updater);
104         }
105     }
106 
107     @Override
108     public synchronized void delete(final StopwordsItem item) {
109         final StopwordsItem StopwordsItem = item;
110         StopwordsItem.setNewInput(StringUtil.EMPTY);
111         try (SynonymUpdater updater = new SynonymUpdater(item)) {
112             reload(updater);
113         }
114     }
115 
116     protected void reload(final SynonymUpdater updater) {
117         try (CurlResponse curlResponse = dictionaryManager.getContentResponse(this)) {
118             reload(updater, curlResponse.getContentAsStream());
119         } catch (final IOException e) {
120             throw new DictionaryException("Failed to parse " + path, e);
121         }
122     }
123 
124     protected void reload(final SynonymUpdater updater, final InputStream in) {
125         final List<StopwordsItem> itemList = new ArrayList<>();
126         try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, Constants.UTF_8))) {
127             long id = 0;
128             String line = null;
129             while ((line = reader.readLine()) != null) {
130                 if (line.length() == 0 || line.charAt(0) == '#') {
131                     if (updater != null) {
132                         updater.write(line);
133                     }
134                     continue; // ignore empty lines and comments
135                 }
136 
137                 final String inputStrings = line;
138                 final String input = unescape(inputStrings);
139 
140                 if (input.length() > 0) {
141                     id++;
142                     final StopwordsItem item = new StopwordsItem(id, input);
143                     if (updater != null) {
144                         final StopwordsItem newItem = updater.write(item);
145                         if (newItem != null) {
146                             itemList.add(newItem);
147                         } else {
148                             id--;
149                         }
150                     } else {
151                         itemList.add(item);
152                     }
153                 }
154             }
155             if (updater != null) {
156                 final StopwordsItem item = updater.commit();
157                 if (item != null) {
158                     itemList.add(item);
159                 }
160             }
161             stopwordsItemList = itemList;
162         } catch (final IOException e) {
163             throw new DictionaryException("Failed to parse " + path, e);
164         }
165     }
166 
167     private String unescape(final String s) {
168         if (s.indexOf('\\') >= 0) {
169             final StringBuilder sb = new StringBuilder();
170             for (int i = 0; i < s.length(); i++) {
171                 final char ch = s.charAt(i);
172                 if (ch == '\\' && i < s.length() - 1) {
173                     i++;
174                     sb.append(s.charAt(i));
175                 } else {
176                     sb.append(ch);
177                 }
178             }
179             return sb.toString();
180         }
181         return s;
182     }
183 
184     public String getSimpleName() {
185         return new File(path).getName();
186     }
187 
188     public synchronized void update(final InputStream in) throws IOException {
189         try (SynonymUpdater updater = new SynonymUpdater(null)) {
190             reload(updater, in);
191         }
192     }
193 
194     @Override
195     public String toString() {
196         return "SynonymFile [path=" + path + ", stopwordsItemList=" + stopwordsItemList + ", id=" + id + "]";
197     }
198 
199     protected class SynonymUpdater implements Closeable {
200 
201         protected boolean isCommit = false;
202 
203         protected File newFile;
204 
205         protected Writer writer;
206 
207         protected StopwordsItem item;
208 
209         protected SynonymUpdater(final StopwordsItem newItem) {
210             try {
211                 newFile = ComponentUtil.getSystemHelper().createTempFile(STOPWORDS, ".txt");
212                 writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), Constants.UTF_8));
213             } catch (final Exception e) {
214                 if (newFile != null) {
215                     newFile.delete();
216                 }
217                 throw new DictionaryException("Failed to write a userDict file.", e);
218             }
219             item = newItem;
220         }
221 
222         public StopwordsItem write(final StopwordsItem oldItem) {
223             try {
224                 if ((item == null) || (item.getId() != oldItem.getId()) || !item.isUpdated()) {
225                     writer.write(oldItem.toLineString());
226                     writer.write(Constants.LINE_SEPARATOR);
227                     return oldItem;
228                 }
229                 if (!item.equals(oldItem)) {
230                     throw new DictionaryException("Stopwords file was updated: old=" + oldItem + " : new=" + item);
231                 }
232                 try {
233                     if (!item.isDeleted()) {
234                         // update
235                         writer.write(item.toLineString());
236                         writer.write(Constants.LINE_SEPARATOR);
237                         return new StopwordsItem(item.getId(), item.getNewInput());
238                     } else {
239                         return null;
240                     }
241                 } finally {
242                     item.setNewInput(null);
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 StopwordsItem 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             CloseableUtil.closeQuietly(writer);
280 
281             if (isCommit) {
282                 try {
283                     dictionaryManager.store(StopwordsFile.this, newFile);
284                 } finally {
285                     newFile.delete();
286                 }
287             } else {
288                 newFile.delete();
289             }
290         }
291     }
292 }