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.stemmeroverride;
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  import java.util.regex.Matcher;
33  import java.util.regex.Pattern;
34  
35  import org.apache.logging.log4j.LogManager;
36  import org.apache.logging.log4j.Logger;
37  import org.codelibs.core.io.CloseableUtil;
38  import org.codelibs.core.lang.StringUtil;
39  import org.codelibs.curl.CurlResponse;
40  import org.codelibs.fess.Constants;
41  import org.codelibs.fess.dict.DictionaryException;
42  import org.codelibs.fess.dict.DictionaryFile;
43  import org.codelibs.fess.util.ComponentUtil;
44  import org.dbflute.optional.OptionalEntity;
45  
46  public class StemmerOverrideFile extends DictionaryFile<StemmerOverrideItem> {
47      private static final Logger logger = LogManager.getLogger(StemmerOverrideFile.class);
48  
49      private static final String STEMMER_OVERRIDE = "stemmeroverride";
50  
51      List<StemmerOverrideItem> stemmerOverrideItemList;
52  
53      public StemmerOverrideFile(final String id, final String path, final Date timestamp) {
54          super(id, path, timestamp);
55      }
56  
57      @Override
58      public String getType() {
59          return STEMMER_OVERRIDE;
60      }
61  
62      @Override
63      public String getPath() {
64          return path;
65      }
66  
67      @Override
68      public synchronized OptionalEntity<StemmerOverrideItem> get(final long id) {
69          if (stemmerOverrideItemList == null) {
70              reload(null);
71          }
72  
73          for (final StemmerOverrideItem stemmerOverrideItem : stemmerOverrideItemList) {
74              if (id == stemmerOverrideItem.getId()) {
75                  return OptionalEntity.of(stemmerOverrideItem);
76              }
77          }
78          return OptionalEntity.empty();
79      }
80  
81      @Override
82      public synchronized PagingList<StemmerOverrideItem> selectList(final int offset, final int size) {
83          if (stemmerOverrideItemList == null) {
84              reload(null);
85          }
86  
87          if (offset >= stemmerOverrideItemList.size() || offset < 0) {
88              return new PagingList<>(Collections.<StemmerOverrideItem> emptyList(), offset, size, stemmerOverrideItemList.size());
89          }
90  
91          int toIndex = offset + size;
92          if (toIndex > stemmerOverrideItemList.size()) {
93              toIndex = stemmerOverrideItemList.size();
94          }
95  
96          return new PagingList<>(stemmerOverrideItemList.subList(offset, toIndex), offset, size, stemmerOverrideItemList.size());
97      }
98  
99      @Override
100     public synchronized void insert(final StemmerOverrideItem item) {
101         try (StemmerOverrideUpdater updater = new StemmerOverrideUpdater(item)) {
102             reload(updater);
103         }
104     }
105 
106     @Override
107     public synchronized void update(final StemmerOverrideItem item) {
108         try (StemmerOverrideUpdater updater = new StemmerOverrideUpdater(item)) {
109             reload(updater);
110         }
111     }
112 
113     @Override
114     public synchronized void delete(final StemmerOverrideItem item) {
115         final StemmerOverrideItem stemmerOverrideItem = item;
116         stemmerOverrideItem.setNewInput(StringUtil.EMPTY);
117         stemmerOverrideItem.setNewOutput(StringUtil.EMPTY);
118         try (StemmerOverrideUpdater updater = new StemmerOverrideUpdater(item)) {
119             reload(updater);
120         }
121     }
122 
123     protected void reload(final StemmerOverrideUpdater updater) {
124         try (CurlResponse curlResponse = dictionaryManager.getContentResponse(this)) {
125             reload(updater, curlResponse.getContentAsStream());
126         } catch (final IOException e) {
127             throw new DictionaryException("Failed to parse " + path, e);
128         }
129     }
130 
131     protected void reload(final StemmerOverrideUpdater updater, final InputStream in) {
132         final Pattern parsePattern = Pattern.compile("(.*)\\s*=>\\s*(.*)\\s*$");
133         final List<StemmerOverrideItem> itemList = new ArrayList<>();
134         try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, Constants.UTF_8))) {
135             long id = 0;
136             String line = null;
137             while ((line = reader.readLine()) != null) {
138                 // Remove comments
139                 final String replacedLine = line.replaceAll("#.*$", StringUtil.EMPTY).trim();
140 
141                 // Skip empty lines or comment lines
142                 if (replacedLine.length() == 0) {
143                     if (updater != null) {
144                         updater.write(line);
145                     }
146                     continue;
147                 }
148 
149                 final Matcher m = parsePattern.matcher(replacedLine);
150 
151                 if (!m.find()) {
152                     logger.warn("Failed to parse {} in {}", line, path);
153                     if (updater != null) {
154                         updater.write("# " + line);
155                     }
156                     continue;
157                 }
158 
159                 final String input = m.group(1).trim();
160                 final String output = m.group(2).trim();
161 
162                 if (input == null || output == null) {
163                     logger.warn("Failed to parse {} to {}", line, path);
164                     if (updater != null) {
165                         updater.write("# " + line);
166                     }
167                     continue;
168                 }
169 
170                 id++;
171                 final StemmerOverrideItem item = new StemmerOverrideItem(id, input, output);
172 
173                 if (updater != null) {
174                     final StemmerOverrideItem newItem = updater.write(item);
175                     if (newItem != null) {
176                         itemList.add(newItem);
177                     } else {
178                         id--;
179                     }
180                 } else {
181                     itemList.add(item);
182                 }
183             }
184             if (updater != null) {
185                 final StemmerOverrideItem item = updater.commit();
186                 if (item != null) {
187                     itemList.add(item);
188                 }
189             }
190             stemmerOverrideItemList = itemList;
191         } catch (final IOException e) {
192             throw new DictionaryException("Failed to parse " + path, e);
193         }
194     }
195 
196     public String getSimpleName() {
197         return new File(path).getName();
198     }
199 
200     public synchronized void update(final InputStream in) throws IOException {
201         try (StemmerOverrideUpdater updater = new StemmerOverrideUpdater(null)) {
202             reload(updater, in);
203         }
204     }
205 
206     @Override
207     public String toString() {
208         return "StemmerOverrideFile [path=" + path + ", stemmerOverrideItemList=" + stemmerOverrideItemList + ", id=" + id + "]";
209     }
210 
211     protected class StemmerOverrideUpdater implements Closeable {
212 
213         protected boolean isCommit = false;
214 
215         protected File newFile;
216 
217         protected Writer writer;
218 
219         protected StemmerOverrideItem item;
220 
221         protected StemmerOverrideUpdater(final StemmerOverrideItem newItem) {
222             try {
223                 newFile = ComponentUtil.getSystemHelper().createTempFile(STEMMER_OVERRIDE, ".txt");
224                 writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), Constants.UTF_8));
225             } catch (final Exception e) {
226                 if (newFile != null) {
227                     newFile.delete();
228                 }
229                 throw new DictionaryException("Failed to write a userDict file.", e);
230             }
231             item = newItem;
232         }
233 
234         public StemmerOverrideItem write(final StemmerOverrideItem oldItem) {
235             try {
236                 if ((item == null) || (item.getId() != oldItem.getId()) || !item.isUpdated()) {
237                     writer.write(oldItem.toLineString());
238                     writer.write(Constants.LINE_SEPARATOR);
239                     return oldItem;
240                 }
241                 if (!item.equals(oldItem)) {
242                     throw new DictionaryException("StemmerOverride file was updated: old=" + oldItem + " : new=" + item);
243                 }
244                 try {
245                     if (!item.isDeleted()) {
246                         // update
247                         writer.write(item.toLineString());
248                         writer.write(Constants.LINE_SEPARATOR);
249                         return new StemmerOverrideItem(item.getId(), item.getNewInput(), item.getNewOutput());
250                     } else {
251                         return null;
252                     }
253                 } finally {
254                     item.setNewInput(null);
255                     item.setNewOutput(null);
256                 }
257             } catch (final IOException e) {
258                 throw new DictionaryException("Failed to write: " + oldItem + " -> " + item, e);
259             }
260         }
261 
262         public void write(final String line) {
263             try {
264                 writer.write(line);
265                 writer.write(Constants.LINE_SEPARATOR);
266             } catch (final IOException e) {
267                 throw new DictionaryException("Failed to write: " + line, e);
268             }
269         }
270 
271         public StemmerOverrideItem commit() {
272             isCommit = true;
273             if (item != null && item.isUpdated()) {
274                 try {
275                     writer.write(item.toLineString());
276                     writer.write(Constants.LINE_SEPARATOR);
277                     return item;
278                 } catch (final IOException e) {
279                     throw new DictionaryException("Failed to write: " + item, e);
280                 }
281             }
282             return null;
283         }
284 
285         @Override
286         public void close() {
287             try {
288                 writer.flush();
289             } catch (final IOException e) {
290                 // ignore
291             }
292             CloseableUtil.closeQuietly(writer);
293 
294             if (isCommit) {
295                 try {
296                     dictionaryManager.store(StemmerOverrideFile.this, newFile);
297                 } finally {
298                     newFile.delete();
299                 }
300             } else {
301                 newFile.delete();
302             }
303         }
304     }
305 
306 }