From 3dcf2a70a46c9b9ca1ef29460afbb3709b39ba0d Mon Sep 17 00:00:00 2001 From: Robert Zubeck Date: Tue, 20 Feb 2007 00:30:55 +0000 Subject: [PATCH] Renaming DictionaryService -> DictionaryManager, since a 'service' has a very specific meaning, and will be treated differently by wrapper generatin scripts. git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@199 c613c5cb-e716-0410-b11b-feb51c14d237 --- .../ezgame/server/DictionaryManager.java | 309 ++++++++++++++++++ 1 file changed, 309 insertions(+) create mode 100644 src/java/com/threerings/ezgame/server/DictionaryManager.java diff --git a/src/java/com/threerings/ezgame/server/DictionaryManager.java b/src/java/com/threerings/ezgame/server/DictionaryManager.java new file mode 100644 index 00000000..347dca7a --- /dev/null +++ b/src/java/com/threerings/ezgame/server/DictionaryManager.java @@ -0,0 +1,309 @@ +// +// $Id$ +// +// Vilya library - tools for developing networked games +// Copyright (C) 2002-2006 Three Rings Design, Inc., All Rights Reserved +// http://www.threerings.net/code/vilya/ +// +// This library is free software; you can redistribute it and/or modify it +// under the terms of the GNU Lesser General Public License as published +// by the Free Software Foundation; either version 2.1 of the License, or +// (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +package com.threerings.ezgame.server; + +import com.samskivert.util.Invoker; +import com.samskivert.util.ResultListener; + +import com.threerings.crowd.server.CrowdServer; + +import com.threerings.presents.client.InvocationService; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.File; +import java.lang.reflect.Array; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Random; +import java.util.Set; +import java.util.Vector; +import java.util.logging.Level; + +import static com.threerings.ezgame.server.Log.log; + +/** + * Manages loading and querying word dictionaries in multiple languages. + * + * NOTE: the service supports lazy loading of language files, but does not + * _unload_ them from memory, leading to increasing memory usage. + * + * NOTE: the dictionary service has not yet been tested with language + * files written in non-default character encodings. + */ +public class DictionaryManager +{ + /** + * Helper class, encapsulates a sorted array of word hashes, + * which can be used to look up the existence of a word. + */ + private class Dictionary + { + /** + * Constructor, loads up the word list and initializes storage. + * This naive version assumes language files are simple list of words, + * with one word per line. + */ + public Dictionary (File wordfile) + { + try + { + if (wordfile.exists() && wordfile.isFile() && wordfile.canRead()) + { + // File length will give us a rough starting point for the word array + long bytecount = wordfile.length(); + int approxWords = (int) (bytecount / 5); // just a heuristic, + _hashes.ensureCapacity (approxWords); // we'll trim the vector later + + // Read it line by line + BufferedReader reader = new BufferedReader (new FileReader (wordfile)); + String line = null; + while ((line = reader.readLine()) != null) + { + // Add the word to the dictionary + int hash = hashWord (line); + _hashes.add (hash); + + // Add each letter to a letter set + int len = line.length (); + for (int i = 0; i < len; i++) + { + char ch = line.charAt (i); + if (! _letters.containsKey (ch)) + { + _letters.put (ch, 1); + } + else + { + _letters.put (ch, _letters.get (ch) + 1); + } + } + + } + + // Trim and sort the vector + _hashes.trimToSize(); + Collections.sort (_hashes); + + log.log (Level.INFO, + "Loaded dictionary file " + wordfile.getName () + + " with " + _hashes.size () + " entries, " + + _letters.size () + " letters."); + + } + else + { + log.log (Level.WARNING, + "Could not access dictionary file " + wordfile.getAbsolutePath ()); + } + } + catch (Exception ex) + { + log.log (Level.WARNING, "Failed to load dictionary file", ex); + _hashes.clear(); // dump everything + } + } + + /** Checks if the specified word exists in the word list */ + public boolean contains (String word) + { + // Hash the word and check + int hash = hashWord (word); + int result = Collections.binarySearch (_hashes, hash); + return (result >= 0); + } + + /** Gets an array of random letters for the language, with uniform distribution. */ + public char[] randomLetters (int count) + { + Set letterSet = _letters.keySet(); + int letterCount = letterSet.size(); + Character [] letters = new Character [letterCount]; + letterSet.toArray (letters); + + char [] results = new char [count]; + + for (int i = 0; i < count; i++) + { + int r = _random.nextInt (letterCount); + char ch = letters[r]; + results[i] = ch; + } + + return results; + } + + + /** Hashes the word, for use in storage */ + private int hashWord (String word) + { + return word.toLowerCase().hashCode(); + } + + // PRIVATE STORAGE + + /** Sorted vector of word hashes */ + private Vector _hashes = new Vector (); + + /** Mapping from letters in this language to their total count */ + private HashMap _letters = new HashMap (); + + /** Random number generator */ + private Random _random = new Random (); + } + + + /** + * Creates the singleton instance of the dictionary service. + */ + public static void init (File dictionaryRoot) + { + _singleton = new DictionaryManager (dictionaryRoot); + } + + /** + * Get an instance of the dictionary service. + */ + public static DictionaryManager getInstance () + { + return _singleton; + } + + /** + * Protected constructor. + */ + protected DictionaryManager (File dictionaryRoot) + { + _dictionaryRoot = dictionaryRoot; + } + + /** + * Returns true if the language is known to be supported by the + * dictionary service (would it be better to return a whole list + * of supported languages instead?) + */ + public void isLanguageSupported ( + final String locale, final InvocationService.ResultListener listener) + { + // TODO: once we have file paths set up, change this to match + // against dictionary files + listener.requestProcessed (locale.toLowerCase().startsWith("en")); + } + + /** + * Retrieves a set of letters from a language definition file, + * and returns a random sampling of /count/ elements. + */ + public void getLetterSet ( + final String locale, final int count, final InvocationService.ResultListener listener) + { + // Create a new unit of work + Invoker.Unit work = new Invoker.Unit ("DictionaryManager.getLetterSet") { + public boolean invoke () + { + Dictionary dict = getDictionary (locale); + char[] chars = dict.randomLetters (count); + StringBuilder sb = new StringBuilder (); + for (char c : chars) + { + sb.append (c); + sb.append (','); + } + sb.deleteCharAt (sb.length() - 1); + + listener.requestProcessed (sb.toString()); + + return true; + } + }; + + // Do the work! + CrowdServer.invoker.postUnit (work); + } + + /** + * Checks if the specified word exists in the given language + */ + public void checkWord ( + final String locale, final String word, final InvocationService.ResultListener listener) + { + // Create a new unit of work + Invoker.Unit work = new Invoker.Unit ("DictionaryManager.checkWord") { + public boolean invoke () + { + Dictionary dict = getDictionary (locale); + boolean result = (dict != null && dict.contains (word)); + listener.requestProcessed (result); + + return true; + } + }; + + // ...and off we go. + CrowdServer.invoker.postUnit (work); + } + + + /** + * Retrieves the dictionary object for a given locale. + * Forces the dictionary file to be loaded, if it hasn't already. + */ + private Dictionary getDictionary (String locale) + { + locale = locale.toLowerCase (); + if (! _dictionaries.containsKey (locale)) + { + try + { + // Make a file name + String filename = locale + ".wordlist"; + File file = new File (_dictionaryRoot, filename); + _dictionaries.put (locale, new Dictionary (file)); + } + catch (Exception e) + { + log.log (Level.WARNING, "Failed to load language file", e); + } + } + + return _dictionaries.get (locale); + } + + + + + // PRIVATE VARIABLES + + /** Singleton instance pointer */ + private static DictionaryManager _singleton; + + /** Root directory where we find dictionary files */ + private File _dictionaryRoot; + + /** Map from locale name to Dictionary object */ + private HashMap _dictionaries = new HashMap (); + + + +}