All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.htmlunit.corejs.javascript.Hashtable Maven / Gradle / Ivy

/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.htmlunit.corejs.javascript;

import java.io.Serializable;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * This generic hash table class is used by Set and Map. It uses a standard HashMap for storing keys
 * and values so that we can handle lots of hash collisions if necessary, and a doubly-linked list
 * to support the iterator capability.
 *
 * 

This second one is important because JavaScript handling of the iterator is completely * different from the way that Java does it. In Java an attempt to modify a collection on a HashMap * or LinkedHashMap while iterating through it (except by using the "remove" method on the Iterator * object itself) results in a ConcurrentModificationException. JavaScript Maps and Sets explicitly * allow the collection to be modified, or even cleared completely, while iterators exist, and even * lets an iterator keep on iterating on a collection that was empty when it was created.. */ public class Hashtable implements Serializable, Iterable { private static final long serialVersionUID = -7151554912419543747L; private final HashMap map = new HashMap<>(); private Entry first = null; private Entry last = null; /** * One entry in the hash table. Override equals and hashcode because this is another area in * which JavaScript and Java differ. This entry also becomes a node in the linked list. */ public static final class Entry implements Serializable { private static final long serialVersionUID = 4086572107122965503L; protected Object key; protected Object value; protected boolean deleted; protected Entry next; protected Entry prev; private final int hashCode; Entry() { hashCode = 0; } Entry(Object k, Object value) { if (k instanceof Number) { if (k instanceof Double || k instanceof BigInteger) { // BigInteger needs to retain its own type, due to // "If Type(x) is different from Type(y), return false." in // ecma262/multipage/abstract-operations.html#sec-samevaluezero key = k; } else { // Hash comparison won't work if we don't do this key = Double.valueOf(((Number) k).doubleValue()); } } else if (k instanceof ConsString) { key = k.toString(); } else { key = k; } if (key == null) { hashCode = 0; } else if (k.equals(ScriptRuntime.negativeZeroObj)) { hashCode = 0; } else { hashCode = key.hashCode(); } this.value = value; } public Object key() { return key; } public Object value() { return value; } /** Zero out key and value and return old value. */ void clear() { key = Undefined.instance; value = Undefined.instance; deleted = true; } @Override public int hashCode() { return hashCode; } @Override public boolean equals(Object o) { if (o == null) { return false; } try { return ScriptRuntime.sameZero(key, ((Entry) o).key); } catch (ClassCastException cce) { return false; } } } private static Entry makeDummy() { final Entry d = new Entry(); d.clear(); return d; } public int size() { return map.size(); } public void put(Object key, Object value) { final Entry nv = new Entry(key, value); if (!map.containsKey(nv)) { // New value -- insert to end of doubly-linked list map.put(nv, nv); if (first == null) { first = last = nv; } else { last.next = nv; nv.prev = last; last = nv; } } else { // Update the existing value and keep it in the same place in the list map.get(nv).value = value; } } /** * @deprecated use getEntry(Object key) instead because this returns null if the entry was not * found or the value of the entry is null */ public Object get(Object key) { final Entry e = new Entry(key, null); final Entry v = map.get(e); if (v == null) { return null; } return v.value; } public Entry getEntry(Object key) { final Entry e = new Entry(key, null); return map.get(e); } public boolean has(Object key) { final Entry e = new Entry(key, null); return map.containsKey(e); } /** * @deprecated use deleteEntry(Object key) instead because this returns null if the entry was * not found or the value of the entry is null */ public Object delete(Object key) { final Entry e = new Entry(key, null); final Entry v = map.remove(e); if (v == null) { return null; } // To keep existing iterators moving forward as specified in EC262, // we will remove the "prev" pointers from the list but leave the "next" // pointers intact. Once we do that, then the only things pointing to // the deleted nodes are existing iterators. Once those are gone, then // these objects will be GCed. // This way, new iterators will not "see" the deleted elements, and // existing iterators will continue from wherever they left off to // continue iterating in insertion order. if (v == first) { if (v == last) { // Removing the only element. Leave it as a dummy or existing iterators // will never stop. v.clear(); v.prev = null; } else { first = v.next; first.prev = null; if (first.next != null) { first.next.prev = first; } } } else { final Entry prev = v.prev; prev.next = v.next; v.prev = null; if (v.next != null) { v.next.prev = prev; } else { assert (v == last); last = prev; } } // Still clear the node in case it is in the chain of some iterator final Object ret = v.value; v.clear(); return ret; } public boolean deleteEntry(Object key) { final Entry e = new Entry(key, null); final Entry v = map.remove(e); if (v == null) { return false; } // To keep existing iterators moving forward as specified in EC262, // we will remove the "prev" pointers from the list but leave the "next" // pointers intact. Once we do that, then the only things pointing to // the deleted nodes are existing iterators. Once those are gone, then // these objects will be GCed. // This way, new iterators will not "see" the deleted elements, and // existing iterators will continue from wherever they left off to // continue iterating in insertion order. if (v == first) { if (v == last) { // Removing the only element. Leave it as a dummy or existing iterators // will never stop. v.clear(); v.prev = null; } else { first = v.next; first.prev = null; if (first.next != null) { first.next.prev = first; } } } else { final Entry prev = v.prev; prev.next = v.next; v.prev = null; if (v.next != null) { v.next.prev = prev; } else { assert (v == last); last = prev; } } // Still clear the node in case it is in the chain of some iterator v.clear(); return true; } public void clear() { // Zero out all the entries so that existing iterators will skip them all Iterator it = iterator(); it.forEachRemaining(Entry::clear); // Replace the existing list with a dummy, and make it the last node // of the current list. If new nodes are added now, existing iterators // will drive forward right into the new list. If they are not, then // nothing is referencing the old list and it'll get GCed. if (first != null) { Entry dummy = makeDummy(); last.next = dummy; first = last = dummy; } // Now we can clear the actual hashtable! map.clear(); } @Override public Iterator iterator() { return new Iter(first); } // The iterator for this class works directly on the linked list so that it implements // the specified iteration behavior, which is very different from Java. private static final class Iter implements Iterator { private Entry pos; Iter(Entry start) { // Keep the logic simpler by having a dummy at the start Entry dummy = makeDummy(); dummy.next = start; this.pos = dummy; } private void skipDeleted() { // Skip forward past deleted elements, which could appear due to // "delete" or a "clear" operation after this iterator was created. // End up just before the next non-deleted node. while ((pos.next != null) && pos.next.deleted) { pos = pos.next; } } @Override public boolean hasNext() { skipDeleted(); return ((pos != null) && (pos.next != null)); } @Override public Entry next() { skipDeleted(); if ((pos == null) || (pos.next == null)) { throw new NoSuchElementException(); } final Entry e = pos.next; pos = pos.next; return e; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy