| /* |
| * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.nashorn.internal.runtime; |
| |
| import static jdk.nashorn.internal.runtime.PropertyHashMap.EMPTY_HASHMAP; |
| import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex; |
| import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; |
| |
| import java.lang.invoke.SwitchPoint; |
| import java.lang.ref.WeakReference; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.WeakHashMap; |
| |
| /** |
| * Map of object properties. The PropertyMap is the "template" for JavaScript object |
| * layouts. It contains a map with prototype names as keys and {@link Property} instances |
| * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor |
| * to form the seed map for the ScriptObject. |
| * <p> |
| * All property maps are immutable. If a property is added, modified or removed, the mutator |
| * will return a new map. |
| */ |
| public final class PropertyMap implements Iterable<Object>, PropertyListener { |
| /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */ |
| public static final int NOT_EXTENSIBLE = 0b0000_0001; |
| /** Does this map contain valid array keys? */ |
| public static final int CONTAINS_ARRAY_KEYS = 0b0000_0010; |
| /** This mask is used to preserve certain flags when cloning the PropertyMap. Others should not be copied */ |
| private static final int CLONEABLE_FLAGS_MASK = 0b0000_1111; |
| /** Has a listener been added to this property map. This flag is not copied when cloning a map. See {@link PropertyListener} */ |
| public static final int IS_LISTENER_ADDED = 0b0001_0000; |
| /** Is this process wide "shared" map?. This flag is not copied when cloning a map */ |
| public static final int IS_SHARED = 0b0010_0000; |
| |
| /** Map status flags. */ |
| private int flags; |
| |
| /** Map of properties. */ |
| private final PropertyHashMap properties; |
| |
| /** Number of fields in use. */ |
| private int fieldCount; |
| |
| /** Number of fields available. */ |
| private int fieldMaximum; |
| |
| /** Length of spill in use. */ |
| private int spillLength; |
| |
| /** {@link SwitchPoint}s for gets on inherited properties. */ |
| private Map<String, SwitchPoint> protoGetSwitches; |
| |
| /** History of maps, used to limit map duplication. */ |
| private HashMap<Property, PropertyMap> history; |
| |
| /** History of prototypes, used to limit map duplication. */ |
| private WeakHashMap<ScriptObject, WeakReference<PropertyMap>> protoHistory; |
| |
| /** Cache for hashCode */ |
| private int hashCode; |
| |
| /** |
| * Constructor. |
| * |
| * @param properties A {@link PropertyHashMap} with initial contents. |
| * @param fieldCount Number of fields in use. |
| * @param fieldMaximum Number of fields available. |
| * @param spillLength Number of spill slots used. |
| * @param containsArrayKeys True if properties contain numeric keys |
| */ |
| private PropertyMap(final PropertyHashMap properties, final int fieldCount, final int fieldMaximum, final int spillLength, final boolean containsArrayKeys) { |
| this.properties = properties; |
| this.fieldCount = fieldCount; |
| this.fieldMaximum = fieldMaximum; |
| this.spillLength = spillLength; |
| if (containsArrayKeys) { |
| setContainsArrayKeys(); |
| } |
| |
| if (Context.DEBUG) { |
| count++; |
| } |
| } |
| |
| /** |
| * Cloning constructor. |
| * |
| * @param propertyMap Existing property map. |
| * @param properties A {@link PropertyHashMap} with a new set of properties. |
| */ |
| private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties) { |
| this.properties = properties; |
| this.flags = propertyMap.getClonedFlags(); |
| this.spillLength = propertyMap.spillLength; |
| this.fieldCount = propertyMap.fieldCount; |
| this.fieldMaximum = propertyMap.fieldMaximum; |
| |
| if (Context.DEBUG) { |
| count++; |
| clonedCount++; |
| } |
| } |
| |
| /** |
| * Cloning constructor. |
| * |
| * @param propertyMap Existing property map. |
| */ |
| private PropertyMap(final PropertyMap propertyMap) { |
| this(propertyMap, propertyMap.properties); |
| } |
| |
| /** |
| * Duplicates this PropertyMap instance. This is used to duplicate 'shared' |
| * maps {@link PropertyMap} used as process wide singletons. Shared maps are |
| * duplicated for every global scope object. That way listeners, proto and property |
| * histories are scoped within a global scope. |
| * |
| * @return Duplicated {@link PropertyMap}. |
| */ |
| public PropertyMap duplicate() { |
| if (Context.DEBUG) { |
| duplicatedCount++; |
| } |
| return new PropertyMap(this.properties, 0, 0, 0, containsArrayKeys()); |
| } |
| |
| /** |
| * Public property map allocator. |
| * |
| * <p>It is the caller's responsibility to make sure that {@code properties} does not contain |
| * properties with keys that are valid array indices.</p> |
| * |
| * @param properties Collection of initial properties. |
| * @param fieldCount Number of fields in use. |
| * @param fieldMaximum Number of fields available. |
| * @param spillLength Number of used spill slots. |
| * @return New {@link PropertyMap}. |
| */ |
| public static PropertyMap newMap(final Collection<Property> properties, final int fieldCount, final int fieldMaximum, final int spillLength) { |
| PropertyHashMap newProperties = EMPTY_HASHMAP.immutableAdd(properties); |
| return new PropertyMap(newProperties, fieldCount, fieldMaximum, spillLength, false); |
| } |
| |
| /** |
| * Public property map allocator. Used by nasgen generated code. |
| * |
| * <p>It is the caller's responsibility to make sure that {@code properties} does not contain |
| * properties with keys that are valid array indices.</p> |
| * |
| * @param properties Collection of initial properties. |
| * @return New {@link PropertyMap}. |
| */ |
| public static PropertyMap newMap(final Collection<Property> properties) { |
| return (properties == null || properties.isEmpty())? newMap() : newMap(properties, 0, 0, 0); |
| } |
| |
| /** |
| * Return a sharable empty map. |
| * |
| * @return New empty {@link PropertyMap}. |
| */ |
| public static PropertyMap newMap() { |
| return new PropertyMap(EMPTY_HASHMAP, 0, 0, 0, false); |
| } |
| |
| /** |
| * Return number of properties in the map. |
| * |
| * @return Number of properties. |
| */ |
| public int size() { |
| return properties.size(); |
| } |
| |
| /** |
| * Return a SwitchPoint used to track changes of a property in a prototype. |
| * |
| * @param proto Object prototype. |
| * @param key {@link Property} key. |
| * |
| * @return A shared {@link SwitchPoint} for the property. |
| */ |
| public SwitchPoint getProtoGetSwitchPoint(final ScriptObject proto, final String key) { |
| assert !isShared() : "proto SwitchPoint from a shared PropertyMap"; |
| |
| if (proto == null) { |
| return null; |
| } |
| |
| if (protoGetSwitches == null) { |
| protoGetSwitches = new HashMap<>(); |
| if (! isListenerAdded()) { |
| proto.addPropertyListener(this); |
| setIsListenerAdded(); |
| } |
| } |
| |
| if (protoGetSwitches.containsKey(key)) { |
| return protoGetSwitches.get(key); |
| } |
| |
| final SwitchPoint switchPoint = new SwitchPoint(); |
| protoGetSwitches.put(key, switchPoint); |
| |
| return switchPoint; |
| } |
| |
| /** |
| * Indicate that a prototype property has changed. |
| * |
| * @param property {@link Property} to invalidate. |
| */ |
| private void invalidateProtoGetSwitchPoint(final Property property) { |
| assert !isShared() : "proto invalidation on a shared PropertyMap"; |
| |
| if (protoGetSwitches != null) { |
| final String key = property.getKey(); |
| final SwitchPoint sp = protoGetSwitches.get(key); |
| if (sp != null) { |
| protoGetSwitches.put(key, new SwitchPoint()); |
| if (Context.DEBUG) { |
| protoInvalidations++; |
| } |
| SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); |
| } |
| } |
| } |
| |
| /** |
| * Indicate that proto itself has changed in hierachy somewhere. |
| */ |
| private void invalidateAllProtoGetSwitchPoints() { |
| assert !isShared() : "proto invalidation on a shared PropertyMap"; |
| |
| if (protoGetSwitches != null) { |
| final Collection<SwitchPoint> sws = protoGetSwitches.values(); |
| SwitchPoint.invalidateAll(sws.toArray(new SwitchPoint[sws.size()])); |
| } |
| } |
| |
| /** |
| * Add a property to the map, re-binding its getters and setters, |
| * if available, to a given receiver. This is typically the global scope. See |
| * {@link ScriptObject#addBoundProperties(ScriptObject)} |
| * |
| * @param property {@link Property} being added. |
| * @param bindTo Object to bind to. |
| * |
| * @return New {@link PropertyMap} with {@link Property} added. |
| */ |
| PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) { |
| return addProperty(new AccessorProperty(property, bindTo)); |
| } |
| |
| /** |
| * Add a property to the map. Cloning or using an existing map if available. |
| * |
| * @param property {@link Property} being added. |
| * |
| * @return New {@link PropertyMap} with {@link Property} added. |
| */ |
| public PropertyMap addProperty(final Property property) { |
| PropertyMap newMap = checkHistory(property); |
| |
| if (newMap == null) { |
| final PropertyHashMap newProperties = properties.immutableAdd(property); |
| newMap = new PropertyMap(this, newProperties); |
| addToHistory(property, newMap); |
| |
| if(!property.isSpill()) { |
| newMap.fieldCount = Math.max(newMap.fieldCount, property.getSlot() + 1); |
| } |
| if (isValidArrayIndex(getArrayIndex(property.getKey()))) { |
| newMap.setContainsArrayKeys(); |
| } |
| |
| newMap.spillLength += property.getSpillCount(); |
| } |
| |
| return newMap; |
| } |
| |
| /** |
| * Remove a property from a map. Cloning or using an existing map if available. |
| * |
| * @param property {@link Property} being removed. |
| * |
| * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found. |
| */ |
| public PropertyMap deleteProperty(final Property property) { |
| PropertyMap newMap = checkHistory(property); |
| final String key = property.getKey(); |
| |
| if (newMap == null && properties.containsKey(key)) { |
| final PropertyHashMap newProperties = properties.immutableRemove(key); |
| newMap = new PropertyMap(this, newProperties); |
| addToHistory(property, newMap); |
| } |
| |
| return newMap; |
| } |
| |
| /** |
| * Replace an existing property with a new one. |
| * |
| * @param oldProperty Property to replace. |
| * @param newProperty New {@link Property}. |
| * |
| * @return New {@link PropertyMap} with {@link Property} replaced. |
| */ |
| PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) { |
| // Add replaces existing property. |
| final PropertyHashMap newProperties = properties.immutableAdd(newProperty); |
| final PropertyMap newMap = new PropertyMap(this, newProperties); |
| |
| /* |
| * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods. |
| * |
| * This replaceProperty method is called only for the following three cases: |
| * |
| * 1. To change flags OR TYPE of an old (cloned) property. We use the same spill slots. |
| * 2. To change one UserAccessor property with another - user getter or setter changed via |
| * Object.defineProperty function. Again, same spill slots are re-used. |
| * 3. Via ScriptObject.setUserAccessors method to set user getter and setter functions |
| * replacing the dummy AccessorProperty with null method handles (added during map init). |
| * |
| * In case (1) and case(2), the property type of old and new property is same. For case (3), |
| * the old property is an AccessorProperty and the new one is a UserAccessorProperty property. |
| */ |
| |
| final boolean sameType = (oldProperty.getClass() == newProperty.getClass()); |
| assert sameType || |
| (oldProperty instanceof AccessorProperty && |
| newProperty instanceof UserAccessorProperty) : "arbitrary replaceProperty attempted"; |
| |
| newMap.flags = getClonedFlags(); |
| |
| /* |
| * spillLength remains same in case (1) and (2) because of slot reuse. Only for case (3), we need |
| * to add spill count of the newly added UserAccessorProperty property. |
| */ |
| newMap.spillLength = spillLength + (sameType? 0 : newProperty.getSpillCount()); |
| return newMap; |
| } |
| |
| /** |
| * Make a new UserAccessorProperty property. getter and setter functions are stored in |
| * this ScriptObject and slot values are used in property object. Note that slots |
| * are assigned speculatively and should be added to map before adding other |
| * properties. |
| * |
| * @param key the property name |
| * @param propertyFlags attribute flags of the property |
| * @return the newly created UserAccessorProperty |
| */ |
| public UserAccessorProperty newUserAccessors(final String key, final int propertyFlags) { |
| int oldSpillLength = spillLength; |
| |
| final int getterSlot = oldSpillLength++; |
| final int setterSlot = oldSpillLength++; |
| |
| return new UserAccessorProperty(key, propertyFlags, getterSlot, setterSlot); |
| } |
| |
| /** |
| * Find a property in the map. |
| * |
| * @param key Key to search for. |
| * |
| * @return {@link Property} matching key. |
| */ |
| public Property findProperty(final String key) { |
| return properties.find(key); |
| } |
| |
| /** |
| * Adds all map properties from another map. |
| * |
| * @param other The source of properties. |
| * |
| * @return New {@link PropertyMap} with added properties. |
| */ |
| public PropertyMap addAll(final PropertyMap other) { |
| assert this != other : "adding property map to itself"; |
| final Property[] otherProperties = other.properties.getProperties(); |
| final PropertyHashMap newProperties = properties.immutableAdd(otherProperties); |
| |
| final PropertyMap newMap = new PropertyMap(this, newProperties); |
| for (final Property property : otherProperties) { |
| if (isValidArrayIndex(getArrayIndex(property.getKey()))) { |
| newMap.setContainsArrayKeys(); |
| } |
| newMap.spillLength += property.getSpillCount(); |
| } |
| |
| return newMap; |
| } |
| |
| /** |
| * Return an array of all properties. |
| * |
| * @return Properties as an array. |
| */ |
| public Property[] getProperties() { |
| return properties.getProperties(); |
| } |
| |
| /** |
| * Prevents the map from having additional properties. |
| * |
| * @return New map with {@link #NOT_EXTENSIBLE} flag set. |
| */ |
| PropertyMap preventExtensions() { |
| final PropertyMap newMap = new PropertyMap(this); |
| newMap.flags |= NOT_EXTENSIBLE; |
| return newMap; |
| } |
| |
| /** |
| * Prevents properties in map from being modified. |
| * |
| * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with |
| * {@link Property#NOT_CONFIGURABLE} set. |
| */ |
| PropertyMap seal() { |
| PropertyHashMap newProperties = EMPTY_HASHMAP; |
| |
| for (final Property oldProperty : properties.getProperties()) { |
| newProperties = newProperties.immutableAdd(oldProperty.addFlags(Property.NOT_CONFIGURABLE)); |
| } |
| |
| final PropertyMap newMap = new PropertyMap(this, newProperties); |
| newMap.flags |= NOT_EXTENSIBLE; |
| |
| return newMap; |
| } |
| |
| /** |
| * Prevents properties in map from being modified or written to. |
| * |
| * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with |
| * {@link Property#NOT_CONFIGURABLE} and {@link Property#NOT_WRITABLE} set. |
| */ |
| PropertyMap freeze() { |
| PropertyHashMap newProperties = EMPTY_HASHMAP; |
| |
| for (Property oldProperty : properties.getProperties()) { |
| int propertyFlags = Property.NOT_CONFIGURABLE; |
| |
| if (!(oldProperty instanceof UserAccessorProperty)) { |
| propertyFlags |= Property.NOT_WRITABLE; |
| } |
| |
| newProperties = newProperties.immutableAdd(oldProperty.addFlags(propertyFlags)); |
| } |
| |
| final PropertyMap newMap = new PropertyMap(this, newProperties); |
| newMap.flags |= NOT_EXTENSIBLE; |
| |
| return newMap; |
| } |
| |
| /** |
| * Make this property map 'shared' one. Shared property map instances are |
| * process wide singleton objects. A shaped map should never be added as a listener |
| * to a proto object. Nor it should have history or proto history. A shared map |
| * is just a template that is meant to be duplicated before use. All nasgen initialized |
| * property maps are shared. |
| * |
| * @return this map after making it as shared |
| */ |
| public PropertyMap setIsShared() { |
| assert !isListenerAdded() : "making PropertyMap shared after listener added"; |
| assert protoHistory == null : "making PropertyMap shared after associating a proto with it"; |
| if (Context.DEBUG) { |
| sharedCount++; |
| } |
| |
| flags |= IS_SHARED; |
| // clear any history on this PropertyMap, won't be used. |
| history = null; |
| return this; |
| } |
| |
| /** |
| * Check for any configurable properties. |
| * |
| * @return {@code true} if any configurable. |
| */ |
| private boolean anyConfigurable() { |
| for (final Property property : properties.getProperties()) { |
| if (property.isConfigurable()) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Check if all properties are frozen. |
| * |
| * @return {@code true} if all are frozen. |
| */ |
| private boolean allFrozen() { |
| for (final Property property : properties.getProperties()) { |
| // check if it is a data descriptor |
| if (!(property instanceof UserAccessorProperty)) { |
| if (property.isWritable()) { |
| return false; |
| } |
| } |
| if (property.isConfigurable()) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Check prototype history for an existing property map with specified prototype. |
| * |
| * @param newProto New prototype object. |
| * |
| * @return Existing {@link PropertyMap} or {@code null} if not found. |
| */ |
| private PropertyMap checkProtoHistory(final ScriptObject newProto) { |
| final PropertyMap cachedMap; |
| if (protoHistory != null) { |
| final WeakReference<PropertyMap> weakMap = protoHistory.get(newProto); |
| cachedMap = (weakMap != null ? weakMap.get() : null); |
| } else { |
| cachedMap = null; |
| } |
| |
| if (Context.DEBUG && cachedMap != null) { |
| protoHistoryHit++; |
| } |
| |
| return cachedMap; |
| } |
| |
| /** |
| * Add a map to the prototype history. |
| * |
| * @param newProto Prototype to add (key.) |
| * @param newMap {@link PropertyMap} associated with prototype. |
| */ |
| private void addToProtoHistory(final ScriptObject newProto, final PropertyMap newMap) { |
| assert !isShared() : "proto history modified on a shared PropertyMap"; |
| |
| if (protoHistory == null) { |
| protoHistory = new WeakHashMap<>(); |
| } |
| |
| protoHistory.put(newProto, new WeakReference<>(newMap)); |
| } |
| |
| /** |
| * Track the modification of the map. |
| * |
| * @param property Mapping property. |
| * @param newMap Modified {@link PropertyMap}. |
| */ |
| private void addToHistory(final Property property, final PropertyMap newMap) { |
| assert !isShared() : "history modified on a shared PropertyMap"; |
| |
| if (!properties.isEmpty()) { |
| if (history == null) { |
| history = new LinkedHashMap<>(); |
| } |
| |
| history.put(property, newMap); |
| } |
| } |
| |
| /** |
| * Check the history for a map that already has the given property added. |
| * |
| * @param property {@link Property} to add. |
| * |
| * @return Existing map or {@code null} if not found. |
| */ |
| private PropertyMap checkHistory(final Property property) { |
| if (history != null) { |
| PropertyMap historicMap = history.get(property); |
| |
| if (historicMap != null) { |
| if (Context.DEBUG) { |
| historyHit++; |
| } |
| |
| return historicMap; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Calculate the hash code for the map. |
| * |
| * @return Computed hash code. |
| */ |
| private int computeHashCode() { |
| int hash = 0; |
| |
| for (final Property property : getProperties()) { |
| hash = hash << 7 ^ hash >> 7; |
| hash ^= property.hashCode(); |
| } |
| |
| return hash; |
| } |
| |
| @Override |
| public int hashCode() { |
| if (hashCode == 0 && !properties.isEmpty()) { |
| hashCode = computeHashCode(); |
| } |
| return hashCode; |
| } |
| |
| @Override |
| public boolean equals(final Object other) { |
| if (!(other instanceof PropertyMap)) { |
| return false; |
| } |
| |
| final PropertyMap otherMap = (PropertyMap)other; |
| |
| if (properties.size() != otherMap.properties.size()) { |
| return false; |
| } |
| |
| final Iterator<Property> iter = properties.values().iterator(); |
| final Iterator<Property> otherIter = otherMap.properties.values().iterator(); |
| |
| while (iter.hasNext() && otherIter.hasNext()) { |
| if (!iter.next().equals(otherIter.next())) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| final StringBuilder sb = new StringBuilder(); |
| |
| sb.append(" ["); |
| boolean isFirst = true; |
| |
| for (final Property property : properties.values()) { |
| if (!isFirst) { |
| sb.append(", "); |
| } |
| |
| isFirst = false; |
| |
| sb.append(ScriptRuntime.safeToString(property.getKey())); |
| final Class<?> ctype = property.getCurrentType(); |
| sb.append(" <"). |
| append(property.getClass().getSimpleName()). |
| append(':'). |
| append(ctype == null ? |
| "undefined" : |
| ctype.getSimpleName()). |
| append('>'); |
| } |
| |
| sb.append(']'); |
| |
| return sb.toString(); |
| } |
| |
| @Override |
| public Iterator<Object> iterator() { |
| return new PropertyMapIterator(this); |
| } |
| |
| /** |
| * Check if this map contains properties with valid array keys |
| * |
| * @return {@code true} if this map contains properties with valid array keys |
| */ |
| public final boolean containsArrayKeys() { |
| return (flags & CONTAINS_ARRAY_KEYS) != 0; |
| } |
| |
| /** |
| * Flag this object as having array keys in defined properties |
| */ |
| private void setContainsArrayKeys() { |
| flags |= CONTAINS_ARRAY_KEYS; |
| } |
| |
| /** |
| * Check whether a {@link PropertyListener} has been added to this map. |
| * |
| * @return {@code true} if {@link PropertyListener} exists |
| */ |
| public boolean isListenerAdded() { |
| return (flags & IS_LISTENER_ADDED) != 0; |
| } |
| |
| /** |
| * Check if this map shared or not. |
| * |
| * @return true if this map is shared. |
| */ |
| public boolean isShared() { |
| return (flags & IS_SHARED) != 0; |
| } |
| |
| /** |
| * Test to see if {@link PropertyMap} is extensible. |
| * |
| * @return {@code true} if {@link PropertyMap} can be added to. |
| */ |
| boolean isExtensible() { |
| return (flags & NOT_EXTENSIBLE) == 0; |
| } |
| |
| /** |
| * Test to see if {@link PropertyMap} is not extensible or any properties |
| * can not be modified. |
| * |
| * @return {@code true} if {@link PropertyMap} is sealed. |
| */ |
| boolean isSealed() { |
| return !isExtensible() && !anyConfigurable(); |
| } |
| |
| /** |
| * Test to see if {@link PropertyMap} is not extensible or all properties |
| * can not be modified. |
| * |
| * @return {@code true} if {@link PropertyMap} is frozen. |
| */ |
| boolean isFrozen() { |
| return !isExtensible() && allFrozen(); |
| } |
| /** |
| * Get the number of fields allocated for this {@link PropertyMap}. |
| * |
| * @return Number of fields allocated. |
| */ |
| int getFieldCount() { |
| return fieldCount; |
| } |
| /** |
| * Get maximum number of fields available for this {@link PropertyMap}. |
| * |
| * @return Number of fields available. |
| */ |
| int getFieldMaximum() { |
| return fieldMaximum; |
| } |
| |
| /** |
| * Get length of spill area associated with this {@link PropertyMap}. |
| * |
| * @return Length of spill area. |
| */ |
| int getSpillLength() { |
| return spillLength; |
| } |
| |
| /** |
| * Change the prototype of objects associated with this {@link PropertyMap}. |
| * |
| * @param oldProto Current prototype object. |
| * @param newProto New prototype object to replace oldProto. |
| * |
| * @return New {@link PropertyMap} with prototype changed. |
| */ |
| PropertyMap changeProto(final ScriptObject oldProto, final ScriptObject newProto) { |
| assert !isShared() : "proto associated with a shared PropertyMap"; |
| |
| if (oldProto == newProto) { |
| return this; |
| } |
| |
| final PropertyMap nextMap = checkProtoHistory(newProto); |
| if (nextMap != null) { |
| return nextMap; |
| } |
| |
| if (Context.DEBUG) { |
| incrementSetProtoNewMapCount(); |
| } |
| |
| final PropertyMap newMap = new PropertyMap(this); |
| addToProtoHistory(newProto, newMap); |
| |
| return newMap; |
| } |
| |
| /** |
| * Indicate that the map has listeners. |
| */ |
| private void setIsListenerAdded() { |
| flags |= IS_LISTENER_ADDED; |
| } |
| |
| /** |
| * Return only the flags that should be copied during cloning. |
| * |
| * @return Subset of flags that should be copied. |
| */ |
| private int getClonedFlags() { |
| return flags & CLONEABLE_FLAGS_MASK; |
| } |
| |
| /** |
| * {@link PropertyMap} iterator. |
| */ |
| private static class PropertyMapIterator implements Iterator<Object> { |
| /** Property iterator. */ |
| final Iterator<Property> iter; |
| |
| /** Current Property. */ |
| Property property; |
| |
| /** |
| * Constructor. |
| * |
| * @param propertyMap {@link PropertyMap} to iterate over. |
| */ |
| PropertyMapIterator(final PropertyMap propertyMap) { |
| iter = Arrays.asList(propertyMap.properties.getProperties()).iterator(); |
| property = iter.hasNext() ? iter.next() : null; |
| skipNotEnumerable(); |
| } |
| |
| /** |
| * Ignore properties that are not enumerable. |
| */ |
| private void skipNotEnumerable() { |
| while (property != null && !property.isEnumerable()) { |
| property = iter.hasNext() ? iter.next() : null; |
| } |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return property != null; |
| } |
| |
| @Override |
| public Object next() { |
| if (property == null) { |
| throw new NoSuchElementException(); |
| } |
| |
| final Object key = property.getKey(); |
| property = iter.next(); |
| skipNotEnumerable(); |
| |
| return key; |
| } |
| |
| @Override |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| /* |
| * PropertyListener implementation. |
| */ |
| |
| @Override |
| public void propertyAdded(final ScriptObject object, final Property prop) { |
| invalidateProtoGetSwitchPoint(prop); |
| } |
| |
| @Override |
| public void propertyDeleted(final ScriptObject object, final Property prop) { |
| invalidateProtoGetSwitchPoint(prop); |
| } |
| |
| @Override |
| public void propertyModified(final ScriptObject object, final Property oldProp, final Property newProp) { |
| invalidateProtoGetSwitchPoint(oldProp); |
| } |
| |
| @Override |
| public void protoChanged(final ScriptObject object, final ScriptObject oldProto, final ScriptObject newProto) { |
| // We may walk and invalidate SwitchPoints for properties inherited |
| // from 'object' or it's old proto chain. But, it may not be worth it. |
| // For example, a new proto may have a user defined getter/setter for |
| // a data property down the chain. So, invalidating all is better. |
| invalidateAllProtoGetSwitchPoints(); |
| } |
| |
| /* |
| * Debugging and statistics. |
| */ |
| |
| // counters updated only in debug mode |
| private static int count; |
| private static int clonedCount; |
| private static int sharedCount; |
| private static int duplicatedCount; |
| private static int historyHit; |
| private static int protoInvalidations; |
| private static int protoHistoryHit; |
| private static int setProtoNewMapCount; |
| |
| /** |
| * @return Total number of maps. |
| */ |
| public static int getCount() { |
| return count; |
| } |
| |
| /** |
| * @return The number of maps that were cloned. |
| */ |
| public static int getClonedCount() { |
| return clonedCount; |
| } |
| |
| /** |
| * @return The number of maps that are shared. |
| */ |
| public static int getSharedCount() { |
| return sharedCount; |
| } |
| |
| /** |
| * @return The number of maps that are duplicated. |
| */ |
| public static int getDuplicatedCount() { |
| return duplicatedCount; |
| } |
| |
| /** |
| * @return The number of times history was successfully used. |
| */ |
| public static int getHistoryHit() { |
| return historyHit; |
| } |
| |
| /** |
| * @return The number of times prototype changes caused invalidation. |
| */ |
| public static int getProtoInvalidations() { |
| return protoInvalidations; |
| } |
| |
| /** |
| * @return The number of times proto history was successfully used. |
| */ |
| public static int getProtoHistoryHit() { |
| return protoHistoryHit; |
| } |
| |
| /** |
| * @return The number of times prototypes were modified. |
| */ |
| public static int getSetProtoNewMapCount() { |
| return setProtoNewMapCount; |
| } |
| |
| /** |
| * Increment the prototype set count. |
| */ |
| private static void incrementSetProtoNewMapCount() { |
| setProtoNewMapCount++; |
| } |
| } |