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