gate.mimir.gus.client.Application.java Source code

Java tutorial

Introduction

Here is the source code for gate.mimir.gus.client.Application.java

Source

/*
 *  Application.java
 *
 *  Copyright (c) 2007-2011, The University of Sheffield.
 *
 *  This file is part of GATE Mmir (see http://gate.ac.uk/family/mimir.html), 
 *  and is free software, licenced under the GNU Affero General Public License,
 *  Version 3, November 2007 (also included with this distribution as file 
 *  LICENCE-AGPL3.html).
 *
 *  A commercial licence is also available for organisations whose business
 *  models preclude the adoption of open source and is subject to a licence
 *  fee charged by the University of Sheffield. Please contact the GATE team
 *  (see http://gate.ac.uk/g8/contact) if you require a commercial licence.
 *
 *  $Id: Application.java 13787 2011-05-03 09:41:36Z valyt $
 */
package gate.mimir.gus.client;

import java.util.ArrayList;
import java.util.List;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.json.client.JSONException;
import com.google.gwt.json.client.JSONObject;
import com.google.gwt.json.client.JSONParser;
import com.google.gwt.json.client.JSONString;
import com.google.gwt.json.client.JSONValue;
import com.google.gwt.user.client.History;
import com.google.gwt.user.client.HistoryListener;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.InvocationException;
import com.google.gwt.user.client.rpc.ServiceDefTarget;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Hyperlink;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.KeyboardListenerAdapter;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.MultiWordSuggestOracle.MultiWordSuggestion;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle;
import com.google.gwt.user.client.ui.SuggestionEvent;
import com.google.gwt.user.client.ui.SuggestionHandler;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

//1234

/**
 * <p>Search GUI for GATE.</p>
 * 
 * <p>Features:</p>
 * <ul>
 * <li>Query autocompletion.</li>
 * <li>KWIC/concordance view of the results.</li>
 * <li>Document and Annotations stack view on user demand for each result.</li>
 * <li>Ranking per document as soon as all the results are retrieved.</li>
 * </ul>
 */
public class Application implements EntryPoint, HistoryListener {

    private final int resultsPerPage = 10;
    private final int contextWindow = 5;
    private final int UNKNOWN_NUMBER_OF_RESULTS = Integer.MAX_VALUE;
    private int numberOfResults = UNKNOWN_NUMBER_OF_RESULTS;
    private int currentPage = 1;
    private String query = "";
    /** Identifier of the current query. */
    private String queryID;
    /** Contains the results of the current page. */
    private ArrayList<QueryResult> resultsPageList = new ArrayList<QueryResult>(10);
    int resultsStartIndex = 0;
    boolean suggestionSelected = false;
    Timer timer;

    private GusServiceAsync gusService;

    /**
     * TextArea used for formulating the query.
     */
    private TextArea searchBox;
    /**
     * The SuggestBox wrapping the search box
     */
    private SuggestBox suggestBox;
    private StatisticsLabel statisticsLabel;
    private FlexTable resultsTable;
    private HorizontalPanel pageListPanel;
    private HorizontalPanel skipLinksPanel;

