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.synonym;
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.dbflute.optional.OptionalEntity;
40  
41  public class SynonymFile extends DictionaryFile<SynonymItem> {
42      private static final String SYNONYM = "synonym";
43  
44      List<SynonymItem> synonymItemList;
45  
46      public SynonymFile(final String id, final String path, final Date timestamp) {
47          super(id, path, timestamp);
48      }
49  
50      @Override
51      public String getType() {
52          return SYNONYM;
53      }
54  
55      @Override
56      public String getPath() {
57          return path;
58      }
59  
60      @Override
61      public synchronized OptionalEntity<SynonymItem> get(final long id) {
62          if (synonymItemList == null) {
63              reload(null, null);
64          }
65  
66          for (final SynonymItem synonymItem : synonymItemList) {
67              if (id == synonymItem.getId()) {
68                  return OptionalEntity.of(synonymItem);
69              }
70          }
71          return OptionalEntity.empty();
72      }
73  
74      @Override
75      public synchronized PagingList<SynonymItem> selectList(final int offset, final int size) {
76          if (synonymItemList == null) {
77              reload(null, null);
78          }
79  
80          if (offset >= synonymItemList.size() || offset < 0) {
81              return new PagingList<>(Collections.<SynonymItem> emptyList(), offset, size, synonymItemList.size());
82          }
83  
84          int toIndex = offset + size;
85          if (toIndex > synonymItemList.size()) {
86              toIndex = synonymItemList.size();
87          }
88  
89          return new PagingList<>(synonymItemList.subList(offset, toIndex), offset, size, synonymItemList.size());
90      }
91  
92      @Override
93      public synchronized void insert(final SynonymItem item) {
94          try (SynonymUpdater updater = new SynonymUpdater(item)) {
95              reload(updater, null);
96          }
97      }
98  
99      @Override
100     public synchronized void update(final SynonymItem item) {
101         try (SynonymUpdater updater = new SynonymUpdater(item)) {
102             reload(updater, null);
103         }
104     }
105 
106     @Override
107     public synchronized void delete(final SynonymItem item) {
108         final SynonymItem synonymItem = item;
109         synonymItem.setNewInputs(StringUtil.EMPTY_STRINGS);
110         synonymItem.setNewOutputs(StringUtil.EMPTY_STRINGS);
111         try (SynonymUpdater updater = new SynonymUpdater(item)) {
112             reload(updater, null);
113         }
114     }
115 
116     protected void reload(final SynonymUpdater updater, final InputStream in) {
117         final List<SynonymItem> 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                 if (line.length() == 0 || line.charAt(0) == '#') {
124                     if (updater != null) {
125                         updater.write(line);
126                     }
127                     continue; // ignore empty lines and comments
128                 }
129 
130                 String[] inputs;
131                 String[] outputs;
132 
133                 final List<String> sides = split(line, "=>");
134                 if (sides.size() > 1) { // explicit mapping
135                     if (sides.size() != 2) {
136                         throw new DictionaryException("more than one explicit mapping specified on the same line");
137                     }
138                     final List<String> inputStrings = split(sides.get(0), ",");
139                     inputs = new String[inputStrings.size()];
140                     for (int i = 0; i < inputs.length; i++) {
141                         inputs[i] = unescape(inputStrings.get(i)).trim();
142                     }
143 
144                     final List<String> outputStrings = split(sides.get(1), ",");
145                     outputs = new String[outputStrings.size()];
146                     for (int i = 0; i < outputs.length; i++) {
147                         outputs[i] = unescape(outputStrings.get(i)).trim();
148                     }
149 
150                     if (inputs.length > 0 && outputs.length > 0) {
151                         id++;
152                         final SynonymItem item = new SynonymItem(id, inputs, outputs);
153                         if (updater != null) {
154                             final SynonymItem newItem = updater.write(item);
155                             if (newItem != null) {
156                                 itemList.add(newItem);
157                             } else {
158                                 id--;
159                             }
160                         } else {
161                             itemList.add(item);
162                         }
163                     }
164                 } else {
165                     final List<String> inputStrings = split(line, ",");
166                     inputs = new String[inputStrings.size()];
167                     for (int i = 0; i < inputs.length; i++) {
168                         inputs[i] = unescape(inputStrings.get(i)).trim();
169                     }
170 
171                     if (inputs.length > 0) {
172                         id++;
173                         final SynonymItem item = new SynonymItem(id, inputs, inputs);
174                         if (updater != null) {
175                             final SynonymItem newItem = updater.write(item);
176                             if (newItem != null) {
177                                 itemList.add(newItem);
178                             } else {
179                                 id--;
180                             }
181                         } else {
182                             itemList.add(item);
183                         }
184                     }
185                 }
186             }
187             if (updater != null) {
188                 final SynonymItem item = updater.commit();
189                 if (item != null) {
190                     itemList.add(item);
191                 }
192             }
193             synonymItemList = itemList;
194         } catch (final IOException e) {
195             throw new DictionaryException("Failed to parse " + path, e);
196         }
197     }
198 
199     private static List<String> split(final String s, final String separator) {
200         final List<String> list = new ArrayList<>(2);
201         StringBuilder sb = new StringBuilder();
202         int pos = 0;
203         final int end = s.length();
204         while (pos < end) {
205             if (s.startsWith(separator, pos)) {
206                 if (sb.length() > 0) {
207                     list.add(sb.toString());
208                     sb = new StringBuilder();
209                 }
210                 pos += separator.length();
211                 continue;
212             }
213 
214             char ch = s.charAt(pos++);
215             if (ch == '\\') {
216                 sb.append(ch);
217                 if (pos >= end) {
218                     break; // ERROR, or let it go?
219                 }
220                 ch = s.charAt(pos++);
221             }
222 
223             sb.append(ch);
224         }
225 
226         if (sb.length() > 0) {
227             list.add(sb.toString());
228         }
229 
230         return list;
231     }
232 
233     private String unescape(final String s) {
234         if (s.indexOf('\\') >= 0) {
235             final StringBuilder sb = new StringBuilder();
236             for (int i = 0; i < s.length(); i++) {
237                 final char ch = s.charAt(i);
238                 if (ch == '\\' && i < s.length() - 1) {
239                     sb.append(s.charAt(++i));
240                 } else {
241                     sb.append(ch);
242                 }
243             }
244             return sb.toString();
245         }
246         return s;
247     }
248 
249     public String getSimpleName() {
250         return new File(path).getName();
251     }
252 
253     public InputStream getInputStream() throws IOException {
254         return new BufferedInputStream(dictionaryManager.getContentInputStream(this));
255     }
256 
257     public synchronized void update(final InputStream in) throws IOException {
258         try (SynonymUpdater updater = new SynonymUpdater(null)) {
259             reload(updater, in);
260         }
261     }
262 
263     @Override
264     public String toString() {
265         return "SynonymFile [path=" + path + ", synonymItemList=" + synonymItemList + ", id=" + id + "]";
266     }
267 
268     protected class SynonymUpdater implements Closeable {
269 
270         protected boolean isCommit = false;
271 
272         protected File newFile;
273 
274         protected Writer writer;
275 
276         protected SynonymItem item;
277 
278         protected SynonymUpdater(final SynonymItem newItem) {
279             try {
280                 newFile = File.createTempFile(SYNONYM, ".txt");
281                 writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), Constants.UTF_8));
282             } catch (final IOException e) {
283                 if (newFile != null) {
284                     newFile.delete();
285                 }
286                 throw new DictionaryException("Failed to write a userDict file.", e);
287             }
288             item = newItem;
289         }
290 
291         public SynonymItem write(final SynonymItem oldItem) {
292             try {
293                 if (item != null && item.getId() == oldItem.getId() && item.isUpdated()) {
294                     if (item.equals(oldItem)) {
295                         try {
296                             if (!item.isDeleted()) {
297                                 // update
298                                 writer.write(item.toLineString());
299                                 writer.write(Constants.LINE_SEPARATOR);
300                                 return new SynonymItem(item.getId(), item.getNewInputs(), item.getNewOutputs());
301                             } else {
302                                 return null;
303                             }
304                         } finally {
305                             item.setNewInputs(null);
306                             item.setNewOutputs(null);
307                         }
308                     } else {
309                         throw new DictionaryException("Synonym file was updated: old=" + oldItem + " : new=" + item);
310                     }
311                 } else {
312                     writer.write(oldItem.toLineString());
313                     writer.write(Constants.LINE_SEPARATOR);
314                     return oldItem;
315                 }
316             } catch (final IOException e) {
317                 throw new DictionaryException("Failed to write: " + oldItem + " -> " + item, e);
318             }
319         }
320 
321         public void write(final String line) {
322             try {
323                 writer.write(line);
324                 writer.write(Constants.LINE_SEPARATOR);
325             } catch (final IOException e) {
326                 throw new DictionaryException("Failed to write: " + line, e);
327             }
328         }
329 
330         public SynonymItem commit() {
331             isCommit = true;
332             if (item != null && item.isUpdated()) {
333                 try {
334                     writer.write(item.toLineString());
335                     writer.write(Constants.LINE_SEPARATOR);
336                     return item;
337                 } catch (final IOException e) {
338                     throw new DictionaryException("Failed to write: " + item, e);
339                 }
340             }
341             return null;
342         }
343 
344         @Override
345         public void close() {
346             try {
347                 writer.flush();
348             } catch (final IOException e) {
349                 // ignore
350             }
351             IOUtils.closeQuietly(writer);
352 
353             if (isCommit) {
354                 try {
355                     dictionaryManager.store(SynonymFile.this, newFile);
356                 } finally {
357                     newFile.delete();
358                 }
359             } else {
360                 newFile.delete();
361             }
362         }
363     }
364 
365 }