annis.gui.resultview.SingleResultPanel.java Source code

Java tutorial

Introduction

Here is the source code for annis.gui.resultview.SingleResultPanel.java

Source

/*
 * Copyright 2011 Corpuslinguistic working group Humboldt University Berlin.
 *
 * 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 annis.gui.resultview;

import annis.CommonHelper;
import annis.gui.AnnisUI;
import annis.gui.ShareSingleMatchGenerator;
import annis.gui.MetaDataPanel;
import annis.gui.QueryController;
import annis.gui.objects.DisplayedResultQuery;
import annis.gui.objects.PagedResultQuery;
import annis.libgui.Helper;
import static annis.libgui.Helper.calculateMarkedAndCoveredIDs;
import annis.libgui.IDGenerator;
import annis.libgui.InstanceConfig;
import annis.libgui.PluginSystem;
import annis.libgui.ResolverProvider;
import static annis.model.AnnisConstants.ANNIS_NS;
import static annis.model.AnnisConstants.FEAT_RELANNIS_NODE;
import annis.model.RelannisNodeFeature;
import annis.resolver.ResolverEntry;
import annis.service.objects.Match;
import com.vaadin.data.Property;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.server.FontAwesome;
import com.vaadin.server.Page;
import com.vaadin.server.Resource;
import com.vaadin.ui.AbstractSelect;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.CssLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Notification;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.UI;
import com.vaadin.ui.Window;
import com.vaadin.ui.themes.ValoTheme;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.SortedSet;
import org.apache.commons.lang3.StringUtils;
import org.corpus_tools.salt.common.SDocument;
import org.corpus_tools.salt.common.SDocumentGraph;
import org.corpus_tools.salt.common.SToken;
import org.corpus_tools.salt.common.SaltProject;
import org.corpus_tools.salt.core.SFeature;
import org.corpus_tools.salt.core.SNode;
import org.slf4j.LoggerFactory;

/**
 *
 * @author thomas
 */
public class SingleResultPanel extends CssLayout implements Button.ClickListener, VisualizerContextChanger {
    private static final long serialVersionUID = 2L;

    private static final String HIDE_KWIC = "hide_kwic";

    private static final String INITIAL_OPEN = "initial_open";

    private static final Resource ICON_RESOURCE = FontAwesome.INFO_CIRCLE;

    private SDocument result;

    private Map<String, String> markedCoveredMap;

    private Map<String, String> markedExactMap;

    private final PluginSystem ps;

    private final AnnisUI ui;

    private List<VisualizerPanel> visualizers;
    private List<ResolverEntry> resolverEntries;

    private final Button btInfo;
    private final Button btLink;

    private final List<String> path;

    private String segmentationName;

    private final HorizontalLayout infoBar;

    private final String corpusName;

    private final String documentName;

    private final QueryController queryController;

    private final long resultNumber;

    private final ResolverProvider resolverProvider;

    private final Set<String> visibleTokenAnnos;

    private ProgressBar reloadVisualizer;

    private final ComboBox lftCtxCombo;

    private final ComboBox rghtCtxCombo;

    private final Map<Long, Boolean> visualizerState;

    private static final org.slf4j.Logger log = LoggerFactory.getLogger(SingleResultPanel.class);

    private final InstanceConfig instanceConfig;

    private PagedResultQuery query;
    private final Match match;

