Monday 7 September 2009

Single Word Suggest Oracle

* This is kind of a data dictionary for the type ahead search box providing suggestions.

This is kind of a data dictionary for the type ahead search box providing suggestions.

This file is similar to the class MultiWordSuggestOracle provided by GWT. The behaviour of the GWT class was different from what was needed. Extending the class was not an option since many methods were private. Also member variables were private without any getters or setters.

The name Single Word Handler explains the purpose of class. Normally all characters are used in matching.



For example, the query "Ka" would match "Kangana Ranawat", as well as "Shahid Kapoor". This is because MultiWord considers each word separately for matching.

In case you do not want the match to happen, i.e. the query "Ka" would match "Kangana Ranawat", but not "Shahid Kapoor", then use this class provided.

The implementation of code is attached.



/**
 * This is kind of a data dictionary for the type ahead search box providing suggestions.
 * 
 * This file is similar to the class MutliWordSuggestOracle provided by GWT.
 * The behaviour of the GWT class was different from what was needed.
 * Extending the class was not an option since many methods were private. Also member 
 * variables were private without any getters or setters.
 * 
 * The name Single Word Handler explains the purpose of class. All characters are used in
 * matching. For example, the query "bar" would match "bar", but not "foo bar".
 * In case you want the match to happen, you should use MultiWordSuggestOracle of GWT.
 */


/*
 * Copyright 2007 Google Inc.
 * 
 * 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 adv.client.tree;


import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.SuggestOracle;


import java.util.*;




public class AdvSingleWordSuggestOracle extends SuggestOracle 
{


  /**
   * Suggestion class for {@link AdvMultiWordSuggestOracle}.
   */
  public static class AdvSingleWordSuggestion implements Suggestion 
  {
    private String displayString;
    private String replacementString;




    /**
     * Constructor for AdvMultiWordSuggestion.
     * 
     * @param replacementString the string to enter into the SuggestBox's text
     *          box if the suggestion is chosen
     * @param displayString the display string
     */
    public AdvSingleWordSuggestion(String replacementString, String displayString) 
    {
      this.replacementString = replacementString;
      this.displayString = displayString;
    }


    public String getDisplayString() 
    {
      return displayString;
    }


    public String getReplacementString() 
    {
      return replacementString;
    }
  }


  private static final char WHITESPACE_CHAR = ' ';
  private static final String WHITESPACE_STRING = " ";


  /**
   * Regular expression used to collapse all whitespace in a query string.
   */
  private static final String NORMALIZE_TO_SINGLE_WHITE_SPACE = "\\s+";


  private static HTML convertMe = GWT.isClient() ? new HTML() : null;


  /**
   * Associates substrings with words.
   */
  private final TreeSet tree = new TreeSet();


  /**
   * Associates individual words with candidates.
   */
  private HashMap> toCandidates = new HashMap>();


  /**
   * Associates candidates with their formatted suggestions.
   */
  private HashMap toRealSuggestions = new HashMap();




  private Response defaultResponse;




  /**
   * Adds a suggestion to the oracle. Each suggestion must be plain text.
   * 
   * @param suggestion the suggestion
   */
  public void add(String suggestion) 
  {
    String candidate = normalizeSuggestion(suggestion);
    // candidates --> real suggestions.
    toRealSuggestions.put(candidate, suggestion);
    tree.add(candidate);
    Set l = toCandidates.get(candidate);
    if (l == null) 
    {
       l = new HashSet();
       toCandidates.put(candidate, l);
    }
    l.add(candidate);    
  }


  /**
   * Adds all suggestions specified. Each suggestion must be plain text.
   * 
   * @param collection the collection
   */
  public final void addAll(Collection collection) 
  {
    for (String suggestion : collection) 
    {
      add(suggestion);
    }
  }


  /**
   * Removes all of the suggestions from the oracle.
   */
  public void clear() 
  {
    tree.clear();
    toCandidates.clear();
    toRealSuggestions.clear();
  }


  @Override
  public boolean isDisplayStringHTML() 
  {
    return true;
  }


  @Override
  public void requestDefaultSuggestions(Request request, Callback callback) 
  {
    if (defaultResponse != null) 
    {
      callback.onSuggestionsReady(request, defaultResponse);
    } 
    else 
    {
      super.requestDefaultSuggestions(request, callback);
    }
  }


  @Override
  public void requestSuggestions(Request request, Callback callback) 
  {
    final List suggestions = computeItemsFor(
        request.getQuery(), request.getLimit());
    Response response = new Response(suggestions);
    callback.onSuggestionsReady(request, response);
  }


  /**
   * Sets the default suggestion collection.
   * 
   * @param suggestionList the default list of suggestions
   */
  public void setDefaultSuggestions(Collection suggestionList) 
  {
    this.defaultResponse = new Response(suggestionList);
  }


  /**
   * A convenience method to set default suggestions using plain text strings.
   * 
   * Note to use this method each default suggestion must be plain text.
   * 
   * @param suggestionList the default list of suggestions
   */
  public final void setDefaultSuggestionsFromText(
      Collection suggestionList) 
  {
    Collection accum = new ArrayList();
    for (String candidate : suggestionList) 
    {
      accum.add(createSuggestion(candidate, candidate));
    }
    setDefaultSuggestions(accum);
  }


  /**
   * Creates the suggestion based on the given replacement and display strings.
   * 
   * @param replacementString the string to enter into the SuggestBox's text box
   *          if the suggestion is chosen
   * @param displayString the display string
   * 
   * @return the suggestion created
   */
  protected AdvSingleWordSuggestion createSuggestion(String replacementString,
      String displayString) 
  {
    return new AdvSingleWordSuggestion(replacementString, displayString);
  }


  String escapeText(String escapeMe) 
  {
    convertMe.setText(escapeMe);
    String escaped = convertMe.getHTML();
    return escaped;
  }


  /**
   * Compute the suggestions that are matches for a given query.
   * 
   * @param query search string
   * @param limit limit
   * @return matching suggestions
   */
  private List computeItemsFor(String query, int limit) 
  {
    query = normalizeSearch(query);


    // Get candidates from search words.
    List candidates = createCandidatesFromSearch(query, limit);


    // Convert candidates to suggestions.
    return convertToFormattedSuggestions(query, candidates);
  }


  /**
   * Returns real suggestions with the given query in strong html
   * font.
   * 
   * @param query query string
   * @param candidates candidates
   * @return real suggestions
   */
  private List convertToFormattedSuggestions(String query,
      List candidates) 
   {
    List suggestions = new ArrayList();


    for (int i = 0; i < candidates.size(); i++) 
    {
      String candidate = candidates.get(i);
      int index = 0;
      int cursor = 0;
      // Use real suggestion for assembly.
      String formattedSuggestion = toRealSuggestions.get(candidate);


      // Create strong search string.
      StringBuffer accum = new StringBuffer();


      while (true) 
      {
        index = candidate.indexOf(query, index);
        if (index == -1) 
        {
          break;
        }
        int endIndex = index + query.length();
        if (index == 0) 
        {
          String part1 = escapeText(formattedSuggestion.substring(cursor, index));
          String part2 = escapeText(formattedSuggestion.substring(index,
              endIndex));
          cursor = endIndex;
          accum.append(part1).append("").append(part2).append(
              "");
        }
        index = endIndex;
      }


      // Check to make sure the search was found in the string.
      if (cursor == 0) 
      {
        continue;
      }


      // Finish creating the formatted string.
      String end = escapeText(formattedSuggestion.substring(cursor));
      
      // After "~", we will have the EmpID; so adding some markup to it.
      /*String[] larySugg = end.split("~");
      end = larySugg[0] + "" + larySugg[1] + "";
      */
      accum.append(end);
      AdvSingleWordSuggestion suggestion = createSuggestion(formattedSuggestion,
          accum.toString());
      suggestions.add(suggestion);      
    }
    return suggestions;
  }


  /**
   * Find the sorted list of candidates that are matches for the given query.
   */
  private List createCandidatesFromSearch(String query, int limit)   
  {
    ArrayList candidates = new ArrayList();


    if (query.length() == 0) 
    {
      return candidates;
    }
    HashSet candidateSet = null;


      // Find the set of candidates that are associated with all the
      // searchWords.
      HashSet thisWordChoices = createCandidatesFromWord(query);
      if (candidateSet == null) {
        candidateSet = thisWordChoices;
      } 
      else 
      {
        candidateSet.retainAll(thisWordChoices);
      }
    
    if (candidateSet != null) {
      candidates.addAll(candidateSet);
      Collections.sort(candidates);
      // Respect limit for number of choices.
      for (int i = candidates.size() - 1; i > limit; i--) 
      {
        candidates.remove(i);
      }
    }
    return candidates;
  }


  /**
   * Creates a set of potential candidates that match the given query.
   * 
   * @param query query string
   * @return possible candidates
   */
  private HashSet createCandidatesFromWord(String query) 
  {
    HashSet candidateSet = new HashSet();
    ArrayList words = new ArrayList();
    Iterator loItr = tree.iterator(); 
    while(loItr.hasNext()) 
    {
        String element = loItr.next();
        if(element.contains(query))
        {
           words.add(element);
        }
    } 
    
    if (words != null) 
    {
      // Find all candidates that contain the given word the search is a
      // subset of.
      for (int i = 0; i < words.size(); i++) 
      {
        Collection belongsTo = toCandidates.get(words.get(i));
        if (belongsTo != null) 
        {
          candidateSet.addAll(belongsTo);
        }
      }
    }
    return candidateSet;
  }


  /**
   * Normalize the search key by making it lower case, removing multiple spaces,
   * apply whitespace masks, and make it lower case.
   */
  private String normalizeSearch(String search) 
  {
    // Use the same whitespace masks and case normalization for the search
    // string as was used with the candidate values.
    search = normalizeSuggestion(search);


    // Remove all excess whitespace from the search string.
    search = search.replaceAll(NORMALIZE_TO_SINGLE_WHITE_SPACE,
        WHITESPACE_STRING);


    return search.trim();
  }


  /**
   * Takes the formatted suggestion, makes it lower case and blanks out any
   * existing whitespace for searching.
   */
  private String normalizeSuggestion(String formattedSuggestion) 
  {
    // Formatted suggestions should already have normalized whitespace. So we
    // can skip that step.


    // Lower case suggestion.
    formattedSuggestion = formattedSuggestion.toLowerCase();


    // Apply whitespace.


    return formattedSuggestion;
  }
}

No comments:

Post a Comment