Shinsuke Sugaya 11 jaren geleden
bovenliggende
commit
446f14257a

+ 352 - 0
src/main/java/jp/sf/fess/action/admin/dict/UserDictAction.java

@@ -0,0 +1,352 @@
+/*
+ * Copyright 2009-2014 the 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 jp.sf.fess.action.admin.dict;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Resource;
+
+import jp.sf.fess.Constants;
+import jp.sf.fess.crud.CommonConstants;
+import jp.sf.fess.crud.CrudMessageException;
+import jp.sf.fess.crud.util.SAStrutsUtil;
+import jp.sf.fess.dict.userdict.UserDictItem;
+import jp.sf.fess.form.admin.dict.UserDictForm;
+import jp.sf.fess.helper.SystemHelper;
+import jp.sf.fess.pager.UserDictPager;
+import jp.sf.fess.service.UserDictService;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.codelibs.sastruts.core.annotation.Token;
+import org.codelibs.sastruts.core.exception.SSCActionMessagesException;
+import org.seasar.framework.beans.util.Beans;
+import org.seasar.framework.util.StringUtil;
+import org.seasar.struts.annotation.ActionForm;
+import org.seasar.struts.annotation.Execute;
+import org.seasar.struts.exception.ActionMessagesException;
+
+public class UserDictAction {
+
+    private static final Log log = LogFactory.getLog(UserDictAction.class);
+
+    @Resource
+    @ActionForm
+    protected UserDictForm userDictForm;
+
+    @Resource
+    protected UserDictService userDictService;
+
+    @Resource
+    protected UserDictPager userDictPager;
+
+    @Resource
+    protected SystemHelper systemHelper;
+
+    public List<UserDictItem> userDictItemItems;
+
+    public String getHelpLink() {
+        return systemHelper.getHelpLink("dict");
+    }
+
+    protected String displayList(final boolean redirect) {
+        // page navi
+        userDictItemItems = userDictService.getUserDictList(
+                userDictForm.dictId, userDictPager);
+
+        // restore from pager
+        Beans.copy(userDictPager, userDictForm.searchParams)
+                .excludes(CommonConstants.PAGER_CONVERSION_RULE).execute();
+
+        if (redirect) {
+            return "index?dictId=" + userDictForm.dictId + "&redirect=true";
+        } else {
+            return "index.jsp";
+        }
+    }
+
+    @Execute(validator = false, input = "error.jsp")
+    public String index() {
+        return displayList(false);
+    }
+
+    @Execute(validator = false, input = "error.jsp", urlPattern = "list/{dictId}/{pageNumber}")
+    public String list() {
+        // page navi
+        if (StringUtil.isNotBlank(userDictForm.pageNumber)) {
+            try {
+                userDictPager.setCurrentPageNumber(Integer
+                        .parseInt(userDictForm.pageNumber));
+            } catch (final NumberFormatException e) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Invalid value: " + userDictForm.pageNumber, e);
+                }
+            }
+        }
+
+        return displayList(false);
+    }
+
+    @Execute(validator = false, input = "error.jsp")
+    public String search() {
+        Beans.copy(userDictForm.searchParams, userDictPager)
+                .excludes(CommonConstants.PAGER_CONVERSION_RULE).execute();
+
+        return displayList(false);
+    }
+
+    @Execute(validator = false, input = "error.jsp")
+    public String reset() {
+        userDictPager.clear();
+
+        return displayList(false);
+    }
+
+    @Execute(validator = false, input = "error.jsp")
+    public String back() {
+        return displayList(false);
+    }
+
+    @Token(save = true, validate = false)
+    @Execute(validator = false, input = "error.jsp")
+    public String editagain() {
+        return "edit.jsp";
+    }
+
+    @Execute(validator = false, input = "error.jsp", urlPattern = "confirmpage/{dictId}/{crudMode}/{id}")
+    public String confirmpage() {
+        if (userDictForm.crudMode != CommonConstants.CONFIRM_MODE) {
+            throw new ActionMessagesException("errors.crud_invalid_mode",
+                    new Object[] { CommonConstants.CONFIRM_MODE,
+                            userDictForm.crudMode });
+        }
+
+        loadUserDict();
+
+        return "confirm.jsp";
+    }
+
+    @Token(save = true, validate = false)
+    @Execute(validator = false, input = "error.jsp")
+    public String createpage() {
+        // page navi
+        userDictForm.initialize();
+        userDictForm.crudMode = CommonConstants.CREATE_MODE;
+
+        return "edit.jsp";
+    }
+
+    @Token(save = true, validate = false)
+    @Execute(validator = false, input = "error.jsp", urlPattern = "editpage/{dictId}/{crudMode}/{id}")
+    public String editpage() {
+        if (userDictForm.crudMode != CommonConstants.EDIT_MODE) {
+            throw new ActionMessagesException("errors.crud_invalid_mode",
+                    new Object[] { CommonConstants.EDIT_MODE,
+                            userDictForm.crudMode });
+        }
+
+        loadUserDict();
+
+        return "edit.jsp";
+    }
+
+    @Token(save = true, validate = false)
+    @Execute(validator = false, input = "error.jsp")
+    public String editfromconfirm() {
+        userDictForm.crudMode = CommonConstants.EDIT_MODE;
+
+        loadUserDict();
+
+        return "edit.jsp";
+    }
+
+    @Token(save = false, validate = true, keep = true)
+    @Execute(validator = true, input = "edit.jsp")
+    public String confirmfromcreate() {
+        return "confirm.jsp";
+    }
+
+    @Token(save = false, validate = true, keep = true)
+    @Execute(validator = true, input = "edit.jsp")
+    public String confirmfromupdate() {
+        return "confirm.jsp";
+    }
+
+    @Token(save = true, validate = false)
+    @Execute(validator = false, input = "error.jsp", urlPattern = "deletepage/{dictId}/{crudMode}/{id}")
+    public String deletepage() {
+        if (userDictForm.crudMode != CommonConstants.DELETE_MODE) {
+            throw new ActionMessagesException("errors.crud_invalid_mode",
+                    new Object[] { CommonConstants.DELETE_MODE,
+                            userDictForm.crudMode });
+        }
+
+        loadUserDict();
+
+        return "confirm.jsp";
+    }
+
+    @Token(save = true, validate = false)
+    @Execute(validator = false, input = "error.jsp")
+    public String deletefromconfirm() {
+        userDictForm.crudMode = CommonConstants.DELETE_MODE;
+
+        loadUserDict();
+
+        return "confirm.jsp";
+    }
+
+    @Token(save = false, validate = true)
+    @Execute(validator = true, input = "edit.jsp")
+    public String create() {
+        try {
+            final UserDictItem userDictItem = createUserDict();
+            userDictService.store(userDictForm.dictId, userDictItem);
+            SAStrutsUtil.addSessionMessage("success.crud_create_crud_table");
+
+            return displayList(true);
+        } catch (final ActionMessagesException e) {
+            log.error(e.getMessage(), e);
+            throw e;
+        } catch (final CrudMessageException e) {
+            log.error(e.getMessage(), e);
+            throw new SSCActionMessagesException(e, e.getMessageId(),
+                    e.getArgs());
+        } catch (final Exception e) {
+            log.error(e.getMessage(), e);
+            throw new SSCActionMessagesException(e,
+                    "errors.crud_failed_to_create_crud_table");
+        }
+    }
+
+    @Token(save = false, validate = true)
+    @Execute(validator = true, input = "edit.jsp")
+    public String update() {
+        try {
+            final UserDictItem userDictItem = createUserDict();
+            userDictService.store(userDictForm.dictId, userDictItem);
+            SAStrutsUtil.addSessionMessage("success.crud_update_crud_table");
+
+            return displayList(true);
+        } catch (final ActionMessagesException e) {
+            log.error(e.getMessage(), e);
+            throw e;
+        } catch (final CrudMessageException e) {
+            log.error(e.getMessage(), e);
+            throw new SSCActionMessagesException(e, e.getMessageId(),
+                    e.getArgs());
+        } catch (final Exception e) {
+            log.error(e.getMessage(), e);
+            throw new SSCActionMessagesException(e,
+                    "errors.crud_failed_to_update_crud_table");
+        }
+    }
+
+    @Token(save = false, validate = true)
+    @Execute(validator = false, input = "error.jsp")
+    public String delete() {
+        if (userDictForm.crudMode != CommonConstants.DELETE_MODE) {
+            throw new ActionMessagesException("errors.crud_invalid_mode",
+                    new Object[] { CommonConstants.DELETE_MODE,
+                            userDictForm.crudMode });
+        }
+
+        try {
+            final UserDictItem userDictItem = userDictService.getUserDict(
+                    userDictForm.dictId, createKeyMap());
+            if (userDictItem == null) {
+                // throw an exception
+                throw new ActionMessagesException(
+                        "errors.crud_could_not_find_crud_table",
+
+                        new Object[] { userDictForm.id });
+
+            }
+
+            userDictService.delete(userDictForm.dictId, userDictItem);
+            SAStrutsUtil.addSessionMessage("success.crud_delete_crud_table");
+
+            return displayList(true);
+        } catch (final ActionMessagesException e) {
+            log.error(e.getMessage(), e);
+            throw e;
+        } catch (final CrudMessageException e) {
+            log.error(e.getMessage(), e);
+            throw new SSCActionMessagesException(e, e.getMessageId(),
+                    e.getArgs());
+        } catch (final Exception e) {
+            log.error(e.getMessage(), e);
+            throw new SSCActionMessagesException(e,
+                    "errors.crud_failed_to_delete_crud_table");
+        }
+    }
+
+    protected void loadUserDict() {
+
+        final UserDictItem userDictItem = userDictService.getUserDict(
+                userDictForm.dictId, createKeyMap());
+        if (userDictItem == null) {
+            // throw an exception
+            throw new ActionMessagesException(
+                    "errors.crud_could_not_find_crud_table",
+                    new Object[] { userDictForm.id });
+
+        }
+
+        userDictForm.id = Long.toString(userDictItem.getId());
+        userDictForm.token = userDictItem.getToken();
+        userDictForm.segmentation = userDictItem.getSegmentation();
+        userDictForm.reading = userDictItem.getReading();
+        userDictForm.pos = userDictItem.getPos();
+    }
+
+    protected UserDictItem createUserDict() {
+        UserDictItem userDictItem;
+        if (userDictForm.crudMode == CommonConstants.EDIT_MODE) {
+            userDictItem = userDictService.getUserDict(userDictForm.dictId,
+                    createKeyMap());
+            if (userDictItem == null) {
+                // throw an exception
+                throw new ActionMessagesException(
+                        "errors.crud_could_not_find_crud_table",
+
+                        new Object[] { userDictForm.id });
+
+            }
+        } else {
+            userDictItem = new UserDictItem(0, Constants.EMPTY_STRING,
+                    Constants.EMPTY_STRING, Constants.EMPTY_STRING,
+                    Constants.EMPTY_STRING);
+        }
+
+        userDictItem.setNewToken(userDictForm.token);
+        userDictItem.setNewSegmentation(userDictForm.segmentation);
+        userDictItem.setNewReading(userDictForm.reading);
+        userDictItem.setNewPos(userDictForm.pos);
+
+        return userDictItem;
+    }
+
+    protected Map<String, String> createKeyMap() {
+        final Map<String, String> keys = new HashMap<String, String>();
+        keys.put("id", userDictForm.id);
+        return keys;
+    }
+
+}

+ 4 - 3
src/main/java/jp/sf/fess/dict/DictionaryManager.java

@@ -23,8 +23,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.UUID;
 
-import jp.sf.fess.dict.synonym.SynonymFile;
-
 import org.seasar.extension.timer.TimeoutManager;
 import org.seasar.extension.timer.TimeoutTarget;
 import org.seasar.extension.timer.TimeoutTask;
@@ -70,7 +68,10 @@ public class DictionaryManager {
 
         final Collection<DictionaryFile<? extends DictionaryItem>> values = fileMap
                 .values();
-        return values.toArray(new SynonymFile[values.size()]);
+        @SuppressWarnings("unchecked")
+        final DictionaryFile<? extends DictionaryItem>[] list = new DictionaryFile[values
+                .size()];
+        return values.toArray(list);
     }
 
     public DictionaryFile<? extends DictionaryItem> getDictionaryFile(

+ 18 - 5
src/main/java/jp/sf/fess/dict/synonym/SynonymFile.java

@@ -117,7 +117,15 @@ public class SynonymFile extends DictionaryFile<SynonymItem> {
 
     @Override
     public synchronized void update(final SynonymItem item) {
-        reload(new SynonymUpdater(file, item));
+        SynonymUpdater updater = null;
+        try {
+            updater = new SynonymUpdater(file, item);
+            reload(updater);
+        } finally {
+            if (updater != null) {
+                updater.close();
+            }
+        }
     }
 
     @Override
@@ -125,7 +133,15 @@ public class SynonymFile extends DictionaryFile<SynonymItem> {
         final SynonymItem synonymItem = item;
         synonymItem.setNewInputs(new String[0]);
         synonymItem.setNewOutputs(new String[0]);
-        reload(new SynonymUpdater(file, synonymItem));
+        SynonymUpdater updater = null;
+        try {
+            updater = new SynonymUpdater(file, synonymItem);
+            reload(updater);
+        } finally {
+            if (updater != null) {
+                updater.close();
+            }
+        }
     }
 
     protected void reload(final SynonymUpdater updater) {
@@ -213,9 +229,6 @@ public class SynonymFile extends DictionaryFile<SynonymItem> {
                     + file.getAbsolutePath(), e);
         } finally {
             IOUtils.closeQuietly(reader);
-            if (updater != null) {
-                updater.close();
-            }
         }
     }
 

+ 311 - 0
src/main/java/jp/sf/fess/dict/userdict/UserDictFile.java

@@ -0,0 +1,311 @@
+/*
+ * Copyright 2009-2014 the 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 jp.sf.fess.dict.userdict;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import jp.sf.fess.Constants;
+import jp.sf.fess.dict.DictionaryException;
+import jp.sf.fess.dict.DictionaryFile;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.lucene.analysis.ja.util.CSVUtil;
+
+public class UserDictFile extends DictionaryFile<UserDictItem> {
+    private static final String USERDICT = "userDict";
+
+    private final File file;
+
+    List<UserDictItem> userDictItemList;
+
+    public UserDictFile(final File file) {
+        this.file = file;
+    }
+
+    @Override
+    public String getType() {
+        return USERDICT;
+    }
+
+    @Override
+    public String getName() {
+        return file.getAbsolutePath();
+    }
+
+    @Override
+    public UserDictItem get(final long id) {
+        for (final UserDictItem userDictItem : userDictItemList) {
+            if (id == userDictItem.getId()) {
+                return userDictItem;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public synchronized PagingList<UserDictItem> selectList(final int offset,
+            final int size) {
+        if (userDictItemList == null) {
+            reload(null);
+        }
+
+        if (offset >= userDictItemList.size() || offset < 0) {
+            return new PagingList<UserDictItem>(
+                    Collections.<UserDictItem> emptyList(), offset, size,
+                    userDictItemList.size());
+        }
+
+        int toIndex = offset + size;
+        if (toIndex > userDictItemList.size()) {
+            toIndex = userDictItemList.size();
+        }
+
+        return new PagingList<UserDictItem>(userDictItemList.subList(offset,
+                toIndex), offset, size, userDictItemList.size());
+    }
+
+    @Override
+    public synchronized void insert(final UserDictItem item) {
+        final UserDictItem userDictItem = item;
+        BufferedWriter bw = null;
+        try {
+            bw = new BufferedWriter(new OutputStreamWriter(
+                    new FileOutputStream(file, true), Constants.UTF_8));
+            bw.newLine();
+            bw.write(userDictItem.toLineString());
+            bw.flush();
+
+            long nextId = 1;
+            if (!userDictItemList.isEmpty()) {
+                final UserDictItem lastItem = userDictItemList
+                        .get(userDictItemList.size() - 1);
+                nextId = lastItem.getId() + 1;
+            }
+            userDictItemList.add(new UserDictItem(nextId, userDictItem
+                    .getNewToken(), userDictItem.getNewSegmentation(),
+                    userDictItem.getNewReading(), userDictItem.getNewPos()));
+        } catch (final IOException e) {
+            throw new DictionaryException("Failed to write: " + item, e);
+        } finally {
+            IOUtils.closeQuietly(bw);
+        }
+    }
+
+    @Override
+    public synchronized void update(final UserDictItem item) {
+        UserDictUpdater updater = null;
+        try {
+            updater = new UserDictUpdater(file, item);
+            reload(updater);
+        } finally {
+            if (updater != null) {
+                updater.close();
+            }
+        }
+    }
+
+    @Override
+    public synchronized void delete(final UserDictItem item) {
+        final UserDictItem userDictItem = item;
+        userDictItem.setNewToken(Constants.EMPTY_STRING);
+        UserDictUpdater updater = null;
+        try {
+            updater = new UserDictUpdater(file, userDictItem);
+            reload(updater);
+        } finally {
+            if (updater != null) {
+                updater.close();
+            }
+        }
+    }
+
+    protected void reload(final UserDictUpdater updater) {
+        final List<UserDictItem> itemList = new ArrayList<UserDictItem>();
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new InputStreamReader(
+                    new FileInputStream(file), Constants.UTF_8));
+            long id = 0;
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                // Remove comments
+                line = line.replaceAll("#.*$", "");
+
+                // Skip empty lines or comment lines
+                if (line.trim().length() == 0) {
+                    if (updater != null) {
+                        updater.write(line);
+                    }
+                    continue;
+                }
+
+                final String[] values = CSVUtil.parse(line);
+                String token = null;
+                String segmentation = null;
+                String reading = null;
+                String pos = null;
+                switch (values.length) {
+                case 4:
+                    pos = values[3];
+                case 3:
+                    reading = values[2];
+                case 2:
+                    segmentation = values[1];
+                case 1:
+                    token = values[0];
+                default:
+                    break;
+                }
+
+                id++;
+                final UserDictItem item = new UserDictItem(id, token,
+                        segmentation, reading, pos);
+                if (updater != null) {
+                    final UserDictItem newItem = updater.write(item);
+                    if (newItem != null) {
+                        itemList.add(newItem);
+                    } else {
+                        id--;
+                    }
+                } else {
+                    itemList.add(item);
+                }
+            }
+            if (updater != null) {
+                updater.commit();
+            }
+            userDictItemList = itemList;
+        } catch (final IOException e) {
+            throw new DictionaryException("Failed to parse "
+                    + file.getAbsolutePath(), e);
+        } finally {
+            IOUtils.closeQuietly(reader);
+        }
+    }
+
+    protected static class UserDictUpdater {
+
+        protected boolean isCommit = false;
+
+        protected File oldFile;
+
+        protected File newFile;
+
+        protected Writer writer;
+
+        protected UserDictItem item;
+
+        protected UserDictUpdater(final File file, final UserDictItem newItem) {
+            try {
+                newFile = File.createTempFile(USERDICT, ".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);
+            }
+            oldFile = file;
+            item = newItem;
+        }
+
+        public UserDictItem write(final UserDictItem oldItem) {
+            try {
+                if (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 UserDictItem(item.getId(),
+                                        item.getNewToken(),
+                                        item.getNewSegmentation(),
+                                        item.getNewReading(), item.getNewPos());
+                            } else {
+                                return null;
+                            }
+                        } finally {
+                            item.setNewToken(null);
+                        }
+                    } else {
+                        throw new DictionaryException(
+                                "UserDict 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 void commit() {
+            isCommit = true;
+        }
+
+        public void close() {
+            try {
+                writer.flush();
+            } catch (final IOException e) {
+                // ignore
+            }
+            IOUtils.closeQuietly(writer);
+
+            if (isCommit) {
+                try {
+                    FileUtils.copyFile(newFile, oldFile);
+                    newFile.delete();
+                } catch (final IOException e) {
+                    throw new DictionaryException("Failed to replace "
+                            + oldFile.getAbsolutePath() + " with "
+                            + newFile.getAbsolutePath(), e);
+                }
+            } else {
+                newFile.delete();
+            }
+        }
+    }
+
+}

+ 208 - 0
src/main/java/jp/sf/fess/dict/userdict/UserDictItem.java

@@ -0,0 +1,208 @@
+/*
+ * Copyright 2009-2014 the 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 jp.sf.fess.dict.userdict;
+
+import jp.sf.fess.dict.DictionaryItem;
+
+public class UserDictItem extends DictionaryItem {
+    private final String token;
+
+    private final String segmentation;
+
+    private final String reading;
+
+    private final String pos;
+
+    private String newToken;
+
+    private String newSegmentation;
+
+    private String newReading;
+
+    private String newPos;
+
+    public UserDictItem(final long id, final String token,
+            final String segmentation, final String reading, final String pos) {
+        this.id = id;
+        this.token = token;
+        this.segmentation = segmentation;
+        this.reading = reading;
+        this.pos = pos;
+
+        if (id == 0) {
+            // create
+            newToken = token;
+            newSegmentation = segmentation;
+            newReading = reading;
+            newPos = pos;
+        }
+    }
+
+    public String getNewToken() {
+        return newToken;
+    }
+
+    public void setNewToken(final String newToken) {
+        this.newToken = newToken;
+    }
+
+    public String getNewSegmentation() {
+        return newSegmentation;
+    }
+
+    public void setNewSegmentation(final String newSegmentation) {
+        this.newSegmentation = newSegmentation;
+    }
+
+    public String getNewReading() {
+        return newReading;
+    }
+
+    public void setNewReading(final String newReading) {
+        this.newReading = newReading;
+    }
+
+    public String getNewPos() {
+        return newPos;
+    }
+
+    public void setNewPos(final String newPos) {
+        this.newPos = newPos;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public String getSegmentation() {
+        return segmentation;
+    }
+
+    public String getReading() {
+        return reading;
+    }
+
+    public String getPos() {
+        return pos;
+    }
+
+    public boolean isUpdated() {
+        return newToken != null;
+    }
+
+    public boolean isDeleted() {
+        return isUpdated() && newToken.length() == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + (pos == null ? 0 : pos.hashCode());
+        result = prime * result + (reading == null ? 0 : reading.hashCode());
+        result = prime * result
+                + (segmentation == null ? 0 : segmentation.hashCode());
+        result = prime * result + (token == null ? 0 : token.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final UserDictItem other = (UserDictItem) obj;
+        if (pos == null) {
+            if (other.pos != null) {
+                return false;
+            }
+        } else if (!pos.equals(other.pos)) {
+            return false;
+        }
+        if (reading == null) {
+            if (other.reading != null) {
+                return false;
+            }
+        } else if (!reading.equals(other.reading)) {
+            return false;
+        }
+        if (segmentation == null) {
+            if (other.segmentation != null) {
+                return false;
+            }
+        } else if (!segmentation.equals(other.segmentation)) {
+            return false;
+        }
+        if (token == null) {
+            if (other.token != null) {
+                return false;
+            }
+        } else if (!token.equals(other.token)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "UserDictItem [token=" + token + ", segmentation="
+                + segmentation + ", reading=" + reading + ", pos=" + pos
+                + ", newToken=" + newToken + ", newSegmentation="
+                + newSegmentation + ", newReading=" + newReading + ", newPos="
+                + newPos + "]";
+    }
+
+    public String toLineString() {
+        final StringBuilder buf = new StringBuilder(100);
+        if (isUpdated()) {
+            buf.append(quoteEscape(newToken));
+            buf.append(',');
+            buf.append(quoteEscape(newSegmentation));
+            buf.append(',');
+            buf.append(quoteEscape(newReading));
+            buf.append(',');
+            buf.append(quoteEscape(newPos));
+        } else {
+            buf.append(quoteEscape(token));
+            buf.append(',');
+            buf.append(quoteEscape(segmentation));
+            buf.append(',');
+            buf.append(quoteEscape(reading));
+            buf.append(',');
+            buf.append(quoteEscape(pos));
+        }
+        return buf.toString();
+    }
+
+    private static String quoteEscape(final String original) {
+        String result = original;
+
+        if (result.indexOf('\"') >= 0) {
+            result = result.replace("\"", "\"\"");
+        }
+        if (result.indexOf(',') >= 0) {
+            result = "\"" + result + "\"";
+        }
+        return result;
+    }
+}

+ 68 - 0
src/main/java/jp/sf/fess/dict/userdict/UserDictLocator.java

@@ -0,0 +1,68 @@
+/*
+ * Copyright 2009-2014 the 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 jp.sf.fess.dict.userdict;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+import jp.sf.fess.dict.DictionaryFile;
+import jp.sf.fess.dict.DictionaryItem;
+import jp.sf.fess.dict.DictionaryLocator;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class UserDictLocator extends DictionaryLocator {
+    private static final Logger logger = LoggerFactory
+            .getLogger(UserDictLocator.class);
+
+    public String userDictFilePrefix = "userdict";
+
+    public List<String> excludedUserDictList;
+
+    @Override
+    public List<DictionaryFile<? extends DictionaryItem>> find() {
+        final List<DictionaryFile<? extends DictionaryItem>> fileList = new ArrayList<DictionaryFile<? extends DictionaryItem>>();
+        for (final String path : searchPathList) {
+            if (logger.isInfoEnabled()) {
+                logger.info("UserDict Files from " + path);
+            }
+            final File[] files = findFiles(path, userDictFilePrefix,
+                    excludedUserDictList);
+            for (final File file : files) {
+                if (logger.isInfoEnabled()) {
+                    logger.info("UserDict File: " + file.getAbsolutePath());
+                }
+                fileList.add(new UserDictFile(file));
+            }
+        }
+        Collections.sort(fileList,
+                new Comparator<DictionaryFile<? extends DictionaryItem>>() {
+                    @Override
+                    public int compare(
+                            final DictionaryFile<? extends DictionaryItem> o1,
+                            final DictionaryFile<? extends DictionaryItem> o2) {
+                        return o1.getName().compareTo(o2.getName());
+                    }
+                });
+        return fileList;
+    }
+
+}

+ 67 - 0
src/main/java/jp/sf/fess/form/admin/dict/UserDictForm.java

@@ -0,0 +1,67 @@
+/*
+ * Copyright 2009-2014 the 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 jp.sf.fess.form.admin.dict;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.seasar.struts.annotation.IntegerType;
+import org.seasar.struts.annotation.LongType;
+import org.seasar.struts.annotation.Maxbytelength;
+import org.seasar.struts.annotation.Required;
+
+public class UserDictForm {
+    @IntegerType
+    public String pageNumber;
+
+    public Map<String, String> searchParams = new HashMap<String, String>();
+
+    @Required
+    public String dictId;
+
+    @IntegerType
+    public int crudMode;
+
+    public String getCurrentPageNumber() {
+        return pageNumber;
+    }
+
+    @Required(target = "confirmfromupdate,update,delete")
+    @LongType
+    public String id;
+
+    @Required(target = "confirmfromcreate,create,confirmfromupdate,update,delete")
+    @Maxbytelength(maxbytelength = 1000)
+    public String token;
+
+    @Required(target = "confirmfromcreate,create,confirmfromupdate,update,delete")
+    @Maxbytelength(maxbytelength = 1000)
+    public String segmentation;
+
+    @Required(target = "confirmfromcreate,create,confirmfromupdate,update,delete")
+    @Maxbytelength(maxbytelength = 1000)
+    public String reading;
+
+    @Required(target = "confirmfromcreate,create,confirmfromupdate,update,delete")
+    @Maxbytelength(maxbytelength = 1000)
+    public String pos;
+
+    public void initialize() {
+        id = null;
+
+    }
+}

+ 118 - 0
src/main/java/jp/sf/fess/pager/UserDictPager.java

@@ -0,0 +1,118 @@
+/*
+ * Copyright 2009-2014 the 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 jp.sf.fess.pager;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class UserDictPager implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private int allRecordCount;
+
+    private int allPageCount;
+
+    private boolean existPrePage;
+
+    private boolean existNextPage;
+
+    private List<Integer> pageNumberList;
+
+    private int pageSize;
+
+    private int currentPageNumber;
+
+    public String id;
+
+    public void clear() {
+        pageSize = getDefaultPageSize();
+        currentPageNumber = getDefaultCurrentPageNumber();
+
+        id = null;
+    }
+
+    protected int getDefaultPageSize() {
+        return 20;
+    }
+
+    protected int getDefaultCurrentPageNumber() {
+        return 1;
+    }
+
+    public int getAllRecordCount() {
+        return allRecordCount;
+    }
+
+    public void setAllRecordCount(final int allRecordCount) {
+        this.allRecordCount = allRecordCount;
+    }
+
+    public int getAllPageCount() {
+        return allPageCount;
+    }
+
+    public void setAllPageCount(final int allPageCount) {
+        this.allPageCount = allPageCount;
+    }
+
+    public boolean isExistPrePage() {
+        return existPrePage;
+    }
+
+    public void setExistPrePage(final boolean existPrePage) {
+        this.existPrePage = existPrePage;
+    }
+
+    public boolean isExistNextPage() {
+        return existNextPage;
+    }
+
+    public void setExistNextPage(final boolean existNextPage) {
+        this.existNextPage = existNextPage;
+    }
+
+    public int getPageSize() {
+        if (pageSize <= 0) {
+            pageSize = getDefaultPageSize();
+        }
+        return pageSize;
+    }
+
+    public void setPageSize(final int pageSize) {
+        this.pageSize = pageSize;
+    }
+
+    public int getCurrentPageNumber() {
+        if (currentPageNumber <= 0) {
+            currentPageNumber = getDefaultCurrentPageNumber();
+        }
+        return currentPageNumber;
+    }
+
+    public void setCurrentPageNumber(final int currentPageNumber) {
+        this.currentPageNumber = currentPageNumber;
+    }
+
+    public List<Integer> getPageNumberList() {
+        return pageNumberList;
+    }
+
+    public void setPageNumberList(final List<Integer> pageNumberList) {
+        this.pageNumberList = pageNumberList;
+    }
+}

+ 98 - 0
src/main/java/jp/sf/fess/service/UserDictService.java

@@ -0,0 +1,98 @@
+/*
+ * Copyright 2009-2014 the 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 jp.sf.fess.service;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Resource;
+
+import jp.sf.fess.crud.CommonConstants;
+import jp.sf.fess.dict.DictionaryFile;
+import jp.sf.fess.dict.DictionaryFile.PagingList;
+import jp.sf.fess.dict.DictionaryManager;
+import jp.sf.fess.dict.userdict.UserDictFile;
+import jp.sf.fess.dict.userdict.UserDictItem;
+import jp.sf.fess.pager.UserDictPager;
+
+import org.seasar.framework.beans.util.Beans;
+import org.seasar.framework.util.StringUtil;
+import org.seasar.struts.exception.ActionMessagesException;
+
+public class UserDictService {
+    @Resource
+    protected DictionaryManager dictionaryManager;
+
+    public List<UserDictItem> getUserDictList(final String dictId,
+            final UserDictPager userDictPager) {
+        final UserDictFile userDictFile = getUserDictFile(dictId);
+
+        final int pageSize = userDictPager.getPageSize();
+        final PagingList<UserDictItem> userDictList = userDictFile.selectList(
+                (userDictPager.getCurrentPageNumber() - 1) * pageSize, pageSize);
+
+        // update pager
+        Beans.copy(userDictList, userDictPager)
+                .includes(CommonConstants.PAGER_CONVERSION_RULE).execute();
+        userDictList.setPageRangeSize(5);
+        userDictPager.setPageNumberList(userDictList.createPageNumberList());
+
+        return userDictList;
+
+    }
+
+    protected UserDictFile getUserDictFile(final String dictId) {
+        final DictionaryFile<?> dictionaryFile = dictionaryManager
+                .getDictionaryFile(dictId);
+        if (dictionaryFile instanceof UserDictFile) {
+            return (UserDictFile) dictionaryFile;
+        }
+        throw new ActionMessagesException("errors.expired_dict_id");
+    }
+
+    public UserDictItem getUserDict(final String dictId,
+            final Map<String, String> paramMap) {
+        final UserDictFile userDictFile = getUserDictFile(dictId);
+
+        final String idStr = paramMap.get("id");
+        if (StringUtil.isNotBlank(idStr)) {
+            try {
+                final long id = Long.parseLong(idStr);
+                return userDictFile.get(id);
+            } catch (final NumberFormatException e) {
+                // ignore
+            }
+        }
+
+        return null;
+    }
+
+    public void store(final String dictId, final UserDictItem userDictItem) {
+        final UserDictFile userDictFile = getUserDictFile(dictId);
+
+        if (userDictItem.getId() == 0) {
+            userDictFile.insert(userDictItem);
+        } else {
+            userDictFile.update(userDictItem);
+        }
+    }
+
+    public void delete(final String dictId, final UserDictItem userDictItem) {
+        final UserDictFile userDictFile = getUserDictFile(dictId);
+        userDictFile.delete(userDictItem);
+    }
+}

+ 19 - 0
src/main/resources/application.properties

@@ -1365,6 +1365,25 @@ labels.dict_synonym_button_edit=Edit
 labels.dict_synonym_button_delete=Delete
 labels.dict_synonym_button_update=Update
 
+# userdict
+labels.dict_userdict_configuration=UserDict List
+labels.dict_userdict_title=UserDict List
+labels.dict_userdict_list_link=List
+labels.dict_userdict_link_create=Create
+labels.dict_userdict_link_update=Update
+labels.dict_userdict_link_delete=Delete
+labels.dict_userdict_link_confirm=Confirm
+labels.dict_userdict_token=Token
+labels.dict_userdict_segmentation=Segmentation
+labels.dict_userdict_reading=Reading
+labels.dict_userdict_pos=POS
+labels.dict_userdict_button_create=Create
+labels.dict_userdict_button_back=Back
+labels.dict_userdict_button_confirm=Confirm
+labels.dict_userdict_button_edit=Edit
+labels.dict_userdict_button_delete=Delete
+labels.dict_userdict_button_update=Update
+
 # CRUD PROPERTIES: BEGIN
 errors.crud_invalid_mode=Invalid mode(expected value is {0}, but it's {1}).
 errors.crud_failed_to_create_crud_table=Failed to create a new data.

+ 19 - 0
src/main/resources/application_ja.properties

@@ -1363,6 +1363,25 @@ labels.dict_synonym_button_edit=\u7de8\u96c6
 labels.dict_synonym_button_delete=\u524a\u9664
 labels.dict_synonym_button_update=\u66f4\u65b0
 
+# userdict
+labels.dict_userdict_configuration=\u30e6\u30fc\u30b6\u30fc\u8f9e\u66f8\u4e00\u89a7
+labels.dict_userdict_title=\u30e6\u30fc\u30b6\u30fc\u8f9e\u66f8\u4e00\u89a7
+labels.dict_userdict_list_link=\u4e00\u89a7
+labels.dict_userdict_link_create=\u4f5c\u6210
+labels.dict_userdict_link_update=\u66f4\u65b0
+labels.dict_userdict_link_delete=\u524a\u9664
+labels.dict_userdict_link_confirm=\u78ba\u8a8d
+labels.dict_userdict_token=\u30c8\u30fc\u30af\u30f3
+labels.dict_userdict_segmentation=\u5206\u5272
+labels.dict_userdict_reading=\u8aad\u307f
+labels.dict_userdict_pos=\u54c1\u8a5e
+labels.dict_userdict_button_create=\u4f5c\u6210
+labels.dict_userdict_button_back=\u623b\u308b
+labels.dict_userdict_button_confirm=\u78ba\u8a8d
+labels.dict_userdict_button_edit=\u7de8\u96c6
+labels.dict_userdict_button_delete=\u524a\u9664
+labels.dict_userdict_button_update=\u66f4\u65b0
+
 # CRUD PROPERTIES: BEGIN
 errors.crud_invalid_mode=\u30e2\u30fc\u30c9\u304c\u9055\u3044\u307e\u3059\u3002(\u6b63\u3057\u3044\u5024\u306f {0} \u3067\u3059\u304c\u3001\u5165\u529b\u3055\u308c\u305f\u5024\u306f {1} \u306b\u306a\u3063\u3066\u3044\u307e\u3059)
 errors.crud_failed_to_create_crud_table=\u30c7\u30fc\u30bf\u306e\u4f5c\u6210\u306b\u5931\u6557\u3057\u307e\u3057\u305f\u3002

+ 11 - 0
src/main/resources/fess_dict.dicon

@@ -3,6 +3,9 @@
 	"http://www.seasar.org/dtd/components24.dtd">
 <components>
 	<component name="dictionaryManager" class="jp.sf.fess.dict.DictionaryManager">
+		<initMethod name="addLocator">
+			<arg>userDictLocator</arg>
+		</initMethod>
 		<initMethod name="addLocator">
 			<arg>synonymLocator</arg>
 		</initMethod>
@@ -16,4 +19,12 @@
 		</initMethod>
 	</component>
 
+	<component name="userDictLocator" class="jp.sf.fess.dict.userdict.UserDictLocator">
+		<property name="excludedUserDictList">{"data", "txlog",
+			"lib", "bin", "contrib"}</property>
+		<initMethod name="addSearchPath">
+			<arg>"${solr.solr.home}"</arg>
+		</initMethod>
+	</component>
+
 </components>

+ 114 - 0
src/main/webapp/WEB-INF/view/admin/dict/userdict/confirm.jsp

@@ -0,0 +1,114 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><tiles:insert template="/WEB-INF/view/common/admin/layout.jsp"
+	flush="true">
+	<tiles:put name="title">
+		<bean:message key="labels.dict_userdict_configuration" />
+	</tiles:put>
+	<tiles:put name="header" value="/WEB-INF/view/common/admin/header.jsp" />
+	<tiles:put name="footer" value="/WEB-INF/view/common/admin/footer.jsp" />
+	<tiles:put name="menu" value="/WEB-INF/view/common/admin/menu.jsp" />
+	<tiles:put name="menuType" value="dict" />
+	<tiles:put name="headerScript" type="string"></tiles:put>
+	<tiles:put name="body" type="string">
+
+		<h3>
+			<bean:message key="labels.dict_userdict_title" />
+		</h3>
+
+		<%-- Message: BEGIN --%>
+		<div>
+			<html:messages id="msg" message="true">
+				<div class="alert-message info"><bean:write name="msg" ignore="true" /></div>
+			</html:messages>
+			<html:errors />
+		</div>
+		<%-- Message: END --%>
+
+			<div>
+				<ul class="pills">
+					<li><s:link href="../index">
+							<bean:message key="labels.dict_list_link" />
+						</s:link></li>
+					<li><s:link href="index?dictId=${f:u(dictId)}">
+							<bean:message key="labels.dict_userdict_list_link" />
+						</s:link></li>
+					<c:if test="${crudMode == 1}">
+					<li class="active"><a href="#"><bean:message
+								key="labels.dict_userdict_link_create" /></a></li>
+					</c:if>
+					<c:if test="${crudMode == 2}">
+					<li class="active"><a href="#"><bean:message
+								key="labels.dict_userdict_link_update" /></a></li>
+					</c:if>
+					<c:if test="${crudMode == 3}">
+					<li class="active"><a href="#"><bean:message
+								key="labels.dict_userdict_link_delete" /></a></li>
+					</c:if>
+					<c:if test="${crudMode == 4}">
+					<li class="active"><a href="#"><bean:message
+								key="labels.dict_userdict_link_confirm" /></a></li>
+					</c:if>
+				</ul>
+			</div>
+
+		<%-- Confirm Form: BEGIN --%>
+		<s:form>
+			<html:hidden property="crudMode" />
+			<div>
+				<html:hidden property="dictId" />
+				<c:if test="${crudMode==2 || crudMode==3 || crudMode==4}">
+					<html:hidden property="id" />
+				</c:if>
+				<table class="bordered-table zebra-striped" style="width: 500px;">
+					<tbody>
+						<tr>
+							<th style="width: 150px;"><bean:message
+									key="labels.dict_userdict_token" /></th>
+							<td>${f:h(token)}<html:hidden property="token" /></td>
+						</tr>
+						<tr>
+							<th><bean:message key="labels.dict_userdict_segmentation" /></th>
+							<td>${f:h(segmentation)}<html:hidden property="segmentation" /></td>
+						</tr>
+						<tr>
+							<th><bean:message key="labels.dict_userdict_reading" /></th>
+							<td>${f:h(reading)}<html:hidden property="reading" /></td>
+						</tr>
+						<tr>
+							<th><bean:message key="labels.dict_userdict_pos" /></th>
+							<td>${f:h(pos)}<html:hidden property="pos" /></td>
+						</tr>
+					</tbody>
+					<tfoot>
+						<tr>
+							<td colspan="2"><c:if test="${crudMode == 1}">
+									<input type="submit" class="btn mini" name="create"
+										value="<bean:message key="labels.dict_userdict_button_create"/>" />
+									<input type="submit" class="btn mini" name="editagain"
+										value="<bean:message key="labels.dict_userdict_button_back"/>" />
+								</c:if> <c:if test="${crudMode == 2}">
+									<input type="submit" class="btn mini" name="update"
+										value="<bean:message key="labels.dict_userdict_button_update"/>" />
+									<input type="submit" class="btn mini" name="editagain"
+										value="<bean:message key="labels.dict_userdict_button_back"/>" />
+								</c:if> <c:if test="${crudMode == 3}">
+									<input type="submit" class="btn mini" name="delete"
+										value="<bean:message key="labels.dict_userdict_button_delete"/>" />
+									<input type="submit" class="btn mini" name="back"
+										value="<bean:message key="labels.dict_userdict_button_back"/>" />
+								</c:if> <c:if test="${crudMode == 4}">
+									<input type="submit" class="btn mini" name="back"
+										value="<bean:message key="labels.dict_userdict_button_back"/>" />
+									<input type="submit" class="btn mini" name="editfromconfirm"
+										value="<bean:message key="labels.dict_userdict_button_edit"/>" />
+									<input type="submit" class="btn mini" name="deletefromconfirm"
+										value="<bean:message key="labels.dict_userdict_button_delete"/>" />
+								</c:if></td>
+						</tr>
+					</tfoot>
+				</table>
+			</div>
+		</s:form>
+		<%-- Confirm Form: BEGIN --%>
+
+	</tiles:put>
+</tiles:insert>

+ 105 - 0
src/main/webapp/WEB-INF/view/admin/dict/userdict/edit.jsp

@@ -0,0 +1,105 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><tiles:insert template="/WEB-INF/view/common/admin/layout.jsp"
+	flush="true">
+	<tiles:put name="title">
+		<bean:message key="labels.dict_userdict_configuration" />
+	</tiles:put>
+	<tiles:put name="header" value="/WEB-INF/view/common/admin/header.jsp" />
+	<tiles:put name="footer" value="/WEB-INF/view/common/admin/footer.jsp" />
+	<tiles:put name="menu" value="/WEB-INF/view/common/admin/menu.jsp" />
+	<tiles:put name="menuType" value="dict" />
+	<tiles:put name="headerScript" type="string"></tiles:put>
+	<tiles:put name="body" type="string">
+
+		<h3>
+			<bean:message key="labels.dict_userdict_title" />
+		</h3>
+
+		<%-- Message: BEGIN --%>
+		<div>
+			<html:messages id="msg" message="true">
+				<div class="alert-message info"><bean:write name="msg" ignore="true" /></div>
+			</html:messages>
+			<html:errors />
+		</div>
+		<%-- Message: END --%>
+
+			<div>
+				<ul class="pills">
+					<li><s:link href="../index">
+							<bean:message key="labels.dict_list_link" />
+						</s:link></li>
+					<li><s:link href="index?dictId=${f:u(dictId)}">
+							<bean:message key="labels.dict_userdict_list_link" />
+						</s:link></li>
+					<c:if test="${crudMode == 1}">
+					<li class="active"><a href="#"><bean:message
+								key="labels.dict_userdict_link_create" /></a></li>
+					</c:if>
+					<c:if test="${crudMode == 2}">
+					<li class="active"><a href="#"><bean:message
+								key="labels.dict_userdict_link_update" /></a></li>
+					</c:if>
+					<c:if test="${crudMode == 3}">
+					<li class="active"><a href="#"><bean:message
+								key="labels.dict_userdict_link_delete" /></a></li>
+					</c:if>
+					<c:if test="${crudMode == 4}">
+					<li class="active"><a href="#"><bean:message
+								key="labels.dict_userdict_link_confirm" /></a></li>
+					</c:if>
+				</ul>
+			</div>
+
+		<%-- Edit Form: BEGIN --%>
+		<s:form>
+			<html:hidden property="crudMode" />
+			<div>
+				<html:hidden property="dictId" />
+				<c:if test="${crudMode==2}">
+					<html:hidden property="id" />
+				</c:if>
+				<table class="bordered-table zebra-striped" style="width: 500px;">
+					<tbody>
+						<tr>
+							<th style="width: 150px;"><bean:message
+									key="labels.dict_userdict_token" /></th>
+							<td><html:text property="token" style="width:98%;" /></td>
+						</tr>
+						<tr>
+							<th><bean:message
+									key="labels.dict_userdict_segmentation" /></th>
+							<td><html:text property="segmentation" style="width:98%;" /></td>
+						</tr>
+						<tr>
+							<th><bean:message
+									key="labels.dict_userdict_reading" /></th>
+							<td><html:text property="reading" style="width:98%;" /></td>
+						</tr>
+						<tr>
+							<th><bean:message
+									key="labels.dict_userdict_pos" /></th>
+							<td><html:text property="pos" style="width:98%;" /></td>
+						</tr>
+					</tbody>
+					<tfoot>
+						<tr>
+							<td colspan="2"><c:if test="${crudMode == 1}">
+									<input type="submit" class="btn mini" name="confirmfromcreate"
+										value="<bean:message key="labels.dict_userdict_button_create"/>" />
+									<input type="submit" class="btn mini" name="back"
+										value="<bean:message key="labels.dict_userdict_button_back"/>" />
+								</c:if> <c:if test="${crudMode == 2}">
+									<input type="submit" class="btn mini" name="confirmfromupdate"
+										value="<bean:message key="labels.dict_userdict_button_confirm"/>" />
+									<input type="submit" class="btn mini" name="back"
+										value="<bean:message key="labels.dict_userdict_button_back"/>" />
+								</c:if></td>
+						</tr>
+					</tfoot>
+				</table>
+			</div>
+		</s:form>
+		<%-- Edit Form: BEGIN --%>
+
+	</tiles:put>
+</tiles:insert>

+ 19 - 0
src/main/webapp/WEB-INF/view/admin/dict/userdict/error.jsp

@@ -0,0 +1,19 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><tiles:insert template="/WEB-INF/view/common/admin/layout.jsp" flush="true">
+	<tiles:put name="title"><bean:message key="labels.dict_userdict_configuration" /></tiles:put>
+	<tiles:put name="header" value="/WEB-INF/view/common/admin/header.jsp" />
+	<tiles:put name="footer" value="/WEB-INF/view/common/admin/footer.jsp" />
+	<tiles:put name="menu" value="/WEB-INF/view/common/admin/menu.jsp" />
+	<tiles:put name="menuType" value="dict" />
+	<tiles:put name="headerScript" type="string"></tiles:put>
+	<tiles:put name="body" type="string">
+
+      <div id="main">
+      
+<html:errors/>
+<br/>
+<s:link href="../index"><bean:message key="labels.dict_button_back"/></s:link>
+
+      </div>
+
+	</tiles:put>
+</tiles:insert>

+ 121 - 0
src/main/webapp/WEB-INF/view/admin/dict/userdict/index.jsp

@@ -0,0 +1,121 @@
+<%@page pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%><tiles:insert template="/WEB-INF/view/common/admin/layout.jsp"
+	flush="true">
+	<tiles:put name="title">
+		<bean:message key="labels.dict_userdict_configuration" />
+	</tiles:put>
+	<tiles:put name="header" value="/WEB-INF/view/common/admin/header.jsp" />
+	<tiles:put name="footer" value="/WEB-INF/view/common/admin/footer.jsp" />
+	<tiles:put name="menu" value="/WEB-INF/view/common/admin/menu.jsp" />
+	<tiles:put name="menuType" value="dict" />
+	<tiles:put name="headerScript" type="string"></tiles:put>
+	<tiles:put name="body" type="string">
+
+		<h3>
+			<bean:message key="labels.dict_userdict_title" />
+		</h3>
+
+		<%-- Message: BEGIN --%>
+		<div>
+			<html:messages id="msg" message="true">
+				<div class="alert-message info"><bean:write name="msg" ignore="true" /></div>
+			</html:messages>
+			<html:errors />
+		</div>
+		<%-- Message: END --%>
+
+		<%-- List: BEGIN --%>
+		<div class="list-table">
+			<div>
+				<ul class="pills">
+					<li><s:link href="../index">
+							<bean:message key="labels.dict_list_link" />
+						</s:link></li>
+					<li class="active"><a href="#"><bean:message
+								key="labels.dict_userdict_list_link" /></a></li>
+					<li><s:link href="createpage?dictId=${f:u(dictId)}">
+							<bean:message key="labels.dict_userdict_link_create" />
+						</s:link></li>
+				</ul>
+			</div>
+
+			<c:if test="${userDictPager.allRecordCount == 0}">
+				<p class="alert-message warning">
+					<bean:message key="labels.list_could_not_find_crud_table" />
+				</p>
+			</c:if>
+			<c:if test="${userDictPager.allRecordCount > 0}">
+				<table class="bordered-table zebra-striped">
+					<thead>
+						<tr>
+							<th style="text-align: left;"><bean:message
+									key="labels.dict_userdict_token" /></th>
+							<th style="text-align: left;"><bean:message
+									key="labels.dict_userdict_reading" /></th>
+							<th style="text-align: center; width: 200px;">&nbsp;</th>
+						</tr>
+					</thead>
+					<tbody>
+						<c:forEach var="data" varStatus="s" items="${userDictItemItems}">
+							<tr class="${s.index % 2 == 0 ? 'row1' : 'row2'}">
+								<td>${f:h(data.token)}</td>
+								<td>${f:h(data.reading)}</td>
+								<td style="text-align: center;"><s:link
+										href="confirmpage/${f:u(dictId)}/4/${f:u(data.id)}">
+										<bean:message key="labels.dict_link_details" />
+									</s:link> <s:link href="editpage/${f:u(dictId)}/2/${f:u(data.id)}">
+										<bean:message key="labels.dict_link_edit" />
+									</s:link> <s:link href="deletepage/${f:u(dictId)}/3/${f:u(data.id)}">
+										<bean:message key="labels.dict_link_delete" />
+									</s:link></td>
+							</tr>
+						</c:forEach>
+					</tbody>
+				</table>
+				<%-- Page Navigation: BEGIN --%>
+				<div class="row center">
+					<div class="pagination">
+						<ul>
+							<c:if test="${userDictPager.existPrePage}">
+								<li class="prev"><s:link
+										href="list/${f:u(dictId)}/${userDictPager.currentPageNumber - 1}">
+										<bean:message key="labels.dict_link_prev_page" />
+									</s:link></li>
+							</c:if>
+							<c:if test="${!userDictPager.existPrePage}">
+								<li class="prev disabled"><a href="#"><bean:message
+											key="labels.dict_link_prev_page" /></a></li>
+							</c:if>
+							<c:forEach var="p" varStatus="s"
+								items="${userDictPager.pageNumberList}">
+								<li
+									<c:if test="${p == userDictPager.currentPageNumber}">class="active"</c:if>>
+									<s:link href="list/${f:u(dictId)}/${p}">${p}</s:link>
+								</li>
+							</c:forEach>
+							<c:if test="${userDictPager.existNextPage}">
+								<li class="next disabled"><s:link
+										href="list/${f:u(dictId)}/${userDictPager.currentPageNumber + 1}">
+										<bean:message key="labels.dict_link_next_page" />
+									</s:link></li>
+							</c:if>
+							<c:if test="${!userDictPager.existNextPage}">
+								<li class="next disabled"><a href="#"><bean:message
+											key="labels.dict_link_next_page" /></a></li>
+							</c:if>
+						</ul>
+					</div>
+
+					<div>
+						<span><bean:message key="labels.pagination_page_guide_msg"
+								arg0="${f:h(userDictPager.currentPageNumber)}"
+								arg1="${f:h(userDictPager.allPageCount)}"
+								arg2="${f:h(userDictPager.allRecordCount)}" /></span>
+					</div>
+				</div>
+				<%-- Page Navigation: END --%>
+			</c:if>
+		</div>
+		<%-- List: END --%>
+
+	</tiles:put>
+</tiles:insert>

+ 169 - 0
src/test/java/jp/sf/fess/dict/userdict/UserDictFileTest.java

@@ -0,0 +1,169 @@
+/*
+ * Copyright 2009-2014 the 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 jp.sf.fess.dict.userdict;
+
+import java.io.File;
+
+import jp.sf.fess.Constants;
+import jp.sf.fess.dict.DictionaryFile.PagingList;
+
+import org.seasar.extension.unit.S2TestCase;
+import org.seasar.framework.util.FileUtil;
+
+public class UserDictFileTest extends S2TestCase {
+
+    private File file1;
+
+    @Override
+    protected void setUp() throws Exception {
+        file1 = File.createTempFile("userdict_", ".txt");
+        FileUtil.write(
+                file1.getAbsolutePath(),
+                "token1,seg1,reading1,pos1\ntoken2,seg2,reading2,pos2\ntoken3,seg3,reading3,pos3"
+                        .getBytes(Constants.UTF_8));
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        file1.delete();
+    }
+
+    public void test_selectList() {
+        final UserDictFile userDictFile = new UserDictFile(file1);
+        final PagingList<UserDictItem> itemList1 = userDictFile.selectList(0,
+                20);
+        assertEquals(3, itemList1.size());
+        assertEquals(3, itemList1.getAllRecordCount());
+        assertEquals(1, itemList1.getCurrentPageNumber());
+        assertEquals(20, itemList1.getPageSize());
+
+        final PagingList<UserDictItem> itemList2 = userDictFile
+                .selectList(2, 2);
+        assertEquals(1, itemList2.size());
+        assertEquals(3, itemList2.getAllRecordCount());
+        assertEquals(2, itemList2.getCurrentPageNumber());
+        assertEquals(2, itemList2.getPageSize());
+
+        assertEquals(0, userDictFile.selectList(5, 5).size());
+        assertEquals(0, userDictFile.selectList(-1, 5).size());
+    }
+
+    public void test_selectList2() {
+        final UserDictFile userDictFile = new UserDictFile(file1);
+        final PagingList<UserDictItem> itemList = userDictFile.selectList(0, 5);
+        for (int i = 0; i < itemList.size(); i++) {
+            final UserDictItem userDictItem = itemList.get(i);
+            assertEquals("token" + (i + 1), userDictItem.getToken());
+            assertEquals("seg" + (i + 1), userDictItem.getSegmentation());
+            assertEquals("reading" + (i + 1), userDictItem.getReading());
+            assertEquals("pos" + (i + 1), userDictItem.getPos());
+            assertFalse(userDictItem.isUpdated());
+            assertFalse(userDictItem.isUpdated());
+        }
+    }
+
+    public void test_insert() {
+        final UserDictFile userDictFile = new UserDictFile(file1);
+        final PagingList<UserDictItem> itemList1 = userDictFile.selectList(0,
+                20);
+        assertEquals(3, itemList1.size());
+
+        final UserDictItem userDictItem1 = new UserDictItem(0, "token4",
+                "seg4", "reading4", "pos4");
+        userDictFile.insert(userDictItem1);
+        final PagingList<UserDictItem> itemList2 = userDictFile.selectList(0,
+                20);
+        assertEquals(4, itemList2.size());
+        assertEquals("token4", itemList2.get(3).getToken());
+        assertEquals("seg4", itemList2.get(3).getSegmentation());
+        assertEquals("reading4", itemList2.get(3).getReading());
+        assertEquals("pos4", itemList2.get(3).getPos());
+
+        final UserDictItem userDictItem2 = new UserDictItem(0, "token5",
+                "seg5", "reading5", "pos5");
+        userDictFile.insert(userDictItem2);
+        final PagingList<UserDictItem> itemList3 = userDictFile.selectList(0,
+                20);
+        assertEquals(5, itemList3.size());
+        assertEquals("token5", itemList3.get(4).getToken());
+        assertEquals("seg5", itemList3.get(4).getSegmentation());
+        assertEquals("reading5", itemList3.get(4).getReading());
+        assertEquals("pos5", itemList3.get(4).getPos());
+    }
+
+    public void test_update() {
+        final UserDictFile userDictFile = new UserDictFile(file1);
+        final PagingList<UserDictItem> itemList1 = userDictFile.selectList(0,
+                20);
+        assertEquals(3, itemList1.size());
+
+        final UserDictItem userDictItem1 = itemList1.get(0);
+        userDictItem1.setNewToken("TOKEN1");
+        userDictItem1.setNewSegmentation("SEG1");
+        userDictItem1.setNewReading("READING1");
+        userDictItem1.setNewPos("POS1");
+        userDictFile.update(userDictItem1);
+        final PagingList<UserDictItem> itemList2 = userDictFile.selectList(0,
+                20);
+        assertEquals(3, itemList2.size());
+        final UserDictItem userDictItem2 = itemList2.get(0);
+        assertEquals("TOKEN1", userDictItem2.getToken());
+        assertEquals("SEG1", userDictItem2.getSegmentation());
+        assertEquals("READING1", userDictItem2.getReading());
+        assertEquals("POS1", userDictItem2.getPos());
+        assertFalse(userDictItem2.isUpdated());
+
+        final UserDictItem userDictItem3 = itemList2.get(2);
+        userDictItem3.setNewToken("TOKEN3");
+        userDictItem3.setNewSegmentation("SEG3");
+        userDictItem3.setNewReading("READING3");
+        userDictItem3.setNewPos("POS3");
+        userDictFile.update(userDictItem3);
+        final PagingList<UserDictItem> itemList3 = userDictFile.selectList(0,
+                20);
+        assertEquals(3, itemList3.size());
+        final UserDictItem userDictItem4 = itemList3.get(2);
+        assertEquals("TOKEN3", userDictItem4.getToken());
+        assertEquals("SEG3", userDictItem4.getSegmentation());
+        assertEquals("READING3", userDictItem4.getReading());
+        assertEquals("POS3", userDictItem4.getPos());
+        assertFalse(userDictItem4.isUpdated());
+    }
+
+    public void test_delete() throws Exception {
+        final UserDictFile userDictFile = new UserDictFile(file1);
+        final PagingList<UserDictItem> itemList1 = userDictFile.selectList(0,
+                20);
+        assertEquals(3, itemList1.size());
+
+        final UserDictItem userDictItem1 = itemList1.get(0);
+        userDictFile.delete(userDictItem1);
+        final PagingList<UserDictItem> itemList2 = userDictFile.selectList(0,
+                20);
+        assertEquals(2, itemList2.size());
+
+        final UserDictItem userDictItem2 = itemList2.get(1);
+        userDictFile.delete(userDictItem2);
+        final PagingList<UserDictItem> itemList3 = userDictFile.selectList(0,
+                20);
+        assertEquals(1, itemList3.size());
+
+        assertEquals("token2,seg2,reading2,pos2" + Constants.LINE_SEPARATOR,
+                new String(FileUtil.getBytes(file1), Constants.UTF_8));
+
+    }
+}

+ 80 - 0
src/test/java/jp/sf/fess/dict/userdict/UserDictItemTest.java

@@ -0,0 +1,80 @@
+/*
+ * Copyright 2009-2014 the 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 jp.sf.fess.dict.userdict;
+
+import org.seasar.extension.unit.S2TestCase;
+
+public class UserDictItemTest extends S2TestCase {
+
+    public void test_new1() {
+        final UserDictItem userDictItem = new UserDictItem(1, "t1", "s1", "r1",
+                "p1");
+        assertEquals(1, userDictItem.getId());
+        assertEquals("t1", userDictItem.getToken());
+        assertEquals("s1", userDictItem.getSegmentation());
+        assertEquals("r1", userDictItem.getReading());
+        assertEquals("p1", userDictItem.getPos());
+        assertNull(userDictItem.getNewToken());
+        assertNull(userDictItem.getNewSegmentation());
+        assertNull(userDictItem.getNewReading());
+        assertNull(userDictItem.getNewPos());
+        assertFalse(userDictItem.isUpdated());
+        assertFalse(userDictItem.isDeleted());
+
+        userDictItem.setNewToken("T1");
+        userDictItem.setNewSegmentation("S1");
+        userDictItem.setNewReading("R1");
+        userDictItem.setNewPos("P1");
+        assertTrue(userDictItem.isUpdated());
+        assertFalse(userDictItem.isDeleted());
+
+        userDictItem.setNewToken("");
+        assertTrue(userDictItem.isUpdated());
+        assertTrue(userDictItem.isDeleted());
+    }
+
+    public void test_equals1() {
+        final UserDictItem userDictItem1 = new UserDictItem(1, "t1", "s1",
+                "r1", "p1");
+
+        assertTrue(userDictItem1.equals(userDictItem1));
+        assertTrue(userDictItem1.equals(new UserDictItem(1, "t1", "s1", "r1",
+                "p1")));
+        assertTrue(userDictItem1.equals(new UserDictItem(2, "t1", "s1", "r1",
+                "p1")));
+        assertFalse(userDictItem1.equals(new UserDictItem(1, "T1", "s1", "r1",
+                "p1")));
+        assertFalse(userDictItem1.equals(new UserDictItem(1, "t1", "S1", "r1",
+                "p1")));
+        assertFalse(userDictItem1.equals(new UserDictItem(1, "t1", "s1", "R1",
+                "p1")));
+        assertFalse(userDictItem1.equals(new UserDictItem(1, "t1", "s1", "r1",
+                "P1")));
+    }
+
+    public void test_toLineString() {
+        assertEquals("t1,s1,r1,p1",
+                new UserDictItem(1, "t1", "s1", "r1", "p1").toLineString());
+        assertEquals("t\"\"1,s\"\"1,r\"\"1,p\"\"1", new UserDictItem(1, "t\"1",
+                "s\"1", "r\"1", "p\"1").toLineString());
+        assertEquals("\"t,1\",\"s,1\",\"r,1\",\"p,1\"", new UserDictItem(1,
+                "t,1", "s,1", "r,1", "p,1").toLineString());
+        assertEquals("\"t\"\",1\",\"s\"\",1\",\"r\"\",1\",\"p\"\",1\"",
+                new UserDictItem(1, "t\",1", "s\",1", "r\",1", "p\",1")
+                        .toLineString());
+    }
+}

+ 95 - 0
src/test/java/jp/sf/fess/dict/userdict/UserDictLocatorTest.java

@@ -0,0 +1,95 @@
+/*
+ * Copyright 2009-2014 the 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 jp.sf.fess.dict.userdict;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+import jp.sf.fess.Constants;
+import jp.sf.fess.dict.DictionaryFile;
+import jp.sf.fess.dict.DictionaryItem;
+
+import org.apache.commons.io.FileUtils;
+import org.seasar.extension.unit.S2TestCase;
+import org.seasar.framework.util.FileUtil;
+
+public class UserDictLocatorTest extends S2TestCase {
+
+    private File testDir;
+
+    private File testDataDir;
+
+    private File testCoreDir;
+
+    private File userDictFile1;
+
+    private File userDictFile2;
+
+    private File userDictFile3;
+
+    private File dummyFile1;
+
+    private File dummyFile2;
+
+    @Override
+    protected void setUp() throws Exception {
+        testDir = File.createTempFile("userdicttest", "_dir");
+        testDir.delete();
+        testDir.mkdirs();
+        testDataDir = new File(testDir, "data");
+        testDataDir.mkdirs();
+        testCoreDir = new File(testDir, "core");
+        testCoreDir.mkdirs();
+        userDictFile1 = new File(testDir, "userdict.txt");
+        FileUtil.write(userDictFile1.getAbsolutePath(),
+                "abc=>123\nxyz,890".getBytes(Constants.UTF_8));
+        userDictFile2 = new File(testDataDir, "userdict_data.txt");
+        FileUtil.write(userDictFile2.getAbsolutePath(),
+                "abc=>123\nxyz,890".getBytes(Constants.UTF_8));
+        userDictFile3 = new File(testCoreDir, "userdict_core.txt");
+        FileUtil.write(userDictFile3.getAbsolutePath(),
+                "abc=>123\nxyz,890".getBytes(Constants.UTF_8));
+        dummyFile1 = new File(testDir, "dummy.txt");
+        FileUtil.write(dummyFile1.getAbsolutePath(),
+                "dummy 1".getBytes(Constants.UTF_8));
+        dummyFile2 = new File(testCoreDir, "dummy.txt");
+        FileUtil.write(dummyFile2.getAbsolutePath(),
+                "dummy 2".getBytes(Constants.UTF_8));
+
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        FileUtils.deleteDirectory(testDir);
+    }
+
+    public void test_find() {
+        final UserDictLocator userDictLocator = new UserDictLocator();
+        userDictLocator.excludedUserDictList = new ArrayList<String>();
+        userDictLocator.excludedUserDictList.add("data");
+        userDictLocator.addSearchPath(testDir.getAbsolutePath());
+        final List<DictionaryFile<? extends DictionaryItem>> list = userDictLocator
+                .find();
+        assertEquals(2, list.size());
+        final DictionaryFile<? extends DictionaryItem> dicFile1 = list.get(0);
+        final DictionaryFile<? extends DictionaryItem> dicFile2 = list.get(1);
+        assertEquals(userDictFile3.getAbsolutePath(), dicFile1.getName());
+        assertEquals(userDictFile1.getAbsolutePath(), dicFile2.getName());
+    }
+
+}