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( "