de.catma.ui.analyzer.AnalyzerView.java Source code

Java tutorial

Introduction

Here is the source code for de.catma.ui.analyzer.AnalyzerView.java

Source

/*   
 *   CATMA Computer Aided Text Markup and Analysis
 *   
 *   Copyright (C) 2009-2013  University Of Hamburg
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.catma.ui.analyzer;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

import org.antlr.runtime.RecognitionException;
import org.vaadin.teemu.wizards.event.WizardCancelledEvent;
import org.vaadin.teemu.wizards.event.WizardCompletedEvent;
import org.vaadin.teemu.wizards.event.WizardProgressListener;
import org.vaadin.teemu.wizards.event.WizardStepActivationEvent;
import org.vaadin.teemu.wizards.event.WizardStepSetChangedEvent;

import com.vaadin.data.util.HierarchicalContainer;
import com.vaadin.event.ShortcutAction.KeyCode;
import com.vaadin.terminal.ClassResource;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Component;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.HorizontalSplitPanel;
import com.vaadin.ui.Label;
import com.vaadin.ui.Panel;
import com.vaadin.ui.TabSheet;
import com.vaadin.ui.TextField;
import com.vaadin.ui.Tree;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.Window;
import com.vaadin.ui.Window.Notification;

import de.catma.CatmaApplication;
import de.catma.backgroundservice.BackgroundServiceProvider;
import de.catma.backgroundservice.ExecutionListener;
import de.catma.document.Corpus;
import de.catma.document.repository.Repository;
import de.catma.document.source.IndexInfoSet;
import de.catma.document.source.SourceDocument;
import de.catma.document.standoffmarkup.usermarkup.UserMarkupCollectionReference;
import de.catma.indexer.IndexedRepository;
import de.catma.queryengine.QueryJob;
import de.catma.queryengine.QueryJob.QueryException;
import de.catma.queryengine.QueryOptions;
import de.catma.queryengine.querybuilder.QueryTree;
import de.catma.queryengine.result.GroupedQueryResultSet;
import de.catma.queryengine.result.QueryResult;
import de.catma.queryengine.result.computation.DistributionComputation;
import de.catma.ui.analyzer.querybuilder.QueryBuilderWizardFactory;
import de.catma.ui.analyzer.querybuilder.TagsetDefinitionDictionary;
import de.catma.ui.repository.MarkupCollectionItem;
import de.catma.ui.tabbedview.ClosableTab;
import de.catma.ui.tabbedview.TabComponent;
import de.catma.util.Equal;

public class AnalyzerView extends VerticalLayout implements ClosableTab, TabComponent,
        GroupedQueryResultSelectionListener, RelevantUserMarkupCollectionProvider {

    static interface CloseListener {
        public void closeRequest(AnalyzerView analyzerView);
    }

    private String userMarkupItemDisplayString = "User Markup Collections";
    private String staticMarkupItemDisplayString = "Static Markup Collections";
    private TextField searchInput;
    private Button btExecSearch;
    private Button btQueryBuilder;
    private Button btWordList;
    private HierarchicalContainer documentsContainer;
    private Tree documentsTree;
    private TabSheet resultTabSheet;
    private PhraseResultPanel phraseResultPanel;
    private IndexedRepository repository;
    private List<String> relevantSourceDocumentIDs;
    private List<String> relevantUserMarkupCollIDs;
    private List<String> relevantStaticMarkupCollIDs;
    private MarkupResultPanel markupResultPanel;
    private Corpus corpus;
    private Integer visualizationId;
    private PropertyChangeListener sourceDocumentChangedListener;
    private PropertyChangeListener userMarkupDocumentChangedListener;
    private CloseListener closeListener;
    private PropertyChangeListener corpusChangedListener;
    private TagsetDefinitionDictionary tagsetDefinitionDictionary;
    private IndexInfoSet indexInfoSet;
    private boolean init = false;
    private Label helpLabel;

    public AnalyzerView(Corpus corpus, IndexedRepository repository, CloseListener closeListener) {

        this.corpus = corpus;
        this.closeListener = closeListener;
        this.relevantSourceDocumentIDs = new ArrayList<String>();
        this.relevantUserMarkupCollIDs = new ArrayList<String>();
        this.relevantStaticMarkupCollIDs = new ArrayList<String>();
        tagsetDefinitionDictionary = new TagsetDefinitionDictionary();
        this.repository = repository;
        this.indexInfoSet = new IndexInfoSet(Collections.<String>emptyList(), Collections.<Character>emptyList(),
                Locale.ENGLISH);
        initComponents();
        initActions();
        initListeners();
    }

    @Override
    public void attach() {
        super.attach();
        if (!init) {
            init = true;
            helpLabel.setIcon(new ClassResource("ui/resources/icon-help.gif", getApplication()));
        }
    }

    private void initListeners() {
        //FIXME: the view doesn't get closed when "All documents" is analyzed and the repo gets closed
        //FIXME: update result panels to prevent stale results
        sourceDocumentChangedListener = new PropertyChangeListener() {

            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getOldValue() == null) { //insert
                    // no action needed
                } else if (evt.getNewValue() == null) { //remove
                    if (!relevantSourceDocumentIDs.isEmpty()) {
                        SourceDocument sourceDocument = (SourceDocument) evt.getOldValue();
                        if (relevantSourceDocumentIDs.contains(sourceDocument.getID())) {
                            tagsetDefinitionDictionary.clear();
                            removeSourceDocumentFromTree(sourceDocument);
                            if (relevantSourceDocumentIDs.isEmpty()) {
                                closeListener.closeRequest(AnalyzerView.this);
                            }
                        }
                    }
                } else { //update
                    String oldId = (String) evt.getOldValue();
                    if (relevantSourceDocumentIDs.contains(oldId)) {
                        removeSourceDocumentFromTree((SourceDocument) evt.getNewValue());
                        addSourceDocument((SourceDocument) evt.getNewValue());
                    }
                    tagsetDefinitionDictionary.clear();
                }
            }
        };
        this.repository.addPropertyChangeListener(Repository.RepositoryChangeEvent.sourceDocumentChanged,
                sourceDocumentChangedListener);

        userMarkupDocumentChangedListener = new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getOldValue() == null) { // insert
                    // no action needed
                } else if (evt.getNewValue() == null) { // remove

                    UserMarkupCollectionReference userMarkupCollectionReference = (UserMarkupCollectionReference) evt
                            .getOldValue();
                    if (relevantUserMarkupCollIDs.contains(userMarkupCollectionReference.getId())) {
                        tagsetDefinitionDictionary.clear();

                        removeUserMarkupCollectionFromTree(userMarkupCollectionReference);
                    }
                } else { // update
                    tagsetDefinitionDictionary.clear();
                    documentsTree.requestRepaint();
                }
            }
        };
        this.repository.addPropertyChangeListener(Repository.RepositoryChangeEvent.userMarkupCollectionChanged,
                userMarkupDocumentChangedListener);

        this.corpusChangedListener = new PropertyChangeListener() {

            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getNewValue() == null) { //remove
                    Corpus corpus = (Corpus) evt.getOldValue();
                    if ((AnalyzerView.this.corpus != null)
                            && Equal.nonNull(AnalyzerView.this.corpus.getId(), corpus.getId())) {

                        //detaching relevant documents from corpus
                        //the documents itself are not removed
                        AnalyzerView.this.corpus = null;
                    }
                } else if (evt.getOldValue() == null) { //add
                    // no action needed
                } else {
                    Corpus corpus = (Corpus) evt.getNewValue();

                    if ((AnalyzerView.this.corpus != null)
                            && Equal.nonNull(AnalyzerView.this.corpus.getId(), corpus.getId())) {
                        //update sourcedoc added
                        if (evt.getOldValue() instanceof SourceDocument) {
                            tagsetDefinitionDictionary.clear();
                            addSourceDocument((SourceDocument) evt.getOldValue());
                        }
                        // update usermarkupcoll added
                        else if (evt.getOldValue() instanceof UserMarkupCollectionReference) {
                            tagsetDefinitionDictionary.clear();
                            addUserMarkupCollection((UserMarkupCollectionReference) evt.getOldValue());
                        }
                    }
                }
            }
        };

        repository.addPropertyChangeListener(Repository.RepositoryChangeEvent.corpusChanged, corpusChangedListener);

    }

    private void addUserMarkupCollection(UserMarkupCollectionReference umcRef) {
        SourceDocument sd = repository.getSourceDocument(umcRef);

        Collection<?> children = documentsTree.getChildren(sd);
        MarkupCollectionItem umcItem = null;
        if (children != null) {
            for (Object child : children) {
                if ((child instanceof MarkupCollectionItem)
                        && ((MarkupCollectionItem) child).isUserMarkupCollectionItem()) {
                    umcItem = (MarkupCollectionItem) child;
                }
            }
        }
        if (umcItem == null) {
            umcItem = new MarkupCollectionItem(sd, userMarkupItemDisplayString, true);
            documentsTree.addItem(umcItem);
            documentsTree.setParent(umcItem, sd);
            addUserMarkupCollection(umcRef, umcItem);
        }
    }

    private void removeUserMarkupCollectionFromTree(UserMarkupCollectionReference userMarkupCollectionReference) {
        relevantUserMarkupCollIDs.remove(userMarkupCollectionReference.getId());
        documentsTree.removeItem(userMarkupCollectionReference);
    }

    private void removeSourceDocumentFromTree(SourceDocument sourceDocument) {
        relevantSourceDocumentIDs.remove(sourceDocument.getID());

        for (UserMarkupCollectionReference umcRef : sourceDocument.getUserMarkupCollectionRefs()) {
            documentsTree.removeItem(umcRef);
            relevantUserMarkupCollIDs.remove(umcRef.getId());
        }

        Collection<?> children = documentsTree.getChildren(sourceDocument);
        if ((children != null) && (children.size() > 0)) {
            documentsTree.removeItem(children.iterator().next());
        }
        documentsTree.removeItem(sourceDocument);
    }

    private void initActions() {
        btExecSearch.addListener(new ClickListener() {

            public void buttonClick(ClickEvent event) {
                executeSearch();
            }

        });
        btWordList.addListener(new ClickListener() {

            public void buttonClick(ClickEvent event) {
                searchInput.setValue("freq>0");
                executeSearch();
            }

        });
        btQueryBuilder.addListener(new ClickListener() {

            public void buttonClick(ClickEvent event) {
                showQueryBuilder();
            }
        });
    }

    private void showQueryBuilder() {
        QueryOptions queryOptions = new QueryOptions(relevantSourceDocumentIDs, relevantUserMarkupCollIDs,
                relevantStaticMarkupCollIDs, indexInfoSet.getUnseparableCharacterSequences(),
                indexInfoSet.getUserDefinedSeparatingCharacters(), indexInfoSet.getLocale(), repository);

        final QueryTree queryTree = new QueryTree();
        QueryBuilderWizardFactory factory = new QueryBuilderWizardFactory(new WizardProgressListener() {

            public void wizardCompleted(WizardCompletedEvent event) {
                event.getWizard().removeListener(this);
                searchInput.setValue(queryTree.toString());
                executeSearch();
            }

            public void wizardCancelled(WizardCancelledEvent event) {
                event.getWizard().removeListener(this);

            }

            public void stepSetChanged(WizardStepSetChangedEvent event) {
                /*noop*/}

            public void activeStepChanged(WizardStepActivationEvent event) {
                /*noop*/}
        }, queryTree, queryOptions, tagsetDefinitionDictionary);

        Window wizardWindow = factory.createWizardWindow("Query Builder", "90%", "85%");

        getApplication().getMainWindow().addWindow(wizardWindow);
        wizardWindow.center();
    }

    private void executeSearch() {

        QueryOptions queryOptions = new QueryOptions(
                relevantSourceDocumentIDs.isEmpty() ? getSourceDocumentIDs(repository.getSourceDocuments())
                        : relevantSourceDocumentIDs,
                relevantUserMarkupCollIDs, relevantStaticMarkupCollIDs,
                indexInfoSet.getUnseparableCharacterSequences(), indexInfoSet.getUserDefinedSeparatingCharacters(),
                indexInfoSet.getLocale(), repository);

        QueryJob job = new QueryJob(searchInput.getValue().toString(), queryOptions);

        ((BackgroundServiceProvider) getApplication()).submit("Searching...", job,
                new ExecutionListener<QueryResult>() {
                    public void done(QueryResult result) {
                        phraseResultPanel.setQueryResult(result);
                        //TODO: lazy?!
                        try {
                            markupResultPanel.setQueryResult(result);
                        } catch (IOException e) {
                            ((CatmaApplication) getApplication()).showAndLogError("Error accessing the repository!",
                                    e);
                        }
                    };

                    public void error(Throwable t) {
                        if (t instanceof QueryException) {
                            QueryJob.QueryException qe = (QueryJob.QueryException) t;
                            String input = qe.getInput();
                            int idx = ((RecognitionException) qe.getCause()).charPositionInLine;
                            if ((idx >= 0) && (input.length() > idx)) {
                                char character = input.charAt(idx);
                                String message = MessageFormat.format(
                                        "<html><p>There is something wrong with your query <b>{0}</b> approximately at positon {1} character <b>{2}</b>.</p> <p>If you are unsure about how to construct a query try the Query Builder!</p></html>",
                                        input, idx + 1, character);
                                getWindow().showNotification("Information", message,
                                        Notification.TYPE_TRAY_NOTIFICATION);
                            } else {
                                String message = MessageFormat.format(
                                        "<html><p>There is something wrong with your query <b>{0}</b>.</p> <p>If you are unsure about how to construct a query try the Query Builder!</p></html>",
                                        input);
                                getWindow().showNotification("Information", message,
                                        Notification.TYPE_TRAY_NOTIFICATION);
                            }
                        } else {
                            ((CatmaApplication) getApplication()).showAndLogError("Error during search!", t);
                        }
                    }
                });
    }

    private List<String> getSourceDocumentIDs(Collection<SourceDocument> sourceDocuments) {
        ArrayList<String> result = new ArrayList<String>();
        for (SourceDocument sd : sourceDocuments) {
            result.add(sd.getID());
        }
        return result;
    }

    public void resultsSelected(GroupedQueryResultSet groupedQueryResultSet) {
        try {
            handleDistributionChartRequest(groupedQueryResultSet);
        } catch (IOException e) {
            ((CatmaApplication) getApplication()).showAndLogError("Error showing the distribution chart", e);
        }
    }

    private void initComponents() {
        setSizeFull();

        Component searchPanel = createSearchPanel();

        Component convenienceButtonPanel = createConvenienceButtonPanel();

        VerticalLayout searchAndConveniencePanel = new VerticalLayout();
        searchAndConveniencePanel.setSpacing(true);
        searchAndConveniencePanel.setMargin(true);
        searchAndConveniencePanel.addComponent(searchPanel);
        searchAndConveniencePanel.addComponent(convenienceButtonPanel);

        Component documentsPanel = createDocumentsPanel();

        HorizontalSplitPanel topPanel = new HorizontalSplitPanel();
        topPanel.setSplitPosition(70);
        topPanel.addComponent(searchAndConveniencePanel);
        topPanel.addComponent(documentsPanel);
        addComponent(topPanel);

        setExpandRatio(topPanel, 0.25f);

        Component resultPanel = createResultPanel();
        resultPanel.setSizeFull();

        addComponent(resultPanel);
        setExpandRatio(resultPanel, 0.75f);
    }

    private Component createResultPanel() {

        resultTabSheet = new TabSheet();
        resultTabSheet.setSizeFull();

        Component resultByPhraseView = createResultByPhraseView();
        resultTabSheet.addTab(resultByPhraseView, "Result by phrase");

        Component resultByMarkupView = createResultByMarkupView();
        resultTabSheet.addTab(resultByMarkupView, "Result by markup");

        return resultTabSheet;
    }

    private Component createResultByMarkupView() {
        markupResultPanel = new MarkupResultPanel(repository, this, this);
        return markupResultPanel;
    }

    private Component createResultByPhraseView() {
        phraseResultPanel = new PhraseResultPanel(repository, this, this);
        return phraseResultPanel;
    }

    private Component createDocumentsPanel() {
        Panel documentsPanel = new Panel();

        documentsContainer = new HierarchicalContainer();
        documentsTree = new Tree();
        documentsTree.setContainerDataSource(documentsContainer);
        documentsTree.setCaption("Documents and collections constraining this search");

        if (corpus != null) {
            for (SourceDocument sd : corpus.getSourceDocuments()) {
                addSourceDocument(sd);
            }
        } else {
            documentsTree.addItem("All documents");
        }

        documentsPanel.addComponent(documentsTree);
        return documentsPanel;
    }

    private void addSourceDocument(SourceDocument sd) {
        relevantSourceDocumentIDs.add(sd.getID());
        //TODO: provide a facility where the user can select between different IndexInfoSets
        indexInfoSet = sd.getSourceContentHandler().getSourceDocumentInfo().getIndexInfoSet();

        documentsTree.addItem(sd);
        MarkupCollectionItem umc = new MarkupCollectionItem(sd, userMarkupItemDisplayString, true);
        documentsTree.addItem(umc);
        documentsTree.setParent(umc, sd);
        for (UserMarkupCollectionReference umcRef : sd.getUserMarkupCollectionRefs()) {
            if (corpus.getUserMarkupCollectionRefs().contains(umcRef)) {
                addUserMarkupCollection(umcRef, umc);
            }
        }
    }

    private void addUserMarkupCollection(UserMarkupCollectionReference umcRef, MarkupCollectionItem umc) {
        this.relevantUserMarkupCollIDs.add(umcRef.getId());
        documentsTree.addItem(umcRef);
        documentsTree.setParent(umcRef, umc);
        documentsTree.setChildrenAllowed(umcRef, false);
    }

    private Component createConvenienceButtonPanel() {
        HorizontalLayout convenienceButtonPanel = new HorizontalLayout();
        convenienceButtonPanel.setSpacing(true);

        btQueryBuilder = new Button("Query Builder");
        convenienceButtonPanel.addComponent(btQueryBuilder);

        btWordList = new Button("Wordlist");
        convenienceButtonPanel.addComponent(btWordList);

        helpLabel = new Label();
        helpLabel.setWidth("20px");
        helpLabel.setDescription("<h3>Hints</h3>" + "<h4>Using the wordlist</h4>"
                + "Click on the  \"Wordlist\"-Button to get a list of all words of your document together with their frequencies."
                + " You can now sort the list by phrase, i. e. the word, or by frequency."
                + "<h4>Building queries</h4>"
                + "You are free to hack your query directly into the Query box, but a large part of all possible queries can be generated with the Query Builder more conveniently."
                + "<h4>Keywords in Context (KWIC)</h4>"
                + "To see your search results in the context of its surrounding text, tick the \"Visible in Kwic\"-check box "
                + "of the desired results." + "<h4>Results by Markup</h4>"
                + "When building Tag Queries where you look for occurrences of certain Tags, sometimes you "
                + "want the results grouped by Tags (especially Subtags) and sometimes you want the results "
                + "grouped by the tagged phrase. The \"Results by markup\" and \"Results by phrase\" tabs give you this choice for Tag Queries.");

        convenienceButtonPanel.addComponent(helpLabel);

        return convenienceButtonPanel;
    }

    private Component createSearchPanel() {
        HorizontalLayout searchPanel = new HorizontalLayout();
        searchPanel.setSpacing(true);
        searchPanel.setWidth("100%");

        searchInput = new TextField();
        searchInput.setCaption("Query");
        searchInput.setWidth("100%");
        searchInput.setImmediate(true);

        searchPanel.addComponent(searchInput);
        searchPanel.setExpandRatio(searchInput, 1.0f);

        btExecSearch = new Button("Execute Query");

        searchPanel.addComponent(btExecSearch);
        searchPanel.setComponentAlignment(btExecSearch, Alignment.BOTTOM_CENTER);

        return searchPanel;
    }

    private void handleDistributionChartRequest(GroupedQueryResultSet groupedQueryResultSet) throws IOException {

        DistributionComputation dc = new DistributionComputation(groupedQueryResultSet, repository,
                relevantSourceDocumentIDs);
        dc.compute();

        this.visualizationId = ((CatmaApplication) getApplication()).addVisualization(visualizationId,
                (corpus == null) ? "All documents" : corpus.toString(), dc);
    }

    public void close() {
        this.repository.removePropertyChangeListener(Repository.RepositoryChangeEvent.sourceDocumentChanged,
                sourceDocumentChangedListener);

        this.repository.removePropertyChangeListener(Repository.RepositoryChangeEvent.userMarkupCollectionChanged,
                userMarkupDocumentChangedListener);

        closeListener = null;
    }

    public void addClickshortCuts() {
        btExecSearch.setClickShortcut(KeyCode.ENTER);
    }

    public void removeClickshortCuts() {
        btExecSearch.removeClickShortcut();
    }

    public List<String> getRelevantUserMarkupCollectionIDs() {
        return Collections.unmodifiableList(relevantUserMarkupCollIDs);
    }

    public Corpus getCorpus() {
        return corpus;
    }
}