< prev index next >

src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/runtime/PropertyMap.java

Print this page




  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.runtime;
  27 
  28 import static jdk.nashorn.internal.runtime.PropertyHashMap.EMPTY_HASHMAP;
  29 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex;
  30 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex;
  31 
  32 import java.io.IOException;
  33 import java.io.ObjectInputStream;
  34 import java.io.ObjectOutputStream;
  35 import java.io.Serializable;
  36 import java.lang.invoke.SwitchPoint;
  37 import java.lang.ref.Reference;
  38 import java.lang.ref.SoftReference;
  39 import java.lang.ref.WeakReference;
  40 import java.util.Arrays;
  41 import java.util.BitSet;
  42 import java.util.Collection;
  43 import java.util.HashMap;
  44 import java.util.Iterator;
  45 import java.util.NoSuchElementException;

  46 import java.util.WeakHashMap;
  47 import java.util.concurrent.atomic.LongAdder;
  48 import jdk.nashorn.internal.runtime.options.Options;
  49 import jdk.nashorn.internal.scripts.JO;
  50 
  51 /**
  52  * Map of object properties. The PropertyMap is the "template" for JavaScript object
  53  * layouts. It contains a map with prototype names as keys and {@link Property} instances
  54  * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor
  55  * to form the seed map for the ScriptObject.
  56  * <p>
  57  * All property maps are immutable. If a property is added, modified or removed, the mutator
  58  * will return a new map.
  59  */
  60 public class PropertyMap implements Iterable<Object>, Serializable {
  61     private static final int INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT =
  62             Math.max(0, Options.getIntProperty("nashorn.propertyMap.softReferenceDerivationLimit", 32));
  63 
  64     /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */
  65     private static final int NOT_EXTENSIBLE         = 0b0000_0001;


  78     /** Number of fields available. */
  79     private final int fieldMaximum;
  80 
  81     /** Length of spill in use. */
  82     private final int spillLength;
  83 
  84     /** Structure class name */
  85     private final String className;
  86 
  87     /**
  88      * Countdown of number of times this property map has been derived from another property map. When it
  89      * reaches zero, the property map will start using weak references instead of soft references to hold on
  90      * to its history elements.
  91      */
  92     private final int softReferenceDerivationLimit;
  93 
  94     /** A reference to the expected shared prototype property map. If this is set this
  95      * property map should only be used if it the same as the actual prototype map. */
  96     private transient SharedPropertyMap sharedProtoMap;
  97 
  98     /** {@link SwitchPoint}s for gets on inherited properties. */
  99     private transient HashMap<Object, SwitchPoint> protoSwitches;
 100 
 101     /** History of maps, used to limit map duplication. */
 102     private transient WeakHashMap<Property, Reference<PropertyMap>> history;
 103 
 104     /** History of prototypes, used to limit map duplication. */
 105     private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory;
 106 
 107     /** property listeners */
 108     private transient PropertyListeners listeners;
 109 
 110     private transient BitSet freeSlots;
 111 
 112     private static final long serialVersionUID = -7041836752008732533L;
 113 
 114     /**
 115      * Constructs a new property map.
 116      *
 117      * @param properties   A {@link PropertyHashMap} with initial contents.
 118      * @param fieldCount   Number of fields in use.
 119      * @param fieldMaximum Number of fields available.
 120      * @param spillLength  Number of spill slots used.
 121      */
 122     private PropertyMap(final PropertyHashMap properties, final int flags, final String className,
 123                         final int fieldCount, final int fieldMaximum, final int spillLength) {
 124         this.properties   = properties;
 125         this.className    = className;
 126         this.fieldCount   = fieldCount;
 127         this.fieldMaximum = fieldMaximum;
 128         this.spillLength  = spillLength;


 130         this.softReferenceDerivationLimit = INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT;
 131 
 132         if (Context.DEBUG) {
 133             count.increment();
 134         }
 135     }
 136 
 137     /**
 138      * Constructs a clone of {@code propertyMap} with changed properties, flags, or boundaries.
 139      *
 140      * @param propertyMap Existing property map.
 141      * @param properties  A {@link PropertyHashMap} with a new set of properties.
 142      */
 143     private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength, final int softReferenceDerivationLimit) {
 144         this.properties   = properties;
 145         this.flags        = flags;
 146         this.spillLength  = spillLength;
 147         this.fieldCount   = fieldCount;
 148         this.fieldMaximum = propertyMap.fieldMaximum;
 149         this.className    = propertyMap.className;
 150         // We inherit the parent property listeners instance. It will be cloned when a new listener is added.
 151         this.listeners    = propertyMap.listeners;
 152         this.freeSlots    = propertyMap.freeSlots;
 153         this.sharedProtoMap = propertyMap.sharedProtoMap;
 154         this.softReferenceDerivationLimit = softReferenceDerivationLimit;
 155 
 156         if (Context.DEBUG) {
 157             count.increment();
 158             clonedCount.increment();
 159         }
 160     }
 161 
 162     /**
 163      * Constructs an exact clone of {@code propertyMap}.
 164      *
 165      * @param propertyMap Existing property map.
 166       */
 167     protected PropertyMap(final PropertyMap propertyMap) {
 168         this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength, propertyMap.softReferenceDerivationLimit);
 169     }
 170 
 171     private void writeObject(final ObjectOutputStream out) throws IOException {


 228 
 229     /**
 230      * Return a sharable empty map.
 231      *
 232      * @return New empty {@link PropertyMap}.
 233      */
 234     public static PropertyMap newMap() {
 235         return newMap(JO.class);
 236     }
 237 
 238     /**
 239      * Return number of properties in the map.
 240      *
 241      * @return Number of properties.
 242      */
 243     public int size() {
 244         return properties.size();
 245     }
 246 
 247     /**
 248      * Get the number of listeners of this map
 249      *
 250      * @return the number of listeners
 251      */
 252     public int getListenerCount() {
 253         return listeners == null ? 0 : listeners.getListenerCount();
 254     }
 255 
 256     /**
 257      * Add {@code listenerMap} as a listener to this property map for the given {@code key}.
 258      *
 259      * @param key the property name
 260      * @param listenerMap the listener map
 261      */
 262     public void addListener(final String key, final PropertyMap listenerMap) {
 263         if (listenerMap != this) {
 264             // We need to clone listener instance when adding a new listener since we share
 265             // the listeners instance with our parent maps that don't need to see the new listener.
 266             listeners = PropertyListeners.addListener(listeners, key, listenerMap);
 267         }
 268     }
 269 
 270     /**
 271      * A new property is being added.
 272      *
 273      * @param property The new Property added.
 274      * @param isSelf was the property added to this map?
 275      */
 276     public void propertyAdded(final Property property, final boolean isSelf) {
 277         if (!isSelf) {
 278             invalidateProtoSwitchPoint(property.getKey());
 279         }
 280         if (listeners != null) {
 281             listeners.propertyAdded(property);
 282         }
 283     }
 284 
 285     /**
 286      * An existing property is being deleted.
 287      *
 288      * @param property The property being deleted.
 289      * @param isSelf was the property deleted from this map?
 290      */
 291     public void propertyDeleted(final Property property, final boolean isSelf) {
 292         if (!isSelf) {
 293             invalidateProtoSwitchPoint(property.getKey());
 294         }
 295         if (listeners != null) {
 296             listeners.propertyDeleted(property);
 297         }
 298     }
 299 
 300     /**
 301      * An existing property is being redefined.
 302      *
 303      * @param oldProperty The old property
 304      * @param newProperty The new property
 305      * @param isSelf was the property modified on this map?
 306      */
 307     public void propertyModified(final Property oldProperty, final Property newProperty, final boolean isSelf) {
 308         if (!isSelf) {
 309             invalidateProtoSwitchPoint(oldProperty.getKey());
 310         }
 311         if (listeners != null) {
 312             listeners.propertyModified(oldProperty, newProperty);
 313         }
 314     }
 315 
 316     /**
 317      * The prototype of an object associated with this {@link PropertyMap} is changed.
 318      *
 319      * @param isSelf was the prototype changed on the object using this map?
 320      */
 321     public void protoChanged(final boolean isSelf) {
 322         if (!isSelf) {
 323             invalidateAllProtoSwitchPoints();
 324         } else if (sharedProtoMap != null) {
 325             sharedProtoMap.invalidateSwitchPoint();
 326         }
 327         if (listeners != null) {
 328             listeners.protoChanged();
 329         }
 330     }
 331 
 332     /**
 333      * Return a SwitchPoint used to track changes of a property in a prototype.
 334      *
 335      * @param key Property key.
 336      * @return A shared {@link SwitchPoint} for the property.
 337      */
 338     public synchronized SwitchPoint getSwitchPoint(final String key) {
 339         if (protoSwitches == null) {
 340             protoSwitches = new HashMap<>();
 341         }
 342 
 343         SwitchPoint switchPoint = protoSwitches.get(key);
 344         if (switchPoint == null) {
 345             switchPoint = new SwitchPoint();
 346             protoSwitches.put(key, switchPoint);
 347         }
 348 
 349         return switchPoint;
 350     }
 351 
 352     /**
 353      * Indicate that a prototype property has changed.
 354      *
 355      * @param key {@link Property} key to invalidate.
 356      */
 357     synchronized void invalidateProtoSwitchPoint(final Object key) {
 358         if (protoSwitches != null) {
 359             final SwitchPoint sp = protoSwitches.get(key);
 360             if (sp != null) {
 361                 protoSwitches.remove(key);
 362                 if (Context.DEBUG) {
 363                     protoInvalidations.increment();
 364                 }
 365                 SwitchPoint.invalidateAll(new SwitchPoint[]{sp});
 366             }
 367         }
 368     }
 369 
 370     /**
 371      * Indicate that proto itself has changed in hierarchy somewhere.
 372      */
 373     synchronized void invalidateAllProtoSwitchPoints() {
 374         if (protoSwitches != null) {
 375             final int size = protoSwitches.size();
 376             if (size > 0) {
 377                 if (Context.DEBUG) {
 378                     protoInvalidations.add(size);
 379                 }
 380                 SwitchPoint.invalidateAll(protoSwitches.values().toArray(new SwitchPoint[0]));
 381                 protoSwitches.clear();
 382             }
 383         }
 384     }
 385 
 386     /**
 387      * Add a property to the map, re-binding its getters and setters,
 388      * if available, to a given receiver. This is typically the global scope. See
 389      * {@link ScriptObject#addBoundProperties(ScriptObject)}
 390      *
 391      * @param property {@link Property} being added.
 392      * @param bindTo   Object to bind to.
 393      *
 394      * @return New {@link PropertyMap} with {@link Property} added.
 395      */
 396     PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) {
 397         // We must not store bound property in the history as bound properties can't be reused.
 398         return addPropertyNoHistory(new AccessorProperty(property, bindTo));
 399     }
 400 
 401     // Get a logical slot index for a property, with spill slot 0 starting at fieldMaximum.
 402     private int logicalSlotIndex(final Property property) {
 403         final int slot = property.getSlot();


 435             }
 436         }
 437         if (freeSlots != null && newProperty != null) {
 438             final int slotIndex = logicalSlotIndex(newProperty);
 439             if (slotIndex > -1 && freeSlots.get(slotIndex)) {
 440                 final BitSet newFreeSlots = freeSlotsCloned ? freeSlots : ((BitSet)freeSlots.clone());
 441                 newFreeSlots.clear(slotIndex);
 442                 freeSlots = newFreeSlots.isEmpty() ? null : newFreeSlots;
 443             }
 444         }
 445     }
 446 
 447     /**
 448      * Add a property to the map without adding it to the history. This should be used for properties that
 449      * can't be shared such as bound properties, or properties that are expected to be added only once.
 450      *
 451      * @param property {@link Property} being added.
 452      * @return New {@link PropertyMap} with {@link Property} added.
 453      */
 454     public final PropertyMap addPropertyNoHistory(final Property property) {
 455         propertyAdded(property, true);
 456         return addPropertyInternal(property);
 457     }
 458 
 459     /**
 460      * Add a property to the map.  Cloning or using an existing map if available.
 461      *
 462      * @param property {@link Property} being added.
 463      *
 464      * @return New {@link PropertyMap} with {@link Property} added.
 465      */
 466     public final synchronized PropertyMap addProperty(final Property property) {
 467         propertyAdded(property, true);
 468         PropertyMap newMap = checkHistory(property);
 469 
 470         if (newMap == null) {
 471             newMap = addPropertyInternal(property);
 472             addToHistory(property, newMap);
 473         }
 474 
 475         return newMap;
 476     }
 477 
 478     private PropertyMap deriveMap(final PropertyHashMap newProperties, final int newFlags, final int newFieldCount, final int newSpillLength) {
 479         return new PropertyMap(this, newProperties, newFlags, newFieldCount, newSpillLength, softReferenceDerivationLimit == 0 ? 0 : softReferenceDerivationLimit - 1);
 480     }
 481 
 482     private PropertyMap addPropertyInternal(final Property property) {
 483         final PropertyHashMap newProperties = properties.immutableAdd(property);
 484         final PropertyMap newMap = deriveMap(newProperties, newFlags(property), newFieldCount(property), newSpillLength(property));
 485         newMap.updateFreeSlots(null, property);
 486         return newMap;
 487     }
 488 
 489     /**
 490      * Remove a property from a map. Cloning or using an existing map if available.
 491      *
 492      * @param property {@link Property} being removed.
 493      *
 494      * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found.
 495      */
 496     public final synchronized PropertyMap deleteProperty(final Property property) {
 497         propertyDeleted(property, true);
 498         PropertyMap newMap = checkHistory(property);
 499         final Object key = property.getKey();
 500 
 501         if (newMap == null && properties.containsKey(key)) {
 502             final PropertyHashMap newProperties = properties.immutableRemove(key);
 503             final boolean isSpill = property.isSpill();
 504             final int slot = property.getSlot();
 505             // If deleted property was last field or spill slot we can make it reusable by reducing field/slot count.
 506             // Otherwise mark it as free in free slots bitset.
 507             if (isSpill && slot >= 0 && slot == spillLength - 1) {
 508                 newMap = deriveMap(newProperties, flags, fieldCount, spillLength - 1);
 509                 newMap.freeSlots = freeSlots;
 510             } else if (!isSpill && slot >= 0 && slot == fieldCount - 1) {
 511                 newMap = deriveMap(newProperties, flags, fieldCount - 1, spillLength);
 512                 newMap.freeSlots = freeSlots;
 513             } else {
 514                 newMap = deriveMap(newProperties, flags, fieldCount, spillLength);
 515                 newMap.updateFreeSlots(property, null);
 516             }
 517             addToHistory(property, newMap);
 518         }
 519 
 520         return newMap;
 521     }
 522 
 523     /**
 524      * Replace an existing property with a new one.
 525      *
 526      * @param oldProperty Property to replace.
 527      * @param newProperty New {@link Property}.
 528      *
 529      * @return New {@link PropertyMap} with {@link Property} replaced.
 530      */
 531     public final PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) {
 532         propertyModified(oldProperty, newProperty, true);
 533         /*
 534          * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods.
 535          *
 536          * This replaceProperty method is called only for the following three cases:
 537          *
 538          *   1. To change flags OR TYPE of an old (cloned) property. We use the same spill slots.
 539          *   2. To change one UserAccessor property with another - user getter or setter changed via
 540          *      Object.defineProperty function. Again, same spill slots are re-used.
 541          *   3. Via ScriptObject.setUserAccessors method to set user getter and setter functions
 542          *      replacing the dummy AccessorProperty with null method handles (added during map init).
 543          *
 544          * In case (1) and case(2), the property type of old and new property is same. For case (3),
 545          * the old property is an AccessorProperty and the new one is a UserAccessorProperty property.
 546          */
 547 
 548         final boolean sameType = oldProperty.getClass() == newProperty.getClass();
 549         assert sameType ||
 550                 oldProperty instanceof AccessorProperty &&
 551                 newProperty instanceof UserAccessorProperty :
 552             "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]";




  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.runtime;
  27 
  28 import static jdk.nashorn.internal.runtime.PropertyHashMap.EMPTY_HASHMAP;
  29 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex;
  30 import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex;
  31 
  32 import java.io.IOException;
  33 import java.io.ObjectInputStream;
  34 import java.io.ObjectOutputStream;
  35 import java.io.Serializable;
  36 import java.lang.invoke.SwitchPoint;
  37 import java.lang.ref.Reference;
  38 import java.lang.ref.SoftReference;
  39 import java.lang.ref.WeakReference;
  40 import java.util.Arrays;
  41 import java.util.BitSet;
  42 import java.util.Collection;
  43 import java.util.Collections;
  44 import java.util.Iterator;
  45 import java.util.NoSuchElementException;
  46 import java.util.Set;
  47 import java.util.WeakHashMap;
  48 import java.util.concurrent.atomic.LongAdder;
  49 import jdk.nashorn.internal.runtime.options.Options;
  50 import jdk.nashorn.internal.scripts.JO;
  51 
  52 /**
  53  * Map of object properties. The PropertyMap is the "template" for JavaScript object
  54  * layouts. It contains a map with prototype names as keys and {@link Property} instances
  55  * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor
  56  * to form the seed map for the ScriptObject.
  57  * <p>
  58  * All property maps are immutable. If a property is added, modified or removed, the mutator
  59  * will return a new map.
  60  */
  61 public class PropertyMap implements Iterable<Object>, Serializable {
  62     private static final int INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT =
  63             Math.max(0, Options.getIntProperty("nashorn.propertyMap.softReferenceDerivationLimit", 32));
  64 
  65     /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */
  66     private static final int NOT_EXTENSIBLE         = 0b0000_0001;


  79     /** Number of fields available. */
  80     private final int fieldMaximum;
  81 
  82     /** Length of spill in use. */
  83     private final int spillLength;
  84 
  85     /** Structure class name */
  86     private final String className;
  87 
  88     /**
  89      * Countdown of number of times this property map has been derived from another property map. When it
  90      * reaches zero, the property map will start using weak references instead of soft references to hold on
  91      * to its history elements.
  92      */
  93     private final int softReferenceDerivationLimit;
  94 
  95     /** A reference to the expected shared prototype property map. If this is set this
  96      * property map should only be used if it the same as the actual prototype map. */
  97     private transient SharedPropertyMap sharedProtoMap;
  98 



  99     /** History of maps, used to limit map duplication. */
 100     private transient WeakHashMap<Property, Reference<PropertyMap>> history;
 101 
 102     /** History of prototypes, used to limit map duplication. */
 103     private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory;
 104 
 105     /** SwitchPoints for properties inherited form this map */
 106     private transient PropertySwitchPoints propertySwitchPoints;
 107 
 108     private transient BitSet freeSlots;
 109 
 110     private static final long serialVersionUID = -7041836752008732533L;
 111 
 112     /**
 113      * Constructs a new property map.
 114      *
 115      * @param properties   A {@link PropertyHashMap} with initial contents.
 116      * @param fieldCount   Number of fields in use.
 117      * @param fieldMaximum Number of fields available.
 118      * @param spillLength  Number of spill slots used.
 119      */
 120     private PropertyMap(final PropertyHashMap properties, final int flags, final String className,
 121                         final int fieldCount, final int fieldMaximum, final int spillLength) {
 122         this.properties   = properties;
 123         this.className    = className;
 124         this.fieldCount   = fieldCount;
 125         this.fieldMaximum = fieldMaximum;
 126         this.spillLength  = spillLength;


 128         this.softReferenceDerivationLimit = INITIAL_SOFT_REFERENCE_DERIVATION_LIMIT;
 129 
 130         if (Context.DEBUG) {
 131             count.increment();
 132         }
 133     }
 134 
 135     /**
 136      * Constructs a clone of {@code propertyMap} with changed properties, flags, or boundaries.
 137      *
 138      * @param propertyMap Existing property map.
 139      * @param properties  A {@link PropertyHashMap} with a new set of properties.
 140      */
 141     private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties, final int flags, final int fieldCount, final int spillLength, final int softReferenceDerivationLimit) {
 142         this.properties   = properties;
 143         this.flags        = flags;
 144         this.spillLength  = spillLength;
 145         this.fieldCount   = fieldCount;
 146         this.fieldMaximum = propertyMap.fieldMaximum;
 147         this.className    = propertyMap.className;
 148         // We inherit the parent property propertySwitchPoints instance. It will be cloned when a new listener is added.
 149         this.propertySwitchPoints = propertyMap.propertySwitchPoints;
 150         this.freeSlots    = propertyMap.freeSlots;
 151         this.sharedProtoMap = propertyMap.sharedProtoMap;
 152         this.softReferenceDerivationLimit = softReferenceDerivationLimit;
 153 
 154         if (Context.DEBUG) {
 155             count.increment();
 156             clonedCount.increment();
 157         }
 158     }
 159 
 160     /**
 161      * Constructs an exact clone of {@code propertyMap}.
 162      *
 163      * @param propertyMap Existing property map.
 164       */
 165     protected PropertyMap(final PropertyMap propertyMap) {
 166         this(propertyMap, propertyMap.properties, propertyMap.flags, propertyMap.fieldCount, propertyMap.spillLength, propertyMap.softReferenceDerivationLimit);
 167     }
 168 
 169     private void writeObject(final ObjectOutputStream out) throws IOException {


 226 
 227     /**
 228      * Return a sharable empty map.
 229      *
 230      * @return New empty {@link PropertyMap}.
 231      */
 232     public static PropertyMap newMap() {
 233         return newMap(JO.class);
 234     }
 235 
 236     /**
 237      * Return number of properties in the map.
 238      *
 239      * @return Number of properties.
 240      */
 241     public int size() {
 242         return properties.size();
 243     }
 244 
 245     /**
 246      * Get the number of property SwitchPoints of this map
 247      *
 248      * @return the number of property SwitchPoints
 249      */
 250     public int getSwitchPointCount() {
 251         return propertySwitchPoints == null ? 0 : propertySwitchPoints.getSwitchPointCount();
 252     }
 253 
 254     /**
 255      * Add a property switchpoint to this property map for the given {@code key}.
 256      *
 257      * @param key the property name
 258      * @param switchPoint the switchpoint
 259      */
 260     public void addSwitchPoint(final String key, final SwitchPoint switchPoint) {

 261         // We need to clone listener instance when adding a new listener since we share
 262         // the propertySwitchPoints instance with our parent maps that don't need to see the new listener.
 263         propertySwitchPoints = PropertySwitchPoints.addSwitchPoint(propertySwitchPoints, key, switchPoint);
















 264     }
 265 
 266     /**
 267      * A property is about to change - invalidate all prototype switchpoints for the given property.
 268      *
 269      * @param property The modified property.

 270      */
 271     public void invalidateProperty(final Property property) {
 272         if (propertySwitchPoints != null) {
 273             propertySwitchPoints.invalidateProperty(property);



















 274         }
 275     }
 276 
 277     /**
 278      * The prototype of an object associated with this {@link PropertyMap} is changed.


 279      */
 280     void protoChanged() {
 281         if (sharedProtoMap != null) {


 282             sharedProtoMap.invalidateSwitchPoint();
 283         }
 284         if (propertySwitchPoints != null) {
 285             propertySwitchPoints.invalidateAll();
 286         }
 287     }
 288 
 289     /**
 290      * Return a SwitchPoint for tracking a property in this map, if one exists.
 291      *
 292      * @param key Property key.
 293      * @return A {@link SwitchPoint} for the property, or null.
 294      */
 295     public synchronized SwitchPoint getSwitchPoint(final String key) {
 296         if (propertySwitchPoints != null) {
 297             final Set<SwitchPoint> existingSwitchPoints = propertySwitchPoints.getSwitchPoints(key);
 298             for (final SwitchPoint switchPoint : existingSwitchPoints) {
 299                 if (switchPoint != null && !switchPoint.hasBeenInvalidated()) {






 300                     return switchPoint;
 301                 }
















 302             }
 303         }
 304 
 305         return null;













 306     }
 307 
 308     /**
 309      * Add a property to the map, re-binding its getters and setters,
 310      * if available, to a given receiver. This is typically the global scope. See
 311      * {@link ScriptObject#addBoundProperties(ScriptObject)}
 312      *
 313      * @param property {@link Property} being added.
 314      * @param bindTo   Object to bind to.
 315      *
 316      * @return New {@link PropertyMap} with {@link Property} added.
 317      */
 318     PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) {
 319         // We must not store bound property in the history as bound properties can't be reused.
 320         return addPropertyNoHistory(new AccessorProperty(property, bindTo));
 321     }
 322 
 323     // Get a logical slot index for a property, with spill slot 0 starting at fieldMaximum.
 324     private int logicalSlotIndex(final Property property) {
 325         final int slot = property.getSlot();


 357             }
 358         }
 359         if (freeSlots != null && newProperty != null) {
 360             final int slotIndex = logicalSlotIndex(newProperty);
 361             if (slotIndex > -1 && freeSlots.get(slotIndex)) {
 362                 final BitSet newFreeSlots = freeSlotsCloned ? freeSlots : ((BitSet)freeSlots.clone());
 363                 newFreeSlots.clear(slotIndex);
 364                 freeSlots = newFreeSlots.isEmpty() ? null : newFreeSlots;
 365             }
 366         }
 367     }
 368 
 369     /**
 370      * Add a property to the map without adding it to the history. This should be used for properties that
 371      * can't be shared such as bound properties, or properties that are expected to be added only once.
 372      *
 373      * @param property {@link Property} being added.
 374      * @return New {@link PropertyMap} with {@link Property} added.
 375      */
 376     public final PropertyMap addPropertyNoHistory(final Property property) {
 377         invalidateProperty(property);
 378         return addPropertyInternal(property);
 379     }
 380 
 381     /**
 382      * Add a property to the map.  Cloning or using an existing map if available.
 383      *
 384      * @param property {@link Property} being added.
 385      *
 386      * @return New {@link PropertyMap} with {@link Property} added.
 387      */
 388     public final synchronized PropertyMap addProperty(final Property property) {
 389         invalidateProperty(property);
 390         PropertyMap newMap = checkHistory(property);
 391 
 392         if (newMap == null) {
 393             newMap = addPropertyInternal(property);
 394             addToHistory(property, newMap);
 395         }
 396 
 397         return newMap;
 398     }
 399 
 400     private PropertyMap deriveMap(final PropertyHashMap newProperties, final int newFlags, final int newFieldCount, final int newSpillLength) {
 401         return new PropertyMap(this, newProperties, newFlags, newFieldCount, newSpillLength, softReferenceDerivationLimit == 0 ? 0 : softReferenceDerivationLimit - 1);
 402     }
 403 
 404     private PropertyMap addPropertyInternal(final Property property) {
 405         final PropertyHashMap newProperties = properties.immutableAdd(property);
 406         final PropertyMap newMap = deriveMap(newProperties, newFlags(property), newFieldCount(property), newSpillLength(property));
 407         newMap.updateFreeSlots(null, property);
 408         return newMap;
 409     }
 410 
 411     /**
 412      * Remove a property from a map. Cloning or using an existing map if available.
 413      *
 414      * @param property {@link Property} being removed.
 415      *
 416      * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found.
 417      */
 418     public final synchronized PropertyMap deleteProperty(final Property property) {
 419         invalidateProperty(property);
 420         PropertyMap newMap = checkHistory(property);
 421         final Object key = property.getKey();
 422 
 423         if (newMap == null && properties.containsKey(key)) {
 424             final PropertyHashMap newProperties = properties.immutableRemove(key);
 425             final boolean isSpill = property.isSpill();
 426             final int slot = property.getSlot();
 427             // If deleted property was last field or spill slot we can make it reusable by reducing field/slot count.
 428             // Otherwise mark it as free in free slots bitset.
 429             if (isSpill && slot >= 0 && slot == spillLength - 1) {
 430                 newMap = deriveMap(newProperties, flags, fieldCount, spillLength - 1);
 431                 newMap.freeSlots = freeSlots;
 432             } else if (!isSpill && slot >= 0 && slot == fieldCount - 1) {
 433                 newMap = deriveMap(newProperties, flags, fieldCount - 1, spillLength);
 434                 newMap.freeSlots = freeSlots;
 435             } else {
 436                 newMap = deriveMap(newProperties, flags, fieldCount, spillLength);
 437                 newMap.updateFreeSlots(property, null);
 438             }
 439             addToHistory(property, newMap);
 440         }
 441 
 442         return newMap;
 443     }
 444 
 445     /**
 446      * Replace an existing property with a new one.
 447      *
 448      * @param oldProperty Property to replace.
 449      * @param newProperty New {@link Property}.
 450      *
 451      * @return New {@link PropertyMap} with {@link Property} replaced.
 452      */
 453     public final PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) {
 454         invalidateProperty(oldProperty);
 455         /*
 456          * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods.
 457          *
 458          * This replaceProperty method is called only for the following three cases:
 459          *
 460          *   1. To change flags OR TYPE of an old (cloned) property. We use the same spill slots.
 461          *   2. To change one UserAccessor property with another - user getter or setter changed via
 462          *      Object.defineProperty function. Again, same spill slots are re-used.
 463          *   3. Via ScriptObject.setUserAccessors method to set user getter and setter functions
 464          *      replacing the dummy AccessorProperty with null method handles (added during map init).
 465          *
 466          * In case (1) and case(2), the property type of old and new property is same. For case (3),
 467          * the old property is an AccessorProperty and the new one is a UserAccessorProperty property.
 468          */
 469 
 470         final boolean sameType = oldProperty.getClass() == newProperty.getClass();
 471         assert sameType ||
 472                 oldProperty instanceof AccessorProperty &&
 473                 newProperty instanceof UserAccessorProperty :
 474             "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]";


< prev index next >