net.sourceforge.docfetcher.gui.SearchQueue.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.docfetcher.gui.SearchQueue.java

Source

/*******************************************************************************
 * Copyright (c) 2011 Tran Nam Quang.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Tran Nam Quang - initial API and implementation
 *******************************************************************************/

package net.sourceforge.docfetcher.gui;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import net.sourceforge.docfetcher.enums.Img;
import net.sourceforge.docfetcher.enums.Msg;
import net.sourceforge.docfetcher.enums.ProgramConf;
import net.sourceforge.docfetcher.gui.ResultPanel.HeaderMode;
import net.sourceforge.docfetcher.gui.filter.FileTypePanel;
import net.sourceforge.docfetcher.gui.filter.FilesizePanel;
import net.sourceforge.docfetcher.gui.filter.IndexPanel;
import net.sourceforge.docfetcher.model.IndexRegistry;
import net.sourceforge.docfetcher.model.LuceneIndex;
import net.sourceforge.docfetcher.model.TreeCheckState;
import net.sourceforge.docfetcher.model.parse.Parser;
import net.sourceforge.docfetcher.model.search.ResultDocument;
import net.sourceforge.docfetcher.model.search.SearchException;
import net.sourceforge.docfetcher.model.search.Searcher;
import net.sourceforge.docfetcher.util.AppUtil;
import net.sourceforge.docfetcher.util.CheckedOutOfMemoryError;
import net.sourceforge.docfetcher.util.Event;
import net.sourceforge.docfetcher.util.Util;
import net.sourceforge.docfetcher.util.annotations.NotNull;
import net.sourceforge.docfetcher.util.annotations.Nullable;
import net.sourceforge.docfetcher.util.collect.ListMap;
import net.sourceforge.docfetcher.util.collect.ListMap.Entry;

import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;

import com.google.common.base.Strings;
import com.google.common.collect.Sets;

/**
 * @author Tran Nam Quang
 */
public final class SearchQueue {

    private static enum GuiEvent {
        SEARCH_OR_LIST, SIZE, TYPE, LOCATION
    }

    private static final String spaces = Strings.repeat(" ", 5);

    private final SearchBar searchBar;
    private final FilesizePanel filesizePanel;
    private final FileTypePanel fileTypePanel;
    private final IndexPanel indexPanel;
    private final ResultPanel resultPanel;
    private final StatusBar statusBar;

    private final Thread thread;
    private final Lock lock = new ReentrantLock(true);
    private final Condition queueNotEmpty = lock.newCondition();
    private final EnumSet<GuiEvent> queue = EnumSet.noneOf(GuiEvent.class);

    @Nullable
    private volatile String query;
    @Nullable
    private volatile Set<String> listDocIds;
    @Nullable
    private List<ResultDocument> results;
    @Nullable
    private Set<String> checkedParsers;
    @Nullable
    private TreeCheckState treeCheckState;
    private boolean allParsersChecked;

    public SearchQueue(@NotNull SearchBar searchBar, @NotNull FilesizePanel filesizePanel,
            @NotNull FileTypePanel fileTypePanel, @NotNull IndexPanel indexPanel, @NotNull ResultPanel resultPanel,
            @NotNull StatusBar statusBar) {
        Util.checkNotNull(searchBar, filesizePanel, fileTypePanel);
        Util.checkNotNull(indexPanel, resultPanel, statusBar);
        this.searchBar = searchBar;
        this.filesizePanel = filesizePanel;
        this.fileTypePanel = fileTypePanel;
        this.indexPanel = indexPanel;
        this.resultPanel = resultPanel;
        this.statusBar = statusBar;

        resultPanel.evtSelection.add(new Event.Listener<List<ResultDocument>>() {
            public void update(List<ResultDocument> eventData) {
                updateResultStatus();
            }
        });

        thread = new Thread(SearchQueue.class.getName()) {
            public void run() {
                while (threadLoop())
                    ;
            }
        };
        thread.start();

        initListeners();
    }

    private void initListeners() {
        searchBar.getControl().addDisposeListener(new DisposeListener() {
            public void widgetDisposed(DisposeEvent e) {
                thread.interrupt();
            }
        });

        searchBar.evtSearch.add(new Event.Listener<String>() {
            public void update(String eventData) {
                lock.lock();
                try {
                    query = eventData;
                    searchBar.setEnabled(false);
                    queue.add(GuiEvent.SEARCH_OR_LIST);
                    queueNotEmpty.signal();
                } finally {
                    lock.unlock();
                }
            }
        });

        filesizePanel.evtValuesChanged.add(new Event.Listener<Void>() {
            public void update(Void eventData) {
                lock.lock();
                try {
                    queue.add(GuiEvent.SIZE);
                    queueNotEmpty.signal();
                } finally {
                    lock.unlock();
                }
            }
        });

        fileTypePanel.evtCheckStatesChanged.add(new Event.Listener<Void>() {
            public void update(Void eventData) {
                lock.lock();
                try {
                    queue.add(GuiEvent.TYPE);
                    queueNotEmpty.signal();
                } finally {
                    lock.unlock();
                }
            }
        });

        indexPanel.evtCheckStatesChanged.add(new Event.Listener<Void>() {
            public void update(Void eventData) {
                lock.lock();
                try {
                    queue.add(GuiEvent.LOCATION);
                    queueNotEmpty.signal();
                } finally {
                    lock.unlock();
                }
            }
        });

        indexPanel.evtListDocuments.add(new Event.Listener<Set<String>>() {
            public void update(Set<String> eventData) {
                lock.lock();
                try {
                    listDocIds = eventData;
                    queue.add(GuiEvent.SEARCH_OR_LIST);
                    queueNotEmpty.signal();
                } finally {
                    lock.unlock();
                }
            }
        });
    }

