/*
* Copyright 2012-2017 CodeLibs Project and the Others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package org.codelibs.fess.dict.seunjeon;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.codelibs.core.lang.StringUtil;
import org.codelibs.fess.Constants;
import org.codelibs.fess.dict.DictionaryException;
import org.codelibs.fess.dict.DictionaryFile;
import org.dbflute.optional.OptionalEntity;
public class SeunjeonFile extends DictionaryFile<SeunjeonItem> {
private static final String SEUNJEON = "seunjeon";
List<SeunjeonItem> seunjeonItemList;
public SeunjeonFile(final String id, final String path, final Date timestamp) {
super(id, path, timestamp);
}
@Override
public String getType() {
return SEUNJEON;
}
@Override
public String getPath() {
return path;
}
@Override
public synchronized OptionalEntity<SeunjeonItem> get(final long id) {
if (seunjeonItemList == null) {
reload(null, null);
}
for (final SeunjeonItem SeunjeonItem : seunjeonItemList) {
if (id == SeunjeonItem.getId()) {
return OptionalEntity.of(SeunjeonItem);
}
}
return OptionalEntity.empty();
}
@Override
public synchronized PagingList<SeunjeonItem> selectList(final int offset, final int size) {
if (seunjeonItemList == null) {
reload(null, null);
}
if (offset >= seunjeonItemList.size() || offset < 0) {
return new PagingList<>(Collections.<SeunjeonItem> emptyList(), offset, size, seunjeonItemList.size());
}
int toIndex = offset + size;
if (toIndex > seunjeonItemList.size()) {
toIndex = seunjeonItemList.size();
}
return new PagingList<>(seunjeonItemList.subList(offset, toIndex), offset, size, seunjeonItemList.size());
}
@Override
public synchronized void insert(final SeunjeonItem item) {
try (SynonymUpdater updater = new SynonymUpdater(item)) {
reload(updater, null);
}
}
@Override
public synchronized void update(final SeunjeonItem item) {
try (SynonymUpdater updater = new SynonymUpdater(item)) {
reload(updater, null);
}
}
@Override
public synchronized void delete(final SeunjeonItem item) {
final SeunjeonItem SeunjeonItem = item;
SeunjeonItem.setNewInputs(StringUtil.EMPTY_STRINGS);
try (SynonymUpdater updater = new SynonymUpdater(item)) {
reload(updater, null);
}
}
protected void reload(final SynonymUpdater updater, final InputStream in) {
final List<SeunjeonItem> itemList = new ArrayList<>();
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(in != null ? in : dictionaryManager.getContentInputStream(this), Constants.UTF_8))) {
long id = 0;
String line = null;
while ((line = reader.readLine()) != null) {
if (line.length() == 0 || line.charAt(0) == '#') {
if (updater != null) {
updater.write(line);
}
continue; // ignore empty lines and comments
}
final List<String> inputStrings = split(line, ",");
final String[] inputs = new String[inputStrings.size()];
for (int i = 0; i < inputs.length; i++) {
inputs[i] = unescape(inputStrings.get(i)).trim();
}
if (inputs.length > 0) {
id++;
final SeunjeonItem item = new SeunjeonItem(id, inputs);
if (updater != null) {
final SeunjeonItem newItem = updater.write(item);
if (newItem != null) {
itemList.add(newItem);
} else {
id--;
}
} else {
itemList.add(item);
}
}
}
if (updater != null) {
final SeunjeonItem item = updater.commit();
if (item != null) {
itemList.add(item);
}
}
seunjeonItemList = itemList;
} catch (final IOException e) {
throw new DictionaryException("Failed to parse " + path, e);
}
}
private static List<String> split(final String s, final String separator) {
final List<String> list = new ArrayList<>(2);
StringBuilder sb = new StringBuilder();
int pos = 0;
final int end = s.length();
while (pos < end) {
if (s.startsWith(separator, pos)) {
if (sb.length() > 0) {
list.add(sb.toString());
sb = new StringBuilder();
}
pos += separator.length();
continue;
}
char ch = s.charAt(pos++);
if (ch == '\\') {
sb.append(ch);
if (pos >= end) {
break; // ERROR, or let it go?
}
ch = s.charAt(pos++);
}
sb.append(ch);
}
if (sb.length() > 0) {
list.add(sb.toString());
}
return list;
}
private String unescape(final String s) {
if (s.indexOf('\\') >= 0) {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) {
final char ch = s.charAt(i);
if (ch == '\\' && i < s.length() - 1) {
sb.append(s.charAt(++i));
} else {
sb.append(ch);
}
}
return sb.toString();
}
return s;
}
public String getSimpleName() {
return new File(path).getName();
}
public InputStream getInputStream() throws IOException {
return new BufferedInputStream(dictionaryManager.getContentInputStream(this));
}
public synchronized void update(final InputStream in) throws IOException {
try (SynonymUpdater updater = new SynonymUpdater(null)) {
reload(updater, in);
}
}
@Override
public String toString() {
return "SynonymFile [path=" + path + ", seunjeonItemList=" + seunjeonItemList + ", id=" + id + "]";
}
protected class SynonymUpdater implements Closeable {
protected boolean isCommit = false;
protected File newFile;
protected Writer writer;
protected SeunjeonItem item;
protected SynonymUpdater(final SeunjeonItem newItem) {
try {
newFile = File.createTempFile(SEUNJEON, ".txt");
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(newFile), Constants.UTF_8));
} catch (final IOException e) {
if (newFile != null) {
newFile.delete();
}
throw new DictionaryException("Failed to write a userDict file.", e);
}
item = newItem;
}
public SeunjeonItem write(final SeunjeonItem oldItem) {
try {
if (item != null && item.getId() == oldItem.getId() && item.isUpdated()) {
if (item.equals(oldItem)) {
try {
if (!item.isDeleted()) {
// update
writer.write(item.toLineString());
writer.write(Constants.LINE_SEPARATOR);
return new SeunjeonItem(item.getId(), item.getNewInputs());
} else {
return null;
}
} finally {
item.setNewInputs(null);
}
} else {
throw new DictionaryException("Seunjeon file was updated: old=" + oldItem + " : new=" + item);
}
} else {
writer.write(oldItem.toLineString());
writer.write(Constants.LINE_SEPARATOR);
return oldItem;
}
} catch (final IOException e) {
throw new DictionaryException("Failed to write: " + oldItem + " -> " + item, e);
}
}
public void write(final String line) {
try {
writer.write(line);
writer.write(Constants.LINE_SEPARATOR);
} catch (final IOException e) {
throw new DictionaryException("Failed to write: " + line, e);
}
}
public SeunjeonItem commit() {
isCommit = true;
if (item != null && item.isUpdated()) {
try {
writer.write(item.toLineString());
writer.write(Constants.LINE_SEPARATOR);
return item;
} catch (final IOException e) {
throw new DictionaryException("Failed to write: " + item, e);
}
}
return null;
}
@Override
public void close() {
try {
writer.flush();
} catch (final IOException e) {
// ignore
}
IOUtils.closeQuietly(writer);
if (isCommit) {
try {
dictionaryManager.store(SeunjeonFile.this, newFile);
} finally {
newFile.delete();
}
} else {
newFile.delete();
}
}
}
}