    /**
     * Initialisation of the GUI elements and the <code>History</code>.
     */
    public void onModuleLoad() {

        // initialize the connection with the server
        gusService = (GusServiceAsync) GWT.create(GusService.class);
        ServiceDefTarget endpoint = (ServiceDefTarget) gusService;
        String rpcUrl = GWT.getHostPageBaseURL() + "gusRpc";
        endpoint.setServiceEntryPoint(rpcUrl);

        //create the search box
        searchBox = new TextArea();
        searchBox.setCharacterWidth(60);
        searchBox.setVisibleLines(5);
        searchBox.setText(getInputQuery());
        //wrap the search box into a suggest box
        suggestBox = new SuggestBox(new MimirOracle(), searchBox);
        suggestBox.setTitle("Press Escape to hide suggestions list; press Ctrl+Space to show it again.");
        RootPanel.get("searchbox").add(suggestBox);

        suggestBox.addKeyUpHandler(new KeyUpHandler() {
            @Override
            public void onKeyUp(KeyUpEvent event) {
                int keyCode = event.getNativeKeyCode();
                if (keyCode == KeyCodes.KEY_ENTER && event.isControlKeyDown()) {
                    // CTRL-ENTER -> fire the query
                    processQuery(searchBox.getText());
                } else if (keyCode == KeyCodes.KEY_ESCAPE) {
                    ((SuggestBox.DefaultSuggestionDisplay) suggestBox.getSuggestionDisplay()).hideSuggestions();
                } else if (keyCode == ' ' && event.isControlKeyDown()) {
                    // CTRL-Space: show suggestions
                    suggestBox.showSuggestionList();
                }
                if (((SuggestBox.DefaultSuggestionDisplay) suggestBox.getSuggestionDisplay())
                        .isSuggestionListShowing()) {
                    // gobble up navigation keys
                    if (keyCode == KeyCodes.KEY_UP || keyCode == KeyCodes.KEY_DOWN
                            || keyCode == KeyCodes.KEY_ENTER) {
                        event.stopPropagation();
                        event.preventDefault();
                    }
                }
            }
        });

        suggestBox.addKeyDownHandler(new KeyDownHandler() {
            @Override
            public void onKeyDown(KeyDownEvent event) {
                int keyCode = event.getNativeKeyCode();
                if (((SuggestBox.DefaultSuggestionDisplay) suggestBox.getSuggestionDisplay())
                        .isSuggestionListShowing()) {
                    // gobble up navigation keys
                    if (keyCode == KeyCodes.KEY_UP || keyCode == KeyCodes.KEY_DOWN
                            || keyCode == KeyCodes.KEY_ENTER) {
                        event.stopPropagation();
                        event.preventDefault();
                    }
                }
            }
        });

        suggestBox.addKeyPressHandler(new KeyPressHandler() {
            @Override
            public void onKeyPress(KeyPressEvent event) {
                int keyCode = event.getNativeEvent().getKeyCode();
                if (((SuggestBox.DefaultSuggestionDisplay) suggestBox.getSuggestionDisplay())
                        .isSuggestionListShowing()) {
                    // gobble up navigation keys
                    if (keyCode == KeyCodes.KEY_UP || keyCode == KeyCodes.KEY_DOWN
                            || keyCode == KeyCodes.KEY_ENTER) {
                        event.stopPropagation();
                        event.preventDefault();
                    }
                }
            }
        });
        //    suggestBox.addKeyPressHandler(new KeyPressHandler() {
        //      @Override
        //      public void onKeyPress(KeyPressEvent event) {
        //        if(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
        //          ((SuggestBox.DefaultSuggestionDisplay)
        //                  suggestBox.getSuggestionDisplay()).hideSuggestions();
        //        } else if(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
        //          if(event.isControlKeyDown()) {
        //            processQuery(searchBox.getText());
        //          } else {
        //            // gobble up the event
        //            event.stopPropagation();
        //          }
        //        } else if(event.getCharCode() == ' ' && event.isControlKeyDown()) {
        //          // CTRL-Space: show suggestions
        //          suggestBox.showSuggestionList();
        //        }
        //      }
        //    });

        Button searchButton = new Button("Search", new ClickHandler() {
            @Override
            public void onClick(ClickEvent event) {
                processQuery(searchBox.getText());
            }
        });
        RootPanel.get("searchbutton").add(searchButton);

        // add a line with statistics for the results in the North panel
        statisticsLabel = new StatisticsLabel("Results", gusService);
        RootPanel.get("resultsbar").clear();
        RootPanel.get("resultsbar").add(statisticsLabel);

        statisticsLabel.addResultCountListener(new StatisticsLabel.ResultCountListener() {
            public void resultCountChanged(int oldCount, int newCount) {
                numberOfResults = newCount;
                updateListOfPages();
            }
        });

        // dock panel split in North, West, Center, East and South panels
        DockPanel dockPanel = new DockPanel();
        dockPanel.setWidth("100%");
        //dockPanel.setSpacing(30);

        // add a table of results in the Center panel
        resultsTable = new FlexTable();
        resultsTable.setCellPadding(5);
        resultsTable.setCellSpacing(0);
        resultsTable.setWidth("100%");
        displayMessage("Please enter a query in the text field above and press Enter.");
        dockPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
        dockPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP);
        dockPanel.add(resultsTable, DockPanel.CENTER);

        // add the list of pages in the South panel
        pageListPanel = new HorizontalPanel();
        pageListPanel.setSpacing(10);
        skipLinksPanel = new HorizontalPanel();
        skipLinksPanel.setSpacing(10);

