PropertyMap.java revision 1036:f0b5e3900a10
1144091Sdas/* 2144091Sdas * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. 3144091Sdas * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4144091Sdas * 5144091Sdas * This code is free software; you can redistribute it and/or modify it 6144091Sdas * under the terms of the GNU General Public License version 2 only, as 7144091Sdas * published by the Free Software Foundation. Oracle designates this 8144091Sdas * particular file as subject to the "Classpath" exception as provided 9144091Sdas * by Oracle in the LICENSE file that accompanied this code. 10144091Sdas * 11144091Sdas * This code is distributed in the hope that it will be useful, but WITHOUT 12144091Sdas * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13144091Sdas * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14144091Sdas * version 2 for more details (a copy is included in the LICENSE file that 15144091Sdas * accompanied this code). 16177764Sdas * 17177764Sdas * You should have received a copy of the GNU General Public License version 18144091Sdas * 2 along with this work; if not, write to the Free Software Foundation, 19144091Sdas * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20144091Sdas * 21144091Sdas * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22144091Sdas * or visit www.oracle.com if you need additional information or have any 23144091Sdas * questions. 24144091Sdas */ 25144091Sdas 26144091Sdaspackage jdk.nashorn.internal.runtime; 27144091Sdas 28144091Sdasimport static jdk.nashorn.internal.runtime.PropertyHashMap.EMPTY_HASHMAP; 29144091Sdasimport static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex; 30144091Sdasimport static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; 31144091Sdas 32144091Sdasimport java.io.IOException; 33144091Sdasimport java.io.ObjectInputStream; 34144091Sdasimport java.io.ObjectOutputStream; 35144091Sdasimport java.io.Serializable; 36144091Sdasimport java.lang.invoke.SwitchPoint; 37144091Sdasimport java.lang.ref.SoftReference; 38144091Sdasimport java.util.Arrays; 39144091Sdasimport java.util.BitSet; 40144091Sdasimport java.util.Collection; 41144091Sdasimport java.util.HashMap; 42144091Sdasimport java.util.Iterator; 43144091Sdasimport java.util.NoSuchElementException; 44144091Sdasimport java.util.WeakHashMap; 45144091Sdasimport jdk.nashorn.internal.scripts.JO; 46144091Sdas 47144091Sdas/** 48144091Sdas * Map of object properties. The PropertyMap is the "template" for JavaScript object 49144091Sdas * layouts. It contains a map with prototype names as keys and {@link Property} instances 50144091Sdas * as values. A PropertyMap is typically passed to the {@link ScriptObject} constructor 51144091Sdas * to form the seed map for the ScriptObject. 52144091Sdas * <p> 53144091Sdas * All property maps are immutable. If a property is added, modified or removed, the mutator 54234535Sdas * will return a new map. 55144091Sdas */ 56144091Sdaspublic final class PropertyMap implements Iterable<Object>, Serializable { 57144091Sdas /** Used for non extensible PropertyMaps, negative logic as the normal case is extensible. See {@link ScriptObject#preventExtensions()} */ 58144091Sdas public static final int NOT_EXTENSIBLE = 0b0000_0001; 59144091Sdas /** Does this map contain valid array keys? */ 60144091Sdas public static final int CONTAINS_ARRAY_KEYS = 0b0000_0010; 61144091Sdas 62144091Sdas /** Map status flags. */ 63144091Sdas private int flags; 64144091Sdas 65144091Sdas /** Map of properties. */ 66144091Sdas private transient PropertyHashMap properties; 67144091Sdas 68144091Sdas /** Number of fields in use. */ 69144091Sdas private int fieldCount; 70144091Sdas 71144091Sdas /** Number of fields available. */ 72144091Sdas private final int fieldMaximum; 73144091Sdas 74144091Sdas /** Length of spill in use. */ 75144091Sdas private int spillLength; 76144091Sdas 77144091Sdas /** Structure class name */ 78144091Sdas private String className; 79144091Sdas 80144091Sdas /** {@link SwitchPoint}s for gets on inherited properties. */ 81144091Sdas private transient HashMap<String, SwitchPoint> protoGetSwitches; 82144091Sdas 83144091Sdas /** History of maps, used to limit map duplication. */ 84144091Sdas private transient WeakHashMap<Property, SoftReference<PropertyMap>> history; 85144091Sdas 86144091Sdas /** History of prototypes, used to limit map duplication. */ 87144091Sdas private transient WeakHashMap<PropertyMap, SoftReference<PropertyMap>> protoHistory; 88144091Sdas 89144091Sdas /** property listeners */ 90144091Sdas private transient PropertyListeners listeners; 91144091Sdas 92144091Sdas private transient BitSet freeSlots; 93144091Sdas 94144091Sdas private static final long serialVersionUID = -7041836752008732533L; 95144091Sdas 96144091Sdas /** 97144091Sdas * Constructor. 98144091Sdas * 99144091Sdas * @param properties A {@link PropertyHashMap} with initial contents. 100144091Sdas * @param fieldCount Number of fields in use. 101144091Sdas * @param fieldMaximum Number of fields available. 102144091Sdas * @param spillLength Number of spill slots used. 103144091Sdas * @param containsArrayKeys True if properties contain numeric keys 104144091Sdas */ 105144091Sdas private PropertyMap(final PropertyHashMap properties, final String className, final int fieldCount, 106144091Sdas final int fieldMaximum, final int spillLength, final boolean containsArrayKeys) { 107144091Sdas this.properties = properties; 108144091Sdas this.className = className; 109144091Sdas this.fieldCount = fieldCount; 110144091Sdas this.fieldMaximum = fieldMaximum; 111144091Sdas this.spillLength = spillLength; 112144091Sdas if (containsArrayKeys) { 113144091Sdas setContainsArrayKeys(); 114144091Sdas } 115144091Sdas 116144091Sdas if (Context.DEBUG) { 117234535Sdas count++; 118144091Sdas } 119144091Sdas } 120144091Sdas 121144091Sdas /** 122144091Sdas * Cloning constructor. 123144091Sdas * 124144091Sdas * @param propertyMap Existing property map. 125144091Sdas * @param properties A {@link PropertyHashMap} with a new set of properties. 126144091Sdas */ 127144091Sdas private PropertyMap(final PropertyMap propertyMap, final PropertyHashMap properties) { 128144091Sdas this.properties = properties; 129144091Sdas this.flags = propertyMap.flags; 130144091Sdas this.spillLength = propertyMap.spillLength; 131144091Sdas this.fieldCount = propertyMap.fieldCount; 132144091Sdas this.fieldMaximum = propertyMap.fieldMaximum; 133234535Sdas // We inherit the parent property listeners instance. It will be cloned when a new listener is added. 134144091Sdas this.listeners = propertyMap.listeners; 135234535Sdas this.freeSlots = propertyMap.freeSlots; 136144091Sdas 137144091Sdas if (Context.DEBUG) { 138144091Sdas count++; 139144091Sdas clonedCount++; 140144091Sdas } 141144091Sdas } 142144091Sdas 143144091Sdas /** 144144091Sdas * Cloning constructor. 145144091Sdas * 146144091Sdas * @param propertyMap Existing property map. 147144091Sdas */ 148144091Sdas private PropertyMap(final PropertyMap propertyMap) { 149144091Sdas this(propertyMap, propertyMap.properties); 150144091Sdas } 151144091Sdas 152144091Sdas private void writeObject(final ObjectOutputStream out) throws IOException { 153144091Sdas out.defaultWriteObject(); 154144091Sdas out.writeObject(properties.getProperties()); 155144091Sdas } 156177764Sdas 157177764Sdas private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException { 158177764Sdas in.defaultReadObject(); 159177764Sdas 160 final Property[] props = (Property[]) in.readObject(); 161 this.properties = EMPTY_HASHMAP.immutableAdd(props); 162 163 assert className != null; 164 final Class<?> structure = Context.forStructureClass(className); 165 for (final Property prop : props) { 166 prop.initMethodHandles(structure); 167 } 168 } 169 170 /** 171 * Public property map allocator. 172 * 173 * <p>It is the caller's responsibility to make sure that {@code properties} does not contain 174 * properties with keys that are valid array indices.</p> 175 * 176 * @param properties Collection of initial properties. 177 * @param className class name 178 * @param fieldCount Number of fields in use. 179 * @param fieldMaximum Number of fields available. 180 * @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 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.getCurrentType() + " => " + newProperty.getCurrentType() + "]"; 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 parentMap New prototype object. 681 * 682 * @return Existing {@link PropertyMap} or {@code null} if not found. 683 */ 684 private PropertyMap checkProtoHistory(final PropertyMap parentMap) { 685 final PropertyMap cachedMap; 686 if (protoHistory != null) { 687 final SoftReference<PropertyMap> weakMap = protoHistory.get(parentMap); 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 parentMap Prototype to add (key.) 704 * @param newMap {@link PropertyMap} associated with prototype. 705 */ 706 private void addToProtoHistory(final PropertyMap parentMap, final PropertyMap newMap) { 707 if (protoHistory == null) { 708 protoHistory = new WeakHashMap<>(); 709 } 710 711 protoHistory.put(parentMap, 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 parentMap = newProto == null ? null : newProto.getMap(); 887 final PropertyMap nextMap = checkProtoHistory(parentMap); 888 if (nextMap != null) { 889 return nextMap; 890 } 891 892 if (Context.DEBUG) { 893 setProtoNewMapCount++; 894 } 895 896 final PropertyMap newMap = new PropertyMap(this); 897 addToProtoHistory(parentMap, newMap); 898 899 return newMap; 900 } 901 902 903 /** 904 * {@link PropertyMap} iterator. 905 */ 906 private static class PropertyMapIterator implements Iterator<Object> { 907 /** Property iterator. */ 908 final Iterator<Property> iter; 909 910 /** Current Property. */ 911 Property property; 912 913 /** 914 * Constructor. 915 * 916 * @param propertyMap {@link PropertyMap} to iterate over. 917 */ 918 PropertyMapIterator(final PropertyMap propertyMap) { 919 iter = Arrays.asList(propertyMap.properties.getProperties()).iterator(); 920 property = iter.hasNext() ? iter.next() : null; 921 skipNotEnumerable(); 922 } 923 924 /** 925 * Ignore properties that are not enumerable. 926 */ 927 private void skipNotEnumerable() { 928 while (property != null && !property.isEnumerable()) { 929 property = iter.hasNext() ? iter.next() : null; 930 } 931 } 932 933 @Override 934 public boolean hasNext() { 935 return property != null; 936 } 937 938 @Override 939 public Object next() { 940 if (property == null) { 941 throw new NoSuchElementException(); 942 } 943 944 final Object key = property.getKey(); 945 property = iter.next(); 946 skipNotEnumerable(); 947 948 return key; 949 } 950 951 @Override 952 public void remove() { 953 throw new UnsupportedOperationException("remove"); 954 } 955 } 956 957 /* 958 * Debugging and statistics. 959 */ 960 961 /** 962 * Debug helper function that returns the diff of two property maps, only 963 * displaying the information that is different and in which map it exists 964 * compared to the other map. Can be used to e.g. debug map guards and 965 * investigate why they fail, causing relink 966 * 967 * @param map0 the first property map 968 * @param map1 the second property map 969 * 970 * @return property map diff as string 971 */ 972 public static String diff(final PropertyMap map0, final PropertyMap map1) { 973 final StringBuilder sb = new StringBuilder(); 974 975 if (map0 != map1) { 976 sb.append(">>> START: Map diff"); 977 boolean found = false; 978 979 for (final Property p : map0.getProperties()) { 980 final Property p2 = map1.findProperty(p.getKey()); 981 if (p2 == null) { 982 sb.append("FIRST ONLY : [" + p + "]"); 983 found = true; 984 } else if (p2 != p) { 985 sb.append("DIFFERENT : [" + p + "] != [" + p2 + "]"); 986 found = true; 987 } 988 } 989 990 for (final Property p2 : map1.getProperties()) { 991 final Property p1 = map0.findProperty(p2.getKey()); 992 if (p1 == null) { 993 sb.append("SECOND ONLY: [" + p2 + "]"); 994 found = true; 995 } 996 } 997 998 //assert found; 999 1000 if (!found) { 1001 sb.append(map0). 1002 append("!="). 1003 append(map1); 1004 } 1005 1006 sb.append("<<< END: Map diff\n"); 1007 } 1008 1009 return sb.toString(); 1010 } 1011 1012 // counters updated only in debug mode 1013 private static int count; 1014 private static int clonedCount; 1015 private static int historyHit; 1016 private static int protoInvalidations; 1017 private static int protoHistoryHit; 1018 private static int setProtoNewMapCount; 1019 1020 /** 1021 * @return Total number of maps. 1022 */ 1023 public static int getCount() { 1024 return count; 1025 } 1026 1027 /** 1028 * @return The number of maps that were cloned. 1029 */ 1030 public static int getClonedCount() { 1031 return clonedCount; 1032 } 1033 1034 /** 1035 * @return The number of times history was successfully used. 1036 */ 1037 public static int getHistoryHit() { 1038 return historyHit; 1039 } 1040 1041 /** 1042 * @return The number of times prototype changes caused invalidation. 1043 */ 1044 public static int getProtoInvalidations() { 1045 return protoInvalidations; 1046 } 1047 1048 /** 1049 * @return The number of times proto history was successfully used. 1050 */ 1051 public static int getProtoHistoryHit() { 1052 return protoHistoryHit; 1053 } 1054 1055 /** 1056 * @return The number of times prototypes were modified. 1057 */ 1058 public static int getSetProtoNewMapCount() { 1059 return setProtoNewMapCount; 1060 } 1061} 1062