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 assert this != other : "adding property map to itself"; 387 final Property[] otherProperties = other.properties.getProperties(); 388 final PropertyHashMap newProperties = properties.immutableAdd(otherProperties); 389 390 final PropertyMap newMap = new PropertyMap(this, newProperties); 391 for (final Property property : otherProperties) { 392 newMap.spillLength += property.getSpillCount(); 393 } 394 395 return newMap; 396 } 397 398 /** 399 * Return an array of all properties. 400 * 401 * @return Properties as an array. 402 */ 403 public Property[] getProperties() { 404 return properties.getProperties(); 405 } 406 407 /** 408 * Prevents the map from having additional properties. 409 * 410 * @return New map with {@link #NOT_EXTENSIBLE} flag set. 411 */ 412 PropertyMap preventExtensions() { 413 final PropertyMap newMap = new PropertyMap(this, this.properties); 414 newMap.flags |= NOT_EXTENSIBLE; 415 return newMap; 416 } 417 418 /** 419 * Prevents properties in map from being modified. 420 * 421 * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with 422 * {@link Property#NOT_CONFIGURABLE} set. 423 */ 424 PropertyMap seal() { 425 PropertyHashMap newProperties = EMPTY_MAP; 426 427 for (final Property oldProperty : properties.getProperties()) { 428 newProperties = newProperties.immutableAdd(oldProperty.addFlags(Property.NOT_CONFIGURABLE)); 429 } 430 431 final PropertyMap newMap = new PropertyMap(this, newProperties); 432 newMap.flags |= NOT_EXTENSIBLE; 433 434 return newMap; 435 } 436 437 /** 438 * Prevents properties in map from being modified or written to. 439 * 440 * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with 441 * {@link Property#NOT_CONFIGURABLE} and {@link Property#NOT_WRITABLE} set. 442 */ 443 PropertyMap freeze() { 444 PropertyHashMap newProperties = EMPTY_MAP; 445 446 for (Property oldProperty : properties.getProperties()) { 447 int propertyFlags = Property.NOT_CONFIGURABLE; 448 449 if (!(oldProperty instanceof UserAccessorProperty)) { 450 propertyFlags |= Property.NOT_WRITABLE; 451 } 452 453 newProperties = newProperties.immutableAdd(oldProperty.addFlags(propertyFlags)); 454 } 455 456 final PropertyMap newMap = new PropertyMap(this, newProperties); 457 newMap.flags |= NOT_EXTENSIBLE; 458 459 return newMap; 460 } 461 462 /** 463 * Check for any configurable properties. 464 * 465 * @return {@code true} if any configurable. 466 */ 467 private boolean anyConfigurable() { 468 for (final Property property : properties.getProperties()) { 469 if (property.isConfigurable()) { 470 return true; 471 } 472 } 473 474 return false; 475 } 476 477 /** 478 * Check if all properties are frozen. 479 * 480 * @return {@code true} if all are frozen. 481 */ 482 private boolean allFrozen() { 483 for (final Property property : properties.getProperties()) { 484 // check if it is a data descriptor 485 if (!(property instanceof UserAccessorProperty)) { 486 if (property.isWritable()) { 487 return false; 488 } 489 } 490 if (property.isConfigurable()) { 491 return false; 492 } 493 } 494 495 return true; 496 } 497 498 /** 499 * Check prototype history for an existing property map with specified prototype. 500 * 501 * @param newProto New prototype object. 502 * 503 * @return Existing {@link PropertyMap} or {@code null} if not found. 504 */ 505 private PropertyMap checkProtoHistory(final ScriptObject newProto) { 506 final PropertyMap cachedMap; 507 if (protoHistory != null) { 508 final WeakReference<PropertyMap> weakMap = protoHistory.get(newProto); 509 cachedMap = (weakMap != null ? weakMap.get() : null); 510 } else { 511 cachedMap = null; 512 } 513 514 if (Context.DEBUG && cachedMap != null) { 515 protoHistoryHit++; 516 } 517 518 return cachedMap; 519 } 520 521 /** 522 * Add a map to the prototype history. 523 * 524 * @param newProto Prototype to add (key.) 525 * @param newMap {@link PropertyMap} associated with prototype. 526 */ 527 private void addToProtoHistory(final ScriptObject newProto, final PropertyMap newMap) { 528 if (protoHistory == null) { 529 protoHistory = new WeakHashMap<>(); 530 } 531 532 protoHistory.put(newProto, new WeakReference<>(newMap)); 533 } 534 535 /** 536 * Track the modification of the map. 537 * 538 * @param property Mapping property. 539 * @param newMap Modified {@link PropertyMap}. 540 */ 541 private void addToHistory(final Property property, final PropertyMap newMap) { 542 if (history == null) { 543 history = new LinkedHashMap<>(); 544 } 545 546 history.put(property, newMap); 547 } 548 549 /** 550 * Check the history for a map that already has the given property added. 551 * 552 * @param property {@link Property} to add. 553 * 554 * @return Existing map or {@code null} if not found. 555 */ 556 private PropertyMap checkHistory(final Property property) { 557 if (history != null) { 558 PropertyMap historicMap = history.get(property); 559 560 if (historicMap != null) { 561 if (Context.DEBUG) { 562 historyHit++; 563 } 564 565 return historicMap; 566 } 567 } 568 569 return null; 570 } 571 572 /** 573 * Calculate the hash code for the map. 574 * 575 * @return Computed hash code. 576 */ 577 private int computeHashCode() { 578 int hash = structure.hashCode(); 579 580 if (proto != null) { 581 hash ^= proto.hashCode(); 582 } 583 584 for (final Property property : getProperties()) { 585 hash = hash << 7 ^ hash >> 7; 586 hash ^= property.hashCode(); 587 } 588 589 return hash; 590 } 591 592 @Override 593 public int hashCode() { 594 return hashCode; 595 } 596 597 @Override 598 public boolean equals(final Object other) { 599 if (!(other instanceof PropertyMap)) { 600 return false; 601 } 602 603 final PropertyMap otherMap = (PropertyMap)other; 604 605 if (structure != otherMap.structure || 606 proto != otherMap.proto || 607 properties.size() != otherMap.properties.size()) { 608 return false; 609 } 610 611 final Iterator<Property> iter = properties.values().iterator(); 612 final Iterator<Property> otherIter = otherMap.properties.values().iterator(); 613 614 while (iter.hasNext() && otherIter.hasNext()) { 615 if (!iter.next().equals(otherIter.next())) { 616 return false; 617 } 618 } 619 620 return true; 621 } 622 623 @Override 624 public String toString() { 625 final StringBuilder sb = new StringBuilder(); 626 627 sb.append(" ["); 628 boolean isFirst = true; 629 630 for (final Property property : properties.values()) { 631 if (!isFirst) { 632 sb.append(", "); 633 } 634 635 isFirst = false; 636 637 sb.append(ScriptRuntime.safeToString(property.getKey())); 638 final Class<?> ctype = property.getCurrentType(); 639 sb.append(" <"). 640 append(property.getClass().getSimpleName()). 641 append(':'). 642 append(ctype == null ? 643 "undefined" : 644 ctype.getSimpleName()). 645 append('>'); 646 } 647 648 sb.append(']'); 649 650 return sb.toString(); 651 } 652 653 @Override 654 public Iterator<Object> iterator() { 655 return new PropertyMapIterator(this); 656 } 657 658 /** 659 * Return map's {@link Context}. 660 * 661 * @return The {@link Context} where the map originated. 662 */ 663 Context getContext() { 664 return context; 665 } 666 667 /** 668 * Check if this map is a prototype 669 * 670 * @return {@code true} if is prototype 671 */ 672 public boolean isPrototype() { 673 return (flags & IS_PROTOTYPE) != 0; 674 } 675 676 /** 677 * Flag this map as having a prototype. 678 */ 679 private void setIsPrototype() { 680 flags |= IS_PROTOTYPE; 681 } 682 683 /** 684 * Check whether a {@link PropertyListener} has been added to this map. 685 * 686 * @return {@code true} if {@link PropertyListener} exists 687 */ 688 public boolean isListenerAdded() { 689 return (flags & IS_LISTENER_ADDED) != 0; 690 } 691 692 /** 693 * Test to see if {@link PropertyMap} is extensible. 694 * 695 * @return {@code true} if {@link PropertyMap} can be added to. 696 */ 697 boolean isExtensible() { 698 return (flags & NOT_EXTENSIBLE) == 0; 699 } 700 701 /** 702 * Test to see if {@link PropertyMap} is not extensible or any properties 703 * can not be modified. 704 * 705 * @return {@code true} if {@link PropertyMap} is sealed. 706 */ 707 boolean isSealed() { 708 return !isExtensible() && !anyConfigurable(); 709 } 710 711 /** 712 * Test to see if {@link PropertyMap} is not extensible or all properties 713 * can not be modified. 714 * 715 * @return {@code true} if {@link PropertyMap} is frozen. 716 */ 717 boolean isFrozen() { 718 return !isExtensible() && allFrozen(); 719 } 720 721 /** 722 * Get length of spill area associated with this {@link PropertyMap}. 723 * 724 * @return Length of spill area. 725 */ 726 int getSpillLength() { 727 return spillLength; 728 } 729 730 /** 731 * Return the prototype of objects associated with this {@link PropertyMap}. 732 * 733 * @return Prototype object. 734 */ 735 ScriptObject getProto() { 736 return proto; 737 } 738 739 /** 740 * Set the prototype of objects associated with this {@link PropertyMap}. 741 * 742 * @param newProto Prototype object to use. 743 * 744 * @return New {@link PropertyMap} with prototype changed. 745 */ 746 PropertyMap setProto(final ScriptObject newProto) { 747 final ScriptObject oldProto = this.proto; 748 749 if (oldProto == newProto) { 750 return this; 751 } 752 753 final PropertyMap nextMap = checkProtoHistory(newProto); 754 if (nextMap != null) { 755 return nextMap; 756 } 757 758 if (Context.DEBUG) { 759 incrementSetProtoNewMapCount(); 760 } 761 final PropertyMap newMap = new PropertyMap(this, this.properties); 762 addToProtoHistory(newProto, newMap); 763 764 newMap.proto = newProto; 765 766 if (oldProto != null && newMap.isListenerAdded()) { 767 oldProto.removePropertyListener(newMap); 768 } 769 770 if (newProto != null) { 771 newProto.getMap().setIsPrototype(); 772 } 773 774 return newMap; 775 } 776 777 /** 778 * Indicate that the map has listeners. 779 */ 780 private void setIsListenerAdded() { 781 flags |= IS_LISTENER_ADDED; 782 } 783 784 /** 785 * Return only the flags that should be copied during cloning. 786 * 787 * @return Subset of flags that should be copied. 788 */ 789 private int getClonedFlags() { 790 return flags & CLONEABLE_FLAGS_MASK; 791 } 792 793 /** 794 * {@link PropertyMap} iterator. 795 */ 796 private static class PropertyMapIterator implements Iterator<Object> { 797 /** Property iterator. */ 798 final Iterator<Property> iter; 799 800 /** Current Property. */ 801 Property property; 802 803 /** 804 * Constructor. 805 * 806 * @param propertyMap {@link PropertyMap} to iterate over. 807 */ 808 PropertyMapIterator(final PropertyMap propertyMap) { 809 iter = Arrays.asList(propertyMap.properties.getProperties()).iterator(); 810 property = iter.hasNext() ? iter.next() : null; 811 skipNotEnumerable(); 812 } 813 814 /** 815 * Ignore properties that are not enumerable. 816 */ 817 private void skipNotEnumerable() { 818 while (property != null && !property.isEnumerable()) { 819 property = iter.hasNext() ? iter.next() : null; 820 } 821 } 822 823 @Override 824 public boolean hasNext() { 825 return property != null; 826 } 827 828 @Override 829 public Object next() { 830 if (property == null) { 831 throw new NoSuchElementException(); 832 } 833 834 final Object key = property.getKey(); 835 property = iter.next(); 836 skipNotEnumerable(); 837 838 return key; 839 } 840 841 @Override 842 public void remove() { 843 throw new UnsupportedOperationException(); 844 } 845 } 846 847 /* 848 * PropertyListener implementation. 849 */ 850 851 @Override 852 public void propertyAdded(final ScriptObject object, final Property prop) { 853 invalidateProtoGetSwitchPoint(prop); 854 } 855 856 @Override 857 public void propertyDeleted(final ScriptObject object, final Property prop) { 858 invalidateProtoGetSwitchPoint(prop); 859 } 860 861 @Override 862 public void propertyModified(final ScriptObject object, final Property oldProp, final Property newProp) { 863 invalidateProtoGetSwitchPoint(oldProp); 864 } 865 866 /* 867 * Debugging and statistics. 868 */ 869 870 // counters updated only in debug mode 871 private static int count; 872 private static int clonedCount; 873 private static int historyHit; 874 private static int protoInvalidations; 875 private static int protoHistoryHit; 876 private static int setProtoNewMapCount; 877 878 /** 879 * @return Total number of maps. 880 */ 881 public static int getCount() { 882 return count; 883 } 884 885 /** 886 * @return The number of maps that were cloned. 887 */ 888 public static int getClonedCount() { 889 return clonedCount; 890 } 891 892 /** 893 * @return The number of times history was successfully used. 894 */ 895 public static int getHistoryHit() { 896 return historyHit; 897 } 898 899 /** 900 * @return The number of times prototype changes caused invalidation. 901 */ 902 public static int getProtoInvalidations() { 903 return protoInvalidations; 904 } 905 906 /** 907 * @return The number of times proto history was successfully used. 908 */ 909 public static int getProtoHistoryHit() { 910 return protoHistoryHit; 911 } 912 913 /** 914 * @return The number of times prototypes were modified. 915 */ 916 public static int getSetProtoNewMapCount() { 917 return setProtoNewMapCount; 918 } 919 920 /** 921 * Increment the prototype set count. 922 */ 923 private static void incrementSetProtoNewMapCount() { 924 setProtoNewMapCount++; 925 } 926 } 927