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