    private boolean threadLoop() {
        final EnumSet<GuiEvent> queueCopy;
        final String query;
        final Set<String> listDocIds;

        lock.lock();
        try {
            while (queue.isEmpty())
                queueNotEmpty.await();

            queueCopy = EnumSet.copyOf(queue);
            queue.clear();
            query = this.query;
            listDocIds = this.listDocIds;
            this.query = null;
            this.listDocIds = null;
        } catch (InterruptedException e) {
            return false;
        } finally {
            lock.unlock();
        }

        IndexRegistry indexRegistry = indexPanel.getIndexRegistry();

        // Run search
        if (queueCopy.contains(GuiEvent.SEARCH_OR_LIST)) {
            try {
                Searcher searcher = indexRegistry.getSearcher(); // might block

                /*
                 * Bug #3538102: The returned searcher is null if
                 * IndexRegistry.getSearcher() was blocking and the thread is
                 * interrupted. This can happen as follows: (1) The user has a
                 * lot of indexes and/or the indexes are very large, so that
                 * loading them on startup takes a long time. (2) During
                 * startup, when the indexes are loaded, the user enters
                 * something into the search field and presses Enter. (3)
                 * DocFetcher blocks because it can't start searching until all
                 * indexes have been loaded. Seeing that the program has
                 * apparently frozen, the user closes the program. This
                 * interrupts the searcher thread, causing the
                 * IndexRegistry.getSearcher() method to unblock and return
                 * null.
                 */
                if (searcher == null)
                    return false;

                if (query != null)
                    results = searcher.search(query);
                else if (listDocIds != null)
                    results = searcher.list(listDocIds);
                else
                    throw new IllegalStateException();
            } catch (SearchException e) {
                AppUtil.showError(e.getMessage(), true, true);
                Util.runSyncExec(searchBar.getControl(), new Runnable() {
                    public void run() {
                        searchBar.setEnabled(true);
                    }
                });

                // Don't return yet, we might have to update the filters
                results = null;
            } catch (CheckedOutOfMemoryError e) {
                UtilGui.showOutOfMemoryMessage(searchBar.getControl(), e);
            }
        }

        // Build parser filter
        if (checkedParsers == null || queueCopy.contains(GuiEvent.TYPE)) {
            Util.runSyncExec(fileTypePanel.getControl(), new Runnable() {
                public void run() {
                    updateParserFilter();
                }
            });
        }

        // Build location filter
        if (treeCheckState == null || queueCopy.contains(GuiEvent.LOCATION))
            treeCheckState = indexRegistry.getTreeCheckState();

        /*
         * No need to update the result panel if the user changed the filter
         * settings before having run any searches.
         */
        if (results == null)
            return true;

        Long[] minMax = filesizePanel.getValuesInKB();
        final List<ResultDocument> visibleResults = new ArrayList<ResultDocument>();

        // Apply filters
        for (ResultDocument doc : results) {
            if (minMax != null) {
                long size = doc.getSizeInKB();
                if (minMax[0] != null && size < minMax[0])
                    continue;
                if (minMax[1] != null && size > minMax[1])
                    continue;
            }
            if (!doc.isEmail()) {
                if (checkedParsers.isEmpty())
                    continue;
                if (!allParsersChecked) {
                    String parserName = doc.getParserName();
                    if (!checkedParsers.contains(parserName))
                        continue;
                }
            }
            if (!treeCheckState.isChecked(doc.getParentPath()))
                continue;
            visibleResults.add(doc);
        }

        boolean filesFound = false;
        boolean emailsFound = false;
        for (LuceneIndex index : indexRegistry.getIndexes()) {
            if (index.isEmailIndex())
                emailsFound = true;
            else
                filesFound = true;
        }
        final HeaderMode mode = HeaderMode.getInstance(filesFound, emailsFound);

        // Set results
        Util.runSyncExec(searchBar.getControl(), new Runnable() {
            public void run() {
                resultPanel.setResults(visibleResults, mode);
                resultPanel.sortByColumn(ProgramConf.Int.InitialSorting.get());
                if (queueCopy.contains(GuiEvent.SEARCH_OR_LIST))
                    resultPanel.getControl().setFocus();
                updateResultStatus(); // Must be done *after* setting the results
                searchBar.setEnabled(true);

                if (query != null)
                    searchBar.addToSearchHistory(query);
            }
        });

        return true;
    }

    private void updateParserFilter() {
        ListMap<Parser, Boolean> map = fileTypePanel.getParserStateMap();
        checkedParsers = Sets.newHashSetWithExpectedSize(map.size());
        for (Entry<Parser, Boolean> entry : map) {
            if (!entry.getValue())
                continue;
            String parserName = entry.getKey().getClass().getSimpleName();
            checkedParsers.add(parserName);
        }
        allParsersChecked = checkedParsers.size() == map.size();
    }

    private void updateResultStatus() {
        int resultCount = resultPanel.getItemCount();
        String msg = Msg.num_results.format(resultCount);
        if (resultCount >= Searcher.MAX_RESULTS)
            msg += "+";
        int selCount = resultPanel.getSelection().size();
        if (selCount > 1)
            msg += spaces + Msg.num_sel_results.format(selCount);
        statusBar.getLeftPart().setContents(Img.INFO.get(), msg);
    }

}