    public SingleResultPanel(final SDocument result, Match match, long resultNumber,
            ResolverProvider resolverProvider, PluginSystem ps, AnnisUI ui, Set<String> visibleTokenAnnos,
            String segmentationName, QueryController controller, InstanceConfig instanceConfig,
            DisplayedResultQuery query) {
        this.ps = ps;
        this.ui = ui;
        this.result = result;
        this.segmentationName = segmentationName;
        this.queryController = controller;
        this.resultNumber = resultNumber;
        this.resolverProvider = resolverProvider;
        this.visibleTokenAnnos = visibleTokenAnnos;
        this.instanceConfig = instanceConfig;
        this.query = query;
        this.match = match;

        calculateHelperVariables();

        setWidth("100%");
        setHeight("-1px");

        if (query != null && query.getSelectedMatches().contains(resultNumber)) {
            addStyleName("selected-match");
        }

        infoBar = new HorizontalLayout();
        infoBar.addStyleName("info-bar");
        infoBar.setWidth("100%");
        infoBar.setHeight("-1px");

        Label lblNumber = new Label("" + (resultNumber + 1));
        infoBar.addComponent(lblNumber);
        lblNumber.setSizeUndefined();

        btInfo = new Button();
        btInfo.setStyleName(ValoTheme.BUTTON_BORDERLESS);
        btInfo.setIcon(ICON_RESOURCE);
        btInfo.setDescription("Show metadata");
        btInfo.addClickListener((Button.ClickListener) this);
        infoBar.addComponent(btInfo);

        btLink = new Button();
        btLink.setStyleName(ValoTheme.BUTTON_BORDERLESS);
        btLink.setIcon(FontAwesome.SHARE_ALT);
        btLink.setDescription("Share match reference");
        btLink.setDisableOnClick(true);
        btLink.addClickListener(new Button.ClickListener() {

            @Override
            public void buttonClick(ClickEvent event) {
                showShareSingleMatchGenerator();
            }
        });
        infoBar.addComponent(btLink);

        /**
         * Extract the top level corpus name and the document name of this single
         * result.
         */
        path = CommonHelper.getCorpusPath(result.getGraph(), result);
        Collections.reverse(path);
        corpusName = path.get(0);
        documentName = path.get(path.size() - 1);

        MinMax minMax = getIds(result.getDocumentGraph());

        // build label
        StringBuilder sb = new StringBuilder("Path: ");
        sb.append(StringUtils.join(path, " > "));
        sb.append(" (" + minMax.segName + " ").append(minMax.min);
        sb.append(" - ").append(minMax.max).append(")");

        Label lblPath = new Label(sb.toString());
        lblPath.addStyleName("path-label");

        lblPath.setWidth("100%");
        lblPath.setHeight("-1px");
        infoBar.addComponent(lblPath);
        infoBar.setExpandRatio(lblPath, 1.0f);
        infoBar.setSpacing(false);

        this.visualizerState = new HashMap<>();

        // init context combox
        lftCtxCombo = new ComboBox();
        rghtCtxCombo = new ComboBox();

        lftCtxCombo.setWidth(50, Unit.PIXELS);
        rghtCtxCombo.setWidth(50, Unit.PIXELS);

        lftCtxCombo.setNullSelectionAllowed(false);
        rghtCtxCombo.setNullSelectionAllowed(false);

        lftCtxCombo.addStyleName(ValoTheme.COMBOBOX_SMALL);
        rghtCtxCombo.addStyleName(ValoTheme.COMBOBOX_SMALL);

        IndexedContainer lftCtxContainer = new IndexedContainer();
        IndexedContainer rghtCtxContainer = new IndexedContainer();

        // and a property for sorting
        lftCtxContainer.addContainerProperty("number", Integer.class, 0);
        rghtCtxContainer.addContainerProperty("number", Integer.class, 0);

        for (int i = 0; i < 30; i += 5) {
            lftCtxContainer.addItem(i).getItemProperty("number").setValue(i);
            rghtCtxContainer.addItem(i).getItemProperty("number").setValue(i);
        }

        int lftContextIdx = query == null ? 0 : query.getLeftContext();
        lftCtxContainer.addItemAt(lftContextIdx, lftContextIdx);
        lftCtxContainer.sort(new Object[] { "number" }, new boolean[] { true });

        int rghtCtxIdx = query == null ? 0 : query.getRightContext();
        rghtCtxContainer.addItem(rghtCtxIdx);

        rghtCtxContainer.sort(new Object[] { "number" }, new boolean[] { true });

        lftCtxCombo.setContainerDataSource(lftCtxContainer);
        rghtCtxCombo.setContainerDataSource(rghtCtxContainer);

        lftCtxCombo.select(lftContextIdx);
        rghtCtxCombo.select(rghtCtxIdx);

        lftCtxCombo.setNewItemsAllowed(true);
        rghtCtxCombo.setNewItemsAllowed(true);

        lftCtxCombo.setImmediate(true);
        rghtCtxCombo.setImmediate(true);

        lftCtxCombo.setNewItemHandler(new AddNewItemHandler(lftCtxCombo));
        rghtCtxCombo.setNewItemHandler(new AddNewItemHandler(rghtCtxCombo));

        lftCtxCombo.addValueChangeListener(new ContextChangeListener(resultNumber, true));
        rghtCtxCombo.addValueChangeListener(new ContextChangeListener(resultNumber, false));

        Label leftCtxLabel = new Label("left context: ");
        Label rightCtxLabel = new Label("right context: ");

        leftCtxLabel.setWidth("-1px");
        rightCtxLabel.setWidth("-1px");

        HorizontalLayout ctxLayout = new HorizontalLayout();
        ctxLayout.setSpacing(true);
        ctxLayout.addComponents(leftCtxLabel, lftCtxCombo, rightCtxLabel, rghtCtxCombo);
        infoBar.addComponent(ctxLayout);

        addComponent(infoBar);
        initVisualizer();
    }

    private void showShareSingleMatchGenerator() {
        // select the current match
        if (ui != null) {
            ui.getQueryState().getSelectedMatches().getValue().clear();
            ui.getQueryState().getSelectedMatches().getValue().add(resultNumber);
            ui.getSearchView().updateFragment(ui.getQueryController().getSearchQuery());
        }

        Window window = new ShareSingleMatchGenerator(resolverEntries, match, query, segmentationName, ps);
        window.setWidth(790, Unit.PIXELS);
        window.setHeight(580, Unit.PIXELS);
        window.setResizable(true);
        window.setModal(true);

        window.addCloseListener(new Window.CloseListener() {

            @Override
            public void windowClose(Window.CloseEvent e) {
                btLink.setEnabled(true);
            }
        });
        window.setCaption("Match reference link");

        UI.getCurrent().addWindow(window);
    }

    public void setSegmentationLayer(String segmentationName) {
        this.segmentationName = segmentationName;

        if (result != null) {
            List<SNode> segNodes = CommonHelper.getSortedSegmentationNodes(segmentationName,
                    result.getDocumentGraph());
            Map<String, Long> markedAndCovered = calculateMarkedAndCoveredIDs(result, segNodes, segmentationName);
            for (VisualizerPanel p : visualizers) {
                p.setSegmentationLayer(segmentationName, markedAndCovered);
            }
        }
    }

    public void setVisibleTokenAnnosVisible(SortedSet<String> annos) {
        for (VisualizerPanel p : visualizers) {
            p.setVisibleTokenAnnosVisible(annos);
        }
    }

    private void calculateHelperVariables() {
        markedCoveredMap = new HashMap<>();

        markedExactMap = Helper.calculateColorsForMarkedExact(result);
    }

    @Override
    public void buttonClick(ClickEvent event) {
        if (event.getButton() == btInfo && result != null) {
            Window infoWindow = new Window("Info for " + result.getId());

            infoWindow.setModal(false);
            MetaDataPanel meta = new MetaDataPanel(path.get(0), path.get(path.size() - 1));
            infoWindow.setContent(meta);
            infoWindow.setWidth("400px");
            infoWindow.setHeight("400px");

            UI.getCurrent().addWindow(infoWindow);
        }
    }

    private void showReloadingProgress() {
        //remove the old visualizer
        for (VisualizerPanel v : visualizers) {
            this.removeComponent(v);
        }

        // first set loading indicator
        reloadVisualizer = new ProgressBar(1.0f);
        reloadVisualizer.setIndeterminate(true);
        reloadVisualizer.setSizeFull();
        reloadVisualizer.setHeight(150, Unit.PIXELS);
        addComponent(reloadVisualizer);
    }

    private void initVisualizer() {
        try {
            ResolverEntry[] entries = resolverProvider == null ? new ResolverEntry[0]
                    : resolverProvider.getResolverEntries(result);
            visualizers = new LinkedList<>();
            resolverEntries = new LinkedList<>();

            List<VisualizerPanel> openVisualizers = new LinkedList<>();

            List<SNode> segNodes = CommonHelper.getSortedSegmentationNodes(segmentationName,
                    result.getDocumentGraph());

            Map<String, Long> markedAndCovered = Helper.calculateMarkedAndCoveredIDs(result, segNodes,
                    segmentationName);
            Helper.calulcateColorsForMarkedAndCovered(result, markedAndCovered, markedCoveredMap);

            String resultID = "" + new Random().nextInt(Integer.MAX_VALUE);

            for (int i = 0; i < entries.length; i++) {
                String htmlID = "resolver-" + resultNumber + "_" + i;

                VisualizerPanel p = new VisualizerPanel(entries[i], result, corpusName, documentName,
                        visibleTokenAnnos, markedAndCovered, markedCoveredMap, markedExactMap, htmlID, resultID,
                        this, segmentationName, ps, instanceConfig);

                visualizers.add(p);
                resolverEntries.add(entries[i]);

                Properties mappings = entries[i].getMappings();

                // check if there is the visibility of a visualizer changed
                // since it the whole result panel was loaded. If not the entry of the
                // resovler entry is used, for determine the visibility status
                if (visualizerState.containsKey(entries[i].getId())) {
                    if (visualizerState.get(entries[i].getId())) {
                        openVisualizers.add(p);
                    }
                } else {
                    if (Boolean.parseBoolean(mappings.getProperty(INITIAL_OPEN, "false"))) {
                        openVisualizers.add(p);
                    }
                }
            } // for each resolver entry

            // attach visualizer
            for (VisualizerPanel p : visualizers) {
                addComponent(p);
            }

            for (VisualizerPanel p : openVisualizers) {
                p.toggleVisualizer(true, null);
            }
        } catch (RuntimeException ex) {
            log.error("problems with initializing Visualizer Panel", ex);
        } catch (Exception ex) {
            log.error("problems with initializing Visualizer Panel", ex);
        }
    }

