< prev index next >

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

Print this page


   1 /*
   2  * Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  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() + "]";


   1 /*
   2  * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  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.Iterator;
  44 import java.util.NoSuchElementException;
  45 import java.util.Set;
  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     /** History of maps, used to limit map duplication. */
  99     private transient WeakHashMap<Property, Reference<PropertyMap>> history;
 100 
 101     /** History of prototypes, used to limit map duplication. */
 102     private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory;
 103 
 104     /** SwitchPoints for properties inherited form this map */
 105     private transient PropertySwitchPoints propertySwitchPoints;
 106 
 107     private transient BitSet freeSlots;
 108 
 109     private static final long serialVersionUID = -7041836752008732533L;
 110 
 111     /**
 112      * Constructs a new property map.
 113      *
 114      * @param properties   A {@link PropertyHashMap} with initial contents.
 115      * @param fieldCount   Number of fields in use.
 116      * @param fieldMaximum Number of fields available.
 117      * @param spillLength  Number of spill slots used.
 118      */
 119     private PropertyMap(final PropertyHashMap properties, final int flags, final String className,
 120                         final int fieldCount, final int fieldMaximum, final int spillLength) {
 121         this.properties   = properties;
 122         this.className    = className;
 123         this.fieldCount   = fieldCount;
 124         this.fieldMaximum = fieldMaximum;
 125         this.spillLength  = spillLength;


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


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

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
















 263     }
 264 
 265     /**
 266      * Method called when a property of an object using this property map is being created,
 267      * modified, or deleted. If a switchpoint for the property exists it will be invalidated.
 268      *
 269      * @param property The changed property.

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



 274         }
 275     }
 276 
 277     /**
 278      * Method called when the prototype of an object using this property map is changed.


















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


 282             sharedProtoMap.invalidateSwitchPoint();
 283         }
 284         if (propertySwitchPoints != null) {
 285             propertySwitchPoints.invalidateInheritedProperties(this);
 286         }
 287     }
 288 
 289     /**
 290      * Returns a SwitchPoint for use with a property inherited from this or a parent map.
 291      * If such a switchpoint exists, it will be invalidated when the property is modified
 292      * in an object using this map. This method returns {@code null} if no swichpoint exists
 293      * for the property.
 294      *
 295      * @param key Property key.
 296      * @return A {@link SwitchPoint} for the property, or null.
 297      */
 298     public synchronized SwitchPoint getSwitchPoint(final String key) {
 299         if (propertySwitchPoints != null) {
 300             final Set<SwitchPoint> existingSwitchPoints = propertySwitchPoints.getSwitchPoints(key);
 301             for (final SwitchPoint switchPoint : existingSwitchPoints) {
 302                 if (switchPoint != null && !switchPoint.hasBeenInvalidated()) {






 303                     return switchPoint;
 304                 }
















 305             }
 306         }
 307 
 308         return null;













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


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


< prev index next >