annis.gui.frequency.FrequencyQueryPanel.java Source code

Java tutorial

Introduction

Here is the source code for annis.gui.frequency.FrequencyQueryPanel.java

Source

/*
 * Copyright 2012 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.frequency;

import annis.gui.QueryController;
import annis.gui.admin.PopupTwinColumnSelect;
import annis.gui.objects.FrequencyQuery;
import annis.gui.objects.QueryUIState;
import annis.libgui.Helper;
import annis.model.QueryAnnotation;
import annis.model.QueryNode;
import annis.service.objects.AnnisAttribute;
import annis.service.objects.FrequencyTable;
import com.google.common.base.Joiner;
import com.google.common.collect.LinkedHashMultimap;
import com.google.common.collect.Multimap;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.GenericType;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.vaadin.data.Container;
import com.vaadin.data.Property;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.event.FieldEvents;
import com.vaadin.event.LayoutEvents;
import com.vaadin.shared.ui.label.ContentMode;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.CheckBox;
import com.vaadin.ui.Component;
import com.vaadin.ui.DefaultFieldFactory;
import com.vaadin.ui.Field;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.Table;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.WeakHashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Thomas Krause <krauseto@hu-berlin.de>
 */
public class FrequencyQueryPanel extends VerticalLayout implements Serializable, FieldEvents.TextChangeListener {

    private static final Logger log = LoggerFactory.getLogger(FrequencyQueryPanel.class);

    private Table tblFrequencyDefinition;
    private final IndexedContainer metaNamesContainer;
    private final Button btAdd;
    private final Button btReset;
    private final CheckBox cbAutomaticMode;
    private Button btDeleteRow;
    private Button btShowFrequencies;
    private int counter;
    private FrequencyResultPanel resultPanel;
    private Button btShowQuery;
    private VerticalLayout queryLayout;
    private final QueryController controller;
    private final Label lblCorpusList;
    private final Label lblAQL;
    private final Label lblErrorOrMsg;
    private transient WeakHashMap<Field, Object> field2ItemID;

    private final ProgressBar pbQuery = new ProgressBar();

    private final QueryUIState state;