    @Override
    public void attach() {
        super.attach();
        if (Helper.isKickstarter(getSession())) {
            btLink.setVisible(false);
        }
        IDGenerator.assignIDForFields(SingleResultPanel.this, infoBar, btInfo);
    }

    @Override
    public void registerVisibilityStatus(long entryId, boolean status) {
        visualizerState.put(entryId, status);
    }

    @Override
    public void changeContext(long resultNumber, int context, boolean left) {
        //delegates the task to the query controller.

        queryController.changeContext(query, match, resultNumber, context, this, left);
    }

    private static class AddNewItemHandler implements AbstractSelect.NewItemHandler {

        final private ComboBox combobox;

        public AddNewItemHandler(ComboBox comboBox) {
            this.combobox = comboBox;
        }

        @Override
        public void addNewItem(String newValue) {

            String ERROR_MESSAGE_HEADER = "Illegal value";

            try {
                int i = Integer.parseInt(newValue);

                if (i < 0) {
                    new Notification(ERROR_MESSAGE_HEADER, "<div><p>context &lt; 0 makes no sense</p></div>",
                            Notification.Type.WARNING_MESSAGE, true).show(Page.getCurrent());
                } else {

                    combobox.getContainerDataSource().addItem(i).getItemProperty("number").setValue(i);

                    if (combobox.getContainerDataSource() instanceof IndexedContainer) {
                        ((IndexedContainer) combobox.getContainerDataSource()).sort(new Object[] { "number" },
                                new boolean[] { true });
                    }

                    combobox.select(i);
                }
            } catch (NumberFormatException ex) {
                new Notification(ERROR_MESSAGE_HEADER, "<div><p>Only numbers are allowed.</p></div>",
                        Notification.Type.WARNING_MESSAGE, true).show(Page.getCurrent());
            }
        }
    }

    private class ContextChangeListener implements Property.ValueChangeListener {

        long resultNumber;

        boolean left;

        public ContextChangeListener(long resultNumber, boolean left) {
            this.resultNumber = resultNumber;
            this.left = left;
        }

        @Override
        public void valueChange(Property.ValueChangeEvent event) {
            showReloadingProgress();
            lftCtxCombo.setEnabled(false);
            rghtCtxCombo.setEnabled(false);
            int ctx = Integer.parseInt(event.getProperty().getValue().toString());
            changeContext(resultNumber, ctx, left);
        }
    }

    private static class MinMax {

        String segName = "tokens";

        long min;

        long max;

    }

    private MinMax getIds(SDocumentGraph graph) {
        List<SToken> sTokens = graph.getTokens();

        MinMax minMax = new MinMax();
        minMax.min = Long.MAX_VALUE;
        minMax.max = Long.MIN_VALUE;

        if (segmentationName == null) {
            minMax.segName = "tokens";

            if (sTokens != null) {
                for (SToken t : sTokens) {
                    SFeature feature = t.getFeature(ANNIS_NS, FEAT_RELANNIS_NODE);
                    if (feature != null && feature.getValue() instanceof RelannisNodeFeature) {
                        RelannisNodeFeature f = (RelannisNodeFeature) feature.getValue();

                        if (minMax.min > f.getTokenIndex()) {
                            minMax.min = f.getTokenIndex();
                        }

                        if (minMax.max < f.getTokenIndex()) {
                            minMax.max = f.getTokenIndex();
                        }
                    }
                }
            }
        } else {
            minMax.segName = segmentationName;

            List<SNode> nodes = CommonHelper.getSortedSegmentationNodes(segmentationName, graph);

            for (SNode n : nodes) {
                RelannisNodeFeature f = RelannisNodeFeature.extract(n);

                if (minMax.min > f.getSegIndex()) {
                    minMax.min = f.getSegIndex();
                }

                if (minMax.max < f.getSegIndex()) {
                    minMax.max = f.getSegIndex();
                }
            }
        }

        minMax.min++;
        minMax.max++;

        return minMax;
    }

    @Override
    public void updateResult(SaltProject p, PagedResultQuery query) {
        this.query = query;
        if (p != null && p.getCorpusGraphs() != null && !p.getCorpusGraphs().isEmpty()
                && p.getCorpusGraphs().get(0) != null && p.getCorpusGraphs().get(0).getDocuments() != null
                && !p.getCorpusGraphs().get(0).getDocuments().isEmpty()) {
            this.result = p.getCorpusGraphs().get(0).getDocuments().get(0);
        }

        removeComponent(reloadVisualizer);
        initVisualizer();

        lftCtxCombo.setEnabled(true);
        rghtCtxCombo.setEnabled(true);
    }

    protected SDocument getResult() {
        return result;
    }
}