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