    public FrequencyQueryPanel(final QueryController controller, QueryUIState state) {
        this.controller = controller;
        this.state = state;

        setWidth("99%");
        setHeight("99%");
        setMargin(true);

        queryLayout = new VerticalLayout();
        queryLayout.setWidth("100%");
        queryLayout.setHeight("100%");

        HorizontalLayout queryDescriptionLayout = new HorizontalLayout();
        queryDescriptionLayout.setSpacing(true);
        queryDescriptionLayout.setWidth("100%");
        queryDescriptionLayout.setHeight("-1px");
        queryLayout.addComponent(queryDescriptionLayout);

        lblCorpusList = new Label("");
        lblCorpusList.setCaption("selected corpora:");
        lblCorpusList.setWidth("100%");

        lblAQL = new Label("");
        lblAQL.setCaption("query to analyze:");
        lblAQL.setWidth("100%");
        lblAQL.addStyleName(Helper.CORPUS_FONT_FORCE);

        queryDescriptionLayout.addComponent(lblCorpusList);
        queryDescriptionLayout.addComponent(lblAQL);

        queryDescriptionLayout.setComponentAlignment(lblCorpusList, Alignment.MIDDLE_LEFT);
        queryDescriptionLayout.setComponentAlignment(lblAQL, Alignment.MIDDLE_RIGHT);

        tblFrequencyDefinition = new Table();
        tblFrequencyDefinition.setImmediate(true);
        tblFrequencyDefinition.setSortEnabled(false);
        tblFrequencyDefinition.setSelectable(true);
        tblFrequencyDefinition.setMultiSelect(true);
        tblFrequencyDefinition.setTableFieldFactory(new FieldFactory());
        tblFrequencyDefinition.setEditable(true);
        tblFrequencyDefinition.addValueChangeListener(new Property.ValueChangeListener() {
            @Override
            public void valueChange(ValueChangeEvent event) {
                if (tblFrequencyDefinition.getValue() == null
                        || ((Set<Object>) tblFrequencyDefinition.getValue()).isEmpty()) {
                    btDeleteRow.setEnabled(false);
                } else {
                    btDeleteRow.setEnabled(true);
                }
            }
        });

        lblErrorOrMsg = new Label("No node with explicit name in OR expression found! "
                + "When using OR expression you need to explicitly name the nodes "
                + "you want to include in the frequency analysis with \"#\", " + "like e.g. in <br />" + "<pre>"
                + "(n1#tok=\"fun\" | n1#tok=\"severity\")" + "</pre>");
        lblErrorOrMsg.setContentMode(ContentMode.HTML);
        lblErrorOrMsg.addStyleName("embedded-warning");
        lblErrorOrMsg.setWidth("100%");
        lblErrorOrMsg.setVisible(false);
        queryLayout.addComponent(lblErrorOrMsg);

        tblFrequencyDefinition.setWidth("100%");
        tblFrequencyDefinition.setHeight("100%");

        tblFrequencyDefinition.setContainerDataSource(state.getFrequencyTableDefinition());

        tblFrequencyDefinition.setColumnHeader("nr", "Node number/name");
        tblFrequencyDefinition.setColumnHeader("annotation", "Selected annotation of node");
        tblFrequencyDefinition.setColumnHeader("comment", "Comment");

        tblFrequencyDefinition.addStyleName(Helper.CORPUS_FONT_FORCE);

        tblFrequencyDefinition.setRowHeaderMode(Table.RowHeaderMode.INDEX);

        tblFrequencyDefinition.setColumnExpandRatio("nr", 0.15f);
        tblFrequencyDefinition.setColumnExpandRatio("annotation", 0.35f);
        tblFrequencyDefinition.setColumnExpandRatio("comment", 0.5f);
        tblFrequencyDefinition.setVisibleColumns("nr", "annotation", "comment");

        queryLayout.addComponent(tblFrequencyDefinition);

        metaNamesContainer = new IndexedContainer();
        PopupTwinColumnSelect metaSelect = new PopupTwinColumnSelect();
        metaSelect.setSelectableContainer(metaNamesContainer);
        metaSelect.setPropertyDataSource(state.getFrequencyMetaData());
        metaSelect.setCaption("Metadata");

        queryLayout.addComponent(metaSelect);

        if (controller != null) {
            createAutomaticEntriesForQuery(state.getAql().getValue());
            updateQueryInfo(state.getAql().getValue());
        }

        HorizontalLayout layoutButtons = new HorizontalLayout();

        btAdd = new Button("Add");
        btAdd.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(ClickEvent event) {
                cbAutomaticMode.setValue(Boolean.FALSE);

                int nr = 1;
                // get the highest number of values from the existing defitions
                for (Object id : tblFrequencyDefinition.getItemIds()) {
                    String textNr = (String) tblFrequencyDefinition.getItem(id).getItemProperty("nr").getValue();
                    try {
                        nr = Math.max(nr, Integer.parseInt(textNr));
                    } catch (NumberFormatException ex) {
                        // was not a number but a named node
                    }
                }
                if (controller != null) {
                    List<QueryNode> nodes = parseQuery(FrequencyQueryPanel.this.state.getAql().getValue());
                    nr = Math.min(nr, nodes.size() - 1);
                    int id = counter++;
                    UserGeneratedFrequencyEntry entry = new UserGeneratedFrequencyEntry();
                    entry.setAnnotation("tok");
                    entry.setComment("");
                    entry.setNr("" + (nr + 1));
                    FrequencyQueryPanel.this.state.getFrequencyTableDefinition().addItem(id, entry);
                }
            }
        });
        layoutButtons.addComponent(btAdd);

        btDeleteRow = new Button("Delete selected row(s)");
        btDeleteRow.setEnabled(false);
        btDeleteRow.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(ClickEvent event) {
                Set<Object> selected = new HashSet((Set<Object>) tblFrequencyDefinition.getValue());
                for (Object o : selected) {
                    cbAutomaticMode.setValue(Boolean.FALSE);
                    tblFrequencyDefinition.removeItem(o);

                }
            }
        });
        layoutButtons.addComponent(btDeleteRow);

        cbAutomaticMode = new CheckBox("Automatic mode", true);
        cbAutomaticMode.setImmediate(true);
        cbAutomaticMode.addValueChangeListener(new Property.ValueChangeListener() {
            @Override
            public void valueChange(ValueChangeEvent event) {
                btShowFrequencies.setEnabled(true);
                if (cbAutomaticMode.getValue()) {
                    tblFrequencyDefinition.removeAllItems();
                    if (controller != null) {
                        createAutomaticEntriesForQuery(FrequencyQueryPanel.this.state.getAql().getValue());
                    }
                }
            }
        });
        layoutButtons.addComponent(cbAutomaticMode);

        btReset = new Button("Reset to default");
        btReset.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(ClickEvent event) {
                cbAutomaticMode.setValue(Boolean.TRUE);
                btShowFrequencies.setEnabled(true);
                tblFrequencyDefinition.removeAllItems();
                if (controller != null) {
                    createAutomaticEntriesForQuery(FrequencyQueryPanel.this.state.getAql().getValue());
                }
            }
        });
        //layoutButtons.addComponent(btReset);

        layoutButtons.setComponentAlignment(btAdd, Alignment.MIDDLE_LEFT);
        layoutButtons.setComponentAlignment(btDeleteRow, Alignment.MIDDLE_LEFT);
        layoutButtons.setComponentAlignment(cbAutomaticMode, Alignment.MIDDLE_RIGHT);
        layoutButtons.setExpandRatio(btAdd, 0.0f);
        layoutButtons.setExpandRatio(btDeleteRow, 0.0f);
        layoutButtons.setExpandRatio(cbAutomaticMode, 1.0f);

        layoutButtons.setMargin(true);
        layoutButtons.setSpacing(true);
        layoutButtons.setHeight("-1px");
        layoutButtons.setWidth("100%");

        queryLayout.addComponent(layoutButtons);

        btShowFrequencies = new Button("Perform frequency analysis");
        btShowFrequencies.setDisableOnClick(true);
        btShowFrequencies.addClickListener(new Button.ClickListener() {
            @Override
            public void buttonClick(ClickEvent event) {

                if (controller != null) {
                    try {
                        if (resultPanel != null) {
                            removeComponent(resultPanel);
                        }
                        queryLayout.setVisible(false);

                        pbQuery.setCaption("Please wait, the frequencies analysis can take some time");
                        pbQuery.setIndeterminate(true);
                        pbQuery.setEnabled(true);
                        pbQuery.setVisible(true);

                        controller.executeFrequency(FrequencyQueryPanel.this);
                    } catch (Exception ex) {
                        btShowFrequencies.setEnabled(true);
                    }
                }

            }
        });
        queryLayout.addComponent(btShowFrequencies);

        queryLayout.setComponentAlignment(tblFrequencyDefinition, Alignment.TOP_CENTER);
        queryLayout.setComponentAlignment(layoutButtons, Alignment.TOP_CENTER);
        queryLayout.setComponentAlignment(btShowFrequencies, Alignment.TOP_CENTER);

        queryLayout.setExpandRatio(tblFrequencyDefinition, 1.0f);
        queryLayout.setExpandRatio(layoutButtons, 0.0f);
        queryLayout.setExpandRatio(btShowFrequencies, 0.0f);

        queryLayout.addLayoutClickListener(new LayoutEvents.LayoutClickListener() {
            @Override
            public void layoutClick(LayoutEvents.LayoutClickEvent event) {
                Component c = event.getClickedComponent();
                if (c instanceof Field) {
                    Object itemID = getField2ItemID().get((Field) c);
                    if (itemID != null) {
                        if (!event.isCtrlKey() && !event.isShiftKey()) {
                            // deselect everything else if no modifier key was clicked
                            tblFrequencyDefinition.setValue(null);
                        }
                        // select the item
                        tblFrequencyDefinition.select(itemID);
                    }
                }
            }
        });

        btShowQuery = new Button("New Analysis", new Button.ClickListener() {

            @Override
            public void buttonClick(ClickEvent event) {
                showQueryDefinitionPanel();
            }
        });
        btShowQuery.setVisible(false);

        pbQuery.setVisible(false);
        addComponent(pbQuery);

        addComponent(queryLayout);
        addComponent(btShowQuery);

        setComponentAlignment(btShowQuery, Alignment.TOP_CENTER);
        setComponentAlignment(pbQuery, Alignment.TOP_CENTER);

        if (controller != null) {
            state.getSelectedCorpora().addValueChangeListener(new Property.ValueChangeListener() {

                @Override
                public void valueChange(ValueChangeEvent event) {
                    if (cbAutomaticMode.getValue()) {
                        createAutomaticEntriesForQuery(FrequencyQueryPanel.this.state.getAql().getValue());
                    }
                    updateQueryInfo(FrequencyQueryPanel.this.state.getAql().getValue());
                }
            });
        }
    }

    public Set<String> getAvailableMetaNames() {
        Set<String> result = new TreeSet<>();
        WebResource service = Helper.getAnnisWebResource();
        // get current corpus selection
        Set<String> corpusSelection = state.getSelectedCorpora().getValue();
        if (service != null) {
            try {
                List<AnnisAttribute> atts = new LinkedList<>();

                for (String corpus : corpusSelection) {
                    atts.addAll(service.path("query").path("corpora").path(corpus).path("annotations")
                            .get(new GenericType<List<AnnisAttribute>>() {
                            }));
                }
                for (AnnisAttribute a : atts) {
                    if (a.getType() == AnnisAttribute.Type.meta) {
                        result.add(a.getName());
                    }
                }
            } catch (ClientHandlerException | UniformInterfaceException ex) {
                log.error(null, ex);
            }
        }
        return result;
    }

    @Override
    public void textChange(FieldEvents.TextChangeEvent event) {
        if (cbAutomaticMode.getValue()) {
            createAutomaticEntriesForQuery(event.getText());
        }
        updateQueryInfo(event.getText());
    }

    public void showResult(FrequencyTable result, FrequencyQuery query) {
        pbQuery.setVisible(false);
        resultPanel = new FrequencyResultPanel(result, query, this);
        addComponent(resultPanel);
        setExpandRatio(resultPanel, 1.0f);

        queryLayout.setVisible(false);
    }

    public void showQueryDefinitionPanel() {
        btShowFrequencies.setEnabled(true);
        pbQuery.setVisible(false);
        btShowQuery.setVisible(false);
        queryLayout.setVisible(true);
        resultPanel.setVisible(false);
    }

    private List<QueryNode> parseQuery(String query) {
        if (query == null || query.isEmpty()) {
            return new LinkedList<>();
        }
        // let the service parse the query
        WebResource res = Helper.getAnnisWebResource();
        List<QueryNode> nodes = res.path("query/parse/nodes").queryParam("q", Helper.encodeJersey(query))
                .get(new GenericType<List<QueryNode>>() {
                });

        return nodes;
    }

    private void createAutomaticEntriesForQuery(String query) {
        if (query == null || query.isEmpty()) {
            return;
        }

        try {

            state.getFrequencyTableDefinition().removeAllItems();
            lblErrorOrMsg.setVisible(false);

            counter = 0;
            List<QueryNode> nodes = parseQuery(query);
            Collections.sort(nodes, new Comparator<QueryNode>() {

                @Override
                public int compare(QueryNode o1, QueryNode o2) {
                    if (o1.getVariable() == null) {
                        return o2 == null ? 0 : -1;
                    }
                    return o1.getVariable().compareTo(o2.getVariable());
                }
            });

            // calculate the nodes that are part of every alternative
            Multimap<String, Integer> alternativesOfVariable = LinkedHashMultimap.create();
            int maxAlternative = 0;
            for (QueryNode n : nodes) {
                if (n.getAlternativeNumber() != null) {
                    maxAlternative = Math.max(n.getAlternativeNumber(), maxAlternative);
                    alternativesOfVariable.put(n.getVariable(), n.getAlternativeNumber());
                }
            }
            Set<String> allowedVariables = new LinkedHashSet<>();
            for (QueryNode n : nodes) {
                // we assume that the alternative numbering is continuous and without gaps
                if (alternativesOfVariable.get(n.getVariable()).size() == (maxAlternative + 1)) {
                    allowedVariables.add(n.getVariable());
                }
            }

            if (maxAlternative > 0 && allowedVariables.isEmpty()) {
                lblErrorOrMsg.setVisible(true);
            }

            Set<UserGeneratedFrequencyEntry> generatedEntries = new HashSet<>();

            for (QueryNode n : nodes) {
                if (!n.isArtificial() && allowedVariables.contains(n.getVariable())) {
                    if (n.getNodeAnnotations().isEmpty()) {
                        UserGeneratedFrequencyEntry entry = new UserGeneratedFrequencyEntry();
                        entry.setAnnotation("tok");
                        entry.setComment("automatically created from " + n.toAQLNodeFragment());
                        entry.setNr(n.getVariable());

                        if (!generatedEntries.contains(entry)) {
                            int id = counter++;
                            state.getFrequencyTableDefinition().addItem(id, entry);
                            generatedEntries.add(entry);
                        }
                    } else {
                        QueryAnnotation firstAnno = n.getNodeAnnotations().iterator().next();

                        UserGeneratedFrequencyEntry entry = new UserGeneratedFrequencyEntry();
                        entry.setAnnotation(firstAnno.getName());
                        entry.setComment("automatically created from " + n.toAQLNodeFragment());
                        entry.setNr(n.getVariable());

                        if (!generatedEntries.contains(entry)) {
                            int id = counter++;
                            state.getFrequencyTableDefinition().addItem(id, entry);
                            generatedEntries.add(entry);
                        }
                    }
                }
            }
        } catch (UniformInterfaceException ex) {
            // non-valid query, ignore
        }

    }

    private void updateQueryInfo(String query) {
        metaNamesContainer.removeAllItems();
        Set<String> allMetaNames = getAvailableMetaNames();
        Set<String> oldSelection = new TreeSet<>(state.getFrequencyMetaData().getValue());
        for (String m : allMetaNames) {
            metaNamesContainer.addItem(m);
        }
        // remove all selections that are no longer present
        oldSelection.retainAll(allMetaNames);
        state.getFrequencyMetaData().setValue(oldSelection);

        Set<String> selectedCorpora = state.getSelectedCorpora().getValue();
        if (selectedCorpora.isEmpty()) {
            lblCorpusList.setValue("none");
        } else {
            lblCorpusList.setValue(Joiner.on(", ").join(selectedCorpora));
        }
        if (query == null || query.isEmpty()) {
            lblAQL.setValue("<empty query>");
        } else {
            lblAQL.setValue(query.replaceAll("[\n\r]+", " "));
        }
    }

    public void notifiyQueryFinished() {
        btShowFrequencies.setEnabled(true);
        btShowQuery.setVisible(true);
    }

    public class FieldFactory extends DefaultFieldFactory {

        public FieldFactory() {
        }

        @Override
        public Field createField(Container container, final Object itemId, Object propertyId, Component uiContext) {
            if ("nr".equals(propertyId) || "annotation".equals(propertyId)) {
                TextField txt = new TextField(container.getContainerProperty(itemId, propertyId));
                txt.setWidth("100%");
                if (itemId != null) {
                    getField2ItemID().put(txt, itemId);
                }
                return txt;
            } else if ("comment".equals(propertyId)) {
                // explicitly request a read-only label
                return null;
            }

            return super.createField(container, itemId, propertyId, uiContext);
        }

    }

    private WeakHashMap<Field, Object> getField2ItemID() {
        if (field2ItemID == null) {
            field2ItemID = new WeakHashMap<>();
        }
        return field2ItemID;
    }

}