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