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