package edu.vt.marian.search; import java.util.*; import java.io.*; import edu.vt.marian.common.*; import edu.vt.marian.search.FullIDTable; /** * Provides a hash table for FullIDs and Strings such that both can be * used as keys to the other. So, a FullID can map to a String, and * that String can reverse-map to the FullID. (I am presuming this * mapping is one-to-one.) *

* This class implicitly relies upon the hashCode method to be * defined in the object that is going to be used as a key object. * * @author Paul Mather * @version 0.95 * @see edu.vt.marian.search.FullIDTable * @see edu.vt.marian.common.FullID * @see edu.vt.marian.common.FullID#hashCode * @see java.util.Hashtable * @see edu.vt.marian.common.Debug */ public class FullIDStringTable extends FullIDTable { /** * Constant denoting the separator between classID and instanceID * in input files. */ private static final int FULLID_DELIMETER = ':'; /** * Constant denoting the default initial StringBuffer size * for toString method if no estimate is provided. */ private static final int DEFAULT_TOSTRING_ESTIMATE = 16; /** * Create an empty hash table for FullIDs and Strings */ public FullIDStringTable(Debug debug) { super(debug); } /** * Create an empty hash table for FullIDs and Strings specifying initial * capacity and load factor. * @param initialCapacity initial size of hash table * @param loadFactor occupancy threshold before growth needed * @param debug debugging object */ public FullIDStringTable(int initialCapacity, float loadFactor, Debug debug) { // We actually double the initialCapacity estimate, because this // hash table will actually store both forward and reverse mappings super(initialCapacity * 2, loadFactor, debug); } /** * Create an empty hash table for FullIDs and Strings specifying initial * capacity, but with default load factor. * @param initialCapacity initial size of hash table * @param debug debugging object */ public FullIDStringTable(int initialCapacity, Debug debug) { // We actually double the initialCapacity estimate, because this // hash table will actually store both forward and reverse mappings super(initialCapacity * 2, debug); } /** * Try to read a token from the StreamTokenizer. This method will * log to the debug object if an IOException occurs when trying * to read the next token, and avoids having endless try/catch * statements for this exception. * @param str StreamTokenizer to read next token from * @return true if a token was successfully read (i.e., no * IOException occurred) */ private boolean gotNextToken( StreamTokenizer str ) { try { str.nextToken(); } catch (IOException e1) { debug.dumpTrace("FullIDStringTable.[BR constructor]: error parsing FullID/String mappings"); return false; } // finally // { // debug.dumpTrace("FullIDStringTable.gotNextToken: read token type = " + str.ttype + " ('" + (char) str.ttype + "')"); // } return true; } /** * Return a new FullID object from the given BufferedReader. * One side effect of this is that the input position is advanced * past the fullID in the StreamTokenizer stream. * @param str StreamTokenizer from which FullID is read * @return FullID object, or null if an error occurred */ private FullID parseFullID( StreamTokenizer str ) { int classID, instanceID; // Try reading a classID if (gotNextToken( str )) { if (str.ttype != StreamTokenizer.TT_NUMBER) { debug.dumpTrace("FullIDStringTable.[BR constructor]: classID expected"); debug.dumpTrace("FullIDStringTable.[BR constructor]: (expected " + StreamTokenizer.TT_NUMBER + ")"); return null; } else { classID = (int) str.nval; // Expect to read ":" next if (gotNextToken( str )) { if (str.ttype != FULLID_DELIMETER) { debug.dumpTrace("FullIDStringTable.[BR constructor]: \":\" expected"); return null; } else { // Read an instanceID if (gotNextToken( str )) { if (str.ttype != StreamTokenizer.TT_NUMBER) { debug.dumpTrace("FullIDStringTable.[BR constructor]: instanceID expected"); return null; } else { instanceID = (int) str.nval; return new FullID(classID, instanceID, debug); } } } } } } return null; } /** * Create a FullID/String hash table from a file of initial * mappings. The input file consists of lines of FullID->String * mappings. Each mapping line is of the form: *

* classID:instanceID\tString\n *

* As each mapping is read, it is inserted into the newly created * hash table as both the forward and the reverse mapping. */ public FullIDStringTable(BufferedReader br, Debug debug) { // Initialise the hash table with default values super(debug); String inputLine; // A line of text from the mappings "file" int lineNumber = 1; // Number of line being read StreamTokenizer str; int token, classID, instanceID; FullID fullID; boolean doneReading = false; // See if a valid input stream was passed to constructor if (br == null) { debug.dumpTrace("FullIDStringTable.[BR constructor]: parameter stream is null. No initial mappings inserted"); return; } // Turn this BufferedReader into a tokeniser to make parsing easier str = new StreamTokenizer(br); str.resetSyntax(); str.eolIsSignificant(true); str.slashStarComments(false); str.slashSlashComments(false); // Now read the contents of the file and insert each entry // into the table do { String s = new String(""); // Enable special parsing of numbers because we want to read a FullID str.parseNumbers(); // Check for TT_EOF if (gotNextToken( str )) { if (str.ttype == StreamTokenizer.TT_EOF) { doneReading = true; break; } else // Push back token just read str.pushBack(); } // Read a FullID fullID = parseFullID( str ); if (fullID == null) return; // Cancel special interpretation of numbers now we've read FullID str.resetSyntax(); // Expect a TAB character if (gotNextToken( str )) { if (str.ttype != '\t') { debug.dumpTrace("FullIDStringTable.[BR constructor]: TAB character expected"); return; } } // Read string until we get to the end of a line do { if (gotNextToken( str )) { if (str.ttype != StreamTokenizer.TT_EOL) s += (char) str.ttype; } else return; } while (str.ttype != StreamTokenizer.TT_EOL); // We have a FullID and String, so add them to the hash table this.put( fullID, s ); this.put( s, fullID ); debug.dumpTrace("FullIDStringTable.[BR constructor]: added mapping " + fullID.toString() + " <-> \"" + s + "\""); } while (!doneReading); } /** * Test whether a given String is in the hash table. * @param string String object to test for existence in hash table * @return True if string is a key for something in the hash table; * false otherwise. */ public boolean isInTable( String string ) { return (this.get( string ) != null); } /** * Insert a FullID->String mapping into the hash table, along with * the reverse mapping. * @param fullID FullID key value * @param string String mapped to by FullID * @return Object if either the FullID or the String were already * in the hash table; null otherwise. * @see java.util.Hashtable#put */ public Object put( FullID fullID, String string ) { Object previousFullIDKey, previousStringKey; // Put the FullID->String mapping into the hash table previousFullIDKey = super.put( fullID, string ); // Put the String->FullID mapping into the hash table previousStringKey = super.put( string, fullID ); // If either previously acted as a key, return that; null otherwise if (previousFullIDKey != null) return previousFullIDKey; else return previousStringKey; } /** * Convert hash table to a printable string representation. * @return String containing object mappings * @see FullID#toString */ public String toString( int estimatedSize ) { Enumeration fullIDKeys; // All FullID keys Object key; // FullID or String key object from hash table FullID fullIDKey; // key cast to FullID StringBuffer stringFullIDHashTable ; boolean first = true; // Flag to denote this is the first mapping printed // Check that estimatedSize is reasonable if (estimatedSize < 0) { estimatedSize = DEFAULT_TOSTRING_ESTIMATE; debug.dumpTrace("FullIDStringTable.toStream: negative estimate passed"); } // Make a StringBuffer, initially of estimatedSize stringFullIDHashTable = new StringBuffer( estimatedSize ); // Initialise it with the opening brace stringFullIDHashTable.append('{'); for (fullIDKeys = this.keys(); fullIDKeys.hasMoreElements();) { // Get the next key and the value to which it hashes. key = fullIDKeys.nextElement(); if (key instanceof FullID) { // We are only interested in FullID keys because // we only want to string-ify the forward, not // the reverse mapping. // Append key and hashed object to string representation. fullIDKey = (FullID) key; if (!first) { stringFullIDHashTable.append(','); } first = false; stringFullIDHashTable.append(fullIDKey.toString()); stringFullIDHashTable.append('-'); stringFullIDHashTable.append('>'); stringFullIDHashTable.append((String) this.get( key )); } } stringFullIDHashTable.append('}'); return stringFullIDHashTable.toString(); } /** * Convert hash table to a printable string representation. * @return String containing object mappings * @see FullID#toString */ public String toString() { return toString( DEFAULT_TOSTRING_ESTIMATE ); } /** * Dump the FullIDTable to an output stream (file). This method * invokes the toStream() method of FullID in writing both the * key and hashed value to the output stream. The format of the * dump is (key, object)*. * @param pw output stream to which hash table should be dumped * @return OK if table dumped successfully; NULL_STREAM if pw was null. */ public int toStream(PrintWriter pw) { Enumeration fullIDKeys; // All FullID keys Object key; // FullID or String key object from hash table String hashed; // String hashed to by FullID key FullID fullIDKey; // FullID key for (fullIDKeys = this.keys(); fullIDKeys.hasMoreElements();) { // Get the next key key = fullIDKeys.nextElement(); if (key instanceof FullID) { // Since this is a FullID key, it hashes to a String hashed = (String) this.get( key ); // Output key and hashed object to output stream. fullIDKey = (FullID) key; fullIDKey.toStream( pw ); pw.println( 1 ); if (hashed != null) pw.println( hashed ); else { pw.println( "" ); debug.dumpTrace( "FullIDStringTable.toStream: FullID hashed to null value" ); } } } return OK; } }