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