PropertyMap.java revision 1178:82d1bb9324cf
1234287Sdim/*
2234287Sdim * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3234287Sdim * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4234287Sdim *
5234287Sdim * This code is free software; you can redistribute it and/or modify it
6234287Sdim * under the terms of the GNU General Public License version 2 only, as
7234287Sdim * published by the Free Software Foundation.  Oracle designates this
8234287Sdim * particular file as subject to the "Classpath" exception as provided
9234287Sdim * by Oracle in the LICENSE file that accompanied this code.
10234287Sdim *
11234287Sdim * This code is distributed in the hope that it will be useful, but WITHOUT
12234287Sdim * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13234287Sdim * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14234287Sdim * version 2 for more details (a copy is included in the LICENSE file that
15234287Sdim * accompanied this code).
16234287Sdim *
17234287Sdim * You should have received a copy of the GNU General Public License version
18234287Sdim * 2 along with this work; if not, write to the Free Software Foundation,
19234287Sdim * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20234287Sdim *
21234287Sdim * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22249423Sdim * or visit www.oracle.com if you need additional information or have any
23234287Sdim * questions.
24234287Sdim */
25234287Sdim
26234287Sdimpackage jdk.nashorn.internal.runtime;
27234287Sdim
28234287Sdimimport static jdk.nashorn.internal.runtime.PropertyHashMap.EMPTY_HASHMAP;
29234287Sdimimport static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex;
30234287Sdimimport static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex;
31234287Sdim
32234287Sdimimport java.io.IOException;
33234287Sdimimport java.io.ObjectInputStream;
34234287Sdimimport java.io.ObjectOutputStream;
35234287Sdimimport java.io.Serializable;
36249423Sdimimport java.lang.invoke.SwitchPoint;
37249423Sdimimport java.lang.ref.SoftReference;
38234287Sdimimport java.util.Arrays;
39234287Sdimimport java.util.BitSet;
40234287Sdimimport java.util.Collection;
41234287Sdimimport java.util.HashMap;
42234287Sdimimport java.util.Iterator;
43234287Sdimimport java.util.NoSuchElementException;
44234287Sdimimport java.util.WeakHashMap;
45234287Sdimimport jdk.nashorn.internal.scripts.JO;
46234287Sdim
47234287Sdim/**
48234287Sdim * Map of object properties. The PropertyMap is the "template" for JavaScript object
49243830Sdim * layouts. It contains a map with prototype names as keys and {@link Property} instances
50234287Sdim * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor
51234287Sdim * to form the seed map for the ScriptObject.
52234287Sdim * <p>
53234287Sdim * All property maps are immutable. If a property is added, modified or removed, the mutator
54234287Sdim * will return a new map.
55234287Sdim */
56234287Sdimpublic final class PropertyMap implements Iterable<Object>, Serializable {
57234287Sdim    /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */
58234287Sdim    public static final int NOT_EXTENSIBLE        = 0b0000_0001;
59234287Sdim    /** Does this map contain valid array keys? */
60249423Sdim    public static final int CONTAINS_ARRAY_KEYS   = 0b0000_0010;
61234287Sdim
62234287Sdim    /** Map status flags. */
63234287Sdim    private int flags;
64234287Sdim
65234287Sdim    /** Map of properties. */
66234287Sdim    private transient PropertyHashMap properties;
67234287Sdim
68234287Sdim    /** Number of fields in use. */
69234287Sdim    private int fieldCount;
70239462Sdim
71243830Sdim    /** Number of fields available. */
72234287Sdim    private final int fieldMaximum;
73234287Sdim
74234287Sdim    /** Length of spill in use. */
75234287Sdim    private int spillLength;
76234287Sdim
77234287Sdim    /** Structure class name */
78234287Sdim    private String className;
79239462Sdim
80234287Sdim    /** {@link SwitchPoint}s for gets on inherited properties. */
81234287Sdim    private transient HashMap<String, SwitchPoint> protoGetSwitches;
82234287Sdim
83234287Sdim    /** History of maps, used to limit map duplication. */
84239462Sdim    private transient WeakHashMap<Property, SoftReference<PropertyMap>> history;
85239462Sdim
86234287Sdim    /** History of prototypes, used to limit map duplication. */
87234287Sdim    private transient WeakHashMap<ScriptObject, SoftReference<PropertyMap>> protoHistory;
88234287Sdim
89234287Sdim    /** property listeners */
90234287Sdim    private transient PropertyListeners listeners;
91234287Sdim
92239462Sdim    private transient BitSet freeSlots;
93239462Sdim
94234287Sdim    private static final long serialVersionUID = -7041836752008732533L;
95239462Sdim
96239462Sdim    /**
97249423Sdim     * Constructor.
98249423Sdim     *
99249423Sdim     * @param properties   A {@link PropertyHashMap} with initial contents.
100249423Sdim     * @param fieldCount   Number of fields in use.
101249423Sdim     * @param fieldMaximum Number of fields available.
102249423Sdim     * @param spillLength  Number of spill slots used.
103249423Sdim     * @param containsArrayKeys True if properties contain numeric keys
104234287Sdim     */
105234287Sdim    private PropertyMap(final PropertyHashMap properties, final String className, final int fieldCount,
106234287Sdim                        final int fieldMaximum, final int spillLength, final boolean containsArrayKeys) {
107234287Sdim        this.properties   = properties;
108234287Sdim        this.className    = className;
109234287Sdim        this.fieldCount   = fieldCount;
110234287Sdim        this.fieldMaximum = fieldMaximum;
111249423Sdim        this.spillLength  = spillLength;
112249423Sdim        if (containsArrayKeys) {
113239462Sdim            setContainsArrayKeys();
114249423Sdim        }
115249423Sdim
116249423Sdim        if (Context.DEBUG) {
117249423Sdim            count++;
118249423Sdim        }
119249423Sdim    }
120249423Sdim
121249423Sdim    /**
122249423Sdim     * Cloning constructor.
123249423Sdim     *
124249423Sdim     * @param propertyMap Existing property map.
125249423Sdim     * @param properties  A {@link PropertyHashMap} with a new set of properties.
126249423Sdim     */
127249423Sdim    private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties) {
128234287Sdim        this.properties   = properties;
129234287Sdim        this.flags        = propertyMap.flags;
130234287Sdim        this.spillLength  = propertyMap.spillLength;
131234287Sdim        this.fieldCount   = propertyMap.fieldCount;
132234287Sdim        this.fieldMaximum = propertyMap.fieldMaximum;
133234287Sdim        // We inherit the parent property listeners instance. It will be cloned when a new listener is added.
134234287Sdim        this.listeners    = propertyMap.listeners;
135234287Sdim        this.freeSlots    = propertyMap.freeSlots;
136234287Sdim
137234287Sdim        if (Context.DEBUG) {
138234287Sdim            count++;
139234287Sdim            clonedCount++;
140234287Sdim        }
141239462Sdim    }
142243830Sdim
143234287Sdim    /**
144234287Sdim     * Cloning constructor.
145234287Sdim     *
146239462Sdim     * @param propertyMap Existing property map.
147234287Sdim      */
148234287Sdim    private PropertyMap(final PropertyMap propertyMap) {
149234287Sdim        this(propertyMap, propertyMap.properties);
150234287Sdim    }
151234287Sdim
152234287Sdim    private void writeObject(final ObjectOutputStream out) throws IOException {
153234287Sdim        out.defaultWriteObject();
154234287Sdim        out.writeObject(properties.getProperties());
155234287Sdim    }
156239462Sdim
157243830Sdim    private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
158239462Sdim        in.defaultReadObject();
159234287Sdim
160234287Sdim        final Property[] props = (Property[]) in.readObject();
161234287Sdim        this.properties = EMPTY_HASHMAP.immutableAdd(props);
162234287Sdim
163234287Sdim        assert className != null;
164234287Sdim        final Class<?> structure = Context.forStructureClass(className);
165239462Sdim        for (final Property prop : props) {
166239462Sdim            prop.initMethodHandles(structure);
167249423Sdim        }
168249423Sdim    }
169249423Sdim
170249423Sdim    /**
171249423Sdim     * Public property map allocator.
172249423Sdim     *
173249423Sdim     * <p>It is the caller's responsibility to make sure that {@code properties} does not contain
174249423Sdim     * properties with keys that are valid array indices.</p>
175249423Sdim     *
176239462Sdim     * @param properties   Collection of initial properties.
177239462Sdim     * @param className    class name
178234287Sdim     * @param fieldCount   Number of fields in use.
179234287Sdim     * @param fieldMaximum Number of fields available.
180234287Sdim     * @param spillLength  Number of used spill slots.
181     * @return New {@link PropertyMap}.
182     */
183    public static PropertyMap newMap(final Collection<Property> properties, final String className, final int fieldCount, final int fieldMaximum,  final int spillLength) {
184        final PropertyHashMap newProperties = EMPTY_HASHMAP.immutableAdd(properties);
185        return new PropertyMap(newProperties, className, fieldCount, fieldMaximum, spillLength, false);
186    }
187
188    /**
189     * Public property map allocator. Used by nasgen generated code.
190     *
191     * <p>It is the caller's responsibility to make sure that {@code properties} does not contain
192     * properties with keys that are valid array indices.</p>
193     *
194     * @param properties Collection of initial properties.
195     * @return New {@link PropertyMap}.
196     */
197    public static PropertyMap newMap(final Collection<Property> properties) {
198        return properties == null || properties.isEmpty()? newMap() : newMap(properties, JO.class.getName(), 0, 0, 0);
199    }
200
201    /**
202     * Return a sharable empty map.
203     *
204     * @return New empty {@link PropertyMap}.
205     */
206    public static PropertyMap newMap() {
207        return new PropertyMap(EMPTY_HASHMAP, JO.class.getName(), 0, 0, 0, false);
208    }
209
210    /**
211     * Return number of properties in the map.
212     *
213     * @return Number of properties.
214     */
215    public int size() {
216        return properties.size();
217    }
218
219    /**
220     * Get the listeners of this map, or null if none exists
221     *
222     * @return the listeners
223     */
224    public PropertyListeners getListeners() {
225        return listeners;
226    }
227
228    /**
229     * Add {@code listenerMap} as a listener to this property map for the given {@code key}.
230     *
231     * @param key the property name
232     * @param listenerMap the listener map
233     */
234    public void addListener(final String key, final PropertyMap listenerMap) {
235        if (listenerMap != this) {
236            // We need to clone listener instance when adding a new listener since we share
237            // the listeners instance with our parent maps that don't need to see the new listener.
238            listeners = PropertyListeners.addListener(listeners, key, listenerMap);
239        }
240    }
241
242    /**
243     * A new property is being added.
244     *
245     * @param property The new Property added.
246     */
247    public void propertyAdded(final Property property) {
248        invalidateProtoGetSwitchPoint(property);
249        if (listeners != null) {
250            listeners.propertyAdded(property);
251        }
252    }
253
254    /**
255     * An existing property is being deleted.
256     *
257     * @param property The property being deleted.
258     */
259    public void propertyDeleted(final Property property) {
260        invalidateProtoGetSwitchPoint(property);
261        if (listeners != null) {
262            listeners.propertyDeleted(property);
263        }
264    }
265
266    /**
267     * An existing property is being redefined.
268     *
269     * @param oldProperty The old property
270     * @param newProperty The new property
271     */
272    public void propertyModified(final Property oldProperty, final Property newProperty) {
273        invalidateProtoGetSwitchPoint(oldProperty);
274        if (listeners != null) {
275            listeners.propertyModified(oldProperty, newProperty);
276        }
277    }
278
279    /**
280     * The prototype of an object associated with this {@link PropertyMap} is changed.
281     */
282    public void protoChanged() {
283        invalidateAllProtoGetSwitchPoints();
284        if (listeners != null) {
285            listeners.protoChanged();
286        }
287    }
288
289    /**
290     * Return a SwitchPoint used to track changes of a property in a prototype.
291     *
292     * @param key Property key.
293     * @return A shared {@link SwitchPoint} for the property.
294     */
295    public synchronized SwitchPoint getSwitchPoint(final String key) {
296        if (protoGetSwitches == null) {
297            protoGetSwitches = new HashMap<>();
298        }
299
300        SwitchPoint switchPoint = protoGetSwitches.get(key);
301        if (switchPoint == null) {
302            switchPoint = new SwitchPoint();
303            protoGetSwitches.put(key, switchPoint);
304        }
305
306        return switchPoint;
307    }
308
309    /**
310     * Indicate that a prototype property has changed.
311     *
312     * @param property {@link Property} to invalidate.
313     */
314    synchronized void invalidateProtoGetSwitchPoint(final Property property) {
315        if (protoGetSwitches != null) {
316
317            final String key = property.getKey();
318            final SwitchPoint sp = protoGetSwitches.get(key);
319            if (sp != null) {
320                protoGetSwitches.remove(key);
321                if (Context.DEBUG) {
322                    protoInvalidations++;
323                }
324                SwitchPoint.invalidateAll(new SwitchPoint[] { sp });
325            }
326        }
327    }
328
329    /**
330     * Indicate that proto itself has changed in hierarchy somewhere.
331     */
332    synchronized void invalidateAllProtoGetSwitchPoints() {
333        if (protoGetSwitches != null && !protoGetSwitches.isEmpty()) {
334            if (Context.DEBUG) {
335                protoInvalidations += protoGetSwitches.size();
336            }
337            SwitchPoint.invalidateAll(protoGetSwitches.values().toArray(new SwitchPoint[protoGetSwitches.values().size()]));
338            protoGetSwitches.clear();
339        }
340    }
341
342    /**
343     * Add a property to the map, re-binding its getters and setters,
344     * if available, to a given receiver. This is typically the global scope. See
345     * {@link ScriptObject#addBoundProperties(ScriptObject)}
346     *
347     * @param property {@link Property} being added.
348     * @param bindTo   Object to bind to.
349     *
350     * @return New {@link PropertyMap} with {@link Property} added.
351     */
352    PropertyMap addPropertyBind(final AccessorProperty property, final Object bindTo) {
353        // No need to store bound property in the history as bound properties can't be reused.
354        return addPropertyNoHistory(new AccessorProperty(property, bindTo));
355    }
356
357    // Get a logical slot index for a property, with spill slot 0 starting at fieldMaximum.
358    private int logicalSlotIndex(final Property property) {
359        final int slot = property.getSlot();
360        if (slot < 0) {
361            return -1;
362        }
363        return property.isSpill() ? slot + fieldMaximum : slot;
364    }
365
366    // Update boundaries and flags after a property has been added
367    private void updateFlagsAndBoundaries(final Property newProperty) {
368        if(newProperty.isSpill()) {
369            spillLength = Math.max(spillLength, newProperty.getSlot() + 1);
370        } else {
371            fieldCount = Math.max(fieldCount, newProperty.getSlot() + 1);
372        }
373        if (isValidArrayIndex(getArrayIndex(newProperty.getKey()))) {
374            setContainsArrayKeys();
375        }
376    }
377
378    // Update the free slots bitmap for a property that has been deleted and/or added.
379    private void updateFreeSlots(final Property oldProperty, final Property newProperty) {
380        // Free slots bitset is possibly shared with parent map, so we must clone it before making modifications.
381        boolean freeSlotsCloned = false;
382        if (oldProperty != null) {
383            final int slotIndex = logicalSlotIndex(oldProperty);
384            if (slotIndex >= 0) {
385                final BitSet newFreeSlots = freeSlots == null ? new BitSet() : (BitSet)freeSlots.clone();
386                assert !newFreeSlots.get(slotIndex);
387                newFreeSlots.set(slotIndex);
388                freeSlots = newFreeSlots;
389                freeSlotsCloned = true;
390            }
391        }
392        if (freeSlots != null && newProperty != null) {
393            final int slotIndex = logicalSlotIndex(newProperty);
394            if (slotIndex > -1 && freeSlots.get(slotIndex)) {
395                final BitSet newFreeSlots = freeSlotsCloned ? freeSlots : ((BitSet)freeSlots.clone());
396                newFreeSlots.clear(slotIndex);
397                freeSlots = newFreeSlots.isEmpty() ? null : newFreeSlots;
398            }
399        }
400    }
401
402    /**
403     * Add a property to the map without adding it to the history. This should be used for properties that
404     * can't be shared such as bound properties, or properties that are expected to be added only once.
405     *
406     * @param property {@link Property} being added.
407     * @return New {@link PropertyMap} with {@link Property} added.
408     */
409    public PropertyMap addPropertyNoHistory(final Property property) {
410        if (listeners != null) {
411            listeners.propertyAdded(property);
412        }
413        final PropertyHashMap newProperties = properties.immutableAdd(property);
414        final PropertyMap newMap = new PropertyMap(this, newProperties);
415        newMap.updateFlagsAndBoundaries(property);
416        newMap.updateFreeSlots(null, property);
417
418        return newMap;
419    }
420
421    /**
422     * Add a property to the map.  Cloning or using an existing map if available.
423     *
424     * @param property {@link Property} being added.
425     *
426     * @return New {@link PropertyMap} with {@link Property} added.
427     */
428    public PropertyMap addProperty(final Property property) {
429        if (listeners != null) {
430            listeners.propertyAdded(property);
431        }
432        PropertyMap newMap = checkHistory(property);
433
434        if (newMap == null) {
435            final PropertyHashMap newProperties = properties.immutableAdd(property);
436            newMap = new PropertyMap(this, newProperties);
437            addToHistory(property, newMap);
438            newMap.updateFlagsAndBoundaries(property);
439            newMap.updateFreeSlots(null, property);
440        }
441
442        return newMap;
443    }
444
445    /**
446     * Remove a property from a map. Cloning or using an existing map if available.
447     *
448     * @param property {@link Property} being removed.
449     *
450     * @return New {@link PropertyMap} with {@link Property} removed or {@code null} if not found.
451     */
452    public PropertyMap deleteProperty(final Property property) {
453        if (listeners != null) {
454            listeners.propertyDeleted(property);
455        }
456        PropertyMap newMap = checkHistory(property);
457        final String key = property.getKey();
458
459        if (newMap == null && properties.containsKey(key)) {
460            final PropertyHashMap newProperties = properties.immutableRemove(key);
461            final boolean isSpill = property.isSpill();
462            final int slot = property.getSlot();
463            // If deleted property was last field or spill slot we can make it reusable by reducing field/slot count.
464            // Otherwise mark it as free in free slots bitset.
465            if (isSpill && slot >= 0 && slot == spillLength - 1) {
466                newMap = new PropertyMap(newProperties, className, fieldCount, fieldMaximum, spillLength - 1, containsArrayKeys());
467                newMap.freeSlots = freeSlots;
468            } else if (!isSpill && slot >= 0 && slot == fieldCount - 1) {
469                newMap = new PropertyMap(newProperties, className, fieldCount - 1, fieldMaximum, spillLength, containsArrayKeys());
470                newMap.freeSlots = freeSlots;
471            } else {
472                newMap = new PropertyMap(this, newProperties);
473                newMap.updateFreeSlots(property, null);
474            }
475            addToHistory(property, newMap);
476        }
477
478        return newMap;
479    }
480
481    /**
482     * Replace an existing property with a new one.
483     *
484     * @param oldProperty Property to replace.
485     * @param newProperty New {@link Property}.
486     *
487     * @return New {@link PropertyMap} with {@link Property} replaced.
488     */
489    public PropertyMap replaceProperty(final Property oldProperty, final Property newProperty) {
490        if (listeners != null) {
491            listeners.propertyModified(oldProperty, newProperty);
492        }
493        // Add replaces existing property.
494        final PropertyHashMap newProperties = properties.immutableReplace(oldProperty, newProperty);
495        final PropertyMap newMap = new PropertyMap(this, newProperties);
496        /*
497         * See ScriptObject.modifyProperty and ScriptObject.setUserAccessors methods.
498         *
499         * This replaceProperty method is called only for the following three cases:
500         *
501         *   1. To change flags OR TYPE of an old (cloned) property. We use the same spill slots.
502         *   2. To change one UserAccessor property with another - user getter or setter changed via
503         *      Object.defineProperty function. Again, same spill slots are re-used.
504         *   3. Via ScriptObject.setUserAccessors method to set user getter and setter functions
505         *      replacing the dummy AccessorProperty with null method handles (added during map init).
506         *
507         * In case (1) and case(2), the property type of old and new property is same. For case (3),
508         * the old property is an AccessorProperty and the new one is a UserAccessorProperty property.
509         */
510
511        final boolean sameType = oldProperty.getClass() == newProperty.getClass();
512        assert sameType ||
513                oldProperty instanceof AccessorProperty &&
514                newProperty instanceof UserAccessorProperty :
515            "arbitrary replaceProperty attempted " + sameType + " oldProperty=" + oldProperty.getClass() + " newProperty=" + newProperty.getClass() + " [" + oldProperty.getLocalType() + " => " + newProperty.getLocalType() + "]";
516
517        newMap.flags = flags;
518
519        /*
520         * spillLength remains same in case (1) and (2) because of slot reuse. Only for case (3), we need
521         * to add spill count of the newly added UserAccessorProperty property.
522         */
523        if (!sameType) {
524            newMap.spillLength = Math.max(spillLength, newProperty.getSlot() + 1);
525            newMap.updateFreeSlots(oldProperty, newProperty);
526        }
527        return newMap;
528    }
529
530    /**
531     * Make a new UserAccessorProperty property. getter and setter functions are stored in
532     * this ScriptObject and slot values are used in property object. Note that slots
533     * are assigned speculatively and should be added to map before adding other
534     * properties.
535     *
536     * @param key the property name
537     * @param propertyFlags attribute flags of the property
538     * @return the newly created UserAccessorProperty
539     */
540    public UserAccessorProperty newUserAccessors(final String key, final int propertyFlags) {
541        return new UserAccessorProperty(key, propertyFlags, getFreeSpillSlot());
542    }
543
544    /**
545     * Find a property in the map.
546     *
547     * @param key Key to search for.
548     *
549     * @return {@link Property} matching key.
550     */
551    public Property findProperty(final String key) {
552        return properties.find(key);
553    }
554
555    /**
556     * Adds all map properties from another map.
557     *
558     * @param other The source of properties.
559     *
560     * @return New {@link PropertyMap} with added properties.
561     */
562    public PropertyMap addAll(final PropertyMap other) {
563        assert this != other : "adding property map to itself";
564        final Property[] otherProperties = other.properties.getProperties();
565        final PropertyHashMap newProperties = properties.immutableAdd(otherProperties);
566
567        final PropertyMap newMap = new PropertyMap(this, newProperties);
568        for (final Property property : otherProperties) {
569            // This method is only safe to use with non-slotted, native getter/setter properties
570            assert property.getSlot() == -1;
571            assert !(isValidArrayIndex(getArrayIndex(property.getKey())));
572        }
573
574        return newMap;
575    }
576
577    /**
578     * Return an array of all properties.
579     *
580     * @return Properties as an array.
581     */
582    public Property[] getProperties() {
583        return properties.getProperties();
584    }
585
586    /**
587     * Prevents the map from having additional properties.
588     *
589     * @return New map with {@link #NOT_EXTENSIBLE} flag set.
590     */
591    PropertyMap preventExtensions() {
592        final PropertyMap newMap = new PropertyMap(this);
593        newMap.flags |= NOT_EXTENSIBLE;
594        return newMap;
595    }
596
597    /**
598     * Prevents properties in map from being modified.
599     *
600     * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with
601     * {@link Property#NOT_CONFIGURABLE} set.
602     */
603    PropertyMap seal() {
604        PropertyHashMap newProperties = EMPTY_HASHMAP;
605
606        for (final Property oldProperty :  properties.getProperties()) {
607            newProperties = newProperties.immutableAdd(oldProperty.addFlags(Property.NOT_CONFIGURABLE));
608        }
609
610        final PropertyMap newMap = new PropertyMap(this, newProperties);
611        newMap.flags |= NOT_EXTENSIBLE;
612
613        return newMap;
614    }
615
616    /**
617     * Prevents properties in map from being modified or written to.
618     *
619     * @return New map with {@link #NOT_EXTENSIBLE} flag set and properties with
620     * {@link Property#NOT_CONFIGURABLE} and {@link Property#NOT_WRITABLE} set.
621     */
622    PropertyMap freeze() {
623        PropertyHashMap newProperties = EMPTY_HASHMAP;
624
625        for (final Property oldProperty : properties.getProperties()) {
626            int propertyFlags = Property.NOT_CONFIGURABLE;
627
628            if (!(oldProperty instanceof UserAccessorProperty)) {
629                propertyFlags |= Property.NOT_WRITABLE;
630            }
631
632            newProperties = newProperties.immutableAdd(oldProperty.addFlags(propertyFlags));
633        }
634
635        final PropertyMap newMap = new PropertyMap(this, newProperties);
636        newMap.flags |= NOT_EXTENSIBLE;
637
638        return newMap;
639    }
640
641    /**
642     * Check for any configurable properties.
643     *
644     * @return {@code true} if any configurable.
645     */
646    private boolean anyConfigurable() {
647        for (final Property property : properties.getProperties()) {
648            if (property.isConfigurable()) {
649               return true;
650            }
651        }
652
653        return false;
654    }
655
656    /**
657     * Check if all properties are frozen.
658     *
659     * @return {@code true} if all are frozen.
660     */
661    private boolean allFrozen() {
662        for (final Property property : properties.getProperties()) {
663            // check if it is a data descriptor
664            if (!(property instanceof UserAccessorProperty)) {
665                if (property.isWritable()) {
666                    return false;
667                }
668            }
669            if (property.isConfigurable()) {
670               return false;
671            }
672        }
673
674        return true;
675    }
676
677    /**
678     * Check prototype history for an existing property map with specified prototype.
679     *
680     * @param proto New prototype object.
681     *
682     * @return Existing {@link PropertyMap} or {@code null} if not found.
683     */
684    private PropertyMap checkProtoHistory(final ScriptObject proto) {
685        final PropertyMap cachedMap;
686        if (protoHistory != null) {
687            final SoftReference<PropertyMap> weakMap = protoHistory.get(proto);
688            cachedMap = (weakMap != null ? weakMap.get() : null);
689        } else {
690            cachedMap = null;
691        }
692
693        if (Context.DEBUG && cachedMap != null) {
694            protoHistoryHit++;
695        }
696
697        return cachedMap;
698    }
699
700    /**
701     * Add a map to the prototype history.
702     *
703     * @param newProto Prototype to add (key.)
704     * @param newMap   {@link PropertyMap} associated with prototype.
705     */
706    private void addToProtoHistory(final ScriptObject newProto, final PropertyMap newMap) {
707        if (protoHistory == null) {
708            protoHistory = new WeakHashMap<>();
709        }
710
711        protoHistory.put(newProto, new SoftReference<>(newMap));
712    }
713
714    /**
715     * Track the modification of the map.
716     *
717     * @param property Mapping property.
718     * @param newMap   Modified {@link PropertyMap}.
719     */
720    private void addToHistory(final Property property, final PropertyMap newMap) {
721        if (history == null) {
722            history = new WeakHashMap<>();
723        }
724
725        history.put(property, new SoftReference<>(newMap));
726    }
727
728    /**
729     * Check the history for a map that already has the given property added.
730     *
731     * @param property {@link Property} to add.
732     *
733     * @return Existing map or {@code null} if not found.
734     */
735    private PropertyMap checkHistory(final Property property) {
736
737        if (history != null) {
738            final SoftReference<PropertyMap> ref = history.get(property);
739            final PropertyMap historicMap = ref == null ? null : ref.get();
740
741            if (historicMap != null) {
742                if (Context.DEBUG) {
743                    historyHit++;
744                }
745
746                return historicMap;
747            }
748        }
749
750        return null;
751    }
752
753    /**
754     * Returns true if the two maps have identical properties in the same order, but allows the properties to differ in
755     * their types. This method is mostly useful for tests.
756     * @param otherMap the other map
757     * @return true if this map has identical properties in the same order as the other map, allowing the properties to
758     * differ in type.
759     */
760    public boolean equalsWithoutType(final PropertyMap otherMap) {
761        if (properties.size() != otherMap.properties.size()) {
762            return false;
763        }
764
765        final Iterator<Property> iter      = properties.values().iterator();
766        final Iterator<Property> otherIter = otherMap.properties.values().iterator();
767
768        while (iter.hasNext() && otherIter.hasNext()) {
769            if (!iter.next().equalsWithoutType(otherIter.next())) {
770                return false;
771            }
772        }
773
774        return true;
775    }
776
777    @Override
778    public String toString() {
779        final StringBuilder sb = new StringBuilder();
780
781        sb.append(Debug.id(this));
782        sb.append(" = {\n");
783
784        for (final Property property : getProperties()) {
785            sb.append('\t');
786            sb.append(property);
787            sb.append('\n');
788        }
789
790        sb.append('}');
791
792        return sb.toString();
793    }
794
795    @Override
796    public Iterator<Object> iterator() {
797        return new PropertyMapIterator(this);
798    }
799
800    /**
801     * Check if this map contains properties with valid array keys
802     *
803     * @return {@code true} if this map contains properties with valid array keys
804     */
805    public final boolean containsArrayKeys() {
806        return (flags & CONTAINS_ARRAY_KEYS) != 0;
807    }
808
809    /**
810     * Flag this object as having array keys in defined properties
811     */
812    private void setContainsArrayKeys() {
813        flags |= CONTAINS_ARRAY_KEYS;
814    }
815
816    /**
817     * Test to see if {@link PropertyMap} is extensible.
818     *
819     * @return {@code true} if {@link PropertyMap} can be added to.
820     */
821    boolean isExtensible() {
822        return (flags & NOT_EXTENSIBLE) == 0;
823    }
824
825    /**
826     * Test to see if {@link PropertyMap} is not extensible or any properties
827     * can not be modified.
828     *
829     * @return {@code true} if {@link PropertyMap} is sealed.
830     */
831    boolean isSealed() {
832        return !isExtensible() && !anyConfigurable();
833    }
834
835    /**
836     * Test to see if {@link PropertyMap} is not extensible or all properties
837     * can not be modified.
838     *
839     * @return {@code true} if {@link PropertyMap} is frozen.
840     */
841    boolean isFrozen() {
842        return !isExtensible() && allFrozen();
843    }
844
845    /**
846     * Return a free field slot for this map, or {@code -1} if none is available.
847     *
848     * @return free field slot or -1
849     */
850    int getFreeFieldSlot() {
851        if (freeSlots != null) {
852            final int freeSlot = freeSlots.nextSetBit(0);
853            if (freeSlot > -1 && freeSlot < fieldMaximum) {
854                return freeSlot;
855            }
856        }
857        if (fieldCount < fieldMaximum) {
858            return fieldCount;
859        }
860        return -1;
861    }
862
863    /**
864     * Get a free spill slot for this map.
865     *
866     * @return free spill slot
867     */
868    int getFreeSpillSlot() {
869        if (freeSlots != null) {
870            final int freeSlot = freeSlots.nextSetBit(fieldMaximum);
871            if (freeSlot > -1) {
872                return freeSlot - fieldMaximum;
873            }
874        }
875        return spillLength;
876    }
877
878    /**
879     * Return a property map with the same layout that is associated with the new prototype object.
880     *
881     * @param newProto New prototype object to replace oldProto.
882     * @return New {@link PropertyMap} with prototype changed.
883     */
884    public PropertyMap changeProto(final ScriptObject newProto) {
885
886        final PropertyMap nextMap = checkProtoHistory(newProto);
887        if (nextMap != null) {
888            return nextMap;
889        }
890
891        if (Context.DEBUG) {
892            setProtoNewMapCount++;
893        }
894
895        final PropertyMap newMap = new PropertyMap(this);
896        addToProtoHistory(newProto, newMap);
897
898        return newMap;
899    }
900
901
902    /**
903     * {@link PropertyMap} iterator.
904     */
905    private static class PropertyMapIterator implements Iterator<Object> {
906        /** Property iterator. */
907        final Iterator<Property> iter;
908
909        /** Current Property. */
910        Property property;
911
912        /**
913         * Constructor.
914         *
915         * @param propertyMap {@link PropertyMap} to iterate over.
916         */
917        PropertyMapIterator(final PropertyMap propertyMap) {
918            iter = Arrays.asList(propertyMap.properties.getProperties()).iterator();
919            property = iter.hasNext() ? iter.next() : null;
920            skipNotEnumerable();
921        }
922
923        /**
924         * Ignore properties that are not enumerable.
925         */
926        private void skipNotEnumerable() {
927            while (property != null && !property.isEnumerable()) {
928                property = iter.hasNext() ? iter.next() : null;
929            }
930        }
931
932        @Override
933        public boolean hasNext() {
934            return property != null;
935        }
936
937        @Override
938        public Object next() {
939            if (property == null) {
940                throw new NoSuchElementException();
941            }
942
943            final Object key = property.getKey();
944            property = iter.next();
945            skipNotEnumerable();
946
947            return key;
948        }
949
950        @Override
951        public void remove() {
952            throw new UnsupportedOperationException("remove");
953        }
954    }
955
956    /*
957     * Debugging and statistics.
958     */
959
960    /**
961     * Debug helper function that returns the diff of two property maps, only
962     * displaying the information that is different and in which map it exists
963     * compared to the other map. Can be used to e.g. debug map guards and
964     * investigate why they fail, causing relink
965     *
966     * @param map0 the first property map
967     * @param map1 the second property map
968     *
969     * @return property map diff as string
970     */
971    public static String diff(final PropertyMap map0, final PropertyMap map1) {
972        final StringBuilder sb = new StringBuilder();
973
974        if (map0 != map1) {
975           sb.append(">>> START: Map diff");
976           boolean found = false;
977
978           for (final Property p : map0.getProperties()) {
979               final Property p2 = map1.findProperty(p.getKey());
980               if (p2 == null) {
981                   sb.append("FIRST ONLY : [" + p + "]");
982                   found = true;
983               } else if (p2 != p) {
984                   sb.append("DIFFERENT  : [" + p + "] != [" + p2 + "]");
985                   found = true;
986               }
987           }
988
989           for (final Property p2 : map1.getProperties()) {
990               final Property p1 = map0.findProperty(p2.getKey());
991               if (p1 == null) {
992                   sb.append("SECOND ONLY: [" + p2 + "]");
993                   found = true;
994               }
995           }
996
997           //assert found;
998
999           if (!found) {
1000                sb.append(map0).
1001                    append("!=").
1002                    append(map1);
1003           }
1004
1005           sb.append("<<< END: Map diff\n");
1006        }
1007
1008        return sb.toString();
1009    }
1010
1011    // counters updated only in debug mode
1012    private static int count;
1013    private static int clonedCount;
1014    private static int historyHit;
1015    private static int protoInvalidations;
1016    private static int protoHistoryHit;
1017    private static int setProtoNewMapCount;
1018
1019    /**
1020     * @return Total number of maps.
1021     */
1022    public static int getCount() {
1023        return count;
1024    }
1025
1026    /**
1027     * @return The number of maps that were cloned.
1028     */
1029    public static int getClonedCount() {
1030        return clonedCount;
1031    }
1032
1033    /**
1034     * @return The number of times history was successfully used.
1035     */
1036    public static int getHistoryHit() {
1037        return historyHit;
1038    }
1039
1040    /**
1041     * @return The number of times prototype changes caused invalidation.
1042     */
1043    public static int getProtoInvalidations() {
1044        return protoInvalidations;
1045    }
1046
1047    /**
1048     * @return The number of times proto history was successfully used.
1049     */
1050    public static int getProtoHistoryHit() {
1051        return protoHistoryHit;
1052    }
1053
1054    /**
1055     * @return The number of times prototypes were modified.
1056     */
1057    public static int getSetProtoNewMapCount() {
1058        return setProtoNewMapCount;
1059    }
1060}
1061