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

org.mozilla.javascript.EmbeddedSlotMap Maven / Gradle / Ivy

Go to download

Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically embedded into Java applications to provide scripting to end users.

There is a newer version: 1.7.15
Show newest version
/* -*- 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.mozilla.javascript;

/*
 * This class implements the SlotMap interface using an embedded hash table. This hash table
 * has the minimum overhead needed to get the job done. In particular, it embeds the Slot
 * directly into the hash table rather than creating an intermediate object, which seems
 * to have a measurable performance benefit.
 */

import java.util.Iterator;
import java.util.NoSuchElementException;

import org.mozilla.javascript.ScriptableObject.SlotAccess;

public class EmbeddedSlotMap
    implements SlotMap {

    private ScriptableObject.Slot[] slots;

    // gateways into the definition-order linked list of slots
    private ScriptableObject.Slot firstAdded;
    private ScriptableObject.Slot lastAdded;

    private int count;

    // initial slot array size, must be a power of 2
    private static final int INITIAL_SLOT_SIZE = 4;

    private static final class Iter
        implements Iterator
    {
        private ScriptableObject.Slot next;

        Iter(ScriptableObject.Slot slot) {
            next = slot;
        }

        @Override
        public boolean hasNext() {
            return next != null;
        }

        @Override
        public ScriptableObject.Slot next() {
            ScriptableObject.Slot ret = next;
            if (ret == null) {
                throw new NoSuchElementException();
            }
            next = next.orderedNext;
            return ret;
        }
    }

    public EmbeddedSlotMap()
    {
    }

    @Override
    public int size() {
        return count;
    }

    @Override
    public boolean isEmpty() {
        return count == 0;
    }

    @Override
    public Iterator iterator() {
        return new Iter(firstAdded);
    }

    /**
     * Locate the slot with the given name or index.
     */
    @Override
    public ScriptableObject.Slot query(Object key, int index)
    {
        if (slots == null) {
            return null;
        }

        final int indexOrHash = (key != null ? key.hashCode() : index);
        final int slotIndex = getSlotIndex(slots.length, indexOrHash);
        for (ScriptableObject.Slot slot = slots[slotIndex];
            slot != null;
            slot = slot.next) {
            Object skey = slot.name;
            if (indexOrHash == slot.indexOrHash &&
                (skey == key ||
                    (key != null && key.equals(skey)))) {
                return slot;
            }
        }
        return null;
    }

    /**
     * Locate the slot with given name or index. Depending on the accessType
     * parameter and the current slot status, a new slot may be allocated.
     *
     * @param key either a String or a Symbol object that identifies the property
     * @param index index or 0 if slot holds property name.
     */
    @Override
    public ScriptableObject.Slot get(Object key, int index, ScriptableObject.SlotAccess accessType)
    {
        if (slots == null && accessType == SlotAccess.QUERY) {
            return null;
        }

        final int indexOrHash = (key != null ? key.hashCode() : index);
        ScriptableObject.Slot slot = null;

        if (slots != null) {
            final int slotIndex = getSlotIndex(slots.length, indexOrHash);
            for (slot = slots[slotIndex];
                 slot != null;
                 slot = slot.next) {
                Object skey = slot.name;
                if (indexOrHash == slot.indexOrHash &&
                        (skey == key ||
                                (key != null && key.equals(skey)))) {
                    break;
                }
            }
            switch (accessType) {
                case QUERY:
                    return slot;
                case MODIFY:
                case MODIFY_CONST:
                    if (slot != null) {
                        return slot;
                    }
                    break;
                case MODIFY_GETTER_SETTER:
                    if (slot instanceof ScriptableObject.GetterSlot) {
                        return slot;
                    }
                    break;
                case CONVERT_ACCESSOR_TO_DATA:
                    if ( !(slot instanceof ScriptableObject.GetterSlot) ) {
                        return slot;
                    }
                    break;
            }
        }

        // A new slot has to be inserted or the old has to be replaced
        // by GetterSlot. Time to synchronize.
        return createSlot(key, indexOrHash, accessType, slot);
    }

    private ScriptableObject.Slot createSlot(Object key, int indexOrHash,
        ScriptableObject.SlotAccess accessType, ScriptableObject.Slot existingSlot) {
        if (count == 0) {
            // Always throw away old slots if any on empty insert.
            slots = new ScriptableObject.Slot[INITIAL_SLOT_SIZE];
        } else if (existingSlot != null) {
            // Re-search the slot list because it is a singly-linked list to find
            // where to replace it with a new object if necessary
            final int insertPos = getSlotIndex(slots.length, indexOrHash);
            ScriptableObject.Slot prev = slots[insertPos];
            ScriptableObject.Slot slot = prev;
            while (slot != null) {
                if (slot.indexOrHash == indexOrHash &&
                    (slot.name == key ||
                        (key != null && key.equals(slot.name)))) {
                    break;
                }
                prev = slot;
                slot = slot.next;
            }

            if (slot != null) {
                // A slot with same name/index already exists. This means that
                // a slot is being redefined from a value to a getter slot or
                // vice versa, or it could be a race in application code.
                // Check if we need to replace the slot depending on the
                // accessType flag and return the appropriate slot instance.
                ScriptableObject.Slot newSlot;

                if (accessType == SlotAccess.MODIFY_GETTER_SETTER
                    && !(slot instanceof ScriptableObject.GetterSlot)) {
                    newSlot = new ScriptableObject.GetterSlot(key, indexOrHash,
                        slot.getAttributes());
                } else if (accessType == SlotAccess.CONVERT_ACCESSOR_TO_DATA
                    && (slot instanceof ScriptableObject.GetterSlot)) {
                    newSlot = new ScriptableObject.Slot(key, indexOrHash, slot.getAttributes());
                } else if (accessType == SlotAccess.MODIFY_CONST) {
                    return null;
                } else {
                    return slot;
                }

                newSlot.value = slot.value;
                newSlot.next = slot.next;

                // Replace new slot in linked list, keeping same order
                if (slot == firstAdded) {
                    firstAdded = newSlot;
                } else {
                    ScriptableObject.Slot ps = firstAdded;
                    while ((ps != null) && (ps.orderedNext != slot)) {
                        ps = ps.orderedNext;
                    }
                    if (ps != null) {
                        ps.orderedNext = newSlot;
                    }
                }
                newSlot.orderedNext = slot.orderedNext;
                if (slot == lastAdded) {
                    lastAdded = newSlot;
                }

                // add new slot to hash table
                if (prev == slot) {
                    slots[insertPos] = newSlot;
                } else {
                    prev.next = newSlot;
                }
                return newSlot;
            }
        }

        // If we get here, then we are going to insert a new slot
        // Check if the table is not too full before inserting.
        if (4 * (count + 1) > 3 * slots.length) {
            // table size must be a power of 2 -- always grow by x2!
            ScriptableObject.Slot[] newSlots = new ScriptableObject.Slot[slots.length * 2];
            copyTable(slots, newSlots);
            slots = newSlots;
        }

        ScriptableObject.Slot newSlot = (accessType == SlotAccess.MODIFY_GETTER_SETTER
                ? new ScriptableObject.GetterSlot(key, indexOrHash, 0)
                : new ScriptableObject.Slot(key, indexOrHash, 0));
        if (accessType == SlotAccess.MODIFY_CONST) {
            newSlot.setAttributes(ScriptableObject.CONST);
        }
        insertNewSlot(newSlot);
        return newSlot;
    }

    @Override
    public void addSlot(ScriptableObject.Slot newSlot) {
        if (slots == null) {
            slots = new ScriptableObject.Slot[INITIAL_SLOT_SIZE];
        }
        insertNewSlot(newSlot);
    }

    private void insertNewSlot(ScriptableObject.Slot newSlot) {
        ++count;
        // add new slot to linked list
        if (lastAdded != null) {
            lastAdded.orderedNext = newSlot;
        }
        if (firstAdded == null) {
            firstAdded = newSlot;
        }
        lastAdded = newSlot;
        // add new slot to hash table, return it
        addKnownAbsentSlot(slots, newSlot);
    }

    @Override
    public void remove(Object key, int index) {
        int indexOrHash = (key != null ? key.hashCode() : index);

        if (count != 0) {
            final int slotIndex = getSlotIndex(slots.length, indexOrHash);
            ScriptableObject.Slot prev = slots[slotIndex];
            ScriptableObject.Slot slot = prev;
            while (slot != null) {
                if (slot.indexOrHash == indexOrHash &&
                        (slot.name == key ||
                                (key != null && key.equals(slot.name))))
                {
                    break;
                }
                prev = slot;
                slot = slot.next;
            }
            if (slot != null) {
                // non-configurable
                if ((slot.getAttributes() & ScriptableObject.PERMANENT) != 0) {
                    Context cx = Context.getContext();
                    if (cx.isStrictMode()) {
                        throw ScriptRuntime.typeError1("msg.delete.property.with.configurable.false", key);
                    }
                    return;
                }
                count--;
                // remove slot from hash table
                if (prev == slot) {
                    slots[slotIndex] = slot.next;
                } else {
                    prev.next = slot.next;
                }

                // remove from ordered list. Previously this was done lazily in
                // getIds() but delete is an infrequent operation so O(n)
                // should be ok

                // ordered list always uses the actual slot
                if (slot == firstAdded) {
                    prev = null;
                    firstAdded = slot.orderedNext;
                } else {
                    prev = firstAdded;
                    while (prev.orderedNext != slot) {
                        prev = prev.orderedNext;
                    }
                    prev.orderedNext = slot.orderedNext;
                }
                if (slot == lastAdded) {
                    lastAdded = prev;
                }
            }
        }
    }

    private void copyTable(ScriptableObject.Slot[] oldSlots, ScriptableObject.Slot[] newSlots)
    {
        for (ScriptableObject.Slot slot : oldSlots) {
            while (slot != null) {
                ScriptableObject.Slot nextSlot = slot.next;
                slot.next = null;
                addKnownAbsentSlot(newSlots, slot);
                slot = nextSlot;
            }
        }
    }

    /**
     * Add slot with keys that are known to absent from the table.
     * This is an optimization to use when inserting into empty table,
     * after table growth or during deserialization.
     */
    private void addKnownAbsentSlot(ScriptableObject.Slot[] addSlots, ScriptableObject.Slot slot)
    {
        final int insertPos = getSlotIndex(addSlots.length, slot.indexOrHash);
        ScriptableObject.Slot old = addSlots[insertPos];
        addSlots[insertPos] = slot;
        slot.next = old;
    }

    private static int getSlotIndex(int tableSize, int indexOrHash)
    {
        // This is a Java trick to efficiently "mod" the hash code by the table size.
        // It only works if the table size is a power of 2.
        // The performance improvement is measurable.
        return indexOrHash & (tableSize - 1);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy