/* * Copyright 2014 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 java.util; import com.google.gwt.core.client.JavaScriptObject; /** * A factory to create JavaScript Map instances. */ class InternalJsMapFactory { private static final JavaScriptObject jsMapCtor = getJsMapConstructor(); private static native JavaScriptObject getJsMapConstructor() /*-{ // Firefox 24 & 25 throws StopIteration to signal the end of iteration. function isCorrectIterationProtocol() { try { return new Map().entries().next().done; } catch(e) { return false; } } if (typeof Map === 'function' && Map.prototype.entries && isCorrectIterationProtocol()) { return Map; } else { return @InternalJsMapFactory::getJsMapPolyFill()(); } }-*/; public static native <V> InternalJsMap<V> newJsMap() /*-{ return new @InternalJsMapFactory::jsMapCtor; }-*/; /** * Returns a partial polyfill for Map that can handle String keys. * <p>Implementation notes: * <p>String keys are mapped to their values via a JS associative map. String keys could collide * with intrinsic properties (like watch, constructor). To avoid that; the polyfill uses * {@code Object.create(null)} so it doesn't inherit any properties. * <p>For legacy browsers where {@code Object.create} is not available or handling of * {@code __proto__} is broken, the polyfill is patched to prepend each key with a ':' while * storing. */ private static native JavaScriptObject getJsMapPolyFill() /*-{ function Stringmap() { this.obj = this.createObject(); }; Stringmap.prototype.createObject = function(key) { return Object.create(null); } Stringmap.prototype.get = function(key) { return this.obj[key]; }; Stringmap.prototype.set = function(key, value) { this.obj[key] = value; }; Stringmap.prototype['delete'] = function(key) { delete this.obj[key]; }; Stringmap.prototype.keys = function() { return Object.getOwnPropertyNames(this.obj); }; Stringmap.prototype.entries = function() { var keys = this.keys(); var map = this; var nextIndex = 0; return { next: function() { if (nextIndex >= keys.length) return {done: true}; var key = keys[nextIndex++]; return {value: [key, map.get(key)], done: false}; } }; }; if (!@InternalJsMapFactory::canHandleObjectCreateAndProto()()) { // Patches the polyfill to drop Object.create(null) and prefix each key with ':" so it will // not interfere with intrinsic fields. Stringmap.prototype.createObject = function() { return {}; }; Stringmap.prototype.get = function(key) { return this.obj[':' + key]; }; Stringmap.prototype.set = function(key, value) { this.obj[':' + key] = value; }; Stringmap.prototype['delete'] = function(key) { delete this.obj[':' + key]; }; Stringmap.prototype.keys = function() { var result = []; for (var key in this.obj) { // char code for ':' is 58 if (key.charCodeAt(0) == 58) { result.push(key.substring(1)); } } return result; }; } return Stringmap; }-*/; /** * Return {@code true} if the browser is modern enough to handle Object.create and also properly * handles '__proto__' field with Object.create(null). (Safari 5, Android, old Firefox) */ private static native boolean canHandleObjectCreateAndProto() /*-{ if (!Object.create || !Object.getOwnPropertyNames) { return false; } var protoField = "__proto__"; var map = Object.create(null); if (map[protoField] !== undefined) { return false; } var keys = Object.getOwnPropertyNames(map); if (keys.length != 0) { return false; } map[protoField] = 42; if (map[protoField] !== 42) { return false; } // For old Firefox version who doesn't have native Map. See the Firefox bug: // https://bugzilla.mozilla.org/show_bug.cgi?id=837630 if (Object.getOwnPropertyNames(map).length == 0) { return false; } // Looks like the browser has a workable handling of proto field. return true; }-*/; private InternalJsMapFactory() { // Hides the constructor. } }