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 
--- EOF ---