blob: d472de9188f05a91d47d272eaafdd6a38e729e6c [file] [log] [blame]
/*
* Copyright 2015 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 javaemul.internal;
import static javaemul.internal.Coercions.ensureInt;
/**
* Hashcode caching for strings.
*/
class StringHashCache {
/**
* The "old" cache; it will be dumped when front is full.
*/
private static Object back = createNativeObject();
/**
* Tracks the number of entries in front.
*/
private static int count = 0;
/**
* The "new" cache; it will become back when it becomes full.
*/
private static Object front = createNativeObject();
/**
* Pulled this number out of thin air.
*/
private static final int MAX_CACHE = 256;
public static int getHashCode(String str) {
// Accesses must to be prefixed with ':' to prevent conflict with built-in
// JavaScript properties.
String key = ":" + str;
// Check the front store.
Object result = getProperty(front, key);
if (!JsUtils.isUndefined(result)) {
return unsafeCastToInt(result);
}
// Check the back store.
result = getProperty(back, key);
int hashCode = JsUtils.isUndefined(result) ? compute(str) : unsafeCastToInt(result);
// Increment can trigger the swap/flush; call after checking back but
// before writing to front.
increment();
JsUtils.setIntProperty(front, key, hashCode);
return hashCode;
}
private static int compute(String str) {
int hashCode = 0;
int n = str.length();
int nBatch = n - 4;
int i = 0;
// Process batches of 4 characters at a time and add them to the hash coercing to 32 bits
while (i < nBatch) {
hashCode = str.charAt(i + 3)
+ 31 * (str.charAt(i + 2)
+ 31 * (str.charAt(i + 1)
+ 31 * (str.charAt(i)
+ 31 * hashCode)));
hashCode = ensureInt(hashCode); // make sure we don't overflow
i += 4;
}
// Now process the leftovers
while (i < n) {
hashCode = hashCode * 31 + str.charAt(i++);
}
hashCode = ensureInt(hashCode); // make sure we don't overflow
return hashCode;
}
private static void increment() {
if (count == MAX_CACHE) {
back = front;
front = createNativeObject();
count = 0;
}
++count;
}
private static native Object getProperty(Object map, String key) /*-{
return map[key];
}-*/;
private static native Object createNativeObject() /*-{
return {};
}-*/;
private static native int unsafeCastToInt(Object o) /*-{
return o;
}-*/;
}