PropertyMap.java revision 1349:1261d91a9e28
1/* 2 * Copyright (c) 2010, 2013, 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 26package jdk.nashorn.internal.runtime; 27 28import static jdk.nashorn.internal.runtime.PropertyHashMap.EMPTY_HASHMAP; 29import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex; 30import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; 31 32import java.io.IOException; 33import java.io.ObjectInputStream; 34import java.io.ObjectOutputStream; 35import java.io.Serializable; 36import java.lang.invoke.SwitchPoint; 37import java.lang.ref.SoftReference; 38import java.util.Arrays; 39import java.util.BitSet; 40import java.util.Collection; 41import java.util.HashMap; 42import java.util.Iterator; 43import java.util.NoSuchElementException; 44import java.util.WeakHashMap; 45import jdk.nashorn.internal.scripts.JO; 46 47/** 48 * Map of object properties. The PropertyMap is the "template" for JavaScript object 49 * layouts. It contains a map with prototype names as keys and {@link Property} instances 50 * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor 51 * to form the seed map for the ScriptObject. 52 * <p> 53 * All property maps are immutable. If a property is added, modified or removed, the mutator 54 * will return a new map. 55 */ 56public final class PropertyMap implements Iterable<Object>, Serializable { 57 /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */ 58 public static final int NOT_EXTENSIBLE = 0b0000_0001; 59 /** Does this map contain valid array keys? */ 60 public static final int CONTAINS_ARRAY_KEYS = 0b0000_0010; 61 62 /** Map status flags. */ 63 private int flags; 64 65 /** Map of properties. */ 66 private transient PropertyHashMap properties; 67 68 /** Number of fields in use. */ 69 private int fieldCount; 70 71 /** Number of fields available. */ 72 private final int fieldMaximum; 73 74 /** Length of spill in use. */ 75 private int spillLength; 76 77 /** Structure class name */ 78 private String className; 79 80 /** {@link SwitchPoint}s for gets on inherited properties. */ 81 private transient HashMap<String, SwitchPoint> protoGetSwitches; 82 83 /** History of maps, used to limit map duplication. */ 84 private transient WeakHashMap<Property, SoftReference<PropertyMap>> history; 85 86 /** History of prototypes, used to limit map duplication. */ 87 private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory; 88 89 /** property listeners */ 90 private transient PropertyListeners listeners; 91 92 private transient BitSet freeSlots; 93 94 private static final long serialVersionUID = -7041836752008732533L; 95 96 /** 97 * Constructor. 98 * 99 * @param properties A {@link PropertyHashMap} with initial contents. 100 * @param fieldCount Number of fields in use. 101 * @param fieldMaximum Number of fields available. 102 * @param spillLength Number of spill slots used. 103 * @param containsArrayKeys True if properties contain numeric keys 104 */ 105 private PropertyMap(final PropertyHashMap properties, final String className, final int fieldCount, 106 final int fieldMaximum, final int spillLength, final boolean containsArrayKeys) { 107 this.properties = properties; 108 this.className = className; 109 this.fieldCount = fieldCount; 110 this.fieldMaximum = fieldMaximum; 111 this.spillLength = spillLength; 112 if (containsArrayKeys) { 113 setContainsArrayKeys(); 114 } 115 116 if (Context.DEBUG) { 117 count++; 118 } 119 } 120 121 /** 122 * Cloning constructor. 123 * 124 * @param propertyMap Existing property map. 125 * @param properties A {@link PropertyHashMap} with a new set of properties. 126 */ 127 private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties) { 128 this.properties = properties; 129 this.flags = propertyMap.flags; 130 this.spillLength = propertyMap.spillLength; 131 this.fieldCount = propertyMap.fieldCount; 132 this.fieldMaximum = propertyMap.fieldMaximum; 133 // We inherit the parent property listeners instance. It will be cloned when a new listener is added. 134 this.listeners = propertyMap.listeners; 135 this.freeSlots = propertyMap.freeSlots; 136 137 if (Context.DEBUG) { 138 count++; 139 clonedCount++; 140 } 141 } 142 143 /** 144 * Cloning constructor. 145 * 146 * @param propertyMap Existing property map. 147 */ 148 private PropertyMap(final PropertyMap propertyMap) { 149 this(propertyMap, propertyMap.properties); 150 } 151 152 private void writeObject(final ObjectOutputStream out) throws IOException { 153 out.defaultWriteObject(); 154 out.writeObject(properties.getProperties()); 155 } 156 157 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 158 in.defaultReadObject(); 159 160 final Property[] props = (Property[]) in.readObject(); 161 this.properties = EMPTY_HASHMAP.immutableAdd(props); 162 163 assert className != null; 164 final Class<?> structure = Context.forStructureClass(className); 165 for (final Property prop : props) { 166 prop.initMethodHandles(structure); 167 } 168 } 169 170 /** 171 * Public property map allocator. 172 * 173 * <p>It is the caller's responsibility to make sure that {@code properties} does not contain 174 * properties with keys that are valid array indices.</p> 175 * 176 * @param properties Collection of initial properties. 177 * @param className class name 178 * @param fieldCount Number of fields in use. 179 * @param fieldMaximum Number of fields available. 180 * @param spillLength Number of used spill slots. 181 * @return New {@link PropertyMap}. 182 */ 183 public static PropertyMap newMap(final Collection<Property> properties, final String className, final int fieldCount, final int fieldMaximum, final int spillLength) { 184 final PropertyHashMap newProperties = EMPTY_HASHMAP.immutableAdd(properties); 185 return new PropertyMap(newProperties, className, fieldCount, fieldMaximum, spillLength, false); 186 } 187 188 /** 189 * Public property map allocator. Used by nasgen generated code. 190 * 191 * <p>It is the caller's responsibility to make sure that {@code properties} does not contain 192 * properties with keys that are valid array indices.</p> 193 * 194 * @param properties Collection of initial properties. 195 * @return New {@link PropertyMap}. 196 */ 197 public static PropertyMap newMap(final Collection<Property> properties) { 198 return properties == null || properties.isEmpty()? newMap() : newMap(properties, JO.class.getName(), 0, 0, 0); 199 } 200 201 /** 202 * Return a sharable empty map for the given object class. 203 * @param clazz the base object class 204 * @return New empty {@link PropertyMap}. 205 */ 206 public static PropertyMap newMap(final Class<? extends ScriptObject> clazz) { 207 return new PropertyMap(EMPTY_HASHMAP, clazz.getName(), 0, 0, 0, false); 208 } 209 210 /** 211 * Return a sharable empty map. 212 * 213 * @return New empty {@link PropertyMap}. 214 */ 215 public static PropertyMap newMap() { 216 return newMap(JO.class); 217 } 218 219 /** 220 * Return number of properties in the map. 221 * 222 * @return Number of properties. 223 */ 224 public int size() { 225 return properties.size(); 226 } 227 228 /** 229 * Get the listeners of this map, or null if none exists 230 * 231 * @return the listeners 232 */ 233 public PropertyListeners getListeners() { 234 return listeners; 235 } 236 237 /** 238 * Add {@code listenerMap} as a listener to this property map for the given {@code key}. 239 * 240 * @param key the property name 241 * @param listenerMap the listener map 242 */ 243 public void addListener(final String key, final PropertyMap listenerMap) { 244 if (listenerMap != this) { 245 // We need to clone listener instance when adding a new listener since we share 246 // the listeners instance with our parent maps that don't need to see the new listener. 247 listeners = PropertyListeners.addListener(listeners, key, listenerMap); 248 } 249 } 250 251 /** 252 * A new property is being added. 253 * 254 * @param property The new Property added. 255 */ 256 public void propertyAdded(final Property property) { 257 invalidateProtoGetSwitchPoint(property); 258 if (listeners != null) { 259 listeners.propertyAdded(property); 260 } 261 } 262 263 /** 264 * An existing property is being deleted. 265 * 266 * @param property The property being deleted. 267 */ 268 public void propertyDeleted(final Property property) { 269 invalidateProtoGetSwitchPoint(property); 270 if (listeners != null) { 271 listeners.propertyDeleted(property); 272 } 273 } 274 275 /** 276 * An existing property is being redefined. 277 * 278 * @param oldProperty The old property 279 * @param newProperty The new property 280 */ 281 public void propertyModified(final Property oldProperty, final Property newProperty) { 282 invalidateProtoGetSwitchPoint(oldProperty); 283 if (listeners != null) { 284 listeners.propertyModified(oldProperty, newProperty); 285 } 286 } 287 288 /** 289 * The prototype of an object associated with this {@link PropertyMap} is changed. 290 */ 291 public void protoChanged() { 292 invalidateAllProtoGetSwitchPoints(); 293 if (listeners != null) { 294 listeners.protoChanged(); 295 } 296 } 297 298 /** 299 * Return a SwitchPoint used to track changes of a property in a prototype. 300 * 301 * @param key Property key. 302 * @return A shared {@link SwitchPoint} for the property. 303 */ 304 public synchronized SwitchPoint getSwitchPoint(final String key) { 305 if (protoGetSwitches == null) { 306 protoGetSwitches = new HashMap<>(); 307 } 308 309 SwitchPoint switchPoint = protoGetSwitches.get(key); 310 if (switchPoint == null) { 311 switchPoint = new SwitchPoint(); 312 protoGetSwitches.put(key, switchPoint); 313 } 314 315 return switchPoint; 316 } 317 318 /** 319 * Indicate that a prototype property has changed. 320 * 321 * @param property {@link Property} to invalidate. 322 */ 323 synchronized void invalidateProtoGetSwitchPoint(final Property property) { 324 if (protoGetSwitches != null) { 325 326 final String key = property.getKey(); 327 final SwitchPoint sp = protoGetSwitches.get(key); 328 if (sp != null) { 329 protoGetSwitches.remove(key); 330 if (Context.DEBUG) { 331 protoInvalidations++; 332 } 333 SwitchPoint.invalidateAll(new SwitchPoint[] { sp }); 334 } 335 } 336 } 337 338 /** 339 * Indicate that proto itself has changed in hierarchy somewhere. 340 */ 341 synchronized void invalidateAllProtoGetSwitchPoints() { 342 if (protoGetSwitches != null) { 343 final int size = protoGetSwitches.size(); 344 if (size > 0) { 345 if (Context.DEBUG) { 346 protoInvalidations += size; 347 } 348 SwitchPoint.invalidateAll(protoGetSwitches.values().toArray(new SwitchPoint[size])); 349 protoGetSwitches.clear(); 350 } 351 } 352 } 353 354 /** 355 * Add a property to the map, re-binding its getters and setters, 356 * if available, to a given receiver. This is typically the global scope. See 357 * {@link ScriptObject#addBoundProperties(ScriptObject)} 358 * 359 * @param property {@link Property} being added. 360 * @param bindTo Object to bind to. 361 * 362 * @return New {@link PropertyMap} with {@link Property} added. 363 */ 364 PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) { 365 // No need to store bound property in the history as bound properties can't be reused. 366 return addPropertyNoHistory(new AccessorProperty(property, bindTo)); 367 } 368 369 // Get a logical slot index for a property, with spill slot 0 starting at fieldMaximum. 370 private int logicalSlotIndex(final Property property) { 371 final int slot = property.getSlot(); 372 if (slot < 0) { 373 return -1; 374 } 375 return property.isSpill() ? slot + fieldMaximum : slot; 376 } 377 378 // Update boundaries and flags after a property has been added 379 private void updateFlagsAndBoundaries(final Property newProperty) { 380 if(newProperty.isSpill()) { 381 spillLength = Math.max(spillLength, newProperty.getSlot() + 1); 382 } else { 383 fieldCount = Math.max(fieldCount, newProperty.getSlot() + 1); 384 } 385 if (isValidArrayIndex(getArrayIndex(newProperty.getKey()))) { 386 setContainsArrayKeys(); 387 } 388 } 389 390 // Update the free slots bitmap for a property that has been deleted and/or added. This method is not synchronized 391 // as it is always invoked on a newly created instance. 392 private void updateFreeSlots(final Property oldProperty, final Property newProperty) { 393 // Free slots bitset is possibly shared with parent map, so we must clone it before making modifications. 394 boolean freeSlotsCloned = false; 395 if (oldProperty != null) { 396 final int slotIndex = logicalSlotIndex(oldProperty); 397 if (slotIndex >= 0) { 398 final BitSet newFreeSlots = freeSlots == null ? new BitSet() : (BitSet)freeSlots.clone(); 399 assert !newFreeSlots.get(slotIndex); 400 newFreeSlots.set(slotIndex); 401 freeSlots = newFreeSlots; 402 freeSlotsCloned = true; 403 } 404 } 405 if (freeSlots != null && newProperty != null) { 406 final int slotIndex = logicalSlotIndex(newProperty); 407 if (slotIndex > -1 && freeSlots.get(slotIndex)) { 408 final BitSet newFreeSlots = freeSlotsCloned ? freeSlots : ((BitSet)freeSlots.clone()); 409 newFreeSlots.clear(slotIndex); 410 freeSlots = newFreeSlots.isEmpty() ? null : newFreeSlots; 411 } 412 } 413 } 414 415 /** 416 * Add a property to the map without adding it to the history. This should be used for properties that 417 * can't be shared such as bound properties, or properties that are expected to be added only once. 418 * 419 * @param property {@link Property} being added. 420 * @return New {@link PropertyMap} with {@link Property} added. 421 */ 422 public PropertyMap addPropertyNoHistory(final Property property) { 423 if (listeners != null) { 424 listeners.propertyAdded(property); 425 } 426 final PropertyHashMap newProperties = properties.immutableAdd(property); 427 final PropertyMap newMap = new PropertyMap(this, newProperties); 428 newMap.updateFlagsAndBoundaries(property); 429 newMap.updateFreeSlots(null, property); 430 431 return newMap; 432 } 433 434 /** 435 * Add a property to the map. Cloning or using an existing map if available. 436 * 437 * @param property {@link Property} being added. 438 * 439 * @return New {@link PropertyMap} with {@link Property} added. 440 */ 441 public synchronized PropertyMap addProperty(final Property property) { 442 if (listeners != null) { 443 listeners.propertyAdded(property); 444 } 445 PropertyMap newMap = checkHistory(property); 446 447 if (newMap == null) { 448 final PropertyHashMap newProperties = properties.immutableAdd(property); 449 newMap = new PropertyMap(this, newProperties); 450 newMap.updateFlagsAndBoundaries(property); 451 newMap.updateFreeSlots(null, property); 452 addToHistory(property, newMap); 453 } 454 455 return newMap; 456 } 457 458 /** 459 * Remove a property from a map. Cloning or using an existing map if available. 460 * 461 * @param property {@link Property} being removed. 462 * 463 * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found. 464 */ 465 public synchronized PropertyMap deleteProperty(final Property property) { 466 if (listeners != null) { 467 listeners.propertyDeleted(property); 468 } 469 PropertyMap newMap = checkHistory(property); 470 final String key = property.getKey(); 471 472 if (newMap == null && properties.containsKey(key)) { 473 final PropertyHashMap newProperties = properties.immutableRemove(key); 474 final boolean isSpill = property.isSpill(); 475 final int slot = property.getSlot(); 476 // If deleted property was last field or spill slot we can make it reusable by reducing field/slot count. 477 // Otherwise mark it as free in free slots bitset. 478 if (isSpill && slot >= 0 && slot == spillLength - 1) { 479 newMap = new PropertyMap(newProperties, className, fieldCount, fieldMaximum, spillLength - 1, containsArrayKeys()); 480 newMap.freeSlots = freeSlots; 481 } else if (!isSpill && slot >= 0 && slot == fieldCount - 1) { 482 newMap = new PropertyMap(newProperties, className, fieldCount - 1, fieldMaximum, spillLength, containsArrayKeys()); 483 newMap.freeSlots = freeSlots; 484 } else { 485 newMap = new PropertyMap(this, newProperties); 486 newMap.updateFreeSlots(property, null); 487 } 488 addToHistory(property, newMap); 489 } 490 491 return newMap; 492 } 493 494 /** 495 * Replace an existing property with a new one. 496 * 497 * @param oldProperty Property to replace. 498 * @param newProperty New {@link Property}. 499 * 500 * @return New {@link PropertyMap} with {@link Property} replaced. 501 */ 502 public PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) { 503 if (listeners != null) { 504 listeners.propertyModified(oldProperty, newProperty); 505 } 506 // Add replaces existing property. 507 final PropertyHashMap newProperties = properties.immutableReplace(oldProperty, newProperty); 508 final PropertyMap newMap = new PropertyMap(this, newProperties); 509 /* 510 * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods. 511 * 512 * This replaceProperty method is called only for the following three cases: 513 * 514 * 1. To change flags OR TYPE of an old (cloned) property. We use the same spill slots. 515 * 2. To change one UserAccessor property with another - user getter or setter changed via 516 * Object.defineProperty function. Again, same spill slots are re-used. 517 * 3. Via ScriptObject.setUserAccessors method to set user getter and setter functions 518 * replacing the dummy AccessorProperty with null method handles (added during map init). 519 * 520 * In case (1) and case(2), the property type of old and new property is same. For case (3), 521 * the old property is an AccessorProperty and the new one is a UserAccessorProperty property. 522 */ 523 524 final boolean sameType = oldProperty.getClass() == newProperty.getClass(); 525 assert sameType || 526 oldProperty instanceof AccessorProperty && 527 newProperty instanceof UserAccessorProperty : 528 "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]"; 529 530 newMap.flags = flags; 531 532 /* 533 * spillLength remains same in case (1) and (2) because of slot reuse. Only for case (3), we need 534 * to add spill count of the newly added UserAccessorProperty property. 535 */ 536 if (!sameType) { 537 newMap.spillLength = Math.max(spillLength, newProperty.getSlot() + 1); 538 newMap.updateFreeSlots(oldProperty, newProperty); 539 } 540 return newMap; 541 } 542 543 /** 544 * Make a new UserAccessorProperty property. getter and setter functions are stored in 545 * this ScriptObject and slot values are used in property object. Note that slots 546 * are assigned speculatively and should be added to map before adding other 547 * properties. 548 * 549 * @param key the property name 550 * @param propertyFlags attribute flags of the property 551 * @return the newly created UserAccessorProperty 552 */ 553 public UserAccessorProperty newUserAccessors(final String key, final int propertyFlags) { 554 return new UserAccessorProperty(key, propertyFlags, getFreeSpillSlot()); 555 } 556 557 /** 558 * Find a property in the map. 559 * 560 * @param key Key to search for. 561 * 562 * @return {@link Property} matching key. 563 */ 564 public Property findProperty(final String key) { 565 return properties.find(key); 566 } 567 568 /** 569 * Adds all map properties from another map. 570 * 571 * @param other The source of properties. 572 * 573 * @return New {@link PropertyMap} with added properties. 574 */ 575 public PropertyMap addAll(final PropertyMap other) { 576 assert this != other : "adding property map to itself"; 577 final Property[] otherProperties = other.properties.getProperties(); 578 final PropertyHashMap newProperties = properties.immutableAdd(otherProperties); 579 580 final PropertyMap newMap = new PropertyMap(this, newProperties); 581 for (final Property property : otherProperties) { 582 // This method is only safe to use with non-slotted, native getter/setter properties 583 assert property.getSlot() == -1; 584 assert !(isValidArrayIndex(getArrayIndex(property.getKey()))); 585 } 586 587 return newMap; 588 } 589 590 /** 591 * Return an array of all properties. 592 * 593 * @return Properties as an array. 594 */ 595 public Property[] getProperties() { 596 return properties.getProperties(); 597 } 598 599 /** 600 * Return the name of the class of objects using this property map. 601 * 602 * @return class name of owner objects. 603 */ 604 public String getClassName() { 605 return className; 606 } 607 608 /** 609 * Prevents the map from having additional properties. 610 * 611 * @return New map with {@link #NOT_EXTENSIBLE} flag set. 612 */ 613 PropertyMap preventExtensions() { 614 final PropertyMap newMap = new PropertyMap(this); 615 newMap.flags |= NOT_EXTENSIBLE; 616 return newMap; 617 } 618 619 /** 620 * Prevents properties in map from being modified. 621 * 622 * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with 623 * {@link Property#NOT_CONFIGURABLE} set. 624 */ 625 PropertyMap seal() { 626 PropertyHashMap newProperties = EMPTY_HASHMAP; 627 628 for (final Property oldProperty : properties.getProperties()) { 629 newProperties = newProperties.immutableAdd(oldProperty.addFlags(Property.NOT_CONFIGURABLE)); 630 } 631 632 final PropertyMap newMap = new PropertyMap(this, newProperties); 633 newMap.flags |= NOT_EXTENSIBLE; 634 635 return newMap; 636 } 637 638 /** 639 * Prevents properties in map from being modified or written to. 640 * 641 * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with 642 * {@link Property#NOT_CONFIGURABLE} and {@link Property#NOT_WRITABLE} set. 643 */ 644 PropertyMap freeze() { 645 PropertyHashMap newProperties = EMPTY_HASHMAP; 646 647 for (final Property oldProperty : properties.getProperties()) { 648 int propertyFlags = Property.NOT_CONFIGURABLE; 649 650 if (!(oldProperty instanceof UserAccessorProperty)) { 651 propertyFlags |= Property.NOT_WRITABLE; 652 } 653 654 newProperties = newProperties.immutableAdd(oldProperty.addFlags(propertyFlags)); 655 } 656 657 final PropertyMap newMap = new PropertyMap(this, newProperties); 658 newMap.flags |= NOT_EXTENSIBLE; 659 660 return newMap; 661 } 662 663 /** 664 * Check for any configurable properties. 665 * 666 * @return {@code true} if any configurable. 667 */ 668 private boolean anyConfigurable() { 669 for (final Property property : properties.getProperties()) { 670 if (property.isConfigurable()) { 671 return true; 672 } 673 } 674 675 return false; 676 } 677 678 /** 679 * Check if all properties are frozen. 680 * 681 * @return {@code true} if all are frozen. 682 */ 683 private boolean allFrozen() { 684 for (final Property property : properties.getProperties()) { 685 // check if it is a data descriptor 686 if (!(property instanceof UserAccessorProperty)) { 687 if (property.isWritable()) { 688 return false; 689 } 690 } 691 if (property.isConfigurable()) { 692 return false; 693 } 694 } 695 696 return true; 697 } 698 699 /** 700 * Check prototype history for an existing property map with specified prototype. 701 * 702 * @param proto New prototype object. 703 * 704 * @return Existing {@link PropertyMap} or {@code null} if not found. 705 */ 706 private PropertyMap checkProtoHistory(final ScriptObject proto) { 707 final PropertyMap cachedMap; 708 if (protoHistory != null) { 709 final SoftReference<PropertyMap> weakMap = protoHistory.get(proto); 710 cachedMap = (weakMap != null ? weakMap.get() : null); 711 } else { 712 cachedMap = null; 713 } 714 715 if (Context.DEBUG && cachedMap != null) { 716 protoHistoryHit++; 717 } 718 719 return cachedMap; 720 } 721 722 /** 723 * Add a map to the prototype history. 724 * 725 * @param newProto Prototype to add (key.) 726 * @param newMap {@link PropertyMap} associated with prototype. 727 */ 728 private void addToProtoHistory(final ScriptObject newProto, final PropertyMap newMap) { 729 if (protoHistory == null) { 730 protoHistory = new WeakHashMap<>(); 731 } 732 733 protoHistory.put(newProto, new SoftReference<>(newMap)); 734 } 735 736 /** 737 * Track the modification of the map. 738 * 739 * @param property Mapping property. 740 * @param newMap Modified {@link PropertyMap}. 741 */ 742 private void addToHistory(final Property property, final PropertyMap newMap) { 743 if (history == null) { 744 history = new WeakHashMap<>(); 745 } 746 747 history.put(property, new SoftReference<>(newMap)); 748 } 749 750 /** 751 * Check the history for a map that already has the given property added. 752 * 753 * @param property {@link Property} to add. 754 * 755 * @return Existing map or {@code null} if not found. 756 */ 757 private PropertyMap checkHistory(final Property property) { 758 759 if (history != null) { 760 final SoftReference<PropertyMap> ref = history.get(property); 761 final PropertyMap historicMap = ref == null ? null : ref.get(); 762 763 if (historicMap != null) { 764 if (Context.DEBUG) { 765 historyHit++; 766 } 767 768 return historicMap; 769 } 770 } 771 772 return null; 773 } 774 775 /** 776 * Returns true if the two maps have identical properties in the same order, but allows the properties to differ in 777 * their types. This method is mostly useful for tests. 778 * @param otherMap the other map 779 * @return true if this map has identical properties in the same order as the other map, allowing the properties to 780 * differ in type. 781 */ 782 public boolean equalsWithoutType(final PropertyMap otherMap) { 783 if (properties.size() != otherMap.properties.size()) { 784 return false; 785 } 786 787 final Iterator<Property> iter = properties.values().iterator(); 788 final Iterator<Property> otherIter = otherMap.properties.values().iterator(); 789 790 while (iter.hasNext() && otherIter.hasNext()) { 791 if (!iter.next().equalsWithoutType(otherIter.next())) { 792 return false; 793 } 794 } 795 796 return true; 797 } 798 799 @Override 800 public String toString() { 801 final StringBuilder sb = new StringBuilder(); 802 803 sb.append(Debug.id(this)); 804 sb.append(" = {\n"); 805 806 for (final Property property : getProperties()) { 807 sb.append('\t'); 808 sb.append(property); 809 sb.append('\n'); 810 } 811 812 sb.append('}'); 813 814 return sb.toString(); 815 } 816 817 @Override 818 public Iterator<Object> iterator() { 819 return new PropertyMapIterator(this); 820 } 821 822 /** 823 * Check if this map contains properties with valid array keys 824 * 825 * @return {@code true} if this map contains properties with valid array keys 826 */ 827 public final boolean containsArrayKeys() { 828 return (flags & CONTAINS_ARRAY_KEYS) != 0; 829 } 830 831 /** 832 * Flag this object as having array keys in defined properties 833 */ 834 private void setContainsArrayKeys() { 835 flags |= CONTAINS_ARRAY_KEYS; 836 } 837 838 /** 839 * Test to see if {@link PropertyMap} is extensible. 840 * 841 * @return {@code true} if {@link PropertyMap} can be added to. 842 */ 843 boolean isExtensible() { 844 return (flags & NOT_EXTENSIBLE) == 0; 845 } 846 847 /** 848 * Test to see if {@link PropertyMap} is not extensible or any properties 849 * can not be modified. 850 * 851 * @return {@code true} if {@link PropertyMap} is sealed. 852 */ 853 boolean isSealed() { 854 return !isExtensible() && !anyConfigurable(); 855 } 856 857 /** 858 * Test to see if {@link PropertyMap} is not extensible or all properties 859 * can not be modified. 860 * 861 * @return {@code true} if {@link PropertyMap} is frozen. 862 */ 863 boolean isFrozen() { 864 return !isExtensible() && allFrozen(); 865 } 866 867 /** 868 * Return a free field slot for this map, or {@code -1} if none is available. 869 * 870 * @return free field slot or -1 871 */ 872 int getFreeFieldSlot() { 873 if (freeSlots != null) { 874 final int freeSlot = freeSlots.nextSetBit(0); 875 if (freeSlot > -1 && freeSlot < fieldMaximum) { 876 return freeSlot; 877 } 878 } 879 if (fieldCount < fieldMaximum) { 880 return fieldCount; 881 } 882 return -1; 883 } 884 885 /** 886 * Get a free spill slot for this map. 887 * 888 * @return free spill slot 889 */ 890 int getFreeSpillSlot() { 891 if (freeSlots != null) { 892 final int freeSlot = freeSlots.nextSetBit(fieldMaximum); 893 if (freeSlot > -1) { 894 return freeSlot - fieldMaximum; 895 } 896 } 897 return spillLength; 898 } 899 900 /** 901 * Return a property map with the same layout that is associated with the new prototype object. 902 * 903 * @param newProto New prototype object to replace oldProto. 904 * @return New {@link PropertyMap} with prototype changed. 905 */ 906 public synchronized PropertyMap changeProto(final ScriptObject newProto) { 907 final PropertyMap nextMap = checkProtoHistory(newProto); 908 if (nextMap != null) { 909 return nextMap; 910 } 911 912 if (Context.DEBUG) { 913 setProtoNewMapCount++; 914 } 915 916 final PropertyMap newMap = new PropertyMap(this); 917 addToProtoHistory(newProto, newMap); 918 919 return newMap; 920 } 921 922 923 /** 924 * {@link PropertyMap} iterator. 925 */ 926 private static class PropertyMapIterator implements Iterator<Object> { 927 /** Property iterator. */ 928 final Iterator<Property> iter; 929 930 /** Current Property. */ 931 Property property; 932 933 /** 934 * Constructor. 935 * 936 * @param propertyMap {@link PropertyMap} to iterate over. 937 */ 938 PropertyMapIterator(final PropertyMap propertyMap) { 939 iter = Arrays.asList(propertyMap.properties.getProperties()).iterator(); 940 property = iter.hasNext() ? iter.next() : null; 941 skipNotEnumerable(); 942 } 943 944 /** 945 * Ignore properties that are not enumerable. 946 */ 947 private void skipNotEnumerable() { 948 while (property != null && !property.isEnumerable()) { 949 property = iter.hasNext() ? iter.next() : null; 950 } 951 } 952 953 @Override 954 public boolean hasNext() { 955 return property != null; 956 } 957 958 @Override 959 public Object next() { 960 if (property == null) { 961 throw new NoSuchElementException(); 962 } 963 964 final Object key = property.getKey(); 965 property = iter.next(); 966 skipNotEnumerable(); 967 968 return key; 969 } 970 971 @Override 972 public void remove() { 973 throw new UnsupportedOperationException("remove"); 974 } 975 } 976 977 /* 978 * Debugging and statistics. 979 */ 980 981 /** 982 * Debug helper function that returns the diff of two property maps, only 983 * displaying the information that is different and in which map it exists 984 * compared to the other map. Can be used to e.g. debug map guards and 985 * investigate why they fail, causing relink 986 * 987 * @param map0 the first property map 988 * @param map1 the second property map 989 * 990 * @return property map diff as string 991 */ 992 public static String diff(final PropertyMap map0, final PropertyMap map1) { 993 final StringBuilder sb = new StringBuilder(); 994 995 if (map0 != map1) { 996 sb.append(">>> START: Map diff"); 997 boolean found = false; 998 999 for (final Property p : map0.getProperties()) { 1000 final Property p2 = map1.findProperty(p.getKey()); 1001 if (p2 == null) { 1002 sb.append("FIRST ONLY : [").append(p).append("]"); 1003 found = true; 1004 } else if (p2 != p) { 1005 sb.append("DIFFERENT : [").append(p).append("] != [").append(p2).append("]"); 1006 found = true; 1007 } 1008 } 1009 1010 for (final Property p2 : map1.getProperties()) { 1011 final Property p1 = map0.findProperty(p2.getKey()); 1012 if (p1 == null) { 1013 sb.append("SECOND ONLY: [").append(p2).append("]"); 1014 found = true; 1015 } 1016 } 1017 1018 //assert found; 1019 1020 if (!found) { 1021 sb.append(map0). 1022 append("!="). 1023 append(map1); 1024 } 1025 1026 sb.append("<<< END: Map diff\n"); 1027 } 1028 1029 return sb.toString(); 1030 } 1031 1032 // counters updated only in debug mode 1033 private static int count; 1034 private static int clonedCount; 1035 private static int historyHit; 1036 private static int protoInvalidations; 1037 private static int protoHistoryHit; 1038 private static int setProtoNewMapCount; 1039 1040 /** 1041 * @return Total number of maps. 1042 */ 1043 public static int getCount() { 1044 return count; 1045 } 1046 1047 /** 1048 * @return The number of maps that were cloned. 1049 */ 1050 public static int getClonedCount() { 1051 return clonedCount; 1052 } 1053 1054 /** 1055 * @return The number of times history was successfully used. 1056 */ 1057 public static int getHistoryHit() { 1058 return historyHit; 1059 } 1060 1061 /** 1062 * @return The number of times prototype changes caused invalidation. 1063 */ 1064 public static int getProtoInvalidations() { 1065 return protoInvalidations; 1066 } 1067 1068 /** 1069 * @return The number of times proto history was successfully used. 1070 */ 1071 public static int getProtoHistoryHit() { 1072 return protoHistoryHit; 1073 } 1074 1075 /** 1076 * @return The number of times prototypes were modified. 1077 */ 1078 public static int getSetProtoNewMapCount() { 1079 return setProtoNewMapCount; 1080 } 1081} 1082