/*
Copyright 1996-2008 Ariba, 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.
$Id: //ariba/platform/util/core/ariba/util/core/InternCharToString.java#5 $
*/
package ariba.util.core;
import ariba.util.log.Log;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
/**
This class implements an instance of a GrowOnlyHashtable with the
following properties:
-- implements a version of intern() that does not have the small
size limitations of the native intern() methods on at least hp and
nt.
-- unlike all current implementations of intern, it does not
require any synchronization for the usual case of a get()
-- exposes methods to intern a string and "intern" a char[]
and retrieve a string.
-- exposes a method to calculate the hashCode of a char[] as
though it were a string. (there is a varient for the sun and the
standard implementations of hashCode() The code dynamically
determines which to use)
This could be modified to allow a subrange of a char[] to be
interned.
Typically the static methods should be used, which will intern the
strings to a single shared table. Only create a new instance of
this class if for some reason you want a separate pool of strings.
@aribaapi private
*/
public class InternCharToString extends GrowOnlyHashtable
{
private static int hashFN = -1;
private static InternCharToString cache = new InternCharToString(1024);
/**
Private method that calculates the hash code for the char[]
<b>val</b> according to the java spec.
*/
private static final int normalHashCode (char[] val)
{
return MathUtil.normalHashCode(val);
}
/**
Private method that calculates the hash code for the char[]
<b>val</b> according to the sun's unique implementation which
they say will be standard in 1.2 (love those standards)
*/
private static final int sunHashCode (char[] val)
{
return MathUtil.sunHashCode(val);
}
/*
Method we are currently using to guarantee a stable hashcode
across all VMS over all time. Do not allow the behavior of
this to change.
*/
public static final int jdk12HashCode (String s)
{
return sunHashCode(s.toCharArray());
}
/**
Private method that calculates the hash code for the char[]
<b>val</b> using the appropriate hash function. Love those
standards)
*/
public static final int hashCodeForChars (char[] val)
{
if (hashFN == 0) {
return normalHashCode(val);
}
if (hashFN == 1) {
return sunHashCode(val);
}
// if things go terribly wrong, go the old route of
// creating a string just to find it's hash code...
String s = new String(val);
return s.hashCode();
}
// force proper typing by only exposing correct interfaces
/**
Public method that interns the specified String <b>s</b>. This
will not provide objects that compare pointer equals with the
native String.intern() method as that method is too slow and
can not accomidate moderate numbers of interned strings.
*/
public static String intern (String s)
{
return cache.privIntern(s);
}
/**
Public method that interns the specified char[] <b>chars</b>.
This method is identical to the previous method which uses a
String as an argument, except that it does not require the
caller to construct a temporary String for lookup.
*/
public static String intern (char[] chars)
{
return cache.privIntern(chars);
}
/**
Public method that interns the specified String <b>s</b>. This
will not provide objects that compare pointer equals with the
native String.intern() method as that method is too slow and
can not accomidate moderate numbers of interned strings.
*/
public String internUnshared (String s)
{
return privIntern(s);
}
/**
Public method that interns the specified char[] <b>chars</b>.
This method is identical to the previous method which uses a
String as an argument, except that it does not require the
caller to construct a temporary String for lookup.
*/
public String internUnshared (char[] chars)
{
return privIntern(chars);
}
/**
Private method for the implementation of intern on Object to
include both String and char[] in the same
implementation. This allows and bad calls to fail at compile
time rather than run time. The object <b>o</b> must be either
a String or a char[] which is enforced through exposure of
those public methods.
*/
private String privIntern (Object o)
{
String result = (String)get(o);
if (result != null) {
return result;
}
// if it is not there on the first pass, synchronize and
// check again to keep intern semantics
synchronized (this) {
result = (String)get(o);
if (result != null) {
return result;
}
String stringToInsert;
if (o instanceof String) {
stringToInsert = (String)o;
}
else {
stringToInsert = new String((char [])o);
}
put(stringToInsert, stringToInsert);
return(stringToInsert);
}
}
private InternCharToString ()
{
super();
Assert.that(false, "do not call default constructor it is private");
}
/**
Constructs an InternCharToString hashtable capable of holding
at least <b>initialCapacity</b> elements before needing to
grow. It also determines which is the correct hash function to
use from the two standards. Most uses of this class should just
call the static intern methods, which will make use of a single,
shared table of strings. Only use this constructor if you want
to make a separate pool of objects for some unusual reason.
*/
public InternCharToString (int initialCapacity)
{
super(initialCapacity);
// Try out a few hash functions on startup to figure out
// which hash function is actually used.
String fooString = "bar";
char []fooArray = new char[3];
fooString.getChars(0, 3, fooArray, 0);
if (fooString.hashCode() == normalHashCode(fooArray)) {
hashFN = 0;
}
if (fooString.hashCode() == sunHashCode(fooArray)) {
hashFN = 1;
}
if (hashFN < 0) {
hashFN = 100;
Log.util.warning(2798);
}
}
/**
Helper function that returns the appropriate hash code for the
object <b>o</b>. It is overridden to compute the hash value
for a char[] as though it were a String.
*/
protected int getHashValueForObject (Object o)
{
if (o instanceof char[]) {
return(hashCodeForChars((char [])o));
}
return o.hashCode();
}
/**
Helper function to determine if two objects are equal. This
method is overriden to allow a char[] to compare equals to a
String. It returns true if <b>obj1</b> and <b>obj2</b>
compare as equal.
*/
protected boolean objectsAreEqualEnough (Object obj1, Object obj2)
{
// obj2 is always a string
if (obj1 == obj2) {
return true;
}
if (obj1 instanceof char[]) {
return(StringUtil.charsEqualString((char [])obj1, (String)obj2));
}
return obj2.equals(obj1);
}
public static void main (String []args)
{
Thread []t = new Thread[20];
ThreadRunner []tr = new ThreadRunner[t.length];
for (int i=0; i<t.length; i++) {
tr[i]=new ThreadRunner();
t[i]=new Thread(tr[i]);
}
// start the threads as closely as possible
for (int i=0; i<t.length; i++) {
t[i].start();
}
for (int i=0; i<t.length; i++) {
try {
t[i].join();
}
catch (InterruptedException ex) {
Assert.that(false, "%s", SystemUtil.stackTrace(ex));
}
}
int totalWins=0;
int totalLines = tr[0].totalLines;
boolean success = true;
for (int i=0; i<t.length; i++) {
Fmt.F(SystemUtil.out(), "thread %s had %s wins\n",
Constants.getInteger(i),
Constants.getInteger(tr[i].winCount));
totalWins+=tr[i].winCount;
String []firstInternedStrings = tr[0].internedStrings;
String []currentInternedStrings = tr[i].internedStrings;
for (int j=0; j<totalLines; j++) {
if (firstInternedStrings[j]!=currentInternedStrings[j]) {
success = false;
Fmt.F(SystemUtil.out(),
"whoops, didn't work. array %s has string %s " +
"with hascode %s rather than %s with hascode %s\n",
Constants.getInteger(i), currentInternedStrings[j],
Constants.getInteger(
System.identityHashCode(currentInternedStrings[j])),
firstInternedStrings[j],
Constants.getInteger(
System.identityHashCode(firstInternedStrings[j])));
}
}
}
Fmt.F(SystemUtil.out(), "total lines = %s, total wins = %s\n",
Constants.getInteger(totalLines),
Constants.getInteger(totalWins));
Assert.that(totalLines == totalWins, "nope, didn't work right");
Assert.that(success, "mismatched lines");
SystemUtil.exit(0);
}
}
class ThreadRunner implements Runnable
{
public ThreadRunner ()
{
}
public String [] internedStrings = new String[100000];
public int winCount=0;
public int totalLines=0;
public void run ()
{
try {
InputStream istream = IOUtil.bufferedInputStream(new File("words"));
char []buf = new char[1024];
String line=null;
String internedData;
while ((line = IOUtil.readLine(istream, buf))!=null) {
internedData = InternCharToString.intern(line);
internedStrings[totalLines]=internedData;
if (internedData == line) {
// whoo hoo, I won getting a line into the table.
winCount++;
}
totalLines++;
}
}
catch (IOException ex) {
Assert.that(false, "%s", SystemUtil.stackTrace(ex));
}
}
}