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