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