| /* |
| * 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; |
| }-*/; |
| } |