        VerticalPanel paginationPanel = new VerticalPanel();
        paginationPanel.setSpacing(5);
        paginationPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
        paginationPanel.add(pageListPanel);
        paginationPanel.add(skipLinksPanel);
        dockPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
        dockPanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_BOTTOM);
        dockPanel.add(paginationPanel, DockPanel.SOUTH);

        // make visible the dockPanel and add it to the body of the page
        RootPanel.get("resultstable").clear();
        RootPanel.get("resultstable").add(dockPanel);
        dockPanel.setVisible(true);

        // Add a history listener
        History.newItem("", false);
        History.addHistoryListener(this);
        History.fireCurrentHistoryState();
    }

    /**
     * Clear the results table, removing zebra stripes from empty rows.
     */
    private void clearTable() {
        resultsTable.clear();
        for (int i = 0; i < resultsTable.getRowCount(); ++i) {
            resultsTable.getRowFormatter().removeStyleName(i, (i % 2 == 0) ? "even" : "odd");
        }
    }

    /**
     * When the user click on a result page link or document link.
     */
    public void onHistoryChanged(String historyToken) {
        if (historyToken.startsWith("page=")) {
            // show a new page of results
            currentPage = Integer.parseInt(historyToken.substring("page=".length()));
            resultsStartIndex = (currentPage - 1) * resultsPerPage;
            resultsPageList.clear();
            clearTable();
            if (queryID == null) {
                displayFirstQueryResults();
            } else {
                displayNextQueryResults(System.currentTimeMillis());
            }
            searchBox.setFocus(true);

        } else {
            // put the focus on the search box
            searchBox.setFocus(true);
        }
    }

    /**
     * When the user execute a query.
     * @param query the string to use as a query
     */
    private void processQuery(String query) {
        if (timer != null) {
            timer.cancel();
        }
        numberOfResults = UNKNOWN_NUMBER_OF_RESULTS;
        if (queryID != null) {
            // release server-side resources for the previous query
            gusService.releaseQuery(queryID, new AsyncCallback<Void>() {

                public void onFailure(Throwable caught) {
                    // do nothing - if we failed to free the query there's not a lot
                    // we can do about it...
                }

                public void onSuccess(Void result) {
                    // nothing to do.
                }

            });
        }
        this.query = query;
        queryID = null;
        History.newItem("page=1", false);
        History.fireCurrentHistoryState();
    }

    /**
     * Display the results for the <code>currentPage</code>
     * in the <code>resultsTable</code>.
     */
    private void displayFirstQueryResults() {
        statisticsLabel.displayMessage(
                "Retrieving " + resultsPerPage + " results starting from " + (resultsStartIndex + 1) + "...");

        final long startTime = System.currentTimeMillis();
        gusService.search(getIndexId(), query, new AsyncCallback<String>() {
            String error = "A very very unexpected exception.";

            public void onFailure(Throwable caught) {
                try {
                    throw caught;
                } catch (IncompatibleRemoteServiceException e) {
                    error = "This client is not compatible with the "
                            + "server. Please cleanup and refresh the browser.";
                } catch (InvocationException e) {
                    error = "The call didn't complete cleanly.";
                } catch (SearchException e) {
                    error = e.getMessage();
                } catch (Throwable e) {
                    error = "A very unexpected exception. Is the server ready? "
                            + "Please wait few seconds before trying again.";
                } finally {
                    displayMessage(error);
                }
            }

            public void onSuccess(String result) {
                JSONValue jsonValue;
                try {
                    jsonValue = JSONParser.parse(result);
                } catch (JSONException e) {
                    displayMessage("Failed to parse JSON response: " + result);
                    return;
                }
                JSONObject jsonObject;
                JSONString jsonString;
                if ((jsonObject = jsonValue.isObject()) != null
                        && (jsonString = jsonObject.get("id").isString()) != null) {
                    queryID = jsonString.stringValue();
                } else {
                    displayMessage("No id returned for this query.");
                    return;
                }
                timer = new Timer() {
                    public void run() {
                        displayNextQueryResults(startTime);
                    }
                };
                // wait for the search to retrieve some results
                timer.schedule(500);
            }
        });
    }

    /**
     * To be called after {@link #displayFirstQueryResults()} to get the results
     * incrementally.
     * @param startTime time at the moment of the query execution
     */
    private void displayNextQueryResults(final long startTime) {

        //    final String message =
        //      "Number=" + resultsPerPage +
        //      " Start=" + resultsStartIndex +
        //      " List=" + resultsPageList.size() +
        //      " Time=" + ((System.currentTimeMillis()-startTime)/1000.0) + " s.";
        //    Window.setStatus(message);

        gusService.getHits(queryID, contextWindow, resultsPerPage - resultsPageList.size(),
                resultsStartIndex + resultsPageList.size(), new AsyncCallback<List<QueryResult>>() {
                    String error = "A very very unexpected exception.";

                    public void onFailure(Throwable caught) {
                        try {
                            throw caught;
                        } catch (IncompatibleRemoteServiceException e) {
                            error = "This client is not compatible with the "
                                    + "server. Please cleanup and refresh the browser.";
                        } catch (InvocationException e) {
                            error = "The call didn't complete cleanly.";
                        } catch (SearchException e) {
                            error = e.getMessage();
                        } catch (Throwable e) {
                            error = "A very unexpected exception. Is the server ready? "
                                    + "Please wait few seconds before trying again.";
                        } finally {
                            displayMessage(error);
                        }
                    }

                    public void onSuccess(List<QueryResult> results) {
                        // no (more) results
                        if (results == null && numberOfResults != UNKNOWN_NUMBER_OF_RESULTS) {
                            if (resultsPageList.size() == 0) {
                                if (numberOfResults == 0) {
                                    displayMessage("Sorry, but we found no results for your query.</p>"
                                            + "<br><p>(Check the syntax if there is an error. "
                                            + "If not, use a more general query or index more documents.)");
                                } else {
                                    displayMessage("Sorry, but we found no more results for your query.</p>"
                                            + "<br><p>The total number of results is " + numberOfResults + ".");
                                }
                            }
                            //          statisticsLabel.setText(statisticsLabel.getText()
                            //            .replaceFirst("unknown", String.valueOf(numberOfResults)));
                            updateListOfPages();

                            // more results
                        } else {
                            if (results != null) {
                                for (QueryResult r : results) {
                                    // add the results to resultsPageList
                                    resultsPageList.add(r);
                                }
                            }
                            updateQueryResults(
                                    String.valueOf(" in " + (System.currentTimeMillis() - startTime) / 1000.0)
                                            + " s.");
                            updateListOfPages();

                            if (resultsPageList.size() < resultsPerPage) {
                                // some results have not been retrived
                                timer = new Timer() {
                                    public void run() {
                                        displayNextQueryResults(startTime);
                                    }
                                };
                                // wait before to execute itself again
                                timer.schedule(500);
                            }
                        }
                    }
                });
    }

    /**
     * Update the results within the page.
     * @param time elapsed time since the query has been executed
     */
    private void updateQueryResults(String time) {

        //update the title of the page
        Window.setTitle("GUS - GATE Unified Search - " + query + " - page " + currentPage);

        // update the statistics line
        statisticsLabel.displayResults(queryID, resultsStartIndex + 1,
                Math.min((currentPage * resultsPerPage), numberOfResults));

        // update the results table
        for (int row = 0; row < resultsPerPage; row++) {
            if (resultsTable.getRowCount() > row && resultsTable.getCellCount(row) > 1
                    && resultsTable.getWidget(row, 0) != null) {
                // non empty row, skip it
                continue;
            }
            if (row >= resultsPageList.size()) {
                // no more results to display
                break;
            }
            int documentID = resultsPageList.get(row).getDocumentID();
            String documentURI = resultsPageList.get(row).getDocumentURI();
            String documentTitle = resultsPageList.get(row).getDocumentTitle();
            if (documentTitle == null || documentTitle.trim().length() == 0) {
                // we got no title to display: use the URI file
                String[] pathElems = documentURI.split("/");
                documentTitle = pathElems[pathElems.length - 1];
            }
            // zebra stripes
            resultsTable.getRowFormatter().addStyleName(row, (row % 2 == 0) ? "even" : "odd");
            resultsTable.getCellFormatter().setHorizontalAlignment(row, 0, HasHorizontalAlignment.ALIGN_LEFT);
            // each cell is a Grid
            Grid hitGrid = new Grid(2, 1);
            hitGrid.getCellFormatter().setHorizontalAlignment(0, 0, HasHorizontalAlignment.ALIGN_LEFT);
            hitGrid.getCellFormatter().setHorizontalAlignment(1, 0, HasHorizontalAlignment.ALIGN_LEFT);
            // top row of grid contains the link(s) 
            String documentURIText = "<span title=\"" + documentURI + "\">" + documentTitle + "</span>";
            if (getUriIsLink().equals("true")) {
                // generate two links: original doc and cached
                FlowPanel linksPanel = new FlowPanel();
                linksPanel.setStylePrimaryName("hit");
                linksPanel.add(new Anchor(documentURIText, true, documentURI, "documentWindow"));
                linksPanel.add(new InlineLabel(" ("));
                linksPanel.add(new Anchor("cached", false, "gusDocument/" + documentID + "?queryId=" + queryID,
                        "documentWindow"));
                linksPanel.add(new InlineLabel(")"));
                hitGrid.setWidget(0, 0, linksPanel);
            } else {
                // generate one link: cached, with document name as text
                hitGrid.setWidget(0, 0, new Anchor(documentURIText, true,
                        "gusDocument/" + documentID + "?queryId=" + queryID, "documentWindow"));
            }
            // second row is the hit text + contexts 
            HTML snippet = new HTML(resultsPageList.get(row).getLeftContext() + "<span class=\"mimir-hit\">"
                    + resultsPageList.get(row).getSpanText() + "</span>"
                    + resultsPageList.get(row).getRightContext());
            snippet.setStylePrimaryName("snippet");
            hitGrid.setWidget(1, 0, snippet);
            resultsTable.setWidget(row, 0, hitGrid);
        }
    }

    private static final NumberFormat numberFormat = NumberFormat.getDecimalFormat();

    /**
     * Update the list of pages at the bottom of the page. 
     */
    private void updateListOfPages() {
        if (numberOfResults != UNKNOWN_NUMBER_OF_RESULTS) {
            pageListPanel.clear();
            pageListPanel.add(new Label("Page"));

            // previous button
            if (currentPage > 1) {
                pageListPanel.add(new Hyperlink("< Prev", "page=" + (currentPage - 1)));
            }

            int lastPage = numberOfResults / resultsPerPage;
            if (numberOfResults % resultsPerPage != 0)
                lastPage += 1;

            for (int num = ((currentPage < 6) ? 1 : (currentPage - 5)); num <= ((currentPage < 6) ? 10
                    : (currentPage + 4)) && num <= lastPage; num++) {
                pageListPanel.add(pageLink(num));
            }

            if (currentPage < lastPage) {
                pageListPanel.add(new Hyperlink("Next >", "page=" + (currentPage + 1)));
            }

            skipLinksPanel.clear();

            if (currentPage > 10 || lastPage > currentPage + 10) {
                // need a skip list
                skipLinksPanel.add(new Label("Skip pages"));

                int longestBackSkip = 1;
                for (int tmp = currentPage; tmp > 0; tmp /= 10, longestBackSkip *= 10)
                    ;

                for (int i = longestBackSkip; i > 1; i /= 10) {
                    if (currentPage > i) {
                        skipLinksPanel.add(skipLink(-i));
                    }
                }

                skipLinksPanel.add(new Label(" "));

                for (int i = 10; currentPage + i <= lastPage; i *= 10) {
                    skipLinksPanel.add(skipLink(i));
                }
            }
        }
    }

    /**
     * Create a link to a given page number.
     */
    private Widget pageLink(int num) {
        if (num == currentPage) {
            return new Label(numberFormat.format(num));
        } else {
            return new Hyperlink(numberFormat.format(num), "page=" + String.valueOf(num));
        }
    }

    /**
     * Create a link to skip a given number of pages.  Negative skips
     * skip backwards, positive skips skip forwards.
     */
    private Widget skipLink(int skip) {
        int targetPage = currentPage + skip;
        if (skip < 0) {
            return new Hyperlink("<" + numberFormat.format(-skip), "page=" + targetPage);
        } else {
            return new Hyperlink(numberFormat.format(skip) + ">", "page=" + targetPage);
        }
    }

    private class MimirOracle extends SuggestOracle {

        public MimirOracle() {
            super();
            gusService.getAnnotationsConfig(getIndexId(), new AsyncCallback<String[][]>() {

                /* (non-Javadoc)
                 * @see com.google.gwt.user.client.rpc.AsyncCallback#onFailure(java.lang.Throwable)
                 */
                public void onFailure(Throwable caught) {
                    //we could not get the data from the server
                    annotationsConfig = new String[][] { new String[] {} };
                }

                /* (non-Javadoc)
                 * @see com.google.gwt.user.client.rpc.AsyncCallback#onSuccess(java.lang.Object)
                 */
                public void onSuccess(String[][] result) {
                    annotationsConfig = result;
                }

            });
        }

        /* (non-Javadoc)
         * @see com.google.gwt.user.client.ui.SuggestOracle#requestSuggestions(com.google.gwt.user.client.ui.SuggestOracle.Request, com.google.gwt.user.client.ui.SuggestOracle.Callback)
         */
        @Override
        public void requestSuggestions(Request request, Callback callback) {
            ArrayList<MultiWordSuggestion> suggestions = new ArrayList<MultiWordSuggestion>();
            String query = request.getQuery();
            int caretIndex = searchBox.getCursorPos();
            int startIndex = query.lastIndexOf('{', caretIndex - 1);
            int endIndex = query.indexOf('{', caretIndex);
            if (endIndex == -1) {
                endIndex = caretIndex;
            }
            int lastClose = query.lastIndexOf("}", caretIndex);
            if (startIndex != -1 && lastClose < startIndex) {
                // an open bracket '{' is present, and not followed by } yet
                //check if we have the annotation type already
                String annType = null;
                boolean nonSpaceSeen = false;
                int charIdx = startIndex + 1;
                for (; charIdx < endIndex; charIdx++) {
                    // this method is deprecated, but the replacement (isWhitespace())
                    // is not implemented in GWT
                    if (Character.isSpace(query.charAt(charIdx))) {
                        if (nonSpaceSeen) {
                            //we found some space, after some actual content was seen
                            annType = query.substring(startIndex + 1, charIdx);
                            break;
                        }
                    } else {
                        nonSpaceSeen = true;
                    }
                }
                if (annType == null) {
                    //we have not found an ann type -> suggest some
                    //the string before the last open {, before the caret
                    String before = query.substring(0, startIndex);
                    //the string after the next {
                    String after = query.substring(endIndex);
                    //the string from the current open {, to the caret, or the next {
                    String middle = (startIndex >= 0 && startIndex < endIndex)
                            ? query.substring(startIndex + 1, endIndex)
                            : "";
                    for (int annTypeId = 0; annTypeId < annotationsConfig.length; annTypeId++) {
                        if (annotationsConfig[annTypeId][0].startsWith(middle)) {
                            //we have identified the annotation type
                            String suggestion = "{" + annotationsConfig[annTypeId][0];
                            suggestions.add(new MultiWordSuggestion(before + suggestion + after, suggestion));
                            //              Window.alert("Suggestion is: \"" + before + suggestion + after + "\"!");
                        }
                    }
                } else {
                    //we know the ann type -> consume everything until the last word
                    int lastSpace = charIdx;
                    int wordCount = 0;
                    boolean inSpace = true;
                    boolean inQuote = false;
                    for (; charIdx < endIndex; charIdx++) {
                        if (inQuote) {
                            //while in quote, consume everything until the closing quote
                            if (query.charAt(charIdx) == '"' && charIdx > 0 && query.charAt(charIdx - 1) != '\\') {
                                inQuote = false;
                                //                wordCount++;
                            }
                        } else {
                            if (Character.isSpace(query.charAt(charIdx))) {
                                lastSpace = charIdx;
                                if (!inSpace) {
                                    //we're starting a new space (so we just finished a word)
                                    wordCount++;
                                    inSpace = true;
                                }
                            } else if (query.charAt(charIdx) == ')') {
                                //closing of REGEX
                                wordCount = 0;
                                inSpace = false;
                            } else if (query.charAt(charIdx) == '"' && charIdx > 0
                                    && query.charAt(charIdx - 1) != '\\') {
                                inQuote = true;
                                inSpace = false;
                            } else {
                                //some other non-space char
                                if (inSpace) {
                                    //we're starting a new word
                                    inSpace = false;
                                }
                            }
                        }
                    }
                    if (inQuote) {
                        //suggest nothing
                    } else {
                        String before = query.substring(0, lastSpace + 1);
                        String after = query.substring(endIndex);
                        String middle = lastSpace < endIndex ? query.substring(lastSpace + 1, endIndex) : "";
                        //we are still typing the feature name or operator
                        //words appear in this sequence: <feature> <operator> <value>
                        if (wordCount % 3 == 0) {
                            //feature
                            //find the ann type
                            for (int annTypeId = 0; annTypeId < annotationsConfig.length; annTypeId++) {
                                if (annotationsConfig[annTypeId][0].equalsIgnoreCase(annType)) {
                                    //suggest some feature names
                                    for (int featId = 1; featId < annotationsConfig[annTypeId].length; featId++) {
                                        if (annotationsConfig[annTypeId][featId].startsWith(middle)) {
                                            String suggestion = annotationsConfig[annTypeId][featId];
                                            suggestions.add(new MultiWordSuggestion(before + suggestion + after,
                                                    suggestion));
                                        }
                                    }
                                    //also offer to close the annotation
                                    String suggestion = "}";
                                    suggestions
                                            .add(new MultiWordSuggestion(before + suggestion + after, suggestion));
                                    //only one ann type can match
                                    break;
                                }
                            }
                        } else if (wordCount % 3 == 1) {
                            //operator
                            String[] strArray = new String[] { "= \"\"", "<", "<=", ">", ">=", ".REGEX()" };
                            for (String suggestion : strArray) {
                                suggestions.add(new MultiWordSuggestion(before + suggestion + after, suggestion));
                            }
                        } else {
                            //value -> no suggestions
                        }
                    }
                }
                //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX        

                //        //the string before the last open {, before the caret
                //        String before = query.substring(0, startIndex);
                //        //the string after the next {
                //        String after = query.substring(endIndex);
                //        //the string from the current open {, to the caret, or the next {
                //        String middle = (startIndex >= 0 && startIndex < endIndex) ?
                //          query.substring(startIndex+1, endIndex): "";
                //        //find the annotation type
                //        String[] inputs = middle.split("\\s");
                //        if(inputs.length == 1){
                //          //we're searching only for annotation type
                //          for(int annTypeId = 0; annTypeId < indexConfig.length; annTypeId++){
                //            if(indexConfig[annTypeId][0].startsWith(inputs[0])){
                //              //we have identified the annotation type
                //              String suggestion = "{" + indexConfig[annTypeId][0];
                //              suggestions.add(new MultiWordSuggestion(
                //                      before + suggestion + after, suggestion));
                //            }
                //          }
                //        } else if(inputs.length > 1){
                //          //we already have the ann type, we need to suggest feature names
                //          for(int annTypeId = 0; annTypeId < indexConfig.length; annTypeId++){
                //            if(indexConfig[annTypeId][0].equalsIgnoreCase(inputs[0])){
                //              //now we need to suggest a feature name
                //              //the before string must contain everything up to the current feature
                //              
                //              String lastPrefix = inputs[inputs.length -1];
                //              if(lastPrefix.indexOf('=') <0){
                //                //we are still typing the feature name
                //                for(int featId = 1; featId < indexConfig[annTypeId].length; 
                //                    featId++){
                //                  if(indexConfig[annTypeId][featId].startsWith(lastPrefix)){
                //                    String suggestion = indexConfig[annTypeId][featId] + " = ";
                //                    suggestions.add(new MultiWordSuggestion(
                //                            before + suggestion + after, suggestion));
                //                  }
                //                }
                //              }
                //              //we only match one annotation type, so we break now
                //              break;
                //            }
                //          }
                //        }//if(inputs.length > 1)
            }

            Response response = new Response(suggestions);
            callback.onSuggestionsReady(request, response);
        }

        private String[][] annotationsConfig = new String[][] { new String[] {} };

    }

    private void displayMessage(String message) {
        clearTable();
        resultsTable.getCellFormatter().setHorizontalAlignment(0, 0, HasHorizontalAlignment.ALIGN_CENTER);
        resultsTable.setWidget(0, 0, new HTML(message.replaceAll("\n", "<br />")));
    }

    private native String getIndexId() /*-{
                                       return $wnd.indexId;
                                       }-*/;

    private native String getUriIsLink() /*-{
                                         return $wnd.uriIsLink;
                                         }-*/;

    //  private native String getInputQuery();
    private native String getInputQuery() /*-{
                                          return $wnd.inputQueryString;
                                          }-*/;

}